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