1 /*
2  * Copyright (C) 2020 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 
17 package android.media.mediaparser.cts;
18 
19 import android.media.MediaCodec;
20 import android.media.MediaFormat;
21 import android.media.MediaParser;
22 import android.util.Pair;
23 
24 import androidx.annotation.Nullable;
25 
26 import com.google.android.exoplayer2.C;
27 import com.google.android.exoplayer2.Format;
28 import com.google.android.exoplayer2.drm.DrmInitData;
29 import com.google.android.exoplayer2.extractor.SeekMap;
30 import com.google.android.exoplayer2.extractor.SeekPoint;
31 import com.google.android.exoplayer2.extractor.TrackOutput;
32 import com.google.android.exoplayer2.testutil.FakeExtractorOutput;
33 import com.google.android.exoplayer2.testutil.FakeTrackOutput;
34 import com.google.android.exoplayer2.upstream.DataReader;
35 import com.google.android.exoplayer2.video.ColorInfo;
36 
37 import java.io.IOException;
38 import java.util.ArrayList;
39 
40 public class MockMediaParserOutputConsumer implements MediaParser.OutputConsumer {
41 
42     private final boolean mUsingInBandCryptoInfo;
43     private final FakeExtractorOutput mFakeExtractorOutput;
44     private final ArrayList<TrackOutput> mTrackOutputs;
45 
46     @Nullable private MediaParser.SeekMap mSeekMap;
47     private int mCompletedSampleCount;
48     @Nullable private MediaCodec.CryptoInfo mLastOutputCryptoInfo;
49 
MockMediaParserOutputConsumer()50     public MockMediaParserOutputConsumer() {
51         this(/* usingInBandCryptoInfo= */ false);
52     }
53 
MockMediaParserOutputConsumer(boolean usingInBandCryptoInfo)54     public MockMediaParserOutputConsumer(boolean usingInBandCryptoInfo) {
55         mUsingInBandCryptoInfo = usingInBandCryptoInfo;
56         mFakeExtractorOutput =
57                 new FakeExtractorOutput(
58                         /* trackOutputFactory= */ (id, type) ->
59                                 new FakeTrackOutput(/* deduplicateConsecutiveFormats= */ true));
60         mTrackOutputs = new ArrayList<>();
61     }
62 
getCompletedSampleCount()63     public int getCompletedSampleCount() {
64         return mCompletedSampleCount;
65     }
66 
67     @Nullable
getLastOutputCryptoInfo()68     public MediaCodec.CryptoInfo getLastOutputCryptoInfo() {
69         return mLastOutputCryptoInfo;
70     }
71 
clearTrackOutputs()72     public void clearTrackOutputs() {
73         mFakeExtractorOutput.clearTrackOutputs();
74     }
75 
76     // OutputConsumer implementation.
77 
78     @Override
onSeekMapFound(MediaParser.SeekMap seekMap)79     public void onSeekMapFound(MediaParser.SeekMap seekMap) {
80         mSeekMap = seekMap;
81         mFakeExtractorOutput.seekMap(
82                 new SeekMap() {
83                     @Override
84                     public boolean isSeekable() {
85                         return seekMap.isSeekable();
86                     }
87 
88                     @Override
89                     public long getDurationUs() {
90                         long durationUs = seekMap.getDurationMicros();
91                         return durationUs != MediaParser.SeekMap.UNKNOWN_DURATION
92                                 ? durationUs
93                                 : C.TIME_UNSET;
94                     }
95 
96                     @Override
97                     public SeekPoints getSeekPoints(long timeUs) {
98                         return toExoPlayerSeekPoints(seekMap.getSeekPoints(timeUs));
99                     }
100                 });
101     }
102 
103     @Override
onTrackCountFound(int numberOfTracks)104     public void onTrackCountFound(int numberOfTracks) {
105         // Do nothing.
106     }
107 
108     @Override
onTrackDataFound(int trackIndex, MediaParser.TrackData trackData)109     public void onTrackDataFound(int trackIndex, MediaParser.TrackData trackData) {
110         while (mTrackOutputs.size() <= trackIndex) {
111             mTrackOutputs.add(mFakeExtractorOutput.track(trackIndex, C.TRACK_TYPE_UNKNOWN));
112         }
113         mTrackOutputs.get(trackIndex).format(toExoPlayerFormat(trackData));
114     }
115 
116     @Override
onSampleDataFound(int trackIndex, MediaParser.InputReader inputReader)117     public void onSampleDataFound(int trackIndex, MediaParser.InputReader inputReader)
118             throws IOException {
119         mFakeExtractorOutput
120                 .track(trackIndex, C.TRACK_TYPE_UNKNOWN)
121                 .sampleData(
122                         new DataReaderAdapter(inputReader), (int) inputReader.getLength(), false);
123     }
124 
125     @Override
onSampleCompleted( int trackIndex, long timeUs, int flags, int size, int offset, @Nullable MediaCodec.CryptoInfo cryptoInfo)126     public void onSampleCompleted(
127             int trackIndex,
128             long timeUs,
129             int flags,
130             int size,
131             int offset,
132             @Nullable MediaCodec.CryptoInfo cryptoInfo) {
133         mCompletedSampleCount++;
134         if (!mUsingInBandCryptoInfo) {
135             mLastOutputCryptoInfo = cryptoInfo;
136         }
137     }
138 
139     // Internal methods.
140 
toExoPlayerSeekPoints( Pair<MediaParser.SeekPoint, MediaParser.SeekPoint> seekPoints)141     private static SeekMap.SeekPoints toExoPlayerSeekPoints(
142             Pair<MediaParser.SeekPoint, MediaParser.SeekPoint> seekPoints) {
143         return new SeekMap.SeekPoints(
144                 toExoPlayerSeekPoint(seekPoints.first), toExoPlayerSeekPoint(seekPoints.second));
145     }
146 
toExoPlayerSeekPoint(MediaParser.SeekPoint seekPoint)147     private static SeekPoint toExoPlayerSeekPoint(MediaParser.SeekPoint seekPoint) {
148         return new SeekPoint(seekPoint.timeMicros, seekPoint.position);
149     }
150 
toExoPlayerFormat(MediaParser.TrackData trackData)151     private static Format toExoPlayerFormat(MediaParser.TrackData trackData) {
152         MediaFormat mediaFormat = trackData.mediaFormat;
153         String sampleMimeType =
154                 mediaFormat.getString(MediaFormat.KEY_MIME, /* defaultValue= */ null);
155         String id =
156                 mediaFormat.containsKey(MediaFormat.KEY_TRACK_ID)
157                         ? String.valueOf(mediaFormat.getInteger(MediaFormat.KEY_TRACK_ID))
158                         : null;
159         String codecs =
160                 mediaFormat.getString(MediaFormat.KEY_CODECS_STRING, /* defaultValue= */ null);
161         int bitrate =
162                 mediaFormat.getInteger(
163                         MediaFormat.KEY_BIT_RATE, /* defaultValue= */ Format.NO_VALUE);
164         int maxInputSize =
165                 mediaFormat.getInteger(
166                         MediaFormat.KEY_MAX_INPUT_SIZE, /* defaultValue= */ Format.NO_VALUE);
167         int width =
168                 mediaFormat.getInteger(MediaFormat.KEY_WIDTH, /* defaultValue= */ Format.NO_VALUE);
169         int height =
170                 mediaFormat.getInteger(MediaFormat.KEY_HEIGHT, /* defaultValue= */ Format.NO_VALUE);
171         float frameRate =
172                 mediaFormat.getFloat(
173                         MediaFormat.KEY_FRAME_RATE, /* defaultValue= */ Format.NO_VALUE);
174         int rotationDegrees =
175                 mediaFormat.getInteger(
176                         MediaFormat.KEY_ROTATION, /* defaultValue= */ Format.NO_VALUE);
177         ArrayList<byte[]> initData = null;
178         if (mediaFormat.containsKey("csd-0")) {
179             initData = new ArrayList<>();
180             int index = 0;
181             while (mediaFormat.containsKey("csd-" + index)) {
182                 initData.add(mediaFormat.getByteBuffer("csd-" + index++).array());
183             }
184         }
185         float pixelAspectWidth =
186                 (float)
187                         mediaFormat.getInteger(
188                                 MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH, /* defaultValue= */ 0);
189         float pixelAspectHeight =
190                 (float)
191                         mediaFormat.getInteger(
192                                 MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT, /* defaultValue= */ 0);
193         float pixelAspectRatio =
194                 pixelAspectHeight == 0 || pixelAspectWidth == 0
195                         ? Format.NO_VALUE
196                         : pixelAspectWidth / pixelAspectHeight;
197         ColorInfo colorInfo = getExoPlayerColorInfo(mediaFormat);
198         DrmInitData drmInitData =
199                 getExoPlayerDrmInitData(
200                         mediaFormat.getString("crypto-mode-fourcc"), trackData.drmInitData);
201 
202         int selectionFlags =
203                 mediaFormat.getInteger(MediaFormat.KEY_IS_AUTOSELECT, /* defaultValue= */ 0) != 0
204                         ? C.SELECTION_FLAG_AUTOSELECT
205                         : 0;
206         selectionFlags |=
207                 mediaFormat.getInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, /* defaultValue= */ 0)
208                                 != 0
209                         ? C.SELECTION_FLAG_FORCED
210                         : 0;
211         selectionFlags |=
212                 mediaFormat.getInteger(MediaFormat.KEY_IS_DEFAULT, /* defaultValue= */ 0) != 0
213                         ? C.SELECTION_FLAG_DEFAULT
214                         : 0;
215 
216         String language = mediaFormat.getString(MediaFormat.KEY_LANGUAGE, /* defaultValue= */ null);
217         int channels =
218                 mediaFormat.getInteger(
219                         MediaFormat.KEY_CHANNEL_COUNT, /* defaultValue= */ Format.NO_VALUE);
220         int sampleRate =
221                 mediaFormat.getInteger(
222                         MediaFormat.KEY_SAMPLE_RATE, /* defaultValue= */ Format.NO_VALUE);
223         int accessibilityChannel =
224                 mediaFormat.getInteger(
225                         MediaFormat.KEY_CAPTION_SERVICE_NUMBER,
226                         /* defaultValue= */ Format.NO_VALUE);
227 
228         return new Format.Builder()
229                 .setId(id)
230                 .setSampleMimeType(sampleMimeType)
231                 .setCodecs(codecs)
232                 .setPeakBitrate(bitrate)
233                 .setMaxInputSize(maxInputSize)
234                 .setWidth(width)
235                 .setHeight(height)
236                 .setFrameRate(frameRate)
237                 .setInitializationData(initData)
238                 .setRotationDegrees(rotationDegrees)
239                 .setPixelWidthHeightRatio(pixelAspectRatio)
240                 .setColorInfo(colorInfo)
241                 .setDrmInitData(drmInitData)
242                 .setChannelCount(channels)
243                 .setSampleRate(sampleRate)
244                 .setSelectionFlags(selectionFlags)
245                 .setLanguage(language)
246                 .setAccessibilityChannel(accessibilityChannel)
247                 .build();
248     }
249 
250     @Nullable
getExoPlayerDrmInitData( @ullable String encryptionScheme, @Nullable android.media.DrmInitData drmInitData)251     private static DrmInitData getExoPlayerDrmInitData(
252             @Nullable String encryptionScheme, @Nullable android.media.DrmInitData drmInitData) {
253         if (drmInitData == null) {
254             return null;
255         }
256         DrmInitData.SchemeData[] schemeDatas =
257                 new DrmInitData.SchemeData[drmInitData.getSchemeInitDataCount()];
258         for (int i = 0; i < schemeDatas.length; i++) {
259             android.media.DrmInitData.SchemeInitData schemeInitData =
260                     drmInitData.getSchemeInitDataAt(i);
261             schemeDatas[i] =
262                     new DrmInitData.SchemeData(
263                             schemeInitData.uuid, schemeInitData.mimeType, schemeInitData.data);
264         }
265         return new DrmInitData(encryptionScheme, schemeDatas);
266     }
267 
getExoPlayerColorInfo(MediaFormat mediaFormat)268     private static ColorInfo getExoPlayerColorInfo(MediaFormat mediaFormat) {
269         int colorSpace = Format.NO_VALUE;
270         if (mediaFormat.containsKey(MediaFormat.KEY_COLOR_FORMAT)) {
271             switch (mediaFormat.getInteger(MediaFormat.KEY_COLOR_FORMAT)) {
272                 case MediaFormat.COLOR_STANDARD_BT601_NTSC:
273                 case MediaFormat.COLOR_STANDARD_BT601_PAL:
274                     colorSpace = C.COLOR_SPACE_BT601;
275                     break;
276                 case MediaFormat.COLOR_STANDARD_BT709:
277                     colorSpace = C.COLOR_SPACE_BT709;
278                     break;
279                 case MediaFormat.COLOR_STANDARD_BT2020:
280                     colorSpace = C.COLOR_SPACE_BT2020;
281                     break;
282                 default:
283                     colorSpace = Format.NO_VALUE;
284             }
285         }
286 
287         int colorRange = Format.NO_VALUE;
288         if (mediaFormat.containsKey(MediaFormat.KEY_COLOR_RANGE)) {
289             switch (mediaFormat.getInteger(MediaFormat.KEY_COLOR_RANGE)) {
290                 case MediaFormat.COLOR_RANGE_FULL:
291                     colorRange = C.COLOR_RANGE_FULL;
292                     break;
293                 case MediaFormat.COLOR_RANGE_LIMITED:
294                     colorRange = C.COLOR_RANGE_LIMITED;
295                     break;
296                 default:
297                     colorRange = Format.NO_VALUE;
298             }
299         }
300 
301         int colorTransfer = Format.NO_VALUE;
302         if (mediaFormat.containsKey(MediaFormat.KEY_COLOR_TRANSFER)) {
303             switch (mediaFormat.getInteger(MediaFormat.KEY_COLOR_TRANSFER)) {
304                 case MediaFormat.COLOR_TRANSFER_HLG:
305                     colorTransfer = C.COLOR_TRANSFER_HLG;
306                     break;
307                 case MediaFormat.COLOR_TRANSFER_SDR_VIDEO:
308                     colorTransfer = C.COLOR_TRANSFER_SDR;
309                     break;
310                 case MediaFormat.COLOR_TRANSFER_ST2084:
311                     colorTransfer = C.COLOR_TRANSFER_ST2084;
312                     break;
313                 case MediaFormat.COLOR_TRANSFER_LINEAR:
314                     // Fall through, there's no mapping.
315                 default:
316                     colorTransfer = Format.NO_VALUE;
317             }
318         }
319         boolean hasHdrInfo = mediaFormat.containsKey(MediaFormat.KEY_HDR_STATIC_INFO);
320         if (colorSpace == Format.NO_VALUE
321                 && colorRange == Format.NO_VALUE
322                 && colorTransfer == Format.NO_VALUE
323                 && !hasHdrInfo) {
324             return null;
325         } else {
326             return new ColorInfo(
327                     colorSpace,
328                     colorRange,
329                     colorTransfer,
330                     hasHdrInfo
331                             ? mediaFormat.getByteBuffer(MediaFormat.KEY_HDR_STATIC_INFO).array()
332                             : null);
333         }
334     }
335 
getSeekMap()336     public MediaParser.SeekMap getSeekMap() {
337         return mSeekMap;
338     }
339 
340     // Internal classes.
341 
342     private static class DataReaderAdapter implements DataReader {
343 
344         private final MediaParser.InputReader mInputReader;
345 
DataReaderAdapter(MediaParser.InputReader inputReader)346         private DataReaderAdapter(MediaParser.InputReader inputReader) {
347             mInputReader = inputReader;
348         }
349 
350         @Override
read(byte[] target, int offset, int length)351         public int read(byte[] target, int offset, int length) throws IOException {
352             return mInputReader.read(target, offset, length);
353         }
354     }
355 }
356