• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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