mirror of
https://github.com/androidx/media.git
synced 2025-05-10 00:59:51 +08:00
Split Cue.toBundle
into serializable and binder-based variants
The serializable form is used when we need to serialize the result into bytes in the sample queue. The binder-based (ultimately filedescriptor-based) form is used for session/controller IPC, in order to avoid sending the bitmap bytes over the IPC. Issue: androidx/media#836 #minor-release PiperOrigin-RevId: 588420836
This commit is contained in:
parent
a98052b3fc
commit
bd19953ac9
@ -57,6 +57,10 @@
|
|||||||
marked as unsupported
|
marked as unsupported
|
||||||
([#693](https://github.com/androidx/media/issues/693)).
|
([#693](https://github.com/androidx/media/issues/693)).
|
||||||
* Text:
|
* Text:
|
||||||
|
* Fix serialization of bitmap cues to resolve `Tried to marshall a Parcel
|
||||||
|
that contained Binder objects` error when using
|
||||||
|
`DefaultExtractorsFactory.setTextTrackTranscodingEnabled`
|
||||||
|
([#836](https://github.com/androidx/media/issues/836)).
|
||||||
* Metadata:
|
* Metadata:
|
||||||
* DRM:
|
* DRM:
|
||||||
* Extend workaround for spurious ClearKey `https://default.url` license
|
* Extend workaround for spurious ClearKey `https://default.url` license
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
package androidx.media3.common.text;
|
package androidx.media3.common.text;
|
||||||
|
|
||||||
import static androidx.media3.common.text.CustomSpanBundler.bundleCustomSpans;
|
import static androidx.media3.common.text.CustomSpanBundler.bundleCustomSpans;
|
||||||
|
import static androidx.media3.common.util.Assertions.checkState;
|
||||||
import static java.lang.annotation.ElementType.FIELD;
|
import static java.lang.annotation.ElementType.FIELD;
|
||||||
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
|
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
|
||||||
import static java.lang.annotation.ElementType.METHOD;
|
import static java.lang.annotation.ElementType.METHOD;
|
||||||
@ -23,7 +24,9 @@ import static java.lang.annotation.ElementType.PARAMETER;
|
|||||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||||
|
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
|
import android.os.Binder;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.Layout;
|
import android.text.Layout;
|
||||||
import android.text.Layout.Alignment;
|
import android.text.Layout.Alignment;
|
||||||
@ -40,6 +43,7 @@ import androidx.media3.common.util.UnstableApi;
|
|||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import com.google.common.base.Objects;
|
import com.google.common.base.Objects;
|
||||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.Documented;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
@ -832,7 +836,8 @@ public final class Cue implements Bundleable {
|
|||||||
private static final String FIELD_CUSTOM_SPANS = Util.intToStringMaxRadix(17);
|
private static final String FIELD_CUSTOM_SPANS = Util.intToStringMaxRadix(17);
|
||||||
private static final String FIELD_TEXT_ALIGNMENT = Util.intToStringMaxRadix(1);
|
private static final String FIELD_TEXT_ALIGNMENT = Util.intToStringMaxRadix(1);
|
||||||
private static final String FIELD_MULTI_ROW_ALIGNMENT = Util.intToStringMaxRadix(2);
|
private static final String FIELD_MULTI_ROW_ALIGNMENT = Util.intToStringMaxRadix(2);
|
||||||
private static final String FIELD_BITMAP = Util.intToStringMaxRadix(3);
|
private static final String FIELD_BITMAP_PARCELABLE = Util.intToStringMaxRadix(3);
|
||||||
|
private static final String FIELD_BITMAP_BYTES = Util.intToStringMaxRadix(18);
|
||||||
private static final String FIELD_LINE = Util.intToStringMaxRadix(4);
|
private static final String FIELD_LINE = Util.intToStringMaxRadix(4);
|
||||||
private static final String FIELD_LINE_TYPE = Util.intToStringMaxRadix(5);
|
private static final String FIELD_LINE_TYPE = Util.intToStringMaxRadix(5);
|
||||||
private static final String FIELD_LINE_ANCHOR = Util.intToStringMaxRadix(6);
|
private static final String FIELD_LINE_ANCHOR = Util.intToStringMaxRadix(6);
|
||||||
@ -847,9 +852,56 @@ public final class Cue implements Bundleable {
|
|||||||
private static final String FIELD_VERTICAL_TYPE = Util.intToStringMaxRadix(15);
|
private static final String FIELD_VERTICAL_TYPE = Util.intToStringMaxRadix(15);
|
||||||
private static final String FIELD_SHEAR_DEGREES = Util.intToStringMaxRadix(16);
|
private static final String FIELD_SHEAR_DEGREES = Util.intToStringMaxRadix(16);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link Bundle} that can be serialized to bytes.
|
||||||
|
*
|
||||||
|
* <p>Prefer the more efficient {@link #toBinderBasedBundle()} if the result doesn't need to be
|
||||||
|
* serialized.
|
||||||
|
*
|
||||||
|
* <p>The {@link Bundle} returned from this method must not be passed to other processes that
|
||||||
|
* might be using a different version of the media3 library.
|
||||||
|
*/
|
||||||
|
@UnstableApi
|
||||||
|
public Bundle toSerializableBundle() {
|
||||||
|
Bundle bundle = toBundleWithoutBitmap();
|
||||||
|
if (bitmap != null) {
|
||||||
|
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||||
|
// The PNG format is lossless, and the quality parameter is ignored.
|
||||||
|
checkState(bitmap.compress(Bitmap.CompressFormat.PNG, /* quality= */ 0, output));
|
||||||
|
bundle.putByteArray(FIELD_BITMAP_BYTES, output.toByteArray());
|
||||||
|
}
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link Bundle} that may contain {@link Binder} references, meaning it cannot be
|
||||||
|
* safely serialized to bytes.
|
||||||
|
*
|
||||||
|
* <p>The {@link Bundle} returned from this method can be safely sent between processes and parsed
|
||||||
|
* by older versions of the media3 library.
|
||||||
|
*
|
||||||
|
* <p>Use {@link #toSerializableBundle()} to get a {@link Bundle} that can be safely serialized.
|
||||||
|
*/
|
||||||
|
@UnstableApi
|
||||||
|
public Bundle toBinderBasedBundle() {
|
||||||
|
Bundle bundle = toBundleWithoutBitmap();
|
||||||
|
if (bitmap != null) {
|
||||||
|
bundle.putParcelable(FIELD_BITMAP_PARCELABLE, bitmap);
|
||||||
|
}
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #toSerializableBundle()} or {@link #toBinderBasedBundle()} instead.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated
|
||||||
public Bundle toBundle() {
|
public Bundle toBundle() {
|
||||||
|
return toBinderBasedBundle();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bundle toBundleWithoutBitmap() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
if (text != null) {
|
if (text != null) {
|
||||||
bundle.putCharSequence(FIELD_TEXT, text);
|
bundle.putCharSequence(FIELD_TEXT, text);
|
||||||
@ -862,9 +914,6 @@ public final class Cue implements Bundleable {
|
|||||||
}
|
}
|
||||||
bundle.putSerializable(FIELD_TEXT_ALIGNMENT, textAlignment);
|
bundle.putSerializable(FIELD_TEXT_ALIGNMENT, textAlignment);
|
||||||
bundle.putSerializable(FIELD_MULTI_ROW_ALIGNMENT, multiRowAlignment);
|
bundle.putSerializable(FIELD_MULTI_ROW_ALIGNMENT, multiRowAlignment);
|
||||||
if (bitmap != null) {
|
|
||||||
bundle.putParcelable(FIELD_BITMAP, bitmap);
|
|
||||||
}
|
|
||||||
bundle.putFloat(FIELD_LINE, line);
|
bundle.putFloat(FIELD_LINE, line);
|
||||||
bundle.putInt(FIELD_LINE_TYPE, lineType);
|
bundle.putInt(FIELD_LINE_TYPE, lineType);
|
||||||
bundle.putInt(FIELD_LINE_ANCHOR, lineAnchor);
|
bundle.putInt(FIELD_LINE_ANCHOR, lineAnchor);
|
||||||
@ -915,9 +964,15 @@ public final class Cue implements Bundleable {
|
|||||||
if (multiRowAlignment != null) {
|
if (multiRowAlignment != null) {
|
||||||
builder.setMultiRowAlignment(multiRowAlignment);
|
builder.setMultiRowAlignment(multiRowAlignment);
|
||||||
}
|
}
|
||||||
@Nullable Bitmap bitmap = bundle.getParcelable(FIELD_BITMAP);
|
@Nullable Bitmap bitmap = bundle.getParcelable(FIELD_BITMAP_PARCELABLE);
|
||||||
if (bitmap != null) {
|
if (bitmap != null) {
|
||||||
builder.setBitmap(bitmap);
|
builder.setBitmap(bitmap);
|
||||||
|
} else {
|
||||||
|
@Nullable byte[] bitmapBytes = bundle.getByteArray(FIELD_BITMAP_BYTES);
|
||||||
|
if (bitmapBytes != null) {
|
||||||
|
builder.setBitmap(
|
||||||
|
BitmapFactory.decodeByteArray(bitmapBytes, /* offset= */ 0, bitmapBytes.length));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (bundle.containsKey(FIELD_LINE) && bundle.containsKey(FIELD_LINE_TYPE)) {
|
if (bundle.containsKey(FIELD_LINE) && bundle.containsKey(FIELD_LINE_TYPE)) {
|
||||||
builder.setLine(bundle.getFloat(FIELD_LINE), bundle.getInt(FIELD_LINE_TYPE));
|
builder.setLine(bundle.getFloat(FIELD_LINE), bundle.getInt(FIELD_LINE_TYPE));
|
||||||
|
@ -70,7 +70,8 @@ public final class CueGroup implements Bundleable {
|
|||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putParcelableArrayList(
|
bundle.putParcelableArrayList(
|
||||||
FIELD_CUES,
|
FIELD_CUES,
|
||||||
BundleCollectionUtil.toBundleArrayList(filterOutBitmapCues(cues), Cue::toBundle));
|
BundleCollectionUtil.toBundleArrayList(
|
||||||
|
filterOutBitmapCues(cues), Cue::toBinderBasedBundle));
|
||||||
bundle.putLong(FIELD_PRESENTATION_TIME_US, presentationTimeUs);
|
bundle.putLong(FIELD_PRESENTATION_TIME_US, presentationTimeUs);
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
@ -21,8 +21,6 @@ import static org.junit.Assert.assertThrows;
|
|||||||
|
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.Parcel;
|
|
||||||
import android.text.Layout;
|
import android.text.Layout;
|
||||||
import android.text.SpannedString;
|
import android.text.SpannedString;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
@ -109,6 +107,51 @@ public class CueTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
public void roundTripViaBinderBasedBundle_yieldsEqualInstance() {
|
||||||
|
Cue cue =
|
||||||
|
new Cue.Builder()
|
||||||
|
.setText(SpannedString.valueOf("text"))
|
||||||
|
.setTextAlignment(Layout.Alignment.ALIGN_CENTER)
|
||||||
|
.setMultiRowAlignment(Layout.Alignment.ALIGN_NORMAL)
|
||||||
|
.setLine(5, Cue.LINE_TYPE_NUMBER)
|
||||||
|
.setLineAnchor(Cue.ANCHOR_TYPE_END)
|
||||||
|
.setPosition(0.4f)
|
||||||
|
.setPositionAnchor(Cue.ANCHOR_TYPE_MIDDLE)
|
||||||
|
.setTextSize(0.2f, Cue.TEXT_SIZE_TYPE_FRACTIONAL)
|
||||||
|
.setSize(0.8f)
|
||||||
|
.setWindowColor(Color.CYAN)
|
||||||
|
.setVerticalType(Cue.VERTICAL_TYPE_RL)
|
||||||
|
.setShearDegrees(-15f)
|
||||||
|
.build();
|
||||||
|
Cue modifiedCue = Cue.fromBundle(cue.toBinderBasedBundle());
|
||||||
|
|
||||||
|
assertThat(modifiedCue).isEqualTo(cue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void roundTripViaSerializableBundle_yieldsEqualInstance() {
|
||||||
|
Cue cue =
|
||||||
|
new Cue.Builder()
|
||||||
|
.setText(SpannedString.valueOf("text"))
|
||||||
|
.setTextAlignment(Layout.Alignment.ALIGN_CENTER)
|
||||||
|
.setMultiRowAlignment(Layout.Alignment.ALIGN_NORMAL)
|
||||||
|
.setLine(5, Cue.LINE_TYPE_NUMBER)
|
||||||
|
.setLineAnchor(Cue.ANCHOR_TYPE_END)
|
||||||
|
.setPosition(0.4f)
|
||||||
|
.setPositionAnchor(Cue.ANCHOR_TYPE_MIDDLE)
|
||||||
|
.setTextSize(0.2f, Cue.TEXT_SIZE_TYPE_FRACTIONAL)
|
||||||
|
.setSize(0.8f)
|
||||||
|
.setWindowColor(Color.CYAN)
|
||||||
|
.setVerticalType(Cue.VERTICAL_TYPE_RL)
|
||||||
|
.setShearDegrees(-15f)
|
||||||
|
.build();
|
||||||
|
Cue modifiedCue = Cue.fromBundle(cue.toSerializableBundle());
|
||||||
|
|
||||||
|
assertThat(modifiedCue).isEqualTo(cue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("deprecation") // Testing deprecated Cue.toBundle() method
|
||||||
public void roundTripViaBundle_yieldsEqualInstance() {
|
public void roundTripViaBundle_yieldsEqualInstance() {
|
||||||
Cue cue =
|
Cue cue =
|
||||||
new Cue.Builder()
|
new Cue.Builder()
|
||||||
@ -125,30 +168,36 @@ public class CueTest {
|
|||||||
.setVerticalType(Cue.VERTICAL_TYPE_RL)
|
.setVerticalType(Cue.VERTICAL_TYPE_RL)
|
||||||
.setShearDegrees(-15f)
|
.setShearDegrees(-15f)
|
||||||
.build();
|
.build();
|
||||||
Cue modifiedCue = parcelAndUnParcelCue(cue);
|
Cue modifiedCue = Cue.fromBundle(cue.toBundle());
|
||||||
|
|
||||||
assertThat(modifiedCue).isEqualTo(cue);
|
assertThat(modifiedCue).isEqualTo(cue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void roundTripViaBundle_withBitmap_yieldsEqualInstance() {
|
public void roundTripViaBinderBasedBundle_withBitmap_yieldsEqualInstance() {
|
||||||
Cue cue =
|
Cue cue =
|
||||||
new Cue.Builder().setBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)).build();
|
new Cue.Builder().setBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)).build();
|
||||||
Cue modifiedCue = parcelAndUnParcelCue(cue);
|
Cue modifiedCue = Cue.fromBundle(cue.toBinderBasedBundle());
|
||||||
|
|
||||||
assertThat(modifiedCue).isEqualTo(cue);
|
assertThat(modifiedCue).isEqualTo(cue);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Cue parcelAndUnParcelCue(Cue cue) {
|
@Test
|
||||||
Parcel parcel = Parcel.obtain();
|
public void roundTripViaSerializableBundle_withBitmap_yieldsEqualInstance() {
|
||||||
try {
|
Cue cue =
|
||||||
parcel.writeBundle(cue.toBundle());
|
new Cue.Builder().setBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)).build();
|
||||||
parcel.setDataPosition(0);
|
Cue modifiedCue = Cue.fromBundle(cue.toSerializableBundle());
|
||||||
|
|
||||||
Bundle bundle = parcel.readBundle();
|
assertThat(modifiedCue).isEqualTo(cue);
|
||||||
return Cue.fromBundle(bundle);
|
|
||||||
} finally {
|
|
||||||
parcel.recycle();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("deprecation") // Testing deprecated Cue.toBundle() method
|
||||||
|
public void roundTripViaBundle_withBitmap_yieldsEqualInstance() {
|
||||||
|
Cue cue =
|
||||||
|
new Cue.Builder().setBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)).build();
|
||||||
|
Cue modifiedCue = Cue.fromBundle(cue.toBundle());
|
||||||
|
|
||||||
|
assertThat(modifiedCue).isEqualTo(cue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
|
|||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
|
import android.graphics.ColorSpace;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.Layout;
|
import android.text.Layout;
|
||||||
import android.text.Spannable;
|
import android.text.Spannable;
|
||||||
@ -35,7 +36,6 @@ import androidx.media3.test.utils.truth.SpannedSubject;
|
|||||||
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;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import org.junit.Ignore;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
@ -96,7 +96,6 @@ public class CueSerializationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore("Currently broken: https://github.com/androidx/media/issues/836")
|
|
||||||
public void serializingBitmapCue() throws Exception {
|
public void serializingBitmapCue() throws Exception {
|
||||||
CueEncoder encoder = new CueEncoder();
|
CueEncoder encoder = new CueEncoder();
|
||||||
CueDecoder decoder = new CueDecoder();
|
CueDecoder decoder = new CueDecoder();
|
||||||
@ -105,7 +104,13 @@ public class CueSerializationTest {
|
|||||||
TestUtil.getByteArray(
|
TestUtil.getByteArray(
|
||||||
ApplicationProvider.getApplicationContext(),
|
ApplicationProvider.getApplicationContext(),
|
||||||
"media/png/non-motion-photo-shortened.png");
|
"media/png/non-motion-photo-shortened.png");
|
||||||
Bitmap bitmap = BitmapFactory.decodeByteArray(imageData, 0, imageData.length);
|
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
|
// Without this hint BitmapFactory reads an 'unknown' RGB color space from the file, which
|
||||||
|
// then causes spurious comparison failures later. Using a named RGB color space allows the
|
||||||
|
// Bitmap.isSameAs comparison to succeed.
|
||||||
|
options.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
|
||||||
|
Bitmap bitmap =
|
||||||
|
BitmapFactory.decodeByteArray(imageData, /* offset= */ 0, imageData.length, options);
|
||||||
Cue bitmapCue = new Cue.Builder().setBitmap(bitmap).build();
|
Cue bitmapCue = new Cue.Builder().setBitmap(bitmap).build();
|
||||||
|
|
||||||
// encoding and decoding
|
// encoding and decoding
|
||||||
|
@ -36,7 +36,8 @@ public final class CueEncoder {
|
|||||||
* @return The serialized byte array.
|
* @return The serialized byte array.
|
||||||
*/
|
*/
|
||||||
public byte[] encode(List<Cue> cues, long durationUs) {
|
public byte[] encode(List<Cue> cues, long durationUs) {
|
||||||
ArrayList<Bundle> bundledCues = BundleCollectionUtil.toBundleArrayList(cues, Cue::toBundle);
|
ArrayList<Bundle> bundledCues =
|
||||||
|
BundleCollectionUtil.toBundleArrayList(cues, Cue::toSerializableBundle);
|
||||||
Bundle allCuesBundle = new Bundle();
|
Bundle allCuesBundle = new Bundle();
|
||||||
allCuesBundle.putParcelableArrayList(CueDecoder.BUNDLE_FIELD_CUES, bundledCues);
|
allCuesBundle.putParcelableArrayList(CueDecoder.BUNDLE_FIELD_CUES, bundledCues);
|
||||||
allCuesBundle.putLong(CueDecoder.BUNDLE_FIELD_DURATION_US, durationUs);
|
allCuesBundle.putLong(CueDecoder.BUNDLE_FIELD_DURATION_US, durationUs);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user