summaryrefslogtreecommitdiffstats
path: root/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/extractor/flac/FlacBinarySearchSeeker.java
blob: d13b1f394dcf8b01e28ea1f322c0a3801883c4d7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/*
 * Copyright (C) 2019 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 org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.flac;

import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.BinarySearchSeeker;
import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.ExtractorInput;
import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.FlacFrameReader;
import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.FlacFrameReader.SampleNumberHolder;
import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.SeekMap;
import org.mozilla.thirdparty.com.google.android.exoplayer2.util.FlacConstants;
import org.mozilla.thirdparty.com.google.android.exoplayer2.util.FlacStreamMetadata;
import java.io.IOException;

/**
 * A {@link SeekMap} implementation for FLAC stream using binary search.
 *
 * <p>This seeker performs seeking by using binary search within the stream, until it finds the
 * frame that contains the target sample.
 */
/* package */ final class FlacBinarySearchSeeker extends BinarySearchSeeker {

  /**
   * Creates a {@link FlacBinarySearchSeeker}.
   *
   * @param flacStreamMetadata The stream metadata.
   * @param frameStartMarker The frame start marker, consisting of the 2 bytes by which every frame
   *     in the stream must start.
   * @param firstFramePosition The byte offset of the first frame in the stream.
   * @param inputLength The length of the stream in bytes.
   */
  public FlacBinarySearchSeeker(
      FlacStreamMetadata flacStreamMetadata,
      int frameStartMarker,
      long firstFramePosition,
      long inputLength) {
    super(
        /* seekTimestampConverter= */ flacStreamMetadata::getSampleNumber,
        new FlacTimestampSeeker(flacStreamMetadata, frameStartMarker),
        flacStreamMetadata.getDurationUs(),
        /* floorTimePosition= */ 0,
        /* ceilingTimePosition= */ flacStreamMetadata.totalSamples,
        /* floorBytePosition= */ firstFramePosition,
        /* ceilingBytePosition= */ inputLength,
        /* approxBytesPerFrame= */ flacStreamMetadata.getApproxBytesPerFrame(),
        /* minimumSearchRange= */ Math.max(
            FlacConstants.MIN_FRAME_HEADER_SIZE, flacStreamMetadata.minFrameSize));
  }

  private static final class FlacTimestampSeeker implements TimestampSeeker {

    private final FlacStreamMetadata flacStreamMetadata;
    private final int frameStartMarker;
    private final SampleNumberHolder sampleNumberHolder;

    private FlacTimestampSeeker(FlacStreamMetadata flacStreamMetadata, int frameStartMarker) {
      this.flacStreamMetadata = flacStreamMetadata;
      this.frameStartMarker = frameStartMarker;
      sampleNumberHolder = new SampleNumberHolder();
    }

    @Override
    public TimestampSearchResult searchForTimestamp(ExtractorInput input, long targetSampleNumber)
        throws IOException, InterruptedException {
      long searchPosition = input.getPosition();

      // Find left frame.
      long leftFrameFirstSampleNumber = findNextFrame(input);
      long leftFramePosition = input.getPeekPosition();

      input.advancePeekPosition(
          Math.max(FlacConstants.MIN_FRAME_HEADER_SIZE, flacStreamMetadata.minFrameSize));

      // Find right frame.
      long rightFrameFirstSampleNumber = findNextFrame(input);
      long rightFramePosition = input.getPeekPosition();

      if (leftFrameFirstSampleNumber <= targetSampleNumber
          && rightFrameFirstSampleNumber > targetSampleNumber) {
        return TimestampSearchResult.targetFoundResult(leftFramePosition);
      } else if (rightFrameFirstSampleNumber <= targetSampleNumber) {
        return TimestampSearchResult.underestimatedResult(
            rightFrameFirstSampleNumber, rightFramePosition);
      } else {
        return TimestampSearchResult.overestimatedResult(
            leftFrameFirstSampleNumber, searchPosition);
      }
    }

    /**
     * Searches for the next frame in {@code input}.
     *
     * <p>The peek position is advanced to the start of the found frame, or at the end of the stream
     * if no frame was found.
     *
     * @param input The input from which to search (starting from the peek position).
     * @return The number of the first sample in the found frame, or the total number of samples in
     *     the stream if no frame was found.
     * @throws IOException If peeking from the input fails. In this case, there is no guarantee on
     *     the peek position.
     * @throws InterruptedException If interrupted while peeking from input. In this case, there is
     *     no guarantee on the peek position.
     */
    private long findNextFrame(ExtractorInput input) throws IOException, InterruptedException {
      while (input.getPeekPosition() < input.getLength() - FlacConstants.MIN_FRAME_HEADER_SIZE
          && !FlacFrameReader.checkFrameHeaderFromPeek(
              input, flacStreamMetadata, frameStartMarker, sampleNumberHolder)) {
        input.advancePeekPosition(1);
      }

      if (input.getPeekPosition() >= input.getLength() - FlacConstants.MIN_FRAME_HEADER_SIZE) {
        input.advancePeekPosition((int) (input.getLength() - input.getPeekPosition()));
        return flacStreamMetadata.totalSamples;
      }

      return sampleNumberHolder.sampleNumber;
    }
  }
}