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