Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AVI extraction support #9915

Merged
merged 73 commits into from
Jun 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
33d22a1
Tracks parsing, SeekMap (Index) started
dburckh Jan 15, 2022
a9c9418
Working!
dburckh Jan 18, 2022
1a1dc44
More efficient header parsing
dburckh Jan 18, 2022
58a2ca6
Fix for movi with LIST('rec ')
dburckh Jan 18, 2022
8d90498
Minor Tweaks
dburckh Jan 19, 2022
d9afe51
Refactor, remove dead code
dburckh Jan 19, 2022
4c76bf1
Add file chooser to UI, Fixed timing issues on H264, Fixed PAR on XVI…
dburckh Jan 22, 2022
5ebaafd
Fix Avc Seek
dburckh Jan 22, 2022
6f41585
AvcAviTrack cleanup
dburckh Jan 22, 2022
d2bb0c2
Add MJPEG Support
dburckh Jan 22, 2022
8d9b895
Fix ID10T timing error
dburckh Jan 22, 2022
3ce652e
Added support for DX50
dburckh Jan 22, 2022
c4cf876
Tests for Mp4vAviTrack and StreamHeaderBox
dburckh Jan 23, 2022
3daa74d
Tests for AudioFormat, VideoFormat and UnboundedIntArray
dburckh Jan 23, 2022
b90333a
Clean up UnboundedIntArray
dburckh Jan 23, 2022
98b487e
Removed unused StreamDataBox
dburckh Jan 23, 2022
77a1873
Fix bugs around seek
dburckh Jan 23, 2022
019aee2
Remapped MP4x to video/mp4x
dburckh Jan 23, 2022
ec26539
BitmapFactoryVideoRenderer improvements
dburckh Jan 23, 2022
167c2f3
Fix alignment in track scanner
dburckh Jan 23, 2022
43b8a9b
Add support for StreamName fixed issues with position alignment
dburckh Jan 23, 2022
09485cb
Passed along suggestedBufferSize to ExoPlayer
dburckh Jan 23, 2022
7ea2d75
Refactor Clock logic. Refactor peeking for MP4V and AVC. Moved AVI …
dburckh Jan 24, 2022
f1d007e
Fix issue where reading mime type wrong in video. More tests
dburckh Jan 25, 2022
c41dc23
Fix crash on streamId out of bounds
dburckh Jan 25, 2022
1d85bf2
Updated seek
dburckh Jan 28, 2022
1ff7829
Fix BitmapFactoryVideoRenderer sync issues
dburckh Jan 29, 2022
a17d36d
Minor cleanup
dburckh Jan 29, 2022
b520b26
More AviExtractor tests
dburckh Jan 29, 2022
9d88db7
Clean up BitmapFactoryVideoRenderer
dburckh Jan 29, 2022
66c240f
AviHeaderBox Tests
dburckh Jan 29, 2022
432ff5e
More AviExtractor tests
dburckh Jan 29, 2022
df9e51d
More AviExtractor tests
dburckh Jan 30, 2022
bec8b44
BitmapFactoryVideoRenderer Tests
dburckh Jan 30, 2022
99e699a
Clean up tests
dburckh Jan 30, 2022
5e7679d
Fix bug with null in aviTracks, add FourCC FMP4
dburckh Jan 30, 2022
bf1a156
Added support for AVC picOrderCountType = 2
dburckh Jan 31, 2022
4f365ce
Merge branch 'google:dev-v2' into dev-v2
dburckh Jan 31, 2022
7c1cf36
Commented verbose log
dburckh Jan 31, 2022
565db92
Don't send meta on 0 length packets
dburckh Jan 31, 2022
0ff238d
Fix int overrun on files with large scale
dburckh Jan 31, 2022
9bd93ad
Add work-around for muxer bug where idx1 offset is from 0, not "movi"
dburckh Jan 31, 2022
d006f3f
Beef up readIdx1 tests
dburckh Jan 31, 2022
5f76e0f
AvcChunkPeeker tests
dburckh Jan 31, 2022
e67d471
Added missing sample file.
dburckh Feb 1, 2022
89d1045
Move list types to the same class. Expose readHeaderList for externa…
dburckh Feb 1, 2022
d1fffb4
Add audio to picker.
dburckh Feb 1, 2022
0896a04
Add bits per second for audio.
dburckh Feb 1, 2022
e9fcc96
Added copyright, better comments, removed dead code.
dburckh Feb 2, 2022
43cfc4a
Revert Demo and remove BitmapFactoryVideoRenderer classes
ojw28 Oct 5, 2021
30257b0
Revert random change
dburckh Feb 2, 2022
1528b8b
Found out RESULT_SEEK is a bad thing. Greatly improved Extractor eff…
dburckh Feb 2, 2022
2d727de
Found out RESULT_SEEK is a bad thing. Greatly improved Extractor eff…
dburckh Feb 2, 2022
aee15f6
Simplify AviTrack
dburckh Feb 4, 2022
5b95229
Refactor to ChunkHander, add Mp3ChunkHandler
dburckh Feb 4, 2022
ba0b991
Optimize AvcChunkHandler to use normal ChunkClock if no B Frames.
dburckh Feb 5, 2022
84d3f62
Fix DefaultExtractorsFactoryTest
dburckh Feb 5, 2022
14c842e
Add coverage for max ref frames
dburckh Feb 5, 2022
3a9f1f9
Improved comments, improved naming consistency.
dburckh Feb 5, 2022
247b576
MpegAudioChunkHandler cleanup and Tests
dburckh Feb 5, 2022
71f094f
MpegAudioChunkHandler seek fixes
dburckh Feb 5, 2022
193ece6
Merge branch 'everything' into avi
dburckh Feb 5, 2022
35c9788
Remove method I didn't end up needing
dburckh Feb 5, 2022
e7cbb3d
Add yet another Divx FourCC variant.
dburckh Feb 6, 2022
05db171
Remove method I didn't end up needing
dburckh Feb 6, 2022
ed2c814
Merge branch 'everything' into avi
dburckh Feb 6, 2022
3886f5f
Code Review Changes
dburckh Feb 14, 2022
ac96a2b
Removed commented code.
dburckh Feb 15, 2022
f604ee5
Changed constants to literals.
dburckh Feb 15, 2022
1a616b4
Added /* package */ to package level variables.
dburckh Feb 15, 2022
4f09fc3
Changed readable values to hex to follow Exo standard
dburckh Feb 15, 2022
e14617f
Merged Robo and non-Robo tests into one file
dburckh Feb 15, 2022
0b629e4
Simplified UnboundedIntArray
dburckh Feb 17, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public final class FileTypes {
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({
UNKNOWN, AC3, AC4, ADTS, AMR, FLAC, FLV, MATROSKA, MP3, MP4, OGG, PS, TS, WAV, WEBVTT, JPEG
UNKNOWN, AC3, AC4, ADTS, AMR, FLAC, FLV, MATROSKA, MP3, MP4, OGG, PS, TS, WAV, WEBVTT, JPEG, AVI
})
public @interface Type {}
/** Unknown file type. */
Expand Down Expand Up @@ -73,6 +73,8 @@ public final class FileTypes {
public static final int WEBVTT = 13;
/** File type for the JPEG format. */
public static final int JPEG = 14;
/** File type for the AVI format. */
public static final int AVI = 15;

@VisibleForTesting /* package */ static final String HEADER_CONTENT_TYPE = "Content-Type";

Expand Down Expand Up @@ -105,6 +107,7 @@ public final class FileTypes {
private static final String EXTENSION_WEBVTT = ".webvtt";
private static final String EXTENSION_JPG = ".jpg";
private static final String EXTENSION_JPEG = ".jpeg";
private static final String EXTENSION_AVI = ".avi";

private FileTypes() {}

Expand Down Expand Up @@ -167,6 +170,8 @@ public static int inferFileTypeFromMimeType(@Nullable String mimeType) {
return FileTypes.WEBVTT;
case MimeTypes.IMAGE_JPEG:
return FileTypes.JPEG;
case MimeTypes.VIDEO_AVI:
return FileTypes.AVI;
default:
return FileTypes.UNKNOWN;
}
Expand Down Expand Up @@ -229,6 +234,8 @@ public static int inferFileTypeFromUri(Uri uri) {
return FileTypes.WEBVTT;
} else if (filename.endsWith(EXTENSION_JPG) || filename.endsWith(EXTENSION_JPEG)) {
return FileTypes.JPEG;
} else if (filename.endsWith(EXTENSION_AVI)) {
return FileTypes.AVI;
} else {
return FileTypes.UNKNOWN;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ public final class MimeTypes {
public static final String VIDEO_FLV = BASE_TYPE_VIDEO + "/x-flv";
public static final String VIDEO_DOLBY_VISION = BASE_TYPE_VIDEO + "/dolby-vision";
public static final String VIDEO_OGG = BASE_TYPE_VIDEO + "/ogg";
public static final String VIDEO_AVI = BASE_TYPE_VIDEO + "/x-msvideo";
public static final String VIDEO_MJPEG = BASE_TYPE_VIDEO + "/mjpeg";
public static final String VIDEO_MP42 = BASE_TYPE_VIDEO + "/mp42";
public static final String VIDEO_MP43 = BASE_TYPE_VIDEO + "/mp43";
public static final String VIDEO_UNKNOWN = BASE_TYPE_VIDEO + "/x-unknown";

// audio/ MIME types
Expand Down
1 change: 0 additions & 1 deletion library/extractor/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ android {
testCoverageEnabled = true
}
}

sourceSets.test.assets.srcDir '../../testdata/src/test/assets/'
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.google.android.exoplayer2.PlaybackException;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.extractor.amr.AmrExtractor;
import com.google.android.exoplayer2.extractor.avi.AviExtractor;
import com.google.android.exoplayer2.extractor.flac.FlacExtractor;
import com.google.android.exoplayer2.extractor.flv.FlvExtractor;
import com.google.android.exoplayer2.extractor.jpeg.JpegExtractor;
Expand Down Expand Up @@ -97,6 +98,7 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
FileTypes.ADTS,
FileTypes.AC3,
FileTypes.AC4,
FileTypes.AVI,
FileTypes.MP3,
FileTypes.JPEG,
};
Expand Down Expand Up @@ -300,7 +302,7 @@ public synchronized Extractor[] createExtractors() {
@Override
public synchronized Extractor[] createExtractors(
Uri uri, Map<String, List<String>> responseHeaders) {
List<Extractor> extractors = new ArrayList<>(/* initialCapacity= */ 14);
List<Extractor> extractors = new ArrayList<>(/* initialCapacity= */ 15);

@FileTypes.Type
int responseHeadersInferredFileType = inferFileTypeFromResponseHeaders(responseHeaders);
Expand Down Expand Up @@ -397,6 +399,9 @@ private void addExtractorsForFileType(@FileTypes.Type int fileType, List<Extract
case FileTypes.JPEG:
extractors.add(new JpegExtractor());
break;
case FileTypes.AVI:
extractors.add(new AviExtractor());
break;
case FileTypes.WEBVTT:
case FileTypes.UNKNOWN:
default:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright (C) 2022 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 com.google.android.exoplayer2.extractor.avi;

import android.util.SparseArray;
import com.google.android.exoplayer2.util.MimeTypes;
import java.nio.ByteBuffer;

/**
* Wrapper for the WAVEFORMATEX structure
*/
public class AudioFormat {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Final?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto for other classes.

Copy link
Author

@dburckh dburckh Feb 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I generally dislike final classes, especially in an open source projects. I've had to use way too much reflection in the past to work-around bugs in final classes. All that said, if it's your standard, I will follow it.

private static final SparseArray<String> FORMAT_MAP = new SparseArray<>();
static {
FORMAT_MAP.put(0x1, MimeTypes.AUDIO_RAW); // WAVE_FORMAT_PCM
FORMAT_MAP.put(0x55, MimeTypes.AUDIO_MPEG); // WAVE_FORMAT_MPEGLAYER3
FORMAT_MAP.put(0xff, MimeTypes.AUDIO_AAC); // WAVE_FORMAT_AAC
FORMAT_MAP.put(0x2000, MimeTypes.AUDIO_AC3); // WAVE_FORMAT_DVM - AC3
FORMAT_MAP.put(0x2001, MimeTypes.AUDIO_DTS); // WAVE_FORMAT_DTS2
}

private final ByteBuffer byteBuffer;

public AudioFormat(ByteBuffer byteBuffer) {
this.byteBuffer = byteBuffer;
}

public String getMimeType() {
return FORMAT_MAP.get(getFormatTag() & 0xffff);
}

public short getFormatTag() {
return byteBuffer.getShort(0);
}
public short getChannels() {
return byteBuffer.getShort(2);
}
public int getSamplesPerSecond() {
return byteBuffer.getInt(4);
}
public int getAvgBytesPerSec() {
return byteBuffer.getInt(8);
}
// 12 - nBlockAlign
public short getBitsPerSample() {
return byteBuffer.getShort(14);
}
public int getCbSize() {
return byteBuffer.getShort(16) & 0xffff;
}
public byte[] getCodecData() {
final int size = getCbSize();
final ByteBuffer temp = byteBuffer.duplicate();
temp.clear();
temp.position(18);
temp.limit(18 + size);
final byte[] data = new byte[size];
temp.get(data);
return data;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
/*
* Copyright (C) 2022 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 com.google.android.exoplayer2.extractor.avi;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.util.NalUnitUtil;
import com.google.android.exoplayer2.util.ParsableNalUnitBitArray;
import java.io.IOException;

/**
* Corrects the time and PAR for H264 streams
* AVC is very rare in AVI due to the rise of the mp4 container
*/
public class AvcChunkHandler extends NalChunkHandler {
private static final int NAL_TYPE_MASK = 0x1f;
private static final int NAL_TYPE_IDR = 5; //I Frame
private static final int NAL_TYPE_SEI = 6;
private static final int NAL_TYPE_SPS = 7;
private static final int NAL_TYPE_PPS = 8;
private static final int NAL_TYPE_AUD = 9;

private final Format.Builder formatBuilder;

private float pixelWidthHeightRatio = 1f;
private NalUnitUtil.SpsData spsData;

public AvcChunkHandler(int id, @NonNull TrackOutput trackOutput,
@NonNull ChunkClock clock, Format.Builder formatBuilder) {
super(id, trackOutput, clock, 16);
this.formatBuilder = formatBuilder;
}

@Nullable
@VisibleForTesting
PicCountClock getPicCountClock() {
if (clock instanceof PicCountClock) {
return (PicCountClock)clock;
} else {
return null;
}
}

@Override
boolean skip(byte nalType) {
if (clock instanceof PicCountClock) {
return false;
} else {
//If the clock is ChunkClock, skip "normal" frames
return nalType >= 0 && nalType <= NAL_TYPE_IDR;
}
}

/**
* Greatly simplified way to calculate the picOrder
* Full logic is here
* https://chromium.googlesource.com/chromium/src/media/+/refs/heads/main/video/h264_poc.cc
*/
void updatePicCountClock(final int nalTypeOffset, final PicCountClock picCountClock) {
final ParsableNalUnitBitArray in = new ParsableNalUnitBitArray(buffer, nalTypeOffset + 1, buffer.length);
//slide_header()
in.readUnsignedExpGolombCodedInt(); //first_mb_in_slice
in.readUnsignedExpGolombCodedInt(); //slice_type
in.readUnsignedExpGolombCodedInt(); //pic_parameter_set_id
if (spsData.separateColorPlaneFlag) {
in.skipBits(2); //colour_plane_id
}
final int frameNum = in.readBits(spsData.frameNumLength); //frame_num
if (!spsData.frameMbsOnlyFlag) {
boolean field_pic_flag = in.readBit(); // field_pic_flag
if (field_pic_flag) {
in.readBit(); // bottom_field_flag
}
}
//We skip IDR in the switch
if (spsData.picOrderCountType == 0) {
int picOrderCountLsb = in.readBits(spsData.picOrderCntLsbLength);
picCountClock.setPicCount(picOrderCountLsb);
return;
} else if (spsData.picOrderCountType == 2) {
picCountClock.setPicCount(frameNum);
return;
}
clock.setIndex(clock.getIndex());
}

@VisibleForTesting
int readSps(ExtractorInput input, int nalTypeOffset) throws IOException {
final int spsStart = nalTypeOffset + 1;
nalTypeOffset = seekNextNal(input, spsStart);
spsData = NalUnitUtil.parseSpsNalUnitPayload(buffer, spsStart, pos);
//If we can have B Frames, upgrade to PicCountClock
final PicCountClock picCountClock;
if (spsData.maxNumRefFrames > 1 && !(clock instanceof PicCountClock)) {
picCountClock = new PicCountClock(clock.durationUs, clock.chunks);
picCountClock.setIndex(clock.getIndex());
clock = picCountClock;
} else {
picCountClock = getPicCountClock();
}
if (picCountClock != null) {
if (spsData.picOrderCountType == 0) {
picCountClock.setMaxPicCount(1 << spsData.picOrderCntLsbLength, 2);
} else if (spsData.picOrderCountType == 2) {
//Plus one because we double the frame number
picCountClock.setMaxPicCount(1 << spsData.frameNumLength, 1);
}
}
if (spsData.pixelWidthHeightRatio != pixelWidthHeightRatio) {
pixelWidthHeightRatio = spsData.pixelWidthHeightRatio;
formatBuilder.setPixelWidthHeightRatio(pixelWidthHeightRatio);
trackOutput.format(formatBuilder.build());
}
return nalTypeOffset;
}

@Override
void processChunk(ExtractorInput input, int nalTypeOffset) throws IOException {
while (true) {
final int nalType = buffer[nalTypeOffset] & NAL_TYPE_MASK;
switch (nalType) {
case 1:
case 2:
case 3:
case 4:
if (clock instanceof PicCountClock) {
updatePicCountClock(nalTypeOffset, (PicCountClock)clock);
}
return;
case NAL_TYPE_IDR: {
final PicCountClock picCountClock = getPicCountClock();
if (picCountClock != null) {
picCountClock.syncIndexes();
}
return;
}
case NAL_TYPE_AUD:
case NAL_TYPE_SEI:
case NAL_TYPE_PPS: {
nalTypeOffset = seekNextNal(input, nalTypeOffset);
//Usually chunks have other NALs after these, so just continue
break;
}
case NAL_TYPE_SPS:
nalTypeOffset = readSps(input, nalTypeOffset);
//Sometimes video frames lurk after these
break;
default:
return;
}
if (nalTypeOffset < 0) {
return;
}
compact();
}
}

@VisibleForTesting(otherwise = VisibleForTesting.NONE)
public NalUnitUtil.SpsData getSpsData() {
return spsData;
}
}
Loading