content: /*
   * 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.extractor.flac.FlacConstants;
  import com.google.android.exoplayer2.testutil.FakeExtractorInput;
  import com.google.android.exoplayer2.testutil.TestUtil;
  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}.
   *
   * <p>Some expected results in these tests have been retrieved using the <a
   * href="https://xiph.org/flac/documentation_tools_flac.html">flac</a> command.
   */
  @RunWith(AndroidJUnit4.class)
  public class FlacFrameReaderTest {
  
    @Test
    public void checkAndReadFrameHeader_validData_updatesPosition() throws Exception {
      FlacStreamMetadataHolder streamMetadataHolder =
          new FlacStreamMetadataHolder(/* flacStreamMetadata= */ null);
      ExtractorInput input =
          buildExtractorInputReadingFromFirstFrame(
              "media/flac/bear_one_metadata_block.flac", streamMetadataHolder);
      int frameStartMarker = FlacMetadataReader.getFrameStartMarker(input);
      ParsableByteArray scratch = new ParsableByteArray(FlacConstants.MAX_FRAME_HEADER_SIZE);
      input.read(scratch.getData(), 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(
              "media/flac/bear_one_metadata_block.flac", streamMetadataHolder);
      int frameStartMarker = FlacMetadataReader.getFrameStartMarker(input);
      ParsableByteArray scratch = new ParsableByteArray(FlacConstants.MAX_FRAME_HEADER_SIZE);
      input.read(scratch.getData(), 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(
              "media/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.getData(), 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(
              "media/flac/bear_one_metadata_block.flac", streamMetadataHolder);
      ParsableByteArray scratch = new ParsableByteArray(FlacConstants.MAX_FRAME_HEADER_SIZE);
      input.read(scratch.getData(), 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 = "media/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(
              "media/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(
              "media/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(
              "media/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 = "media/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(
              "media/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(
              "media/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("media/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.getData(), 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 {
      ExtractorInput input = buildExtractorInput(file);
  
      input.skipFully(FlacConstants.STREAM_MARKER_SIZE);
  
      boolean lastMetadataBlock = false;
      while (!lastMetadataBlock) {
        lastMetadataBlock = FlacMetadataReader.readMetadataBlock(input, streamMetadataHolder);
      }
  
      return input;
    }
  }
  