JpegExtractor: support JFIF segment preceding Exif segment

#minor-release

PiperOrigin-RevId: 364561115
This commit is contained in:
kimvde 2021-03-23 15:18:12 +00:00 committed by Ian Baker
parent 3fbfc0ec0e
commit 2c76bc5a4c
9 changed files with 184 additions and 9 deletions

View file

@ -47,6 +47,8 @@
* Extractors:
* Add support for `GContainer` and `GContainerItem` XMP namespace prefixes
in JPEG motion photo parsing.
* Allow JFIF APP0 marker segment preceding Exif APP1 segment in
`JpegExtractor`.
* Remove deprecated symbols:
* Remove `Player.DefaultEventListener`. Use `Player.EventListener`
instead.
@ -99,9 +101,8 @@
SmoothStreaming.
* IMA extension:
* Fix error caused by `AdPlaybackState` ad group times being cleared,
which can occur if the `ImaAdsLoader` is released while an ad is
pending loading
([#8693](https://github.com/google/ExoPlayer/issues/8693)).
which can occur if the `ImaAdsLoader` is released while an ad is pending
loading ([#8693](https://github.com/google/ExoPlayer/issues/8693)).
* Upgrade IMA SDK dependency to 3.22.3, fixing an issue with
`NullPointerExceptions` within `WebView` callbacks
([#8447](https://github.com/google/ExoPlayer/issues/8447)).

View file

@ -60,10 +60,11 @@ public final class JpegExtractor implements Extractor {
private static final int STATE_READING_MOTION_PHOTO_VIDEO = 5;
private static final int STATE_ENDED = 6;
private static final int JPEG_EXIF_HEADER_LENGTH = 12;
private static final int EXIF_ID_CODE_LENGTH = 6;
private static final long EXIF_HEADER = 0x45786966; // Exif
private static final int MARKER_SOI = 0xFFD8; // Start of image marker
private static final int MARKER_SOS = 0xFFDA; // Start of scan (image data) marker
private static final int MARKER_APP0 = 0xFFE0; // Application data 0 marker
private static final int MARKER_APP1 = 0xFFE1; // Application data 1 marker
private static final String HEADER_XMP_APP1 = "http://ns.adobe.com/xap/1.0/";
@ -85,21 +86,33 @@ public final class JpegExtractor implements Extractor {
@Nullable private MotionPhotoMetadata motionPhotoMetadata;
private @MonotonicNonNull ExtractorInput lastExtractorInput;
private @MonotonicNonNull StartOffsetExtractorInput mp4ExtractorStartOffsetExtractorInput;
private @MonotonicNonNull Mp4Extractor mp4Extractor;
@Nullable private Mp4Extractor mp4Extractor;
public JpegExtractor() {
scratch = new ParsableByteArray(JPEG_EXIF_HEADER_LENGTH);
scratch = new ParsableByteArray(EXIF_ID_CODE_LENGTH);
mp4StartPosition = C.POSITION_UNSET;
}
@Override
public boolean sniff(ExtractorInput input) throws IOException {
// See ITU-T.81 (1992) subsection B.1.1.3 and Exif version 2.2 (2002) subsection 4.5.4.
input.peekFully(scratch.getData(), /* offset= */ 0, JPEG_EXIF_HEADER_LENGTH);
if (scratch.readUnsignedShort() != MARKER_SOI || scratch.readUnsignedShort() != MARKER_APP1) {
if (peekMarker(input) != MARKER_SOI) {
return false;
}
scratch.skipBytes(2); // Unused segment length
marker = peekMarker(input);
// Even though JFIF and Exif standards are incompatible in theory, Exif files often contain a
// JFIF APP0 marker segment preceding the Exif APP1 marker segment. Skip the JFIF segment if
// present.
if (marker == MARKER_APP0) {
advancePeekPositionToNextSegment(input);
marker = peekMarker(input);
}
if (marker != MARKER_APP1) {
return false;
}
input.advancePeekPosition(2); // Unused segment length
scratch.reset(/* limit= */ EXIF_ID_CODE_LENGTH);
input.peekFully(scratch.getData(), /* offset= */ 0, EXIF_ID_CODE_LENGTH);
return scratch.readUnsignedInt() == EXIF_HEADER && scratch.readUnsignedShort() == 0; // Exif\0\0
}
@ -152,6 +165,7 @@ public final class JpegExtractor implements Extractor {
public void seek(long position, long timeUs) {
if (position == 0) {
state = STATE_READING_MARKER;
mp4Extractor = null;
} else if (state == STATE_READING_MOTION_PHOTO_VIDEO) {
checkNotNull(mp4Extractor).seek(position, timeUs);
}
@ -164,6 +178,19 @@ public final class JpegExtractor implements Extractor {
}
}
private int peekMarker(ExtractorInput input) throws IOException {
scratch.reset(/* limit= */ 2);
input.peekFully(scratch.getData(), /* offset= */ 0, /* length= */ 2);
return scratch.readUnsignedShort();
}
private void advancePeekPositionToNextSegment(ExtractorInput input) throws IOException {
scratch.reset(/* limit= */ 2);
input.peekFully(scratch.getData(), /* offset= */ 0, /* length= */ 2);
int segmentLength = scratch.readUnsignedShort() - 2;
input.advancePeekPosition(segmentLength);
}
private void readMarker(ExtractorInput input) throws IOException {
scratch.reset(/* limit= */ 2);
input.readFully(scratch.getData(), /* offset= */ 0, /* length= */ 2);

View file

@ -45,6 +45,14 @@ public final class JpegExtractorTest {
JpegExtractor::new, "media/jpeg/pixel-motion-photo-shortened.jpg", simulationConfig);
}
@Test
public void samplePixelMotionPhotoJfifSegmentShortened() throws Exception {
ExtractorAsserts.assertBehavior(
JpegExtractor::new,
"media/jpeg/pixel-motion-photo-jfif-segment-shortened.jpg",
simulationConfig);
}
@Test
public void samplePixelMotionPhotoVideoRemovedShortened() throws Exception {
ExtractorAsserts.assertBehavior(

View file

@ -0,0 +1,32 @@
seekMap:
isSeekable = true
duration = 867000
getPosition(0) = [[timeUs=0, position=6425]]
getPosition(1) = [[timeUs=0, position=6425]]
getPosition(433500) = [[timeUs=0, position=6425]]
getPosition(867000) = [[timeUs=0, position=6425]]
numberOfTracks = 2
track 0:
total output bytes = 3865
sample count = 1
format 0:
id = 1
sampleMimeType = video/avc
codecs = avc1.64000A
maxInputSize = 3895
width = 180
height = 120
pixelWidthHeightRatio = 0.5
initializationData:
data = length 32, hash 1F3D6E87
data = length 10, hash 7A0D0F2B
sample 0:
time = 0
flags = 536870913
data = length 3865, hash 5B0DEEC7
track 1024:
total output bytes = 0
sample count = 0
format 0:
metadata = entries=[Motion photo metadata: photoStartPosition=0, photoSize=6377, photoPresentationTimestampUs=1232840, videoStartPosition=6377, videoSize=4686]
tracksEnded = true

View file

@ -0,0 +1,32 @@
seekMap:
isSeekable = true
duration = 867000
getPosition(0) = [[timeUs=0, position=6425]]
getPosition(1) = [[timeUs=0, position=6425]]
getPosition(433500) = [[timeUs=0, position=6425]]
getPosition(867000) = [[timeUs=0, position=6425]]
numberOfTracks = 2
track 0:
total output bytes = 3865
sample count = 1
format 0:
id = 1
sampleMimeType = video/avc
codecs = avc1.64000A
maxInputSize = 3895
width = 180
height = 120
pixelWidthHeightRatio = 0.5
initializationData:
data = length 32, hash 1F3D6E87
data = length 10, hash 7A0D0F2B
sample 0:
time = 0
flags = 536870913
data = length 3865, hash 5B0DEEC7
track 1024:
total output bytes = 0
sample count = 0
format 0:
metadata = entries=[Motion photo metadata: photoStartPosition=0, photoSize=6377, photoPresentationTimestampUs=1232840, videoStartPosition=6377, videoSize=4686]
tracksEnded = true

View file

@ -0,0 +1,32 @@
seekMap:
isSeekable = true
duration = 867000
getPosition(0) = [[timeUs=0, position=6425]]
getPosition(1) = [[timeUs=0, position=6425]]
getPosition(433500) = [[timeUs=0, position=6425]]
getPosition(867000) = [[timeUs=0, position=6425]]
numberOfTracks = 2
track 0:
total output bytes = 3865
sample count = 1
format 0:
id = 1
sampleMimeType = video/avc
codecs = avc1.64000A
maxInputSize = 3895
width = 180
height = 120
pixelWidthHeightRatio = 0.5
initializationData:
data = length 32, hash 1F3D6E87
data = length 10, hash 7A0D0F2B
sample 0:
time = 0
flags = 536870913
data = length 3865, hash 5B0DEEC7
track 1024:
total output bytes = 0
sample count = 0
format 0:
metadata = entries=[Motion photo metadata: photoStartPosition=0, photoSize=6377, photoPresentationTimestampUs=1232840, videoStartPosition=6377, videoSize=4686]
tracksEnded = true

View file

@ -0,0 +1,32 @@
seekMap:
isSeekable = true
duration = 867000
getPosition(0) = [[timeUs=0, position=6425]]
getPosition(1) = [[timeUs=0, position=6425]]
getPosition(433500) = [[timeUs=0, position=6425]]
getPosition(867000) = [[timeUs=0, position=6425]]
numberOfTracks = 2
track 0:
total output bytes = 3865
sample count = 1
format 0:
id = 1
sampleMimeType = video/avc
codecs = avc1.64000A
maxInputSize = 3895
width = 180
height = 120
pixelWidthHeightRatio = 0.5
initializationData:
data = length 32, hash 1F3D6E87
data = length 10, hash 7A0D0F2B
sample 0:
time = 0
flags = 536870913
data = length 3865, hash 5B0DEEC7
track 1024:
total output bytes = 0
sample count = 0
format 0:
metadata = entries=[Motion photo metadata: photoStartPosition=0, photoSize=6377, photoPresentationTimestampUs=1232840, videoStartPosition=6377, videoSize=4686]
tracksEnded = true

View file

@ -0,0 +1,11 @@
seekMap:
isSeekable = false
duration = UNSET TIME
getPosition(0) = [[timeUs=0, position=0]]
numberOfTracks = 1
track 1024:
total output bytes = 0
sample count = 0
format 0:
metadata = entries=[]
tracksEnded = true

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB