1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.google.android.exoplayer2.testutil; 17 18 import static com.google.common.truth.Truth.assertThat; 19 20 import androidx.annotation.Nullable; 21 import com.google.android.exoplayer2.C; 22 import com.google.android.exoplayer2.Format; 23 import com.google.android.exoplayer2.extractor.TrackOutput; 24 import com.google.android.exoplayer2.testutil.Dumper.Dumpable; 25 import com.google.android.exoplayer2.upstream.DataReader; 26 import com.google.android.exoplayer2.util.Assertions; 27 import com.google.android.exoplayer2.util.Function; 28 import com.google.android.exoplayer2.util.ParsableByteArray; 29 import com.google.android.exoplayer2.util.Util; 30 import java.io.EOFException; 31 import java.io.IOException; 32 import java.util.ArrayList; 33 import java.util.Arrays; 34 import java.util.Collections; 35 import java.util.List; 36 import org.checkerframework.checker.nullness.compatqual.NullableType; 37 38 /** 39 * A fake {@link TrackOutput}. 40 */ 41 public final class FakeTrackOutput implements TrackOutput, Dumper.Dumpable { 42 43 private final ArrayList<DumpableSampleInfo> sampleInfos; 44 private final ArrayList<Dumpable> dumpables; 45 46 private byte[] sampleData; 47 private int formatCount; 48 private boolean receivedSampleInFormat; 49 50 @Nullable public Format lastFormat; 51 FakeTrackOutput()52 public FakeTrackOutput() { 53 sampleInfos = new ArrayList<>(); 54 dumpables = new ArrayList<>(); 55 sampleData = Util.EMPTY_BYTE_ARRAY; 56 formatCount = 0; 57 receivedSampleInFormat = true; 58 } 59 clear()60 public void clear() { 61 sampleInfos.clear(); 62 dumpables.clear(); 63 sampleData = Util.EMPTY_BYTE_ARRAY; 64 formatCount = 0; 65 receivedSampleInFormat = true; 66 } 67 68 @Override format(Format format)69 public void format(Format format) { 70 receivedSampleInFormat = false; 71 addFormat(format); 72 } 73 74 @Override sampleData( DataReader input, int length, boolean allowEndOfInput, @SampleDataPart int sampleDataPart)75 public int sampleData( 76 DataReader input, int length, boolean allowEndOfInput, @SampleDataPart int sampleDataPart) 77 throws IOException { 78 byte[] newData = new byte[length]; 79 int bytesAppended = input.read(newData, 0, length); 80 if (bytesAppended == C.RESULT_END_OF_INPUT) { 81 if (allowEndOfInput) { 82 return C.RESULT_END_OF_INPUT; 83 } 84 throw new EOFException(); 85 } 86 newData = Arrays.copyOf(newData, bytesAppended); 87 sampleData = TestUtil.joinByteArrays(sampleData, newData); 88 return bytesAppended; 89 } 90 91 @Override sampleData(ParsableByteArray data, int length, @SampleDataPart int sampleDataPart)92 public void sampleData(ParsableByteArray data, int length, @SampleDataPart int sampleDataPart) { 93 byte[] newData = new byte[length]; 94 data.readBytes(newData, 0, length); 95 sampleData = TestUtil.joinByteArrays(sampleData, newData); 96 } 97 98 @Override sampleMetadata( long timeUs, @C.BufferFlags int flags, int size, int offset, @Nullable CryptoData cryptoData)99 public void sampleMetadata( 100 long timeUs, 101 @C.BufferFlags int flags, 102 int size, 103 int offset, 104 @Nullable CryptoData cryptoData) { 105 receivedSampleInFormat = true; 106 if (lastFormat == null) { 107 throw new IllegalStateException("TrackOutput must receive format before sampleMetadata"); 108 } 109 if (lastFormat.maxInputSize != Format.NO_VALUE && size > lastFormat.maxInputSize) { 110 throw new IllegalStateException("Sample size exceeds Format.maxInputSize"); 111 } 112 if (dumpables.isEmpty()) { 113 addFormat(lastFormat); 114 } 115 addSampleInfo( 116 timeUs, flags, sampleData.length - offset - size, sampleData.length - offset, cryptoData); 117 } 118 assertSampleCount(int count)119 public void assertSampleCount(int count) { 120 assertThat(sampleInfos).hasSize(count); 121 } 122 assertSample( int index, byte[] data, long timeUs, int flags, @Nullable CryptoData cryptoData)123 public void assertSample( 124 int index, byte[] data, long timeUs, int flags, @Nullable CryptoData cryptoData) { 125 byte[] actualData = getSampleData(index); 126 assertThat(actualData).isEqualTo(data); 127 assertThat(getSampleTimeUs(index)).isEqualTo(timeUs); 128 assertThat(getSampleFlags(index)).isEqualTo(flags); 129 assertThat(getSampleCryptoData(index)).isEqualTo(cryptoData); 130 } 131 getSampleData(int index)132 public byte[] getSampleData(int index) { 133 return Arrays.copyOfRange(sampleData, getSampleStartOffset(index), getSampleEndOffset(index)); 134 } 135 getSampleData(int fromIndex, int toIndex)136 private byte[] getSampleData(int fromIndex, int toIndex) { 137 return Arrays.copyOfRange(sampleData, fromIndex, toIndex); 138 } 139 getSampleTimeUs(int index)140 public long getSampleTimeUs(int index) { 141 return sampleInfos.get(index).timeUs; 142 } 143 getSampleFlags(int index)144 public int getSampleFlags(int index) { 145 return sampleInfos.get(index).flags; 146 } 147 148 @Nullable getSampleCryptoData(int index)149 public CryptoData getSampleCryptoData(int index) { 150 return sampleInfos.get(index).cryptoData; 151 } 152 getSampleCount()153 public int getSampleCount() { 154 return sampleInfos.size(); 155 } 156 getSampleTimesUs()157 public List<Long> getSampleTimesUs() { 158 List<Long> sampleTimesUs = new ArrayList<>(); 159 for (DumpableSampleInfo sampleInfo : sampleInfos) { 160 sampleTimesUs.add(sampleInfo.timeUs); 161 } 162 return Collections.unmodifiableList(sampleTimesUs); 163 } 164 165 @Override dump(Dumper dumper)166 public void dump(Dumper dumper) { 167 dumper.add("total output bytes", sampleData.length); 168 dumper.add("sample count", sampleInfos.size()); 169 if (dumpables.isEmpty() && lastFormat != null) { 170 new DumpableFormat(lastFormat, 0).dump(dumper); 171 } 172 for (int i = 0; i < dumpables.size(); i++) { 173 dumpables.get(i).dump(dumper); 174 } 175 } 176 getSampleStartOffset(int index)177 private int getSampleStartOffset(int index) { 178 return sampleInfos.get(index).startOffset; 179 } 180 getSampleEndOffset(int index)181 private int getSampleEndOffset(int index) { 182 return sampleInfos.get(index).endOffset; 183 } 184 addFormat(Format format)185 private void addFormat(Format format) { 186 lastFormat = format; 187 dumpables.add(new DumpableFormat(format, formatCount)); 188 formatCount++; 189 } 190 addSampleInfo( long timeUs, int flags, int startOffset, int endOffset, @Nullable CryptoData cryptoData)191 private void addSampleInfo( 192 long timeUs, int flags, int startOffset, int endOffset, @Nullable CryptoData cryptoData) { 193 DumpableSampleInfo sampleInfo = 194 new DumpableSampleInfo(timeUs, flags, startOffset, endOffset, cryptoData, getSampleCount()); 195 sampleInfos.add(sampleInfo); 196 dumpables.add(sampleInfo); 197 } 198 199 private final class DumpableSampleInfo implements Dumper.Dumpable { 200 public final long timeUs; 201 public final int flags; 202 public final int startOffset; 203 public final int endOffset; 204 @Nullable public final CryptoData cryptoData; 205 public final int index; 206 DumpableSampleInfo( long timeUs, int flags, int startOffset, int endOffset, @Nullable CryptoData cryptoData, int index)207 public DumpableSampleInfo( 208 long timeUs, 209 int flags, 210 int startOffset, 211 int endOffset, 212 @Nullable CryptoData cryptoData, 213 int index) { 214 this.timeUs = timeUs; 215 this.flags = flags; 216 this.startOffset = startOffset; 217 this.endOffset = endOffset; 218 this.cryptoData = cryptoData; 219 this.index = index; 220 } 221 222 @Override dump(Dumper dumper)223 public void dump(Dumper dumper) { 224 dumper 225 .startBlock("sample " + index) 226 .add("time", timeUs) 227 .add("flags", flags) 228 .add("data", getSampleData(startOffset, endOffset)); 229 if (cryptoData != null) { 230 dumper.add("crypto mode", cryptoData.cryptoMode); 231 dumper.add("encryption key", cryptoData.encryptionKey); 232 } 233 dumper.endBlock(); 234 } 235 236 @Override equals(@ullable Object o)237 public boolean equals(@Nullable Object o) { 238 if (this == o) { 239 return true; 240 } 241 if (o == null || getClass() != o.getClass()) { 242 return false; 243 } 244 DumpableSampleInfo that = (DumpableSampleInfo) o; 245 return timeUs == that.timeUs 246 && flags == that.flags 247 && startOffset == that.startOffset 248 && endOffset == that.endOffset 249 && index == that.index 250 && Util.areEqual(cryptoData, that.cryptoData); 251 } 252 253 @Override hashCode()254 public int hashCode() { 255 int result = (int) timeUs; 256 result = 31 * result + flags; 257 result = 31 * result + startOffset; 258 result = 31 * result + endOffset; 259 result = 31 * result + (cryptoData == null ? 0 : cryptoData.hashCode()); 260 result = 31 * result + index; 261 return result; 262 } 263 } 264 265 private static final class DumpableFormat implements Dumper.Dumpable { 266 private final Format format; 267 public final int index; 268 269 private static final Format DEFAULT_FORMAT = new Format.Builder().build(); 270 DumpableFormat(Format format, int index)271 public DumpableFormat(Format format, int index) { 272 this.format = format; 273 this.index = index; 274 } 275 276 @Override dump(Dumper dumper)277 public void dump(Dumper dumper) { 278 dumper.startBlock("format " + index); 279 addIfNonDefault(dumper, "averageBitrate", format -> format.averageBitrate); 280 addIfNonDefault(dumper, "peakBitrate", format -> format.peakBitrate); 281 addIfNonDefault(dumper, "id", format -> format.id); 282 addIfNonDefault(dumper, "containerMimeType", format -> format.containerMimeType); 283 addIfNonDefault(dumper, "sampleMimeType", format -> format.sampleMimeType); 284 addIfNonDefault(dumper, "codecs", format -> format.codecs); 285 addIfNonDefault(dumper, "maxInputSize", format -> format.maxInputSize); 286 addIfNonDefault(dumper, "width", format -> format.width); 287 addIfNonDefault(dumper, "height", format -> format.height); 288 addIfNonDefault(dumper, "frameRate", format -> format.frameRate); 289 addIfNonDefault(dumper, "rotationDegrees", format -> format.rotationDegrees); 290 addIfNonDefault(dumper, "pixelWidthHeightRatio", format -> format.pixelWidthHeightRatio); 291 addIfNonDefault(dumper, "channelCount", format -> format.channelCount); 292 addIfNonDefault(dumper, "sampleRate", format -> format.sampleRate); 293 addIfNonDefault(dumper, "pcmEncoding", format -> format.pcmEncoding); 294 addIfNonDefault(dumper, "encoderDelay", format -> format.encoderDelay); 295 addIfNonDefault(dumper, "encoderPadding", format -> format.encoderPadding); 296 addIfNonDefault(dumper, "subsampleOffsetUs", format -> format.subsampleOffsetUs); 297 addIfNonDefault(dumper, "selectionFlags", format -> format.selectionFlags); 298 addIfNonDefault(dumper, "language", format -> format.language); 299 addIfNonDefault(dumper, "label", format -> format.label); 300 if (format.drmInitData != null) { 301 dumper.add("drmInitData", format.drmInitData.hashCode()); 302 } 303 addIfNonDefault(dumper, "metadata", format -> format.metadata); 304 if (!format.initializationData.isEmpty()) { 305 dumper.startBlock("initializationData"); 306 for (int i = 0; i < format.initializationData.size(); i++) { 307 dumper.add("data", format.initializationData.get(i)); 308 } 309 dumper.endBlock(); 310 } 311 dumper.endBlock(); 312 } 313 314 @Override equals(@ullable Object o)315 public boolean equals(@Nullable Object o) { 316 if (this == o) { 317 return true; 318 } 319 if (o == null || getClass() != o.getClass()) { 320 return false; 321 } 322 DumpableFormat that = (DumpableFormat) o; 323 return index == that.index && format.equals(that.format); 324 } 325 326 @Override hashCode()327 public int hashCode() { 328 int result = format.hashCode(); 329 result = 31 * result + index; 330 return result; 331 } 332 addIfNonDefault( Dumper dumper, String field, Function<Format, @NullableType Object> getFieldFunction)333 private void addIfNonDefault( 334 Dumper dumper, String field, Function<Format, @NullableType Object> getFieldFunction) { 335 @Nullable Object thisValue = getFieldFunction.apply(format); 336 @Nullable Object defaultValue = getFieldFunction.apply(DEFAULT_FORMAT); 337 if (!Util.areEqual(thisValue, defaultValue)) { 338 dumper.add(field, thisValue); 339 } 340 } 341 } 342 } 343