blob: aea6c1974ccd7953deadc1e3813604bee6921ab5 [file] [log] [blame]
/*
* 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);
}
}
}