| /* |
| * Copyright (C) 2020 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.media.mediaparser.cts; |
| |
| import android.media.MediaCodec; |
| import android.media.MediaFormat; |
| import android.media.MediaParser; |
| import android.util.Pair; |
| |
| import androidx.annotation.Nullable; |
| |
| import com.google.android.exoplayer2.C; |
| import com.google.android.exoplayer2.Format; |
| import com.google.android.exoplayer2.drm.DrmInitData; |
| import com.google.android.exoplayer2.extractor.SeekMap; |
| import com.google.android.exoplayer2.extractor.SeekPoint; |
| import com.google.android.exoplayer2.extractor.TrackOutput; |
| import com.google.android.exoplayer2.testutil.FakeExtractorOutput; |
| import com.google.android.exoplayer2.testutil.FakeTrackOutput; |
| import com.google.android.exoplayer2.upstream.DataReader; |
| import com.google.android.exoplayer2.video.ColorInfo; |
| |
| import java.io.IOException; |
| import java.util.ArrayList; |
| |
| public class MockMediaParserOutputConsumer implements MediaParser.OutputConsumer { |
| |
| private final boolean mUsingInBandCryptoInfo; |
| private final FakeExtractorOutput mFakeExtractorOutput; |
| private final ArrayList<TrackOutput> mTrackOutputs; |
| |
| @Nullable private MediaParser.SeekMap mSeekMap; |
| private int mCompletedSampleCount; |
| @Nullable private MediaCodec.CryptoInfo mLastOutputCryptoInfo; |
| |
| public MockMediaParserOutputConsumer() { |
| this(/* usingInBandCryptoInfo= */ false); |
| } |
| |
| public MockMediaParserOutputConsumer(boolean usingInBandCryptoInfo) { |
| mUsingInBandCryptoInfo = usingInBandCryptoInfo; |
| mFakeExtractorOutput = |
| new FakeExtractorOutput( |
| /* trackOutputFactory= */ (id, type) -> |
| new FakeTrackOutput(/* deduplicateConsecutiveFormats= */ true)); |
| mTrackOutputs = new ArrayList<>(); |
| } |
| |
| public int getCompletedSampleCount() { |
| return mCompletedSampleCount; |
| } |
| |
| @Nullable |
| public MediaCodec.CryptoInfo getLastOutputCryptoInfo() { |
| return mLastOutputCryptoInfo; |
| } |
| |
| public void clearTrackOutputs() { |
| mFakeExtractorOutput.clearTrackOutputs(); |
| } |
| |
| // OutputConsumer implementation. |
| |
| @Override |
| public void onSeekMapFound(MediaParser.SeekMap seekMap) { |
| mSeekMap = seekMap; |
| mFakeExtractorOutput.seekMap( |
| new SeekMap() { |
| @Override |
| public boolean isSeekable() { |
| return seekMap.isSeekable(); |
| } |
| |
| @Override |
| public long getDurationUs() { |
| long durationUs = seekMap.getDurationMicros(); |
| return durationUs != MediaParser.SeekMap.UNKNOWN_DURATION |
| ? durationUs |
| : C.TIME_UNSET; |
| } |
| |
| @Override |
| public SeekPoints getSeekPoints(long timeUs) { |
| return toExoPlayerSeekPoints(seekMap.getSeekPoints(timeUs)); |
| } |
| }); |
| } |
| |
| @Override |
| public void onTrackCountFound(int numberOfTracks) { |
| // Do nothing. |
| } |
| |
| @Override |
| public void onTrackDataFound(int trackIndex, MediaParser.TrackData trackData) { |
| while (mTrackOutputs.size() <= trackIndex) { |
| mTrackOutputs.add(mFakeExtractorOutput.track(trackIndex, C.TRACK_TYPE_UNKNOWN)); |
| } |
| mTrackOutputs.get(trackIndex).format(toExoPlayerFormat(trackData)); |
| } |
| |
| @Override |
| public void onSampleDataFound(int trackIndex, MediaParser.InputReader inputReader) |
| throws IOException { |
| mFakeExtractorOutput |
| .track(trackIndex, C.TRACK_TYPE_UNKNOWN) |
| .sampleData( |
| new DataReaderAdapter(inputReader), (int) inputReader.getLength(), false); |
| } |
| |
| @Override |
| public void onSampleCompleted( |
| int trackIndex, |
| long timeUs, |
| int flags, |
| int size, |
| int offset, |
| @Nullable MediaCodec.CryptoInfo cryptoInfo) { |
| mCompletedSampleCount++; |
| if (!mUsingInBandCryptoInfo) { |
| mLastOutputCryptoInfo = cryptoInfo; |
| } |
| } |
| |
| // Internal methods. |
| |
| private static SeekMap.SeekPoints toExoPlayerSeekPoints( |
| Pair<MediaParser.SeekPoint, MediaParser.SeekPoint> seekPoints) { |
| return new SeekMap.SeekPoints( |
| toExoPlayerSeekPoint(seekPoints.first), toExoPlayerSeekPoint(seekPoints.second)); |
| } |
| |
| private static SeekPoint toExoPlayerSeekPoint(MediaParser.SeekPoint seekPoint) { |
| return new SeekPoint(seekPoint.timeMicros, seekPoint.position); |
| } |
| |
| private static Format toExoPlayerFormat(MediaParser.TrackData trackData) { |
| MediaFormat mediaFormat = trackData.mediaFormat; |
| String sampleMimeType = |
| mediaFormat.getString(MediaFormat.KEY_MIME, /* defaultValue= */ null); |
| String id = |
| mediaFormat.containsKey(MediaFormat.KEY_TRACK_ID) |
| ? String.valueOf(mediaFormat.getInteger(MediaFormat.KEY_TRACK_ID)) |
| : null; |
| String codecs = |
| mediaFormat.getString(MediaFormat.KEY_CODECS_STRING, /* defaultValue= */ null); |
| int bitrate = |
| mediaFormat.getInteger( |
| MediaFormat.KEY_BIT_RATE, /* defaultValue= */ Format.NO_VALUE); |
| int maxInputSize = |
| mediaFormat.getInteger( |
| MediaFormat.KEY_MAX_INPUT_SIZE, /* defaultValue= */ Format.NO_VALUE); |
| int width = |
| mediaFormat.getInteger(MediaFormat.KEY_WIDTH, /* defaultValue= */ Format.NO_VALUE); |
| int height = |
| mediaFormat.getInteger(MediaFormat.KEY_HEIGHT, /* defaultValue= */ Format.NO_VALUE); |
| float frameRate = |
| mediaFormat.getFloat( |
| MediaFormat.KEY_FRAME_RATE, /* defaultValue= */ Format.NO_VALUE); |
| int rotationDegrees = |
| mediaFormat.getInteger( |
| MediaFormat.KEY_ROTATION, /* defaultValue= */ Format.NO_VALUE); |
| ArrayList<byte[]> initData = null; |
| if (mediaFormat.containsKey("csd-0")) { |
| initData = new ArrayList<>(); |
| int index = 0; |
| while (mediaFormat.containsKey("csd-" + index)) { |
| initData.add(mediaFormat.getByteBuffer("csd-" + index++).array()); |
| } |
| } |
| float pixelAspectWidth = |
| (float) |
| mediaFormat.getInteger( |
| MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH, /* defaultValue= */ 0); |
| float pixelAspectHeight = |
| (float) |
| mediaFormat.getInteger( |
| MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT, /* defaultValue= */ 0); |
| float pixelAspectRatio = |
| pixelAspectHeight == 0 || pixelAspectWidth == 0 |
| ? Format.NO_VALUE |
| : pixelAspectWidth / pixelAspectHeight; |
| ColorInfo colorInfo = getExoPlayerColorInfo(mediaFormat); |
| DrmInitData drmInitData = |
| getExoPlayerDrmInitData( |
| mediaFormat.getString("crypto-mode-fourcc"), trackData.drmInitData); |
| |
| int selectionFlags = |
| mediaFormat.getInteger(MediaFormat.KEY_IS_AUTOSELECT, /* defaultValue= */ 0) != 0 |
| ? C.SELECTION_FLAG_AUTOSELECT |
| : 0; |
| selectionFlags |= |
| mediaFormat.getInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, /* defaultValue= */ 0) |
| != 0 |
| ? C.SELECTION_FLAG_FORCED |
| : 0; |
| selectionFlags |= |
| mediaFormat.getInteger(MediaFormat.KEY_IS_DEFAULT, /* defaultValue= */ 0) != 0 |
| ? C.SELECTION_FLAG_DEFAULT |
| : 0; |
| |
| String language = mediaFormat.getString(MediaFormat.KEY_LANGUAGE, /* defaultValue= */ null); |
| int channels = |
| mediaFormat.getInteger( |
| MediaFormat.KEY_CHANNEL_COUNT, /* defaultValue= */ Format.NO_VALUE); |
| int sampleRate = |
| mediaFormat.getInteger( |
| MediaFormat.KEY_SAMPLE_RATE, /* defaultValue= */ Format.NO_VALUE); |
| int accessibilityChannel = |
| mediaFormat.getInteger( |
| MediaFormat.KEY_CAPTION_SERVICE_NUMBER, |
| /* defaultValue= */ Format.NO_VALUE); |
| |
| return new Format.Builder() |
| .setId(id) |
| .setSampleMimeType(sampleMimeType) |
| .setCodecs(codecs) |
| .setPeakBitrate(bitrate) |
| .setMaxInputSize(maxInputSize) |
| .setWidth(width) |
| .setHeight(height) |
| .setFrameRate(frameRate) |
| .setInitializationData(initData) |
| .setRotationDegrees(rotationDegrees) |
| .setPixelWidthHeightRatio(pixelAspectRatio) |
| .setColorInfo(colorInfo) |
| .setDrmInitData(drmInitData) |
| .setChannelCount(channels) |
| .setSampleRate(sampleRate) |
| .setSelectionFlags(selectionFlags) |
| .setLanguage(language) |
| .setAccessibilityChannel(accessibilityChannel) |
| .build(); |
| } |
| |
| @Nullable |
| private static DrmInitData getExoPlayerDrmInitData( |
| @Nullable String encryptionScheme, @Nullable android.media.DrmInitData drmInitData) { |
| if (drmInitData == null) { |
| return null; |
| } |
| DrmInitData.SchemeData[] schemeDatas = |
| new DrmInitData.SchemeData[drmInitData.getSchemeInitDataCount()]; |
| for (int i = 0; i < schemeDatas.length; i++) { |
| android.media.DrmInitData.SchemeInitData schemeInitData = |
| drmInitData.getSchemeInitDataAt(i); |
| schemeDatas[i] = |
| new DrmInitData.SchemeData( |
| schemeInitData.uuid, schemeInitData.mimeType, schemeInitData.data); |
| } |
| return new DrmInitData(encryptionScheme, schemeDatas); |
| } |
| |
| private static ColorInfo getExoPlayerColorInfo(MediaFormat mediaFormat) { |
| int colorSpace = Format.NO_VALUE; |
| if (mediaFormat.containsKey(MediaFormat.KEY_COLOR_FORMAT)) { |
| switch (mediaFormat.getInteger(MediaFormat.KEY_COLOR_FORMAT)) { |
| case MediaFormat.COLOR_STANDARD_BT601_NTSC: |
| case MediaFormat.COLOR_STANDARD_BT601_PAL: |
| colorSpace = C.COLOR_SPACE_BT601; |
| break; |
| case MediaFormat.COLOR_STANDARD_BT709: |
| colorSpace = C.COLOR_SPACE_BT709; |
| break; |
| case MediaFormat.COLOR_STANDARD_BT2020: |
| colorSpace = C.COLOR_SPACE_BT2020; |
| break; |
| default: |
| colorSpace = Format.NO_VALUE; |
| } |
| } |
| |
| int colorRange = Format.NO_VALUE; |
| if (mediaFormat.containsKey(MediaFormat.KEY_COLOR_RANGE)) { |
| switch (mediaFormat.getInteger(MediaFormat.KEY_COLOR_RANGE)) { |
| case MediaFormat.COLOR_RANGE_FULL: |
| colorRange = C.COLOR_RANGE_FULL; |
| break; |
| case MediaFormat.COLOR_RANGE_LIMITED: |
| colorRange = C.COLOR_RANGE_LIMITED; |
| break; |
| default: |
| colorRange = Format.NO_VALUE; |
| } |
| } |
| |
| int colorTransfer = Format.NO_VALUE; |
| if (mediaFormat.containsKey(MediaFormat.KEY_COLOR_TRANSFER)) { |
| switch (mediaFormat.getInteger(MediaFormat.KEY_COLOR_TRANSFER)) { |
| case MediaFormat.COLOR_TRANSFER_HLG: |
| colorTransfer = C.COLOR_TRANSFER_HLG; |
| break; |
| case MediaFormat.COLOR_TRANSFER_SDR_VIDEO: |
| colorTransfer = C.COLOR_TRANSFER_SDR; |
| break; |
| case MediaFormat.COLOR_TRANSFER_ST2084: |
| colorTransfer = C.COLOR_TRANSFER_ST2084; |
| break; |
| case MediaFormat.COLOR_TRANSFER_LINEAR: |
| // Fall through, there's no mapping. |
| default: |
| colorTransfer = Format.NO_VALUE; |
| } |
| } |
| boolean hasHdrInfo = mediaFormat.containsKey(MediaFormat.KEY_HDR_STATIC_INFO); |
| if (colorSpace == Format.NO_VALUE |
| && colorRange == Format.NO_VALUE |
| && colorTransfer == Format.NO_VALUE |
| && !hasHdrInfo) { |
| return null; |
| } else { |
| return new ColorInfo( |
| colorSpace, |
| colorRange, |
| colorTransfer, |
| hasHdrInfo |
| ? mediaFormat.getByteBuffer(MediaFormat.KEY_HDR_STATIC_INFO).array() |
| : null); |
| } |
| } |
| |
| public MediaParser.SeekMap getSeekMap() { |
| return mSeekMap; |
| } |
| |
| // Internal classes. |
| |
| private static class DataReaderAdapter implements DataReader { |
| |
| private final MediaParser.InputReader mInputReader; |
| |
| private DataReaderAdapter(MediaParser.InputReader inputReader) { |
| mInputReader = inputReader; |
| } |
| |
| @Override |
| public int read(byte[] target, int offset, int length) throws IOException { |
| return mInputReader.read(target, offset, length); |
| } |
| } |
| } |