From f76b4fe63ece2df4d94319110f91186a390abffb Mon Sep 17 00:00:00 2001 From: kimvde Date: Fri, 3 Jan 2020 12:11:10 +0000 Subject: [PATCH] Add unit tests to FLAC extractor related classes PiperOrigin-RevId: 287973192 --- .../exoplayer2/extractor/FlacFrameReader.java | 2 +- .../extractor/FlacFrameReaderTest.java | 323 ++++++++++++++ .../extractor/FlacMetadataReaderTest.java | 408 ++++++++++++++++++ .../util/FlacStreamMetadataTest.java | 24 ++ 4 files changed, 756 insertions(+), 1 deletion(-) create mode 100644 library/core/src/test/java/com/google/android/exoplayer2/extractor/FlacFrameReaderTest.java create mode 100644 library/core/src/test/java/com/google/android/exoplayer2/extractor/FlacMetadataReaderTest.java diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/FlacFrameReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/FlacFrameReader.java index 1e498cb677..f014eaa565 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/FlacFrameReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/FlacFrameReader.java @@ -167,7 +167,7 @@ public final class FlacFrameReader { * @param data The array to read the data from, whose position must correspond to the block size * bits. * @param blockSizeKey The key in the block size lookup table. - * @return The block size in samples. + * @return The block size in samples, or -1 if the {@code blockSizeKey} is invalid. */ public static int readFrameBlockSizeSamplesFromKey(ParsableByteArray data, int blockSizeKey) { switch (blockSizeKey) { diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/FlacFrameReaderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/FlacFrameReaderTest.java new file mode 100644 index 0000000000..87487a4199 --- /dev/null +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/FlacFrameReaderTest.java @@ -0,0 +1,323 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.extractor; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.exoplayer2.extractor.FlacFrameReader.SampleNumberHolder; +import com.google.android.exoplayer2.extractor.FlacMetadataReader.FlacStreamMetadataHolder; +import com.google.android.exoplayer2.testutil.FakeExtractorInput; +import com.google.android.exoplayer2.testutil.TestUtil; +import com.google.android.exoplayer2.util.FlacConstants; +import com.google.android.exoplayer2.util.ParsableByteArray; +import java.io.IOException; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Unit tests for {@link FlacFrameReader}. + * + *

Some expected results in these tests have been retrieved using the flac command. + */ +@RunWith(AndroidJUnit4.class) +public class FlacFrameReaderTest { + + @Test + public void checkAndReadFrameHeader_validData_updatesPosition() throws Exception { + FlacStreamMetadataHolder streamMetadataHolder = + new FlacStreamMetadataHolder(/* flacStreamMetadata= */ null); + ExtractorInput input = + buildExtractorInputReadingFromFirstFrame( + "flac/bear_one_metadata_block.flac", streamMetadataHolder); + int frameStartMarker = FlacMetadataReader.getFrameStartMarker(input); + ParsableByteArray scratch = new ParsableByteArray(FlacConstants.MAX_FRAME_HEADER_SIZE); + input.read(scratch.data, 0, FlacConstants.MAX_FRAME_HEADER_SIZE); + + FlacFrameReader.checkAndReadFrameHeader( + scratch, + streamMetadataHolder.flacStreamMetadata, + frameStartMarker, + new SampleNumberHolder()); + + assertThat(scratch.getPosition()).isEqualTo(FlacConstants.MIN_FRAME_HEADER_SIZE); + } + + @Test + public void checkAndReadFrameHeader_validData_isTrue() throws Exception { + FlacStreamMetadataHolder streamMetadataHolder = + new FlacStreamMetadataHolder(/* flacStreamMetadata= */ null); + ExtractorInput input = + buildExtractorInputReadingFromFirstFrame( + "flac/bear_one_metadata_block.flac", streamMetadataHolder); + int frameStartMarker = FlacMetadataReader.getFrameStartMarker(input); + ParsableByteArray scratch = new ParsableByteArray(FlacConstants.MAX_FRAME_HEADER_SIZE); + input.read(scratch.data, 0, FlacConstants.MAX_FRAME_HEADER_SIZE); + + boolean result = + FlacFrameReader.checkAndReadFrameHeader( + scratch, + streamMetadataHolder.flacStreamMetadata, + frameStartMarker, + new SampleNumberHolder()); + + assertThat(result).isTrue(); + } + + @Test + public void checkAndReadFrameHeader_validData_writesSampleNumber() throws Exception { + FlacStreamMetadataHolder streamMetadataHolder = + new FlacStreamMetadataHolder(/* flacStreamMetadata= */ null); + ExtractorInput input = + buildExtractorInputReadingFromFirstFrame( + "flac/bear_one_metadata_block.flac", streamMetadataHolder); + int frameStartMarker = FlacMetadataReader.getFrameStartMarker(input); + // Skip first frame. + input.skip(5030); + ParsableByteArray scratch = new ParsableByteArray(FlacConstants.MAX_FRAME_HEADER_SIZE); + input.read(scratch.data, 0, FlacConstants.MAX_FRAME_HEADER_SIZE); + SampleNumberHolder sampleNumberHolder = new SampleNumberHolder(); + + FlacFrameReader.checkAndReadFrameHeader( + scratch, streamMetadataHolder.flacStreamMetadata, frameStartMarker, sampleNumberHolder); + + assertThat(sampleNumberHolder.sampleNumber).isEqualTo(4096); + } + + @Test + public void checkAndReadFrameHeader_invalidData_isFalse() throws Exception { + FlacStreamMetadataHolder streamMetadataHolder = + new FlacStreamMetadataHolder(/* flacStreamMetadata= */ null); + ExtractorInput input = + buildExtractorInputReadingFromFirstFrame( + "flac/bear_one_metadata_block.flac", streamMetadataHolder); + ParsableByteArray scratch = new ParsableByteArray(FlacConstants.MAX_FRAME_HEADER_SIZE); + input.read(scratch.data, 0, FlacConstants.MAX_FRAME_HEADER_SIZE); + + // The first bytes of the frame are not equal to the frame start marker. + boolean result = + FlacFrameReader.checkAndReadFrameHeader( + scratch, + streamMetadataHolder.flacStreamMetadata, + /* frameStartMarker= */ -1, + new SampleNumberHolder()); + + assertThat(result).isFalse(); + } + + @Test + public void checkFrameHeaderFromPeek_validData_doesNotUpdatePositions() throws Exception { + String file = "flac/bear_one_metadata_block.flac"; + FlacStreamMetadataHolder streamMetadataHolder = + new FlacStreamMetadataHolder(/* flacStreamMetadata= */ null); + ExtractorInput input = buildExtractorInputReadingFromFirstFrame(file, streamMetadataHolder); + int frameStartMarker = FlacMetadataReader.getFrameStartMarker(input); + long peekPosition = input.getPosition(); + // Set read position to 0. + input = buildExtractorInput(file); + input.advancePeekPosition((int) peekPosition); + + FlacFrameReader.checkFrameHeaderFromPeek( + input, streamMetadataHolder.flacStreamMetadata, frameStartMarker, new SampleNumberHolder()); + + assertThat(input.getPosition()).isEqualTo(0); + assertThat(input.getPeekPosition()).isEqualTo(peekPosition); + } + + @Test + public void checkFrameHeaderFromPeek_validData_isTrue() throws Exception { + FlacStreamMetadataHolder streamMetadataHolder = + new FlacStreamMetadataHolder(/* flacStreamMetadata= */ null); + ExtractorInput input = + buildExtractorInputReadingFromFirstFrame( + "flac/bear_one_metadata_block.flac", streamMetadataHolder); + int frameStartMarker = FlacMetadataReader.getFrameStartMarker(input); + + boolean result = + FlacFrameReader.checkFrameHeaderFromPeek( + input, + streamMetadataHolder.flacStreamMetadata, + frameStartMarker, + new SampleNumberHolder()); + + assertThat(result).isTrue(); + } + + @Test + public void checkFrameHeaderFromPeek_validData_writesSampleNumber() throws Exception { + FlacStreamMetadataHolder streamMetadataHolder = + new FlacStreamMetadataHolder(/* flacStreamMetadata= */ null); + ExtractorInput input = + buildExtractorInputReadingFromFirstFrame( + "flac/bear_one_metadata_block.flac", streamMetadataHolder); + int frameStartMarker = FlacMetadataReader.getFrameStartMarker(input); + // Skip first frame. + input.skip(5030); + SampleNumberHolder sampleNumberHolder = new SampleNumberHolder(); + + FlacFrameReader.checkFrameHeaderFromPeek( + input, streamMetadataHolder.flacStreamMetadata, frameStartMarker, sampleNumberHolder); + + assertThat(sampleNumberHolder.sampleNumber).isEqualTo(4096); + } + + @Test + public void checkFrameHeaderFromPeek_invalidData_isFalse() throws Exception { + FlacStreamMetadataHolder streamMetadataHolder = + new FlacStreamMetadataHolder(/* flacStreamMetadata= */ null); + ExtractorInput input = + buildExtractorInputReadingFromFirstFrame( + "flac/bear_one_metadata_block.flac", streamMetadataHolder); + + // The first bytes of the frame are not equal to the frame start marker. + boolean result = + FlacFrameReader.checkFrameHeaderFromPeek( + input, + streamMetadataHolder.flacStreamMetadata, + /* frameStartMarker= */ -1, + new SampleNumberHolder()); + + assertThat(result).isFalse(); + } + + @Test + public void checkFrameHeaderFromPeek_invalidData_doesNotUpdatePositions() throws Exception { + String file = "flac/bear_one_metadata_block.flac"; + FlacStreamMetadataHolder streamMetadataHolder = + new FlacStreamMetadataHolder(/* flacStreamMetadata= */ null); + ExtractorInput input = buildExtractorInputReadingFromFirstFrame(file, streamMetadataHolder); + long peekPosition = input.getPosition(); + // Set read position to 0. + input = buildExtractorInput(file); + input.advancePeekPosition((int) peekPosition); + + // The first bytes of the frame are not equal to the frame start marker. + FlacFrameReader.checkFrameHeaderFromPeek( + input, + streamMetadataHolder.flacStreamMetadata, + /* frameStartMarker= */ -1, + new SampleNumberHolder()); + + assertThat(input.getPosition()).isEqualTo(0); + assertThat(input.getPeekPosition()).isEqualTo(peekPosition); + } + + @Test + public void getFirstSampleNumber_doesNotUpdateReadPositionAndAlignsPeekPosition() + throws Exception { + FlacStreamMetadataHolder streamMetadataHolder = + new FlacStreamMetadataHolder(/* flacStreamMetadata= */ null); + ExtractorInput input = + buildExtractorInputReadingFromFirstFrame( + "flac/bear_one_metadata_block.flac", streamMetadataHolder); + long initialReadPosition = input.getPosition(); + // Advance peek position after block size bits. + input.advancePeekPosition(FlacConstants.MAX_FRAME_HEADER_SIZE); + + FlacFrameReader.getFirstSampleNumber(input, streamMetadataHolder.flacStreamMetadata); + + assertThat(input.getPosition()).isEqualTo(initialReadPosition); + assertThat(input.getPeekPosition()).isEqualTo(input.getPosition()); + } + + @Test + public void getFirstSampleNumber_returnsSampleNumber() throws Exception { + FlacStreamMetadataHolder streamMetadataHolder = + new FlacStreamMetadataHolder(/* flacStreamMetadata= */ null); + ExtractorInput input = + buildExtractorInputReadingFromFirstFrame( + "flac/bear_one_metadata_block.flac", streamMetadataHolder); + // Skip first frame. + input.skip(5030); + + long result = + FlacFrameReader.getFirstSampleNumber(input, streamMetadataHolder.flacStreamMetadata); + + assertThat(result).isEqualTo(4096); + } + + @Test + public void readFrameBlockSizeSamplesFromKey_keyIs1_returnsCorrectBlockSize() { + int result = + FlacFrameReader.readFrameBlockSizeSamplesFromKey( + new ParsableByteArray(/* limit= */ 0), /* blockSizeKey= */ 1); + + assertThat(result).isEqualTo(192); + } + + @Test + public void readFrameBlockSizeSamplesFromKey_keyBetween2and5_returnsCorrectBlockSize() { + int result = + FlacFrameReader.readFrameBlockSizeSamplesFromKey( + new ParsableByteArray(/* limit= */ 0), /* blockSizeKey= */ 3); + + assertThat(result).isEqualTo(1152); + } + + @Test + public void readFrameBlockSizeSamplesFromKey_keyBetween6And7_returnsCorrectBlockSize() + throws Exception { + ExtractorInput input = buildExtractorInput("flac/bear_one_metadata_block.flac"); + // Skip to block size bits of last frame. + input.skipFully(164033); + ParsableByteArray scratch = new ParsableByteArray(2); + input.readFully(scratch.data, 0, 2); + + int result = FlacFrameReader.readFrameBlockSizeSamplesFromKey(scratch, /* blockSizeKey= */ 7); + + assertThat(result).isEqualTo(496); + } + + @Test + public void readFrameBlockSizeSamplesFromKey_keyBetween8and15_returnsCorrectBlockSize() { + int result = + FlacFrameReader.readFrameBlockSizeSamplesFromKey( + new ParsableByteArray(/* limit= */ 0), /* blockSizeKey= */ 11); + + assertThat(result).isEqualTo(2048); + } + + @Test + public void readFrameBlockSizeSamplesFromKey_invalidKey_returnsCorrectBlockSize() { + int result = + FlacFrameReader.readFrameBlockSizeSamplesFromKey( + new ParsableByteArray(/* limit= */ 0), /* blockSizeKey= */ 25); + + assertThat(result).isEqualTo(-1); + } + + private static ExtractorInput buildExtractorInput(String file) throws IOException { + byte[] fileData = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), file); + return new FakeExtractorInput.Builder().setData(fileData).build(); + } + + private ExtractorInput buildExtractorInputReadingFromFirstFrame( + String file, FlacStreamMetadataHolder streamMetadataHolder) + throws IOException, InterruptedException { + ExtractorInput input = buildExtractorInput(file); + + input.skipFully(FlacConstants.STREAM_MARKER_SIZE); + + boolean lastMetadataBlock = false; + while (!lastMetadataBlock) { + lastMetadataBlock = FlacMetadataReader.readMetadataBlock(input, streamMetadataHolder); + } + + return input; + } +} diff --git a/library/core/src/test/java/com/google/android/exoplayer2/extractor/FlacMetadataReaderTest.java b/library/core/src/test/java/com/google/android/exoplayer2/extractor/FlacMetadataReaderTest.java new file mode 100644 index 0000000000..390e806807 --- /dev/null +++ b/library/core/src/test/java/com/google/android/exoplayer2/extractor/FlacMetadataReaderTest.java @@ -0,0 +1,408 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.extractor; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.android.exoplayer2.ParserException; +import com.google.android.exoplayer2.extractor.FlacMetadataReader.FlacStreamMetadataHolder; +import com.google.android.exoplayer2.metadata.Metadata; +import com.google.android.exoplayer2.metadata.flac.PictureFrame; +import com.google.android.exoplayer2.metadata.flac.VorbisComment; +import com.google.android.exoplayer2.testutil.FakeExtractorInput; +import com.google.android.exoplayer2.testutil.TestUtil; +import com.google.android.exoplayer2.util.FlacConstants; +import com.google.android.exoplayer2.util.FlacStreamMetadata; +import com.google.android.exoplayer2.util.ParsableByteArray; +import java.io.IOException; +import java.util.ArrayList; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Unit tests for {@link FlacMetadataReader}. + * + *

Most expected results in these tests have been retrieved using the metaflac command. + */ +@RunWith(AndroidJUnit4.class) +public class FlacMetadataReaderTest { + + @Test + public void peekId3Metadata_updatesPeekPosition() throws Exception { + ExtractorInput input = buildExtractorInput("flac/bear_with_id3_enabled.flac"); + + FlacMetadataReader.peekId3Metadata(input, /* parseData= */ false); + + assertThat(input.getPosition()).isEqualTo(0); + assertThat(input.getPeekPosition()).isNotEqualTo(0); + } + + @Test + public void peekId3Metadata_parseData_returnsNonEmptyMetadata() throws Exception { + ExtractorInput input = buildExtractorInput("flac/bear_with_id3_enabled.flac"); + + Metadata metadata = FlacMetadataReader.peekId3Metadata(input, /* parseData= */ true); + + assertThat(metadata).isNotNull(); + assertThat(metadata.length()).isNotEqualTo(0); + } + + @Test + public void peekId3Metadata_doNotParseData_returnsNull() throws Exception { + ExtractorInput input = buildExtractorInput("flac/bear_with_id3_enabled.flac"); + + Metadata metadata = FlacMetadataReader.peekId3Metadata(input, /* parseData= */ false); + + assertThat(metadata).isNull(); + } + + @Test + public void peekId3Metadata_noId3Metadata_returnsNull() throws Exception { + String fileWithoutId3Metadata = "flac/bear.flac"; + ExtractorInput input = buildExtractorInput(fileWithoutId3Metadata); + + Metadata metadata = FlacMetadataReader.peekId3Metadata(input, /* parseData= */ true); + + assertThat(metadata).isNull(); + } + + @Test + public void checkAndPeekStreamMarker_updatesPeekPosition() throws Exception { + ExtractorInput input = buildExtractorInput("flac/bear.flac"); + + FlacMetadataReader.checkAndPeekStreamMarker(input); + + assertThat(input.getPosition()).isEqualTo(0); + assertThat(input.getPeekPosition()).isEqualTo(FlacConstants.STREAM_MARKER_SIZE); + } + + @Test + public void checkAndPeekStreamMarker_validData_isTrue() throws Exception { + ExtractorInput input = buildExtractorInput("flac/bear.flac"); + + boolean result = FlacMetadataReader.checkAndPeekStreamMarker(input); + + assertThat(result).isTrue(); + } + + @Test + public void checkAndPeekStreamMarker_invalidData_isFalse() throws Exception { + ExtractorInput input = buildExtractorInput("mp3/bear.mp3"); + + boolean result = FlacMetadataReader.checkAndPeekStreamMarker(input); + + assertThat(result).isFalse(); + } + + @Test + public void readId3Metadata_updatesReadPositionAndAlignsPeekPosition() throws Exception { + ExtractorInput input = buildExtractorInput("flac/bear_with_id3_enabled.flac"); + // Advance peek position after ID3 metadata. + FlacMetadataReader.peekId3Metadata(input, /* parseData= */ false); + input.advancePeekPosition(1); + + FlacMetadataReader.readId3Metadata(input, /* parseData= */ false); + + assertThat(input.getPosition()).isNotEqualTo(0); + assertThat(input.getPeekPosition()).isEqualTo(input.getPosition()); + } + + @Test + public void readId3Metadata_parseData_returnsNonEmptyMetadata() throws Exception { + ExtractorInput input = buildExtractorInput("flac/bear_with_id3_enabled.flac"); + + Metadata metadata = FlacMetadataReader.readId3Metadata(input, /* parseData= */ true); + + assertThat(metadata).isNotNull(); + assertThat(metadata.length()).isNotEqualTo(0); + } + + @Test + public void readId3Metadata_doNotParseData_returnsNull() throws Exception { + ExtractorInput input = buildExtractorInput("flac/bear_with_id3_enabled.flac"); + + Metadata metadata = FlacMetadataReader.readId3Metadata(input, /* parseData= */ false); + + assertThat(metadata).isNull(); + } + + @Test + public void readId3Metadata_noId3Metadata_returnsNull() throws Exception { + ExtractorInput input = buildExtractorInput("flac/bear.flac"); + + Metadata metadata = FlacMetadataReader.readId3Metadata(input, /* parseData= */ true); + + assertThat(metadata).isNull(); + } + + @Test + public void readStreamMarker_updatesReadPosition() throws Exception { + ExtractorInput input = buildExtractorInput("flac/bear.flac"); + + FlacMetadataReader.readStreamMarker(input); + + assertThat(input.getPosition()).isEqualTo(FlacConstants.STREAM_MARKER_SIZE); + assertThat(input.getPeekPosition()).isEqualTo(input.getPosition()); + } + + @Test(expected = ParserException.class) + public void readStreamMarker_invalidData_throwsException() throws Exception { + ExtractorInput input = buildExtractorInput("mp3/bear.mp3"); + + FlacMetadataReader.readStreamMarker(input); + } + + @Test + public void readMetadataBlock_updatesReadPositionAndAlignsPeekPosition() throws Exception { + ExtractorInput input = buildExtractorInput("flac/bear.flac"); + input.skipFully(FlacConstants.STREAM_MARKER_SIZE); + // Advance peek position after metadata block. + input.advancePeekPosition(FlacConstants.STREAM_INFO_BLOCK_SIZE + 1); + + FlacMetadataReader.readMetadataBlock( + input, new FlacStreamMetadataHolder(/* flacStreamMetadata= */ null)); + + assertThat(input.getPosition()).isNotEqualTo(0); + assertThat(input.getPeekPosition()).isEqualTo(input.getPosition()); + } + + @Test + public void readMetadataBlock_lastMetadataBlock_isTrue() throws Exception { + ExtractorInput input = buildExtractorInput("flac/bear_one_metadata_block.flac"); + input.skipFully(FlacConstants.STREAM_MARKER_SIZE); + + boolean result = + FlacMetadataReader.readMetadataBlock( + input, new FlacStreamMetadataHolder(/* flacStreamMetadata= */ null)); + + assertThat(result).isTrue(); + } + + @Test + public void readMetadataBlock_notLastMetadataBlock_isFalse() throws Exception { + ExtractorInput input = buildExtractorInput("flac/bear.flac"); + input.skipFully(FlacConstants.STREAM_MARKER_SIZE); + + boolean result = + FlacMetadataReader.readMetadataBlock( + input, new FlacStreamMetadataHolder(/* flacStreamMetadata= */ null)); + + assertThat(result).isFalse(); + } + + @Test + public void readMetadataBlock_streamInfoBlock_setsStreamMetadata() throws Exception { + ExtractorInput input = buildExtractorInput("flac/bear.flac"); + input.skipFully(FlacConstants.STREAM_MARKER_SIZE); + FlacStreamMetadataHolder metadataHolder = + new FlacStreamMetadataHolder(/* flacStreamMetadata= */ null); + + FlacMetadataReader.readMetadataBlock(input, metadataHolder); + + assertThat(metadataHolder.flacStreamMetadata).isNotNull(); + assertThat(metadataHolder.flacStreamMetadata.sampleRate).isEqualTo(48000); + } + + @Test + public void readMetadataBlock_seekTableBlock_updatesStreamMetadata() throws Exception { + ExtractorInput input = buildExtractorInput("flac/bear.flac"); + // Skip to seek table block. + input.skipFully(FlacConstants.STREAM_MARKER_SIZE + FlacConstants.STREAM_INFO_BLOCK_SIZE); + FlacStreamMetadataHolder metadataHolder = new FlacStreamMetadataHolder(buildStreamMetadata()); + long originalSampleRate = metadataHolder.flacStreamMetadata.sampleRate; + + FlacMetadataReader.readMetadataBlock(input, metadataHolder); + + assertThat(metadataHolder.flacStreamMetadata).isNotNull(); + // Check that metadata passed has not been erased. + assertThat(metadataHolder.flacStreamMetadata.sampleRate).isEqualTo(originalSampleRate); + assertThat(metadataHolder.flacStreamMetadata.seekTable).isNotNull(); + assertThat(metadataHolder.flacStreamMetadata.seekTable.pointSampleNumbers.length).isEqualTo(32); + } + + @Test + public void readMetadataBlock_vorbisCommentBlock_updatesStreamMetadata() throws Exception { + ExtractorInput input = buildExtractorInput("flac/bear_with_vorbis_comments.flac"); + // Skip to Vorbis comment block. + input.skipFully(640); + FlacStreamMetadataHolder metadataHolder = new FlacStreamMetadataHolder(buildStreamMetadata()); + long originalSampleRate = metadataHolder.flacStreamMetadata.sampleRate; + + FlacMetadataReader.readMetadataBlock(input, metadataHolder); + + assertThat(metadataHolder.flacStreamMetadata).isNotNull(); + // Check that metadata passed has not been erased. + assertThat(metadataHolder.flacStreamMetadata.sampleRate).isEqualTo(originalSampleRate); + Metadata metadata = + metadataHolder.flacStreamMetadata.getMetadataCopyWithAppendedEntriesFrom(null); + assertThat(metadata).isNotNull(); + VorbisComment vorbisComment = (VorbisComment) metadata.get(0); + assertThat(vorbisComment.key).isEqualTo("TITLE"); + assertThat(vorbisComment.value).isEqualTo("test title"); + } + + @Test + public void readMetadataBlock_pictureBlock_updatesStreamMetadata() throws Exception { + ExtractorInput input = buildExtractorInput("flac/bear_with_picture.flac"); + // Skip to picture block. + input.skipFully(640); + FlacStreamMetadataHolder metadataHolder = new FlacStreamMetadataHolder(buildStreamMetadata()); + long originalSampleRate = metadataHolder.flacStreamMetadata.sampleRate; + + FlacMetadataReader.readMetadataBlock(input, metadataHolder); + + assertThat(metadataHolder.flacStreamMetadata).isNotNull(); + // Check that metadata passed has not been erased. + assertThat(metadataHolder.flacStreamMetadata.sampleRate).isEqualTo(originalSampleRate); + Metadata metadata = + metadataHolder.flacStreamMetadata.getMetadataCopyWithAppendedEntriesFrom(null); + assertThat(metadata).isNotNull(); + PictureFrame pictureFrame = (PictureFrame) metadata.get(0); + assertThat(pictureFrame.pictureType).isEqualTo(3); + assertThat(pictureFrame.mimeType).isEqualTo("image/png"); + assertThat(pictureFrame.description).isEqualTo(""); + assertThat(pictureFrame.width).isEqualTo(371); + assertThat(pictureFrame.height).isEqualTo(320); + assertThat(pictureFrame.depth).isEqualTo(24); + assertThat(pictureFrame.colors).isEqualTo(0); + assertThat(pictureFrame.pictureData).hasLength(30943); + } + + @Test + public void readMetadataBlock_blockToSkip_updatesReadPosition() throws Exception { + ExtractorInput input = buildExtractorInput("flac/bear.flac"); + // Skip to padding block. + input.skipFully(640); + FlacStreamMetadataHolder metadataHolder = new FlacStreamMetadataHolder(buildStreamMetadata()); + + FlacMetadataReader.readMetadataBlock(input, metadataHolder); + + assertThat(input.getPosition()).isGreaterThan(640); + assertThat(input.getPeekPosition()).isEqualTo(input.getPosition()); + } + + @Test(expected = IllegalArgumentException.class) + public void readMetadataBlock_nonStreamInfoBlockWithNullStreamMetadata_throwsException() + throws Exception { + ExtractorInput input = buildExtractorInput("flac/bear.flac"); + // Skip to seek table block. + input.skipFully(FlacConstants.STREAM_MARKER_SIZE + FlacConstants.STREAM_INFO_BLOCK_SIZE); + + FlacMetadataReader.readMetadataBlock( + input, new FlacStreamMetadataHolder(/* flacStreamMetadata= */ null)); + } + + @Test + public void readSeekTableMetadataBlock_updatesPosition() throws Exception { + ExtractorInput input = buildExtractorInput("flac/bear.flac"); + // Skip to seek table block. + input.skipFully(FlacConstants.STREAM_MARKER_SIZE + FlacConstants.STREAM_INFO_BLOCK_SIZE); + int seekTableBlockSize = 598; + ParsableByteArray scratch = new ParsableByteArray(seekTableBlockSize); + input.read(scratch.data, 0, seekTableBlockSize); + + FlacMetadataReader.readSeekTableMetadataBlock(scratch); + + assertThat(scratch.getPosition()).isEqualTo(seekTableBlockSize); + } + + @Test + public void readSeekTableMetadataBlock_returnsCorrectSeekPoints() throws Exception { + ExtractorInput input = buildExtractorInput("flac/bear.flac"); + // Skip to seek table block. + input.skipFully(FlacConstants.STREAM_MARKER_SIZE + FlacConstants.STREAM_INFO_BLOCK_SIZE); + int seekTableBlockSize = 598; + ParsableByteArray scratch = new ParsableByteArray(seekTableBlockSize); + input.read(scratch.data, 0, seekTableBlockSize); + + FlacStreamMetadata.SeekTable seekTable = FlacMetadataReader.readSeekTableMetadataBlock(scratch); + + assertThat(seekTable.pointOffsets[0]).isEqualTo(0); + assertThat(seekTable.pointSampleNumbers[0]).isEqualTo(0); + assertThat(seekTable.pointOffsets[31]).isEqualTo(160602); + assertThat(seekTable.pointSampleNumbers[31]).isEqualTo(126976); + } + + @Test + public void readSeekTableMetadataBlock_ignoresPlaceholders() throws IOException { + byte[] fileData = + TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), "flac/bear.flac"); + ParsableByteArray scratch = new ParsableByteArray(fileData); + // Skip to seek table block. + scratch.skipBytes(FlacConstants.STREAM_MARKER_SIZE + FlacConstants.STREAM_INFO_BLOCK_SIZE); + + FlacStreamMetadata.SeekTable seekTable = FlacMetadataReader.readSeekTableMetadataBlock(scratch); + + // Seek point at index 32 is a placeholder. + assertThat(seekTable.pointSampleNumbers).hasLength(32); + } + + @Test + public void getFrameStartMarker_doesNotUpdateReadPositionAndAlignsPeekPosition() + throws Exception { + ExtractorInput input = buildExtractorInput("flac/bear.flac"); + int firstFramePosition = 8880; + input.skipFully(firstFramePosition); + // Advance the peek position after the frame start marker. + input.advancePeekPosition(3); + + FlacMetadataReader.getFrameStartMarker(input); + + assertThat(input.getPosition()).isEqualTo(firstFramePosition); + assertThat(input.getPeekPosition()).isEqualTo(input.getPosition()); + } + + @Test + public void getFrameStartMarker_returnsCorrectFrameStartMarker() throws Exception { + ExtractorInput input = buildExtractorInput("flac/bear.flac"); + // Skip to first frame. + input.skipFully(8880); + + int result = FlacMetadataReader.getFrameStartMarker(input); + + assertThat(result).isEqualTo(0xFFF8); + } + + @Test(expected = ParserException.class) + public void getFrameStartMarker_invalidData_throwsException() throws Exception { + ExtractorInput input = buildExtractorInput("flac/bear.flac"); + + // Input position is incorrect. + FlacMetadataReader.getFrameStartMarker(input); + } + + private static ExtractorInput buildExtractorInput(String file) throws IOException { + byte[] fileData = TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), file); + return new FakeExtractorInput.Builder().setData(fileData).build(); + } + + private static FlacStreamMetadata buildStreamMetadata() { + return new FlacStreamMetadata( + /* minBlockSizeSamples= */ 10, + /* maxBlockSizeSamples= */ 20, + /* minFrameSize= */ 5, + /* maxFrameSize= */ 10, + /* sampleRate= */ 44100, + /* channels= */ 2, + /* bitsPerSample= */ 8, + /* totalSamples= */ 1000, + /* vorbisComments= */ new ArrayList<>(), + /* pictureFrames= */ new ArrayList<>()); + } +} diff --git a/library/core/src/test/java/com/google/android/exoplayer2/util/FlacStreamMetadataTest.java b/library/core/src/test/java/com/google/android/exoplayer2/util/FlacStreamMetadataTest.java index ddaa550b7f..d1b0363d20 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/util/FlacStreamMetadataTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/util/FlacStreamMetadataTest.java @@ -17,9 +17,12 @@ package com.google.android.exoplayer2.util; import static com.google.common.truth.Truth.assertThat; +import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.flac.VorbisComment; +import com.google.android.exoplayer2.testutil.TestUtil; +import java.io.IOException; import java.util.ArrayList; import org.junit.Test; import org.junit.runner.RunWith; @@ -28,6 +31,27 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public final class FlacStreamMetadataTest { + @Test + public void constructFromByteArray_setsFieldsCorrectly() throws IOException { + byte[] fileData = + TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), "flac/bear.flac"); + + FlacStreamMetadata streamMetadata = + new FlacStreamMetadata( + fileData, FlacConstants.STREAM_MARKER_SIZE + FlacConstants.METADATA_BLOCK_HEADER_SIZE); + + assertThat(streamMetadata.minBlockSizeSamples).isEqualTo(4096); + assertThat(streamMetadata.maxBlockSizeSamples).isEqualTo(4096); + assertThat(streamMetadata.minFrameSize).isEqualTo(445); + assertThat(streamMetadata.maxFrameSize).isEqualTo(5776); + assertThat(streamMetadata.sampleRate).isEqualTo(48000); + assertThat(streamMetadata.sampleRateLookupKey).isEqualTo(10); + assertThat(streamMetadata.channels).isEqualTo(2); + assertThat(streamMetadata.bitsPerSample).isEqualTo(16); + assertThat(streamMetadata.bitsPerSampleLookupKey).isEqualTo(4); + assertThat(streamMetadata.totalSamples).isEqualTo(131568); + } + @Test public void parseVorbisComments() { ArrayList commentsList = new ArrayList<>();