mirror of
https://github.com/samsonjs/media.git
synced 2026-03-25 09:25:53 +00:00
JpegExtractor: support JFIF segment preceding Exif segment
#minor-release PiperOrigin-RevId: 364561115
This commit is contained in:
parent
3fbfc0ec0e
commit
2c76bc5a4c
9 changed files with 184 additions and 9 deletions
|
|
@ -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)).
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
BIN
testdata/src/test/assets/media/jpeg/pixel-motion-photo-jfif-segment-shortened.jpg
vendored
Normal file
BIN
testdata/src/test/assets/media/jpeg/pixel-motion-photo-jfif-segment-shortened.jpg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
Loading…
Reference in a new issue