From 5082d1f28232d192084e5472a15e2a6275c691e2 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 4 Jan 2017 08:19:04 -0800 Subject: [PATCH] Some boilerplate for DASH EMSG support - Parse EMSG leaf atoms during FMP4 extraction (although currently they're just discarded). - Add readNullTerminatedString to ParsableByteArray. Issue: #2176 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=143555965 --- .../util/ParsableByteArrayTest.java | 67 +++++++++++++++++++ .../exoplayer2/extractor/mp4/Atom.java | 1 + .../extractor/mp4/FragmentedMp4Extractor.java | 8 ++- .../exoplayer2/util/ParsableByteArray.java | 48 +++++++++---- 4 files changed, 110 insertions(+), 14 deletions(-) diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/util/ParsableByteArrayTest.java b/library/src/androidTest/java/com/google/android/exoplayer2/util/ParsableByteArrayTest.java index a747930152c..49719b95f79 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer2/util/ParsableByteArrayTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer2/util/ParsableByteArrayTest.java @@ -371,6 +371,73 @@ public void testReadEmptyString() { assertNull(parser.readLine()); } + public void testReadNullTerminatedStringWithLengths() { + byte[] bytes = new byte[] { + 'f', 'o', 'o', 0, 'b', 'a', 'r', 0 + }; + // Test with lengths that match NUL byte positions. + ParsableByteArray parser = new ParsableByteArray(bytes); + assertEquals("foo", parser.readNullTerminatedString(4)); + assertEquals(4, parser.getPosition()); + assertEquals("bar", parser.readNullTerminatedString(4)); + assertEquals(8, parser.getPosition()); + assertNull(parser.readNullTerminatedString()); + // Test with lengths that do not match NUL byte positions. + parser = new ParsableByteArray(bytes); + assertEquals("fo", parser.readNullTerminatedString(2)); + assertEquals(2, parser.getPosition()); + assertEquals("o", parser.readNullTerminatedString(2)); + assertEquals(4, parser.getPosition()); + assertEquals("bar", parser.readNullTerminatedString(3)); + assertEquals(7, parser.getPosition()); + assertEquals("", parser.readNullTerminatedString(1)); + assertEquals(8, parser.getPosition()); + assertNull(parser.readNullTerminatedString()); + // Test with limit at NUL + parser = new ParsableByteArray(bytes, 4); + assertEquals("foo", parser.readNullTerminatedString(4)); + assertEquals(4, parser.getPosition()); + assertNull(parser.readNullTerminatedString()); + // Test with limit before NUL + parser = new ParsableByteArray(bytes, 3); + assertEquals("foo", parser.readNullTerminatedString(3)); + assertEquals(3, parser.getPosition()); + assertNull(parser.readNullTerminatedString()); + } + + public void testReadNullTerminatedString() { + byte[] bytes = new byte[] { + 'f', 'o', 'o', 0, 'b', 'a', 'r', 0 + }; + // Test normal case. + ParsableByteArray parser = new ParsableByteArray(bytes); + assertEquals("foo", parser.readNullTerminatedString()); + assertEquals(4, parser.getPosition()); + assertEquals("bar", parser.readNullTerminatedString()); + assertEquals(8, parser.getPosition()); + assertNull(parser.readNullTerminatedString()); + // Test with limit at NUL. + parser = new ParsableByteArray(bytes, 4); + assertEquals("foo", parser.readNullTerminatedString()); + assertEquals(4, parser.getPosition()); + assertNull(parser.readNullTerminatedString()); + // Test with limit before NUL. + parser = new ParsableByteArray(bytes, 3); + assertEquals("foo", parser.readNullTerminatedString()); + assertEquals(3, parser.getPosition()); + assertNull(parser.readNullTerminatedString()); + } + + public void testReadNullTerminatedStringWithoutEndingNull() { + byte[] bytes = new byte[] { + 'f', 'o', 'o', 0, 'b', 'a', 'r' + }; + ParsableByteArray parser = new ParsableByteArray(bytes); + assertEquals("foo", parser.readNullTerminatedString()); + assertEquals("bar", parser.readNullTerminatedString()); + assertNull(parser.readNullTerminatedString()); + } + public void testReadSingleLineWithoutEndingTrail() { byte[] bytes = new byte[] { 'f', 'o', 'o' diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java b/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java index 2eac7926e72..c8ee8ff8c38 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/Atom.java @@ -127,6 +127,7 @@ public static final int TYPE_mean = Util.getIntegerCodeForString("mean"); public static final int TYPE_name = Util.getIntegerCodeForString("name"); public static final int TYPE_data = Util.getIntegerCodeForString("data"); + public static final int TYPE_emsg = Util.getIntegerCodeForString("emsg"); public static final int TYPE_st3d = Util.getIntegerCodeForString("st3d"); public static final int TYPE_sv3d = Util.getIntegerCodeForString("sv3d"); public static final int TYPE_proj = Util.getIntegerCodeForString("proj"); diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java index 75d7cc555cf..3c25d47bf99 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java @@ -339,6 +339,8 @@ private void onLeafAtomRead(LeafAtom leaf, long inputPosition) throws ParserExce ChunkIndex segmentIndex = parseSidx(leaf.data, inputPosition); extractorOutput.seekMap(segmentIndex); haveOutputSeekMap = true; + } else if (leaf.type == Atom.TYPE_emsg) { + onEmsgLeafAtomRead(leaf); } } @@ -417,6 +419,10 @@ private void onMoofContainerAtomRead(ContainerAtom moof) throws ParserException } } + private void onEmsgLeafAtomRead(LeafAtom emsg) { + // Do nothing. + } + /** * Parses a trex atom (defined in 14496-12). */ @@ -1134,7 +1140,7 @@ private static boolean shouldParseLeafAtom(int atom) { || atom == Atom.TYPE_trun || atom == Atom.TYPE_pssh || atom == Atom.TYPE_saiz || atom == Atom.TYPE_saio || atom == Atom.TYPE_senc || atom == Atom.TYPE_uuid || atom == Atom.TYPE_sbgp || atom == Atom.TYPE_sgpd || atom == Atom.TYPE_elst - || atom == Atom.TYPE_mehd; + || atom == Atom.TYPE_mehd || atom == Atom.TYPE_emsg; } /** Returns whether the extractor should decode a container atom with type {@code atom}. */ diff --git a/library/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java b/library/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java index b8d635a053c..89862d8c75f 100644 --- a/library/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java +++ b/library/src/main/java/com/google/android/exoplayer2/util/ParsableByteArray.java @@ -424,11 +424,24 @@ public String readString(int length) { } /** - * Reads the next {@code length} bytes as UTF-8 characters. A terminating NUL byte is ignored, + * Reads the next {@code length} bytes as characters in the specified {@link Charset}. + * + * @param length The number of bytes to read. + * @param charset The character set of the encoded characters. + * @return The string encoded by the bytes in the specified character set. + */ + public String readString(int length, Charset charset) { + String result = new String(data, position, length, charset); + position += length; + return result; + } + + /** + * Reads the next {@code length} bytes as UTF-8 characters. A terminating NUL byte is discarded, * if present. * * @param length The number of bytes to read. - * @return The string encoded by the bytes. + * @return The string, not including any terminating NUL byte. */ public String readNullTerminatedString(int length) { if (length == 0) { @@ -439,22 +452,31 @@ public String readNullTerminatedString(int length) { if (lastIndex < limit && data[lastIndex] == 0) { stringLength--; } - String result = new String(data, position, stringLength, Charset.defaultCharset()); + String result = new String(data, position, stringLength); position += length; return result; } /** - * Reads the next {@code length} bytes as characters in the specified {@link Charset}. + * Reads up to the next NUL byte (or the limit) as UTF-8 characters. * - * @param length The number of bytes to read. - * @param charset The character set of the encoded characters. - * @return The string encoded by the bytes in the specified character set. + * @return The string not including any terminating NUL byte, or null if the end of the data has + * already been reached. */ - public String readString(int length, Charset charset) { - String result = new String(data, position, length, charset); - position += length; - return result; + public String readNullTerminatedString() { + if (bytesLeft() == 0) { + return null; + } + int stringLimit = position; + while (stringLimit < limit && data[stringLimit] != 0) { + stringLimit++; + } + String string = new String(data, position, stringLimit - position); + position = stringLimit; + if (position < limit) { + position++; + } + return string; } /** @@ -464,8 +486,8 @@ public String readString(int length, Charset charset) { * ('\n'), or a carriage return followed immediately by a line feed ('\r\n'). The system's default * charset (UTF-8) is used. * - * @return A String containing the contents of the line, not including any line-termination - * characters, or null if the end of the stream has been reached. + * @return The line not including any line-termination characters, or null if the end of the data + * has already been reached. */ public String readLine() { if (bytesLeft() == 0) {