Add support for StreamName fixed issues with position alignment

This commit is contained in:
Dustin 2022-01-23 13:45:22 -07:00
parent 167c2f3fc0
commit 43b8a9b336
6 changed files with 164 additions and 66 deletions

View File

@ -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<Box> streamChildren = streamList.getChildren();
for (int i=0;i<streamChildren.size();i++) {
final Box residentBox = streamChildren.get(i);
if (residentBox instanceof StreamHeaderBox) {
final StreamHeaderBox streamHeader = (StreamHeaderBox) residentBox;
final StreamFormatBox streamFormat = (StreamFormatBox) peekNext(streamChildren, i, StreamFormatBox.STRF);
if (streamFormat != null) {
i++;
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);
final Format.Builder builder = new Format.Builder();
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 Format.Builder builder = new Format.Builder();
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++;
}
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);

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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));
}
}

View File

@ -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;
}
}

View File

@ -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());
}
}