Support AVIF in exoplayer
https://developer.android.com/media/platform/supported-formats#image-formats was updated to include AVIF support in API 34+, so <unknown commit> updated our associated Util's to reflect this. After that change, ExoPlayer's BitmapFactoryImageDecoder will be able to decode AVIF, but the player won't be able to detect or extract it. Add this support for completeness, so that ExoPlayer can continue to say it supports all formats in https://developer.android.com/media/platform/supported-formats#image-formats. PiperOrigin-RevId: 633956245
This commit is contained in:
parent
0e5a5e0294
commit
7b357337d2
@ -79,7 +79,7 @@
|
||||
* Image:
|
||||
* Add support for non-square DASH thumbnail grids
|
||||
([#1300](https://github.com/androidx/media/pull/1300)).
|
||||
* Add AVIF decoding support for API 34+.
|
||||
* Add support for AVIF for API 34+.
|
||||
* DRM:
|
||||
* Allow setting a `LoadErrorHandlingPolicy` on
|
||||
`DefaultDrmSessionManagerProvider`
|
||||
|
@ -58,6 +58,7 @@ public final class FileTypes {
|
||||
* <li>{@link #WEBP}
|
||||
* <li>{@link #BMP}
|
||||
* <li>{@link #HEIF}
|
||||
* <li>{@link #AVIF}
|
||||
* </ul>
|
||||
*/
|
||||
@Documented
|
||||
@ -65,7 +66,7 @@ public final class FileTypes {
|
||||
@Target(TYPE_USE)
|
||||
@IntDef({
|
||||
UNKNOWN, AC3, AC4, ADTS, AMR, FLAC, FLV, MATROSKA, MP3, MP4, OGG, PS, TS, WAV, WEBVTT, JPEG,
|
||||
MIDI, AVI, PNG, WEBP, BMP, HEIF
|
||||
MIDI, AVI, PNG, WEBP, BMP, HEIF, AVIF
|
||||
})
|
||||
public @interface Type {}
|
||||
|
||||
@ -135,6 +136,9 @@ public final class FileTypes {
|
||||
/** File type for the HEIF format. */
|
||||
public static final int HEIF = 20;
|
||||
|
||||
/** File type for the AVIF format. */
|
||||
public static final int AVIF = 21;
|
||||
|
||||
@VisibleForTesting /* package */ static final String HEADER_CONTENT_TYPE = "Content-Type";
|
||||
|
||||
private static final String EXTENSION_AC3 = ".ac3";
|
||||
@ -175,6 +179,7 @@ public final class FileTypes {
|
||||
private static final String EXTENSION_BMP = ".bmp";
|
||||
private static final String EXTENSION_DIB = ".dib";
|
||||
private static final String EXTENSION_HEIC = ".heic";
|
||||
private static final String EXTENSION_AVIF = ".avif";
|
||||
|
||||
private FileTypes() {}
|
||||
|
||||
@ -247,7 +252,10 @@ public final class FileTypes {
|
||||
case MimeTypes.IMAGE_BMP:
|
||||
return FileTypes.BMP;
|
||||
case MimeTypes.IMAGE_HEIF:
|
||||
case MimeTypes.IMAGE_HEIC:
|
||||
return FileTypes.HEIF;
|
||||
case MimeTypes.IMAGE_AVIF:
|
||||
return FileTypes.AVIF;
|
||||
default:
|
||||
return FileTypes.UNKNOWN;
|
||||
}
|
||||
@ -323,6 +331,8 @@ public final class FileTypes {
|
||||
return FileTypes.BMP;
|
||||
} else if (filename.endsWith(EXTENSION_HEIC)) {
|
||||
return FileTypes.HEIF;
|
||||
} else if (filename.endsWith(EXTENSION_AVIF)) {
|
||||
return FileTypes.AVIF;
|
||||
} else {
|
||||
return FileTypes.UNKNOWN;
|
||||
}
|
||||
|
@ -63,7 +63,8 @@ public class ParameterizedImagePlaybackTest {
|
||||
"png/media3test.png",
|
||||
"bmp/non-motion-photo-shortened-cropped.bmp",
|
||||
"png/non-motion-photo-shortened.png",
|
||||
"webp/ic_launcher_round.webp")),
|
||||
"webp/ic_launcher_round.webp",
|
||||
"avif/white-1x1.avif")),
|
||||
/* predicate= */ input -> !input.isEmpty()));
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,7 @@ import androidx.media3.common.util.TimestampAdjuster;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.extractor.amr.AmrExtractor;
|
||||
import androidx.media3.extractor.avi.AviExtractor;
|
||||
import androidx.media3.extractor.avif.AvifExtractor;
|
||||
import androidx.media3.extractor.bmp.BmpExtractor;
|
||||
import androidx.media3.extractor.flac.FlacExtractor;
|
||||
import androidx.media3.extractor.flv.FlvExtractor;
|
||||
@ -94,6 +95,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
* <li>WEBP ({@link WebpExtractor})
|
||||
* <li>BMP ({@link BmpExtractor})
|
||||
* <li>HEIF ({@link HeifExtractor})
|
||||
* <li>AVIF ({@link AvifExtractor})
|
||||
* <li>MIDI, if available, the MIDI extension's {@code androidx.media3.decoder.midi.MidiExtractor}
|
||||
* is used.
|
||||
* </ul>
|
||||
@ -128,7 +130,8 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
|
||||
FileTypes.PNG,
|
||||
FileTypes.WEBP,
|
||||
FileTypes.BMP,
|
||||
FileTypes.HEIF
|
||||
FileTypes.HEIF,
|
||||
FileTypes.AVIF
|
||||
};
|
||||
|
||||
private static final ExtensionLoader FLAC_EXTENSION_LOADER =
|
||||
@ -569,6 +572,9 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
|
||||
extractors.add(new HeifExtractor());
|
||||
}
|
||||
break;
|
||||
case FileTypes.AVIF:
|
||||
extractors.add(new AvifExtractor());
|
||||
break;
|
||||
case FileTypes.WEBVTT:
|
||||
case FileTypes.UNKNOWN:
|
||||
default:
|
||||
|
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2024 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.avif;
|
||||
|
||||
import androidx.media3.common.C;
|
||||
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.SingleSampleExtractor;
|
||||
import java.io.IOException;
|
||||
|
||||
/** Extracts data from the AVIF (.avif) container format. */
|
||||
@UnstableApi
|
||||
public final class AvifExtractor implements Extractor {
|
||||
|
||||
// Specification reference: ISO/IEC 23008-12:2022
|
||||
private static final int AVIF_FILE_SIGNATURE_PART_1 = 0x66747970;
|
||||
private static final int AVIF_FILE_SIGNATURE_PART_2 = 0x61766966;
|
||||
private static final int FILE_SIGNATURE_SEGMENT_LENGTH = 4;
|
||||
|
||||
private final ParsableByteArray scratch;
|
||||
private final SingleSampleExtractor imageExtractor;
|
||||
|
||||
/** Creates an instance. */
|
||||
public AvifExtractor() {
|
||||
scratch = new ParsableByteArray(FILE_SIGNATURE_SEGMENT_LENGTH);
|
||||
imageExtractor =
|
||||
new SingleSampleExtractor(
|
||||
/* fileSignature= */ C.INDEX_UNSET,
|
||||
/* fileSignatureLength= */ C.LENGTH_UNSET,
|
||||
MimeTypes.IMAGE_AVIF);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sniff(ExtractorInput input) throws IOException {
|
||||
input.advancePeekPosition(4);
|
||||
return readAndCompareFourBytes(input, AVIF_FILE_SIGNATURE_PART_1)
|
||||
&& readAndCompareFourBytes(input, AVIF_FILE_SIGNATURE_PART_2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(ExtractorOutput output) {
|
||||
imageExtractor.init(output);
|
||||
}
|
||||
|
||||
@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, timeUs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
private boolean readAndCompareFourBytes(ExtractorInput input, int bytesToCompare)
|
||||
throws IOException {
|
||||
scratch.reset(/* limit= */ FILE_SIGNATURE_SEGMENT_LENGTH);
|
||||
input.peekFully(scratch.getData(), /* offset= */ 0, FILE_SIGNATURE_SEGMENT_LENGTH);
|
||||
return scratch.readUnsignedInt() == bytesToCompare;
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
@NonNullApi
|
||||
package androidx.media3.extractor.avif;
|
||||
|
||||
import androidx.media3.common.util.NonNullApi;
|
@ -22,6 +22,7 @@ import android.net.Uri;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.extractor.amr.AmrExtractor;
|
||||
import androidx.media3.extractor.avi.AviExtractor;
|
||||
import androidx.media3.extractor.avif.AvifExtractor;
|
||||
import androidx.media3.extractor.bmp.BmpExtractor;
|
||||
import androidx.media3.extractor.flac.FlacExtractor;
|
||||
import androidx.media3.extractor.flv.FlvExtractor;
|
||||
@ -82,7 +83,8 @@ public final class DefaultExtractorsFactoryTest {
|
||||
PngExtractor.class,
|
||||
WebpExtractor.class,
|
||||
BmpExtractor.class,
|
||||
HeifExtractor.class)
|
||||
HeifExtractor.class,
|
||||
AvifExtractor.class)
|
||||
.inOrder();
|
||||
}
|
||||
|
||||
@ -129,7 +131,8 @@ public final class DefaultExtractorsFactoryTest {
|
||||
PngExtractor.class,
|
||||
WebpExtractor.class,
|
||||
BmpExtractor.class,
|
||||
HeifExtractor.class)
|
||||
HeifExtractor.class,
|
||||
AvifExtractor.class)
|
||||
.inOrder();
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2024 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.avif;
|
||||
|
||||
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 AvifExtractor}. */
|
||||
@RunWith(ParameterizedRobolectricTestRunner.class)
|
||||
public final class AvifExtractorTest {
|
||||
|
||||
@ParameterizedRobolectricTestRunner.Parameters(name = "{0}")
|
||||
public static ImmutableList<ExtractorAsserts.SimulationConfig> params() {
|
||||
return ExtractorAsserts.configs();
|
||||
}
|
||||
|
||||
@ParameterizedRobolectricTestRunner.Parameter
|
||||
public ExtractorAsserts.SimulationConfig simulationConfig;
|
||||
|
||||
@Test
|
||||
public void sampleAvif() throws Exception {
|
||||
ExtractorAsserts.assertBehavior(
|
||||
AvifExtractor::new, "media/avif/white-1x1.avif", simulationConfig);
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
seekMap:
|
||||
isSeekable = true
|
||||
duration = UNSET TIME
|
||||
getPosition(0) = [[timeUs=0, position=0]]
|
||||
getPosition(1) = [[timeUs=1, position=0]]
|
||||
numberOfTracks = 1
|
||||
track 1024:
|
||||
total output bytes = 295
|
||||
sample count = 1
|
||||
format 0:
|
||||
sampleMimeType = image/avif
|
||||
sample 0:
|
||||
time = 0
|
||||
flags = 1
|
||||
data = length 295, hash 24CB12
|
||||
tracksEnded = true
|
@ -0,0 +1,16 @@
|
||||
seekMap:
|
||||
isSeekable = true
|
||||
duration = UNSET TIME
|
||||
getPosition(0) = [[timeUs=0, position=0]]
|
||||
getPosition(1) = [[timeUs=1, position=0]]
|
||||
numberOfTracks = 1
|
||||
track 1024:
|
||||
total output bytes = 295
|
||||
sample count = 1
|
||||
format 0:
|
||||
sampleMimeType = image/avif
|
||||
sample 0:
|
||||
time = 0
|
||||
flags = 1
|
||||
data = length 295, hash 24CB12
|
||||
tracksEnded = true
|
BIN
libraries/test_data/src/test/assets/media/avif/white-1x1.avif
Normal file
BIN
libraries/test_data/src/test/assets/media/avif/white-1x1.avif
Normal file
Binary file not shown.
After Width: | Height: | Size: 295 B |
@ -0,0 +1,5 @@
|
||||
ImageOutput:
|
||||
rendered image count = 1
|
||||
image output #1:
|
||||
presentationTimeUs = 0
|
||||
bitmap hash = 443865884
|
@ -0,0 +1,8 @@
|
||||
ImageOutput:
|
||||
rendered image count = 2
|
||||
image output #1:
|
||||
presentationTimeUs = 0
|
||||
bitmap hash = -389047680
|
||||
image output #2:
|
||||
presentationTimeUs = 0
|
||||
bitmap hash = 443865884
|
@ -0,0 +1,11 @@
|
||||
ImageOutput:
|
||||
rendered image count = 3
|
||||
image output #1:
|
||||
presentationTimeUs = 0
|
||||
bitmap hash = -389047680
|
||||
image output #2:
|
||||
presentationTimeUs = 0
|
||||
bitmap hash = 1367007828
|
||||
image output #3:
|
||||
presentationTimeUs = 0
|
||||
bitmap hash = 443865884
|
@ -0,0 +1,8 @@
|
||||
ImageOutput:
|
||||
rendered image count = 2
|
||||
image output #1:
|
||||
presentationTimeUs = 0
|
||||
bitmap hash = -389047680
|
||||
image output #2:
|
||||
presentationTimeUs = 0
|
||||
bitmap hash = 1367007828
|
@ -0,0 +1,5 @@
|
||||
ImageOutput:
|
||||
rendered image count = 1
|
||||
image output #1:
|
||||
presentationTimeUs = 0
|
||||
bitmap hash = -389047680
|
@ -0,0 +1,8 @@
|
||||
ImageOutput:
|
||||
rendered image count = 2
|
||||
image output #1:
|
||||
presentationTimeUs = 0
|
||||
bitmap hash = -1851534335
|
||||
image output #2:
|
||||
presentationTimeUs = 0
|
||||
bitmap hash = 443865884
|
@ -0,0 +1,11 @@
|
||||
ImageOutput:
|
||||
rendered image count = 3
|
||||
image output #1:
|
||||
presentationTimeUs = 0
|
||||
bitmap hash = -1851534335
|
||||
image output #2:
|
||||
presentationTimeUs = 0
|
||||
bitmap hash = -389047680
|
||||
image output #3:
|
||||
presentationTimeUs = 0
|
||||
bitmap hash = 443865884
|
@ -0,0 +1,14 @@
|
||||
ImageOutput:
|
||||
rendered image count = 4
|
||||
image output #1:
|
||||
presentationTimeUs = 0
|
||||
bitmap hash = -1851534335
|
||||
image output #2:
|
||||
presentationTimeUs = 0
|
||||
bitmap hash = -389047680
|
||||
image output #3:
|
||||
presentationTimeUs = 0
|
||||
bitmap hash = 1367007828
|
||||
image output #4:
|
||||
presentationTimeUs = 0
|
||||
bitmap hash = 443865884
|
@ -0,0 +1,11 @@
|
||||
ImageOutput:
|
||||
rendered image count = 3
|
||||
image output #1:
|
||||
presentationTimeUs = 0
|
||||
bitmap hash = -1851534335
|
||||
image output #2:
|
||||
presentationTimeUs = 0
|
||||
bitmap hash = -389047680
|
||||
image output #3:
|
||||
presentationTimeUs = 0
|
||||
bitmap hash = 1367007828
|
@ -0,0 +1,8 @@
|
||||
ImageOutput:
|
||||
rendered image count = 2
|
||||
image output #1:
|
||||
presentationTimeUs = 0
|
||||
bitmap hash = -1851534335
|
||||
image output #2:
|
||||
presentationTimeUs = 0
|
||||
bitmap hash = -389047680
|
@ -0,0 +1,11 @@
|
||||
ImageOutput:
|
||||
rendered image count = 3
|
||||
image output #1:
|
||||
presentationTimeUs = 0
|
||||
bitmap hash = -1851534335
|
||||
image output #2:
|
||||
presentationTimeUs = 0
|
||||
bitmap hash = 1367007828
|
||||
image output #3:
|
||||
presentationTimeUs = 0
|
||||
bitmap hash = 443865884
|
@ -0,0 +1,8 @@
|
||||
ImageOutput:
|
||||
rendered image count = 2
|
||||
image output #1:
|
||||
presentationTimeUs = 0
|
||||
bitmap hash = -1851534335
|
||||
image output #2:
|
||||
presentationTimeUs = 0
|
||||
bitmap hash = 1367007828
|
@ -0,0 +1,5 @@
|
||||
ImageOutput:
|
||||
rendered image count = 1
|
||||
image output #1:
|
||||
presentationTimeUs = 0
|
||||
bitmap hash = -1851534335
|
@ -0,0 +1,8 @@
|
||||
ImageOutput:
|
||||
rendered image count = 2
|
||||
image output #1:
|
||||
presentationTimeUs = 0
|
||||
bitmap hash = 1367007828
|
||||
image output #2:
|
||||
presentationTimeUs = 0
|
||||
bitmap hash = 443865884
|
@ -0,0 +1,5 @@
|
||||
ImageOutput:
|
||||
rendered image count = 1
|
||||
image output #1:
|
||||
presentationTimeUs = 0
|
||||
bitmap hash = 1367007828
|
Loading…
x
Reference in New Issue
Block a user