diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 1a2e1a383c..a01ba0169a 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -12,6 +12,8 @@ * Extractors: * Support TS packets without PTS flag ([#9294](https://github.com/google/ExoPlayer/issues/9294)). + * ID3: Fix issue decoding ID3 tags containing UTF-16 encoded strings + ([#9087](https://github.com/google/ExoPlayer/issues/9087)). * Video: * Request smaller decoder input buffers for Dolby Vision. This fixes an issue that could cause UHD Dolby Vision playbacks to fail on some diff --git a/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java b/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java index 0d97cef846..46bd482178 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/metadata/id3/Id3Decoder.java @@ -806,9 +806,9 @@ public final class Id3Decoder extends SimpleMetadataDecoder { return terminationPos; } - // Otherwise ensure an even index and look for a second zero byte. + // Otherwise ensure an even offset from the start, and look for a second zero byte. while (terminationPos < data.length - 1) { - if (terminationPos % 2 == 0 && data[terminationPos + 1] == (byte) 0) { + if ((terminationPos - fromIndex) % 2 == 0 && data[terminationPos + 1] == (byte) 0) { return terminationPos; } terminationPos = indexOfZeroByte(data, terminationPos + 1); diff --git a/library/common/src/test/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java b/library/common/src/test/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java index f08b8f8740..833a290cea 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/metadata/id3/Id3DecoderTest.java @@ -38,6 +38,7 @@ public final class Id3DecoderTest { @Test public void decodeTxxxFrame() { + // Test UTF-8. byte[] rawId3 = buildSingleFrameTag( "TXXX", @@ -53,6 +54,21 @@ public final class Id3DecoderTest { assertThat(textInformationFrame.description).isEmpty(); assertThat(textInformationFrame.value).isEqualTo("mdialog_VINDICO1527664_start"); + // Test UTF-16. + rawId3 = + buildSingleFrameTag( + "TXXX", + new byte[] { + 1, 0, 72, 0, 101, 0, 108, 0, 108, 0, 111, 0, 32, 0, 87, 0, 111, 0, 114, 0, 108, 0, + 100, 0, 0 + }); + metadata = decoder.decode(rawId3, rawId3.length); + assertThat(metadata.length()).isEqualTo(1); + textInformationFrame = (TextInformationFrame) metadata.get(0); + assertThat(textInformationFrame.id).isEqualTo("TXXX"); + assertThat(textInformationFrame.description).isEqualTo("Hello World"); + assertThat(textInformationFrame.value).isEmpty(); + // Test empty. rawId3 = buildSingleFrameTag("TXXX", new byte[0]); metadata = decoder.decode(rawId3, rawId3.length); @@ -220,6 +236,43 @@ public final class Id3DecoderTest { assertThat(apicFrame.description).isEqualTo("Hello World"); assertThat(apicFrame.pictureData).hasLength(10); assertThat(apicFrame.pictureData).isEqualTo(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}); + + // Test with UTF-16 description at even offset. + rawId3 = + buildSingleFrameTag( + "APIC", + new byte[] { + 1, 105, 109, 97, 103, 101, 47, 106, 112, 101, 103, 0, 16, 0, 72, 0, 101, 0, 108, 0, + 108, 0, 111, 0, 32, 0, 87, 0, 111, 0, 114, 0, 108, 0, 100, 0, 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 0 + }); + decoder = new Id3Decoder(); + metadata = decoder.decode(rawId3, rawId3.length); + assertThat(metadata.length()).isEqualTo(1); + apicFrame = (ApicFrame) metadata.get(0); + assertThat(apicFrame.mimeType).isEqualTo("image/jpeg"); + assertThat(apicFrame.pictureType).isEqualTo(16); + assertThat(apicFrame.description).isEqualTo("Hello World"); + assertThat(apicFrame.pictureData).hasLength(10); + assertThat(apicFrame.pictureData).isEqualTo(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}); + + // Test with UTF-16 description at odd offset. + rawId3 = + buildSingleFrameTag( + "APIC", + new byte[] { + 1, 105, 109, 97, 103, 101, 47, 112, 110, 103, 0, 16, 0, 72, 0, 101, 0, 108, 0, 108, 0, + 111, 0, 32, 0, 87, 0, 111, 0, 114, 0, 108, 0, 100, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 + }); + decoder = new Id3Decoder(); + metadata = decoder.decode(rawId3, rawId3.length); + assertThat(metadata.length()).isEqualTo(1); + apicFrame = (ApicFrame) metadata.get(0); + assertThat(apicFrame.mimeType).isEqualTo("image/png"); + assertThat(apicFrame.pictureType).isEqualTo(16); + assertThat(apicFrame.description).isEqualTo("Hello World"); + assertThat(apicFrame.pictureData).hasLength(10); + assertThat(apicFrame.pictureData).isEqualTo(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}); } @Test