mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Add getPsshInfo()
API to MediaExtractorCompat
This method retrieves PSSH (Protection System Specific Header) data from the media as a map of UUID-to-bytes. PiperOrigin-RevId: 697974134
This commit is contained in:
parent
b9c9d95b90
commit
d702e1d496
@ -19,6 +19,7 @@ import static androidx.media3.common.C.PLAYREADY_UUID;
|
|||||||
import static androidx.media3.common.C.WIDEVINE_UUID;
|
import static androidx.media3.common.C.WIDEVINE_UUID;
|
||||||
import static androidx.media3.common.MimeTypes.AUDIO_AAC;
|
import static androidx.media3.common.MimeTypes.AUDIO_AAC;
|
||||||
import static androidx.media3.common.MimeTypes.VIDEO_H264;
|
import static androidx.media3.common.MimeTypes.VIDEO_H264;
|
||||||
|
import static androidx.media3.common.MimeTypes.VIDEO_MP4;
|
||||||
import static androidx.media3.test.utils.TestUtil.buildTestData;
|
import static androidx.media3.test.utils.TestUtil.buildTestData;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static org.junit.Assert.assertThrows;
|
import static org.junit.Assert.assertThrows;
|
||||||
@ -32,6 +33,7 @@ import android.media.metrics.MediaMetricsManager;
|
|||||||
import android.media.metrics.PlaybackSession;
|
import android.media.metrics.PlaybackSession;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.PersistableBundle;
|
import android.os.PersistableBundle;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.DrmInitData;
|
import androidx.media3.common.DrmInitData;
|
||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
@ -51,6 +53,7 @@ import androidx.media3.extractor.SeekMap.SeekPoints;
|
|||||||
import androidx.media3.extractor.SeekPoint;
|
import androidx.media3.extractor.SeekPoint;
|
||||||
import androidx.media3.extractor.TrackOutput;
|
import androidx.media3.extractor.TrackOutput;
|
||||||
import androidx.media3.extractor.mp4.Mp4Extractor;
|
import androidx.media3.extractor.mp4.Mp4Extractor;
|
||||||
|
import androidx.media3.extractor.mp4.PsshAtomUtil;
|
||||||
import androidx.media3.test.utils.TestUtil;
|
import androidx.media3.test.utils.TestUtil;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
@ -63,6 +66,7 @@ import java.nio.ByteBuffer;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import okhttp3.mockwebserver.MockResponse;
|
import okhttp3.mockwebserver.MockResponse;
|
||||||
import okhttp3.mockwebserver.MockWebServer;
|
import okhttp3.mockwebserver.MockWebServer;
|
||||||
@ -631,16 +635,15 @@ public class MediaExtractorCompatTest {
|
|||||||
@Test
|
@Test
|
||||||
public void getTrackFormat_withBothTrackAndSeekMapDurationsSet_prioritizesTrackDuration()
|
public void getTrackFormat_withBothTrackAndSeekMapDurationsSet_prioritizesTrackDuration()
|
||||||
throws IOException {
|
throws IOException {
|
||||||
TrackOutput[] outputs = new TrackOutput[1];
|
|
||||||
fakeExtractor.addReadAction(
|
fakeExtractor.addReadAction(
|
||||||
(input, seekPosition) -> {
|
(input, seekPosition) -> {
|
||||||
outputs[0] = extractorOutput.track(/* id= */ 0, C.TRACK_TYPE_VIDEO);
|
TrackOutput output = extractorOutput.track(/* id= */ 0, C.TRACK_TYPE_VIDEO);
|
||||||
extractorOutput.endTracks();
|
extractorOutput.endTracks();
|
||||||
extractorOutput.seekMap(
|
extractorOutput.seekMap(
|
||||||
new FakeSeekMap(
|
new FakeSeekMap(
|
||||||
/* durationUs= */ 1_000_000L, (timeUs) -> new SeekPoints(SeekPoint.START)));
|
/* durationUs= */ 1_000_000L, (timeUs) -> new SeekPoints(SeekPoint.START)));
|
||||||
outputs[0].format(PLACEHOLDER_FORMAT_VIDEO);
|
output.format(PLACEHOLDER_FORMAT_VIDEO);
|
||||||
outputs[0].durationUs(2_000_000L);
|
output.durationUs(2_000_000L);
|
||||||
return Extractor.RESULT_CONTINUE;
|
return Extractor.RESULT_CONTINUE;
|
||||||
});
|
});
|
||||||
mediaExtractorCompat.setDataSource(PLACEHOLDER_URI, /* offset= */ 0);
|
mediaExtractorCompat.setDataSource(PLACEHOLDER_URI, /* offset= */ 0);
|
||||||
@ -655,15 +658,14 @@ public class MediaExtractorCompatTest {
|
|||||||
@Test
|
@Test
|
||||||
public void getTrackFormat_withOnlySeekMapDurationSet_returnsSeekMapDuration()
|
public void getTrackFormat_withOnlySeekMapDurationSet_returnsSeekMapDuration()
|
||||||
throws IOException {
|
throws IOException {
|
||||||
TrackOutput[] outputs = new TrackOutput[1];
|
|
||||||
fakeExtractor.addReadAction(
|
fakeExtractor.addReadAction(
|
||||||
(input, seekPosition) -> {
|
(input, seekPosition) -> {
|
||||||
outputs[0] = extractorOutput.track(/* id= */ 0, C.TRACK_TYPE_VIDEO);
|
TrackOutput output = extractorOutput.track(/* id= */ 0, C.TRACK_TYPE_VIDEO);
|
||||||
extractorOutput.endTracks();
|
extractorOutput.endTracks();
|
||||||
extractorOutput.seekMap(
|
extractorOutput.seekMap(
|
||||||
new FakeSeekMap(
|
new FakeSeekMap(
|
||||||
/* durationUs= */ 1_000_000L, (timeUs) -> new SeekPoints(SeekPoint.START)));
|
/* durationUs= */ 1_000_000L, (timeUs) -> new SeekPoints(SeekPoint.START)));
|
||||||
outputs[0].format(PLACEHOLDER_FORMAT_VIDEO);
|
output.format(PLACEHOLDER_FORMAT_VIDEO);
|
||||||
return Extractor.RESULT_CONTINUE;
|
return Extractor.RESULT_CONTINUE;
|
||||||
});
|
});
|
||||||
mediaExtractorCompat.setDataSource(PLACEHOLDER_URI, /* offset= */ 0);
|
mediaExtractorCompat.setDataSource(PLACEHOLDER_URI, /* offset= */ 0);
|
||||||
@ -678,12 +680,11 @@ public class MediaExtractorCompatTest {
|
|||||||
@Test
|
@Test
|
||||||
public void getTrackFormat_withNoTrackOrSeekMapDurationSet_returnsNoDuration()
|
public void getTrackFormat_withNoTrackOrSeekMapDurationSet_returnsNoDuration()
|
||||||
throws IOException {
|
throws IOException {
|
||||||
TrackOutput[] outputs = new TrackOutput[1];
|
|
||||||
fakeExtractor.addReadAction(
|
fakeExtractor.addReadAction(
|
||||||
(input, seekPosition) -> {
|
(input, seekPosition) -> {
|
||||||
outputs[0] = extractorOutput.track(/* id= */ 0, C.TRACK_TYPE_VIDEO);
|
TrackOutput output = extractorOutput.track(/* id= */ 0, C.TRACK_TYPE_VIDEO);
|
||||||
extractorOutput.endTracks();
|
extractorOutput.endTracks();
|
||||||
outputs[0].format(
|
output.format(
|
||||||
new Format.Builder()
|
new Format.Builder()
|
||||||
.setSampleMimeType(MimeTypes.VIDEO_H264)
|
.setSampleMimeType(MimeTypes.VIDEO_H264)
|
||||||
.setCodecs("avc.123")
|
.setCodecs("avc.123")
|
||||||
@ -729,11 +730,10 @@ public class MediaExtractorCompatTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getDrmInitData_withNoTracksHavingDrmInitData_returnsNull() throws IOException {
|
public void getDrmInitData_withNoTracksHavingDrmInitData_returnsNull() throws IOException {
|
||||||
TrackOutput[] outputs = new TrackOutput[1];
|
|
||||||
fakeExtractor.addReadAction(
|
fakeExtractor.addReadAction(
|
||||||
(input, seekPosition) -> {
|
(input, seekPosition) -> {
|
||||||
outputs[0] = extractorOutput.track(/* id= */ 0, C.TRACK_TYPE_VIDEO);
|
TrackOutput output = extractorOutput.track(/* id= */ 0, C.TRACK_TYPE_VIDEO);
|
||||||
outputs[0].format(PLACEHOLDER_FORMAT_VIDEO);
|
output.format(PLACEHOLDER_FORMAT_VIDEO);
|
||||||
extractorOutput.endTracks();
|
extractorOutput.endTracks();
|
||||||
return Extractor.RESULT_CONTINUE;
|
return Extractor.RESULT_CONTINUE;
|
||||||
});
|
});
|
||||||
@ -746,16 +746,13 @@ public class MediaExtractorCompatTest {
|
|||||||
@Test
|
@Test
|
||||||
public void getDrmInitData_withSingleTrackHavingDrmInitData_returnsDrmInitData()
|
public void getDrmInitData_withSingleTrackHavingDrmInitData_returnsDrmInitData()
|
||||||
throws IOException {
|
throws IOException {
|
||||||
TrackOutput[] outputs = new TrackOutput[1];
|
|
||||||
DrmInitData.SchemeData schemeData =
|
DrmInitData.SchemeData schemeData =
|
||||||
new DrmInitData.SchemeData(
|
new DrmInitData.SchemeData(WIDEVINE_UUID, VIDEO_H264, buildTestData(128, /* seed= */ 1));
|
||||||
WIDEVINE_UUID, VIDEO_H264, buildTestData(128, 1 /* data seed */));
|
|
||||||
DrmInitData drmInitData = new DrmInitData(schemeData);
|
DrmInitData drmInitData = new DrmInitData(schemeData);
|
||||||
fakeExtractor.addReadAction(
|
fakeExtractor.addReadAction(
|
||||||
(input, seekPosition) -> {
|
(input, seekPosition) -> {
|
||||||
outputs[0] = extractorOutput.track(/* id= */ 0, C.TRACK_TYPE_VIDEO);
|
TrackOutput output = extractorOutput.track(/* id= */ 0, C.TRACK_TYPE_VIDEO);
|
||||||
outputs[0].format(
|
output.format(PLACEHOLDER_FORMAT_VIDEO.buildUpon().setDrmInitData(drmInitData).build());
|
||||||
PLACEHOLDER_FORMAT_VIDEO.buildUpon().setDrmInitData(drmInitData).build());
|
|
||||||
extractorOutput.endTracks();
|
extractorOutput.endTracks();
|
||||||
return Extractor.RESULT_CONTINUE;
|
return Extractor.RESULT_CONTINUE;
|
||||||
});
|
});
|
||||||
@ -768,16 +765,15 @@ public class MediaExtractorCompatTest {
|
|||||||
@Test
|
@Test
|
||||||
public void getDrmInitData_withMultipleTracksHavingDrmInitData_returnsFirstNonNullDrmInitData()
|
public void getDrmInitData_withMultipleTracksHavingDrmInitData_returnsFirstNonNullDrmInitData()
|
||||||
throws IOException {
|
throws IOException {
|
||||||
TrackOutput[] outputs = new TrackOutput[3];
|
|
||||||
DrmInitData.SchemeData firstSchemeData =
|
DrmInitData.SchemeData firstSchemeData =
|
||||||
new DrmInitData.SchemeData(WIDEVINE_UUID, AUDIO_AAC, buildTestData(128, 1 /* data seed */));
|
new DrmInitData.SchemeData(WIDEVINE_UUID, AUDIO_AAC, buildTestData(128, /* seed= */ 1));
|
||||||
DrmInitData firstDrmInitData = new DrmInitData(firstSchemeData);
|
DrmInitData firstDrmInitData = new DrmInitData(firstSchemeData);
|
||||||
DrmInitData.SchemeData secondSchemeData =
|
DrmInitData.SchemeData secondSchemeData =
|
||||||
new DrmInitData.SchemeData(
|
new DrmInitData.SchemeData(PLAYREADY_UUID, AUDIO_AAC, buildTestData(128, /* seed= */ 2));
|
||||||
PLAYREADY_UUID, AUDIO_AAC, buildTestData(128, 2 /* data seed */));
|
|
||||||
DrmInitData secondDrmInitData = new DrmInitData(secondSchemeData);
|
DrmInitData secondDrmInitData = new DrmInitData(secondSchemeData);
|
||||||
fakeExtractor.addReadAction(
|
fakeExtractor.addReadAction(
|
||||||
(input, seekPosition) -> {
|
(input, seekPosition) -> {
|
||||||
|
TrackOutput[] outputs = new TrackOutput[3];
|
||||||
outputs[0] = extractorOutput.track(/* id= */ 0, C.TRACK_TYPE_VIDEO);
|
outputs[0] = extractorOutput.track(/* id= */ 0, C.TRACK_TYPE_VIDEO);
|
||||||
outputs[0].format(PLACEHOLDER_FORMAT_VIDEO);
|
outputs[0].format(PLACEHOLDER_FORMAT_VIDEO);
|
||||||
outputs[1] = extractorOutput.track(/* id= */ 1, C.TRACK_TYPE_AUDIO);
|
outputs[1] = extractorOutput.track(/* id= */ 1, C.TRACK_TYPE_AUDIO);
|
||||||
@ -1025,6 +1021,48 @@ public class MediaExtractorCompatTest {
|
|||||||
assertThat(bundle.getInt(MediaExtractor.MetricsConstants.TRACKS)).isEqualTo(2);
|
assertThat(bundle.getInt(MediaExtractor.MetricsConstants.TRACKS)).isEqualTo(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getPsshInfo_withMediaWithoutPsshData_returnsNull() throws IOException {
|
||||||
|
DrmInitData.SchemeData schemeData =
|
||||||
|
new DrmInitData.SchemeData(WIDEVINE_UUID, VIDEO_H264, buildTestData(128, /* seed= */ 1));
|
||||||
|
DrmInitData drmInitData = new DrmInitData(schemeData);
|
||||||
|
fakeExtractor.addReadAction(
|
||||||
|
(input, seekPosition) -> {
|
||||||
|
TrackOutput output = extractorOutput.track(/* id= */ 0, C.TRACK_TYPE_VIDEO);
|
||||||
|
output.format(PLACEHOLDER_FORMAT_VIDEO.buildUpon().setDrmInitData(drmInitData).build());
|
||||||
|
extractorOutput.endTracks();
|
||||||
|
return Extractor.RESULT_CONTINUE;
|
||||||
|
});
|
||||||
|
|
||||||
|
mediaExtractorCompat.setDataSource(PLACEHOLDER_URI, /* offset= */ 0);
|
||||||
|
|
||||||
|
assertThat(mediaExtractorCompat.getPsshInfo()).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getPsshInfo_withMediaHavingPsshData_returnsCorrectPsshMap() throws IOException {
|
||||||
|
byte[] rawSchemeData = new byte[] {0, 1, 2, 3, 4, 5};
|
||||||
|
DrmInitData.SchemeData schemeData =
|
||||||
|
new DrmInitData.SchemeData(
|
||||||
|
WIDEVINE_UUID, VIDEO_MP4, PsshAtomUtil.buildPsshAtom(WIDEVINE_UUID, rawSchemeData));
|
||||||
|
DrmInitData drmInitData = new DrmInitData(schemeData);
|
||||||
|
fakeExtractor.addReadAction(
|
||||||
|
(input, seekPosition) -> {
|
||||||
|
TrackOutput output = extractorOutput.track(/* id= */ 0, C.TRACK_TYPE_VIDEO);
|
||||||
|
output.format(PLACEHOLDER_FORMAT_VIDEO.buildUpon().setDrmInitData(drmInitData).build());
|
||||||
|
extractorOutput.endTracks();
|
||||||
|
return Extractor.RESULT_CONTINUE;
|
||||||
|
});
|
||||||
|
mediaExtractorCompat.setDataSource(PLACEHOLDER_URI, /* offset= */ 0);
|
||||||
|
|
||||||
|
@Nullable Map<UUID, byte[]> psshMap = mediaExtractorCompat.getPsshInfo();
|
||||||
|
|
||||||
|
assertThat(psshMap).isNotNull();
|
||||||
|
assertThat(psshMap).isNotEmpty();
|
||||||
|
assertThat(psshMap).hasSize(1);
|
||||||
|
assertThat(psshMap.get(WIDEVINE_UUID)).isEqualTo(rawSchemeData);
|
||||||
|
}
|
||||||
|
|
||||||
// Internal methods.
|
// Internal methods.
|
||||||
|
|
||||||
private void assertReadSample(int trackIndex, long timeUs, int size, byte... sampleData) {
|
private void assertReadSample(int trackIndex, long timeUs, int size, byte... sampleData) {
|
||||||
|
@ -72,6 +72,7 @@ import androidx.media3.extractor.SeekMap.SeekPoints;
|
|||||||
import androidx.media3.extractor.SeekPoint;
|
import androidx.media3.extractor.SeekPoint;
|
||||||
import androidx.media3.extractor.TrackOutput;
|
import androidx.media3.extractor.TrackOutput;
|
||||||
import androidx.media3.extractor.mp4.Mp4Extractor;
|
import androidx.media3.extractor.mp4.Mp4Extractor;
|
||||||
|
import androidx.media3.extractor.mp4.PsshAtomUtil;
|
||||||
import com.google.common.base.Joiner;
|
import com.google.common.base.Joiner;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
@ -84,9 +85,11 @@ import java.lang.annotation.RetentionPolicy;
|
|||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
|
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -682,6 +685,36 @@ public final class MediaExtractorCompat {
|
|||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts PSSH data from the media, if present.
|
||||||
|
*
|
||||||
|
* @return A {@link Map} of UUID-to-byte[] pairs, where the {@link UUID} identifies the crypto
|
||||||
|
* scheme, and the byte array contains the scheme-specific data. Returns {@code null} if no
|
||||||
|
* PSSH data exists.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public Map<UUID, byte[]> getPsshInfo() {
|
||||||
|
@Nullable DrmInitData drmInitData = getDrmInitData();
|
||||||
|
if (drmInitData == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<UUID, byte[]> psshDataMap = new HashMap<>();
|
||||||
|
for (int i = 0; i < drmInitData.schemeDataCount; i++) {
|
||||||
|
DrmInitData.SchemeData schemeData = drmInitData.get(i);
|
||||||
|
if (schemeData.data == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable PsshAtomUtil.PsshAtom parsedPsshAtom = PsshAtomUtil.parsePsshAtom(schemeData.data);
|
||||||
|
if (parsedPsshAtom != null) {
|
||||||
|
psshDataMap.put(parsedPsshAtom.uuid, parsedPsshAtom.schemeData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return psshDataMap.isEmpty() ? null : psshDataMap;
|
||||||
|
}
|
||||||
|
|
||||||
@VisibleForTesting(otherwise = NONE)
|
@VisibleForTesting(otherwise = NONE)
|
||||||
public Allocator getAllocator() {
|
public Allocator getAllocator() {
|
||||||
return allocator;
|
return allocator;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user