From 43b8a9b3363de1b481a894edf606ba8bdc3bd4a7 Mon Sep 17 00:00:00 2001 From: Dustin Date: Sun, 23 Jan 2022 13:45:22 -0700 Subject: [PATCH] Add support for StreamName fixed issues with position alignment --- .../extractor/avi/AviExtractor.java | 140 ++++++++++-------- .../exoplayer2/extractor/avi/BoxFactory.java | 4 +- .../extractor/avi/StreamNameBox.java | 22 +++ .../exoplayer2/extractor/avi/DataHelper.java | 7 + .../exoplayer2/extractor/avi/ListBuilder.java | 32 ++++ .../extractor/avi/StreamNameBoxTest.java | 25 ++++ 6 files changed, 164 insertions(+), 66 deletions(-) create mode 100644 library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/StreamNameBox.java create mode 100644 library/extractor/src/test/java/com/google/android/exoplayer2/extractor/avi/ListBuilder.java create mode 100644 library/extractor/src/test/java/com/google/android/exoplayer2/extractor/avi/StreamNameBoxTest.java diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/AviExtractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/AviExtractor.java index 594800e292..5389b57abc 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/AviExtractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/AviExtractor.java @@ -92,6 +92,13 @@ public class AviExtractor implements Extractor { } } + static long alignPosition(long position) { + if ((position & 1) == 1) { + position++; + } + return position; + } + public AviExtractor() { this(0); } @@ -212,70 +219,73 @@ public class AviExtractor implements Extractor { for (Box box : headerList.getChildren()) { if (box instanceof ListBox && ((ListBox) box).getListType() == STRL) { final ListBox streamList = (ListBox) box; - final List streamChildren = streamList.getChildren(); - for (int i=0;i 0) { - builder.setInitializationData(Collections.singletonList(audioFormat.getCodecData())); - } - trackOutput.format(builder.build()); - idTrackMap.put('0' | (('0' + streamId) << 8) | ('w' << 16) | ('b' << 24), - new AviTrack(streamId, streamHeader, trackOutput)); - } - } - streamId++; - } + final StreamHeaderBox streamHeader = streamList.getChild(StreamHeaderBox.class); + final StreamFormatBox streamFormat = streamList.getChild(StreamFormatBox.class); + if (streamHeader == null) { + Log.w(TAG, "Missing Stream Header"); + continue; } + if (streamFormat == null) { + Log.w(TAG, "Missing Stream Format"); + continue; + } + final Format.Builder builder = new Format.Builder(); + builder.setId(streamId); + final StreamNameBox streamName = streamList.getChild(StreamNameBox.class); + if (streamName != null) { + builder.setLabel(streamName.getName()); + } + if (streamHeader.isVideo()) { + final String mimeType = streamHeader.getMimeType(); + if (mimeType == null) { + Log.w(TAG, "Unknown FourCC: " + toString(streamHeader.getFourCC())); + continue; + } + final VideoFormat videoFormat = streamFormat.getVideoFormat(); + final TrackOutput trackOutput = output.track(streamId, C.TRACK_TYPE_VIDEO); + builder.setWidth(videoFormat.getWidth()); + builder.setHeight(videoFormat.getHeight()); + builder.setFrameRate(streamHeader.getFrameRate()); + builder.setSampleMimeType(mimeType); + + final AviTrack aviTrack; + switch (mimeType) { + case MimeTypes.VIDEO_MP4V: + aviTrack = new Mp4vAviTrack(streamId, streamHeader, trackOutput, builder); + break; + case MimeTypes.VIDEO_H264: + aviTrack = new AvcAviTrack(streamId, streamHeader, trackOutput, builder); + break; + default: + aviTrack = new AviTrack(streamId, streamHeader, trackOutput); + } + trackOutput.format(builder.build()); + idTrackMap.put('0' | (('0' + streamId) << 8) | ('d' << 16) | ('c' << 24), aviTrack); + durationUs = streamHeader.getUsPerSample() * streamHeader.getLength(); + } else if (streamHeader.isAudio()) { + final AudioFormat audioFormat = streamFormat.getAudioFormat(); + final TrackOutput trackOutput = output.track(streamId, C.TRACK_TYPE_AUDIO); + final String mimeType = audioFormat.getMimeType(); + builder.setSampleMimeType(mimeType); + //builder.setCodecs(audioFormat.getCodec()); + builder.setChannelCount(audioFormat.getChannels()); + builder.setSampleRate(audioFormat.getSamplesPerSecond()); + if (audioFormat.getFormatTag() == AudioFormat.WAVE_FORMAT_PCM) { + final short bps = audioFormat.getBitsPerSample(); + if (bps == 8) { + builder.setPcmEncoding(C.ENCODING_PCM_8BIT); + } else if (bps == 16){ + builder.setPcmEncoding(C.ENCODING_PCM_16BIT); + } + } + if (MimeTypes.AUDIO_AAC.equals(mimeType) && audioFormat.getCbSize() > 0) { + builder.setInitializationData(Collections.singletonList(audioFormat.getCodecData())); + } + trackOutput.format(builder.build()); + idTrackMap.put('0' | (('0' + streamId) << 8) | ('w' << 16) | ('b' << 24), + new AviTrack(streamId, streamHeader, trackOutput)); + } + streamId++; } } output.endTracks(); @@ -290,7 +300,7 @@ public class AviExtractor implements Extractor { final long size = getUInt(byteBuffer); final long position = input.getPosition(); //-4 because we over read for the LIST type - long nextBox = position + size - 4; + long nextBox = alignPosition(position + size - 4); if (tag == ListBox.LIST) { final int listType = byteBuffer.getInt(); if (listType == MOVI) { @@ -430,7 +440,7 @@ public class AviExtractor implements Extractor { if (id == ListBox.LIST) { seekPosition.position = input.getPosition() + 4; } else { - seekPosition.position = input.getPosition() + size; + seekPosition.position = alignPosition(input.getPosition() + size); if (id != JUNK) { Log.w(TAG, "Unknown tag=" + toString(id) + " pos=" + (input.getPosition() - 8) + " size=" + size + " moviEnd=" + moviEnd); diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/BoxFactory.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/BoxFactory.java index 61b05119fc..8921cde14d 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/BoxFactory.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/BoxFactory.java @@ -6,7 +6,7 @@ import java.nio.ByteBuffer; import java.util.Arrays; public class BoxFactory { - static int[] types = {AviHeaderBox.AVIH, StreamHeaderBox.STRH, StreamFormatBox.STRF}; + static int[] types = {AviHeaderBox.AVIH, StreamHeaderBox.STRH, StreamFormatBox.STRF, StreamNameBox.STRN}; static { Arrays.sort(types); } @@ -23,6 +23,8 @@ public class BoxFactory { return new StreamHeaderBox(type, size, boxBuffer); case StreamFormatBox.STRF: return new StreamFormatBox(type, size, boxBuffer); + case StreamNameBox.STRN: + return new StreamNameBox(type, size, boxBuffer); default: return null; } diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/StreamNameBox.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/StreamNameBox.java new file mode 100644 index 0000000000..e8f31766c9 --- /dev/null +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/avi/StreamNameBox.java @@ -0,0 +1,22 @@ +package com.google.android.exoplayer2.extractor.avi; + +import java.nio.ByteBuffer; + +public class StreamNameBox extends ResidentBox { + public static final int STRN = 's' | ('t' << 8) | ('r' << 16) | ('n' << 24); + + StreamNameBox(int type, int size, ByteBuffer byteBuffer) { + super(type, size, byteBuffer); + } + + public String getName() { + int len = byteBuffer.capacity(); + if (byteBuffer.get(len - 1) == 0) { + len -= 1; + } + final byte[] bytes = new byte[len]; + byteBuffer.position(0); + byteBuffer.get(bytes); + return new String(bytes); + } +} diff --git a/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/avi/DataHelper.java b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/avi/DataHelper.java index 4762a40e1b..2731878d90 100644 --- a/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/avi/DataHelper.java +++ b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/avi/DataHelper.java @@ -6,6 +6,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.Arrays; public class DataHelper { //Base path "\ExoPlayer\library\extractor\." @@ -43,4 +44,10 @@ public class DataHelper { byteBuffer.order(ByteOrder.LITTLE_ENDIAN); return new StreamFormatBox(StreamFormatBox.STRF, buffer.length, byteBuffer); } + + public static StreamNameBox getStreamNameBox(final String name) { + byte[] bytes = name.getBytes(); + bytes = Arrays.copyOf(bytes, bytes.length + 1); + return new StreamNameBox(StreamNameBox.STRN, bytes.length, ByteBuffer.wrap(bytes)); + } } diff --git a/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/avi/ListBuilder.java b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/avi/ListBuilder.java new file mode 100644 index 0000000000..3be00dd152 --- /dev/null +++ b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/avi/ListBuilder.java @@ -0,0 +1,32 @@ +package com.google.android.exoplayer2.extractor.avi; + +import java.nio.ByteBuffer; + +public class ListBuilder { + private ByteBuffer byteBuffer; + + public ListBuilder(int listType) { + byteBuffer = AviExtractor.allocate(12); + byteBuffer.putInt(ListBox.LIST); + byteBuffer.putInt(12); + byteBuffer.putInt(listType); + } + + public void addBox(final ResidentBox box) { + long boxLen = 4 + 4 + box.getSize(); + if ((boxLen & 1) == 1) { + boxLen++; + } + final ByteBuffer boxBuffer = AviExtractor.allocate(byteBuffer.capacity() + (int)boxLen); + byteBuffer.clear(); + boxBuffer.put(byteBuffer); + boxBuffer.putInt(box.getType()); + boxBuffer.putInt((int)box.getSize()); + boxBuffer.put(box.getByteBuffer()); + byteBuffer = boxBuffer; + } + public ByteBuffer build() { + byteBuffer.putInt(4, byteBuffer.capacity() - 8); + return byteBuffer; + } +} diff --git a/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/avi/StreamNameBoxTest.java b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/avi/StreamNameBoxTest.java new file mode 100644 index 0000000000..5190837b60 --- /dev/null +++ b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/avi/StreamNameBoxTest.java @@ -0,0 +1,25 @@ +package com.google.android.exoplayer2.extractor.avi; + +import com.google.android.exoplayer2.testutil.FakeExtractorInput; +import java.io.IOException; +import java.nio.ByteBuffer; +import org.junit.Assert; +import org.junit.Test; + +public class StreamNameBoxTest { + @Test + public void createStreamName_givenList() throws IOException { + final String name = "Test"; + final ListBuilder listBuilder = new ListBuilder(AviExtractor.STRL); + listBuilder.addBox(DataHelper.getStreamNameBox(name)); + final ByteBuffer listBuffer = listBuilder.build(); + final FakeExtractorInput fakeExtractorInput = new FakeExtractorInput.Builder().setData(listBuffer.array()).build(); + fakeExtractorInput.skipFully(8); + ListBox listBox = ListBox.newInstance(listBuffer.capacity() - 8, new BoxFactory(), fakeExtractorInput); + Assert.assertEquals(1, listBox.getChildren().size()); + final StreamNameBox streamNameBox = (StreamNameBox) listBox.getChildren().get(0); + //Test + nullT = 5 bytes, so verify that the input is properly aligned + Assert.assertEquals(0, fakeExtractorInput.getPosition() & 1); + Assert.assertEquals(name, streamNameBox.getName()); + } +}