diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 581eb4fd10..f27f499dcb 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -19,6 +19,7 @@ `SampleConsumer.queueInputBitmap` to `TimestampIterator`. * Track Selection: * Extractors: + * Add `BmpExtractor`. * Audio: * Add support for Opus gapless metadata during offload playback. * Video: diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/SingleSampleExtractorHelper.java b/libraries/extractor/src/main/java/androidx/media3/extractor/SingleSampleExtractorHelper.java new file mode 100644 index 0000000000..4bca96713a --- /dev/null +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/SingleSampleExtractorHelper.java @@ -0,0 +1,128 @@ +/* + * Copyright 2023 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 androidx.media3.extractor; + +import static androidx.media3.common.C.BUFFER_FLAG_KEY_FRAME; +import static androidx.media3.common.util.Assertions.checkNotNull; +import static androidx.media3.extractor.Extractor.RESULT_CONTINUE; +import static androidx.media3.extractor.Extractor.RESULT_END_OF_INPUT; +import static java.lang.annotation.ElementType.TYPE_USE; + +import androidx.annotation.IntDef; +import androidx.media3.common.C; +import androidx.media3.common.Format; +import androidx.media3.common.util.ParsableByteArray; +import androidx.media3.common.util.UnstableApi; +import androidx.media3.extractor.mp4.Mp4Extractor; +import java.io.IOException; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.RequiresNonNull; + +/** + * Extracts data by loading all the bytes into one sample. + * + *
Used as a component in other extractors.
+ */
+@UnstableApi
+/* package */ public final class SingleSampleExtractorHelper {
+
+ /** Parser states. */
+ @Documented
+ @Retention(RetentionPolicy.SOURCE)
+ @Target(TYPE_USE)
+ @IntDef({STATE_READING, STATE_ENDED})
+ private @interface State {}
+
+ private static final int STATE_READING = 1;
+ private static final int STATE_ENDED = 2;
+
+ /**
+ * The identifier to use for the image track. Chosen to avoid colliding with track IDs used by
+ * {@link Mp4Extractor} for motion photos.
+ */
+ public static final int IMAGE_TRACK_ID = 1024;
+
+ private static final int FIXED_READ_LENGTH = 1024;
+
+ private int size;
+ private @State int state;
+ private @MonotonicNonNull ExtractorOutput extractorOutput;
+ private @MonotonicNonNull TrackOutput trackOutput;
+
+ public boolean sniff(ExtractorInput input, int fileSignature, int fileSignatureLength)
+ throws IOException {
+ ParsableByteArray scratch = new ParsableByteArray(fileSignatureLength);
+ input.peekFully(scratch.getData(), /* offset= */ 0, fileSignatureLength);
+ return scratch.readUnsignedShort() == fileSignature;
+ }
+
+ public void init(ExtractorOutput output, String containerMimeType) {
+ extractorOutput = output;
+ outputImageTrackAndSeekMap(containerMimeType);
+ }
+
+ public @Extractor.ReadResult int read(ExtractorInput input, PositionHolder seekPosition)
+ throws IOException {
+ switch (state) {
+ case STATE_READING:
+ readSegment(input);
+ return RESULT_CONTINUE;
+ case STATE_ENDED:
+ return RESULT_END_OF_INPUT;
+ default:
+ throw new IllegalStateException();
+ }
+ }
+
+ private void readSegment(ExtractorInput input) throws IOException {
+ int result =
+ checkNotNull(trackOutput).sampleData(input, FIXED_READ_LENGTH, /* allowEndOfInput= */ true);
+ if (result == C.RESULT_END_OF_INPUT) {
+ state = STATE_ENDED;
+ @C.BufferFlags int flags = BUFFER_FLAG_KEY_FRAME;
+ trackOutput.sampleMetadata(
+ /* timeUs= */ 0, flags, size, /* offset= */ 0, /* cryptoData= */ null);
+ size = 0;
+ } else {
+ size += result;
+ }
+ }
+
+ public void seek(long position) {
+ if (position == 0 || state == STATE_READING) {
+ state = STATE_READING;
+ size = 0;
+ }
+ }
+
+ @RequiresNonNull("this.extractorOutput")
+ private void outputImageTrackAndSeekMap(String containerMimeType) {
+ trackOutput = extractorOutput.track(IMAGE_TRACK_ID, C.TRACK_TYPE_IMAGE);
+ trackOutput.format(
+ new Format.Builder()
+ .setContainerMimeType(containerMimeType)
+ .setTileCountHorizontal(1)
+ .setTileCountVertical(1)
+ .build());
+ extractorOutput.endTracks();
+ extractorOutput.seekMap(new SingleSampleSeekMap(/* durationUs= */ C.TIME_UNSET));
+ state = STATE_READING;
+ }
+}
diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/bmp/BmpExtractor.java b/libraries/extractor/src/main/java/androidx/media3/extractor/bmp/BmpExtractor.java
new file mode 100644
index 0000000000..cac98de6ee
--- /dev/null
+++ b/libraries/extractor/src/main/java/androidx/media3/extractor/bmp/BmpExtractor.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2023 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 androidx.media3.extractor.bmp;
+
+import androidx.media3.common.MimeTypes;
+import androidx.media3.common.util.UnstableApi;
+import androidx.media3.extractor.Extractor;
+import androidx.media3.extractor.ExtractorInput;
+import androidx.media3.extractor.ExtractorOutput;
+import androidx.media3.extractor.PositionHolder;
+import androidx.media3.extractor.SingleSampleExtractorHelper;
+import java.io.IOException;
+
+/** Extracts data from the BMP container format. */
+@UnstableApi
+public final class BmpExtractor implements Extractor {
+ private static final int BMP_FILE_SIGNATURE_LENGTH = 2;
+ private static final int BMP_FILE_SIGNATURE = 0x424D;
+
+ private final SingleSampleExtractorHelper imageExtractor;
+
+ /** Creates an instance. */
+ public BmpExtractor() {
+ imageExtractor = new SingleSampleExtractorHelper();
+ }
+
+ @Override
+ public boolean sniff(ExtractorInput input) throws IOException {
+ return imageExtractor.sniff(input, BMP_FILE_SIGNATURE, BMP_FILE_SIGNATURE_LENGTH);
+ }
+
+ @Override
+ public void init(ExtractorOutput output) {
+ imageExtractor.init(output, MimeTypes.IMAGE_BMP);
+ }
+
+ @Override
+ public @ReadResult int read(ExtractorInput input, PositionHolder seekPosition)
+ throws IOException {
+ return imageExtractor.read(input, seekPosition);
+ }
+
+ @Override
+ public void seek(long position, long timeUs) {
+ imageExtractor.seek(position);
+ }
+
+ @Override
+ public void release() {
+ // Do nothing.
+ }
+}
diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/ImageExtractorUtil.java b/libraries/extractor/src/main/java/androidx/media3/extractor/bmp/package-info.java
similarity index 57%
rename from libraries/extractor/src/main/java/androidx/media3/extractor/ImageExtractorUtil.java
rename to libraries/extractor/src/main/java/androidx/media3/extractor/bmp/package-info.java
index 744820956d..95d2d61840 100644
--- a/libraries/extractor/src/main/java/androidx/media3/extractor/ImageExtractorUtil.java
+++ b/libraries/extractor/src/main/java/androidx/media3/extractor/bmp/package-info.java
@@ -13,20 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package androidx.media3.extractor;
+@NonNullApi
+package androidx.media3.extractor.bmp;
-import androidx.media3.common.util.UnstableApi;
-import androidx.media3.extractor.mp4.Mp4Extractor;
-
-/** Utilities for image extractors. */
-@UnstableApi
-public class ImageExtractorUtil {
-
- /**
- * The identifier to use for the image track. Chosen to avoid colliding with track IDs used by
- * {@link Mp4Extractor} for motion photos.
- */
- public static final int IMAGE_TRACK_ID = 1024;
-
- private ImageExtractorUtil() {}
-}
+import androidx.media3.common.util.NonNullApi;
diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/jpeg/JpegExtractor.java b/libraries/extractor/src/main/java/androidx/media3/extractor/jpeg/JpegExtractor.java
index f9fc1d98b9..142ea71e6c 100644
--- a/libraries/extractor/src/main/java/androidx/media3/extractor/jpeg/JpegExtractor.java
+++ b/libraries/extractor/src/main/java/androidx/media3/extractor/jpeg/JpegExtractor.java
@@ -16,7 +16,7 @@
package androidx.media3.extractor.jpeg;
import static androidx.media3.common.util.Assertions.checkNotNull;
-import static androidx.media3.extractor.ImageExtractorUtil.IMAGE_TRACK_ID;
+import static androidx.media3.extractor.SingleSampleExtractorHelper.IMAGE_TRACK_ID;
import static java.lang.annotation.ElementType.TYPE_USE;
import androidx.annotation.IntDef;
diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/png/PngExtractor.java b/libraries/extractor/src/main/java/androidx/media3/extractor/png/PngExtractor.java
index afc645362d..45c56ef042 100644
--- a/libraries/extractor/src/main/java/androidx/media3/extractor/png/PngExtractor.java
+++ b/libraries/extractor/src/main/java/androidx/media3/extractor/png/PngExtractor.java
@@ -15,127 +15,49 @@
*/
package androidx.media3.extractor.png;
-import static androidx.media3.common.C.BUFFER_FLAG_KEY_FRAME;
-import static androidx.media3.common.util.Assertions.checkNotNull;
-import static androidx.media3.extractor.ImageExtractorUtil.IMAGE_TRACK_ID;
-import static java.lang.annotation.ElementType.TYPE_USE;
-
-import androidx.annotation.IntDef;
-import androidx.media3.common.C;
-import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes;
-import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.extractor.Extractor;
import androidx.media3.extractor.ExtractorInput;
import androidx.media3.extractor.ExtractorOutput;
import androidx.media3.extractor.PositionHolder;
-import androidx.media3.extractor.SingleSampleSeekMap;
-import androidx.media3.extractor.TrackOutput;
+import androidx.media3.extractor.SingleSampleExtractorHelper;
import java.io.IOException;
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
-import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/** Extracts data from the PNG container format. */
@UnstableApi
-// TODO: b/289989902 - Move methods of this class into ImageExtractorUtil once there are multiple
-// image extractors.
public final class PngExtractor implements Extractor {
- /** Parser states. */
- @Documented
- @Retention(RetentionPolicy.SOURCE)
- @Target(TYPE_USE)
- @IntDef({STATE_READING_IMAGE, STATE_ENDED})
- private @interface State {}
-
- private static final int STATE_READING_IMAGE = 1;
- private static final int STATE_ENDED = 2;
-
- private static final int PNG_FILE_SIGNATURE_LENGTH = 2;
// See PNG (Portable Network Graphics) Specification, Version 1.2, Section 12.12 and Section 3.1.
private static final int PNG_FILE_SIGNATURE = 0x8950;
- private static final int FIXED_READ_LENGTH = 1024;
+ private static final int PNG_FILE_SIGNATURE_LENGTH = 2;
- private final ParsableByteArray scratch;
-
- private int size;
- private @State int state;
- private @MonotonicNonNull ExtractorOutput extractorOutput;
- private @MonotonicNonNull TrackOutput trackOutput;
+ private final SingleSampleExtractorHelper imageExtractor;
/** Creates an instance. */
public PngExtractor() {
- scratch = new ParsableByteArray(PNG_FILE_SIGNATURE_LENGTH);
+ imageExtractor = new SingleSampleExtractorHelper();
}
@Override
public boolean sniff(ExtractorInput input) throws IOException {
- scratch.reset(/* limit= */ PNG_FILE_SIGNATURE_LENGTH);
- input.peekFully(scratch.getData(), /* offset= */ 0, PNG_FILE_SIGNATURE_LENGTH);
- return scratch.readUnsignedShort() == PNG_FILE_SIGNATURE;
+ return imageExtractor.sniff(input, PNG_FILE_SIGNATURE, PNG_FILE_SIGNATURE_LENGTH);
}
@Override
public void init(ExtractorOutput output) {
- extractorOutput = output;
- outputImageTrackAndSeekMap();
+ imageExtractor.init(output, MimeTypes.IMAGE_PNG);
}
@Override
public @ReadResult int read(ExtractorInput input, PositionHolder seekPosition)
throws IOException {
- switch (state) {
- case STATE_READING_IMAGE:
- readSegment(input);
- return RESULT_CONTINUE;
- case STATE_ENDED:
- return RESULT_END_OF_INPUT;
- default:
- throw new IllegalStateException();
- }
- }
-
- private void readSegment(ExtractorInput input) throws IOException {
- int result =
- checkNotNull(trackOutput).sampleData(input, FIXED_READ_LENGTH, /* allowEndOfInput= */ true);
- if (result == C.RESULT_END_OF_INPUT) {
- state = STATE_ENDED;
- @C.BufferFlags int flags = BUFFER_FLAG_KEY_FRAME;
- trackOutput.sampleMetadata(
- /* timeUs= */ 0, flags, size, /* offset= */ 0, /* cryptoData= */ null);
- size = 0;
- } else {
- size += result;
- }
- }
-
- @RequiresNonNull("this.extractorOutput")
- private void outputImageTrackAndSeekMap() {
- trackOutput = extractorOutput.track(IMAGE_TRACK_ID, C.TRACK_TYPE_IMAGE);
- trackOutput.format(
- new Format.Builder()
- .setContainerMimeType(MimeTypes.IMAGE_PNG)
- .setTileCountHorizontal(1)
- .setTileCountVertical(1)
- .build());
- extractorOutput.endTracks();
- extractorOutput.seekMap(new SingleSampleSeekMap(/* durationUs= */ C.TIME_UNSET));
- state = STATE_READING_IMAGE;
+ return imageExtractor.read(input, seekPosition);
}
@Override
public void seek(long position, long timeUs) {
- if (position == 0) {
- state = STATE_READING_IMAGE;
- }
- if (state == STATE_READING_IMAGE) {
- size = 0;
- }
+ imageExtractor.seek(position);
}
@Override
diff --git a/libraries/extractor/src/test/java/androidx/media3/extractor/bmp/BmpExtractorTest.java b/libraries/extractor/src/test/java/androidx/media3/extractor/bmp/BmpExtractorTest.java
new file mode 100644
index 0000000000..c5b410ad13
--- /dev/null
+++ b/libraries/extractor/src/test/java/androidx/media3/extractor/bmp/BmpExtractorTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2023 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 androidx.media3.extractor.bmp;
+
+import androidx.media3.test.utils.ExtractorAsserts;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.ParameterizedRobolectricTestRunner;
+
+/** Unit tests for {@link BmpExtractor}. */
+@RunWith(ParameterizedRobolectricTestRunner.class)
+public final class BmpExtractorTest {
+
+ @ParameterizedRobolectricTestRunner.Parameters(name = "{0}")
+ public static ImmutableList