summaryrefslogtreecommitdiffstats
path: root/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/extractor/ts/PsDurationReader.java
blob: a5960fbe1518e49a9080d87597d663428d4309d2 (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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
/*
 * Copyright (C) 2018 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.ts;

import org.mozilla.thirdparty.com.google.android.exoplayer2.C;
import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.Extractor;
import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.ExtractorInput;
import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.PositionHolder;
import org.mozilla.thirdparty.com.google.android.exoplayer2.util.ParsableByteArray;
import org.mozilla.thirdparty.com.google.android.exoplayer2.util.TimestampAdjuster;
import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util;
import java.io.IOException;

/**
 * A reader that can extract the approximate duration from a given MPEG program stream (PS).
 *
 * <p>This reader extracts the duration by reading system clock reference (SCR) values from the
 * header of a pack at the start and at the end of the stream, calculating the difference, and
 * converting that into stream duration. This reader also handles the case when a single SCR
 * wraparound takes place within the stream, which can make SCR values at the beginning of the
 * stream larger than SCR values at the end. This class can only be used once to read duration from
 * a given stream, and the usage of the class is not thread-safe, so all calls should be made from
 * the same thread.
 *
 * <p>Note: See ISO/IEC 13818-1, Table 2-33 for details of the SCR field in pack_header.
 */
/* package */ final class PsDurationReader {

  private static final int TIMESTAMP_SEARCH_BYTES = 20000;

  private final TimestampAdjuster scrTimestampAdjuster;
  private final ParsableByteArray packetBuffer;

  private boolean isDurationRead;
  private boolean isFirstScrValueRead;
  private boolean isLastScrValueRead;

  private long firstScrValue;
  private long lastScrValue;
  private long durationUs;

  /* package */ PsDurationReader() {
    scrTimestampAdjuster = new TimestampAdjuster(/* firstSampleTimestampUs= */ 0);
    firstScrValue = C.TIME_UNSET;
    lastScrValue = C.TIME_UNSET;
    durationUs = C.TIME_UNSET;
    packetBuffer = new ParsableByteArray();
  }

  /** Returns true if a PS duration has been read. */
  public boolean isDurationReadFinished() {
    return isDurationRead;
  }

  public TimestampAdjuster getScrTimestampAdjuster() {
    return scrTimestampAdjuster;
  }

  /**
   * Reads a PS duration from the input.
   *
   * <p>This reader reads the duration by reading SCR values from the header of a pack at the start
   * and at the end of the stream, calculating the difference, and converting that into stream
   * duration.
   *
   * @param input The {@link ExtractorInput} from which data should be read.
   * @param seekPositionHolder If {@link Extractor#RESULT_SEEK} is returned, this holder is updated
   *     to hold the position of the required seek.
   * @return One of the {@code RESULT_} values defined in {@link Extractor}.
   * @throws IOException If an error occurred reading from the input.
   * @throws InterruptedException If the thread was interrupted.
   */
  public @Extractor.ReadResult int readDuration(
      ExtractorInput input, PositionHolder seekPositionHolder)
      throws IOException, InterruptedException {
    if (!isLastScrValueRead) {
      return readLastScrValue(input, seekPositionHolder);
    }
    if (lastScrValue == C.TIME_UNSET) {
      return finishReadDuration(input);
    }
    if (!isFirstScrValueRead) {
      return readFirstScrValue(input, seekPositionHolder);
    }
    if (firstScrValue == C.TIME_UNSET) {
      return finishReadDuration(input);
    }

    long minScrPositionUs = scrTimestampAdjuster.adjustTsTimestamp(firstScrValue);
    long maxScrPositionUs = scrTimestampAdjuster.adjustTsTimestamp(lastScrValue);
    durationUs = maxScrPositionUs - minScrPositionUs;
    return finishReadDuration(input);
  }

  /** Returns the duration last read from {@link #readDuration(ExtractorInput, PositionHolder)}. */
  public long getDurationUs() {
    return durationUs;
  }

  /**
   * Returns the SCR value read from the next pack in the stream, given the buffer at the pack
   * header start position (just behind the pack start code).
   */
  public static long readScrValueFromPack(ParsableByteArray packetBuffer) {
    int originalPosition = packetBuffer.getPosition();
    if (packetBuffer.bytesLeft() < 9) {
      // We require at 9 bytes for pack header to read scr value
      return C.TIME_UNSET;
    }
    byte[] scrBytes = new byte[9];
    packetBuffer.readBytes(scrBytes, /* offset= */ 0, scrBytes.length);
    packetBuffer.setPosition(originalPosition);
    if (!checkMarkerBits(scrBytes)) {
      return C.TIME_UNSET;
    }
    return readScrValueFromPackHeader(scrBytes);
  }

  private int finishReadDuration(ExtractorInput input) {
    packetBuffer.reset(Util.EMPTY_BYTE_ARRAY);
    isDurationRead = true;
    input.resetPeekPosition();
    return Extractor.RESULT_CONTINUE;
  }

  private int readFirstScrValue(ExtractorInput input, PositionHolder seekPositionHolder)
      throws IOException, InterruptedException {
    int bytesToSearch = (int) Math.min(TIMESTAMP_SEARCH_BYTES, input.getLength());
    int searchStartPosition = 0;
    if (input.getPosition() != searchStartPosition) {
      seekPositionHolder.position = searchStartPosition;
      return Extractor.RESULT_SEEK;
    }

    packetBuffer.reset(bytesToSearch);
    input.resetPeekPosition();
    input.peekFully(packetBuffer.data, /* offset= */ 0, bytesToSearch);

    firstScrValue = readFirstScrValueFromBuffer(packetBuffer);
    isFirstScrValueRead = true;
    return Extractor.RESULT_CONTINUE;
  }

  private long readFirstScrValueFromBuffer(ParsableByteArray packetBuffer) {
    int searchStartPosition = packetBuffer.getPosition();
    int searchEndPosition = packetBuffer.limit();
    for (int searchPosition = searchStartPosition;
        searchPosition < searchEndPosition - 3;
        searchPosition++) {
      int nextStartCode = peekIntAtPosition(packetBuffer.data, searchPosition);
      if (nextStartCode == PsExtractor.PACK_START_CODE) {
        packetBuffer.setPosition(searchPosition + 4);
        long scrValue = readScrValueFromPack(packetBuffer);
        if (scrValue != C.TIME_UNSET) {
          return scrValue;
        }
      }
    }
    return C.TIME_UNSET;
  }

  private int readLastScrValue(ExtractorInput input, PositionHolder seekPositionHolder)
      throws IOException, InterruptedException {
    long inputLength = input.getLength();
    int bytesToSearch = (int) Math.min(TIMESTAMP_SEARCH_BYTES, inputLength);
    long searchStartPosition = inputLength - bytesToSearch;
    if (input.getPosition() != searchStartPosition) {
      seekPositionHolder.position = searchStartPosition;
      return Extractor.RESULT_SEEK;
    }

    packetBuffer.reset(bytesToSearch);
    input.resetPeekPosition();
    input.peekFully(packetBuffer.data, /* offset= */ 0, bytesToSearch);

    lastScrValue = readLastScrValueFromBuffer(packetBuffer);
    isLastScrValueRead = true;
    return Extractor.RESULT_CONTINUE;
  }

  private long readLastScrValueFromBuffer(ParsableByteArray packetBuffer) {
    int searchStartPosition = packetBuffer.getPosition();
    int searchEndPosition = packetBuffer.limit();
    for (int searchPosition = searchEndPosition - 4;
        searchPosition >= searchStartPosition;
        searchPosition--) {
      int nextStartCode = peekIntAtPosition(packetBuffer.data, searchPosition);
      if (nextStartCode == PsExtractor.PACK_START_CODE) {
        packetBuffer.setPosition(searchPosition + 4);
        long scrValue = readScrValueFromPack(packetBuffer);
        if (scrValue != C.TIME_UNSET) {
          return scrValue;
        }
      }
    }
    return C.TIME_UNSET;
  }

  private int peekIntAtPosition(byte[] data, int position) {
    return (data[position] & 0xFF) << 24
        | (data[position + 1] & 0xFF) << 16
        | (data[position + 2] & 0xFF) << 8
        | (data[position + 3] & 0xFF);
  }

  private static boolean checkMarkerBits(byte[] scrBytes) {
    // Verify the 01xxx1xx marker on the 0th byte
    if ((scrBytes[0] & 0xC4) != 0x44) {
      return false;
    }
    // 1st byte belongs to scr field.
    // Verify the xxxxx1xx marker on the 2nd byte
    if ((scrBytes[2] & 0x04) != 0x04) {
      return false;
    }
    // 3rd byte belongs to scr field.
    // Verify the xxxxx1xx marker on the 4rd byte
    if ((scrBytes[4] & 0x04) != 0x04) {
      return false;
    }
    // Verify the xxxxxxx1 marker on the 5th byte
    if ((scrBytes[5] & 0x01) != 0x01) {
      return false;
    }
    // 6th and 7th bytes belongs to program_max_rate field.
    // Verify the xxxxxx11 marker on the 8th byte
    return (scrBytes[8] & 0x03) == 0x03;
  }

  /**
   * Returns the value of SCR base - 33 bits in big endian order from the PS pack header, ignoring
   * the marker bits. Note: See ISO/IEC 13818-1, Table 2-33 for details of the SCR field in
   * pack_header.
   *
   * <p>We ignore SCR Ext, because it's too small to have any significance.
   */
  private static long readScrValueFromPackHeader(byte[] scrBytes) {
    return ((scrBytes[0] & 0b00111000L) >> 3) << 30
        | (scrBytes[0] & 0b00000011L) << 28
        | (scrBytes[1] & 0xFFL) << 20
        | ((scrBytes[2] & 0b11111000L) >> 3) << 15
        | (scrBytes[2] & 0b00000011L) << 13
        | (scrBytes[3] & 0xFFL) << 5
        | (scrBytes[4] & 0b11111000L) >> 3;
  }
}