mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Added support for AVC picOrderCountType = 2
This commit is contained in:
parent
5e7679df53
commit
bf1a15652d
5 changed files with 62 additions and 43 deletions
|
|
@ -18,6 +18,7 @@ public class AvcChunkPeeker extends NalChunkPeeker {
|
||||||
private static final int NAL_TYPE_SEI = 6;
|
private static final int NAL_TYPE_SEI = 6;
|
||||||
private static final int NAL_TYPE_SPS = 7;
|
private static final int NAL_TYPE_SPS = 7;
|
||||||
private static final int NAL_TYPE_PPS = 8;
|
private static final int NAL_TYPE_PPS = 8;
|
||||||
|
private static final int NAL_TYPE_AUD = 9;
|
||||||
|
|
||||||
private final PicCountClock picCountClock;
|
private final PicCountClock picCountClock;
|
||||||
private final Format.Builder formatBuilder;
|
private final Format.Builder formatBuilder;
|
||||||
|
|
@ -26,15 +27,14 @@ public class AvcChunkPeeker extends NalChunkPeeker {
|
||||||
private float pixelWidthHeightRatio = 1f;
|
private float pixelWidthHeightRatio = 1f;
|
||||||
private NalUnitUtil.SpsData spsData;
|
private NalUnitUtil.SpsData spsData;
|
||||||
|
|
||||||
public AvcChunkPeeker(Format.Builder formatBuilder, TrackOutput trackOutput, long durationUs,
|
public AvcChunkPeeker(Format.Builder formatBuilder, TrackOutput trackOutput, LinearClock clock) {
|
||||||
int length) {
|
|
||||||
super(16);
|
super(16);
|
||||||
this.formatBuilder = formatBuilder;
|
this.formatBuilder = formatBuilder;
|
||||||
this.trackOutput = trackOutput;
|
this.trackOutput = trackOutput;
|
||||||
picCountClock = new PicCountClock(durationUs, length);
|
picCountClock = new PicCountClock(clock.durationUs, clock.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PicCountClock getPicCountClock() {
|
public LinearClock getClock() {
|
||||||
return picCountClock;
|
return picCountClock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,7 +58,7 @@ public class AvcChunkPeeker extends NalChunkPeeker {
|
||||||
if (spsData.separateColorPlaneFlag) {
|
if (spsData.separateColorPlaneFlag) {
|
||||||
in.skipBits(2); //colour_plane_id
|
in.skipBits(2); //colour_plane_id
|
||||||
}
|
}
|
||||||
in.readBits(spsData.frameNumLength); //frame_num
|
final int frameNum = in.readBits(spsData.frameNumLength); //frame_num
|
||||||
if (!spsData.frameMbsOnlyFlag) {
|
if (!spsData.frameMbsOnlyFlag) {
|
||||||
boolean field_pic_flag = in.readBit(); // field_pic_flag
|
boolean field_pic_flag = in.readBit(); // field_pic_flag
|
||||||
if (field_pic_flag) {
|
if (field_pic_flag) {
|
||||||
|
|
@ -71,6 +71,9 @@ public class AvcChunkPeeker extends NalChunkPeeker {
|
||||||
//Log.d("Test", "FrameNum: " + frame + " cnt=" + picOrderCountLsb);
|
//Log.d("Test", "FrameNum: " + frame + " cnt=" + picOrderCountLsb);
|
||||||
picCountClock.setPicCount(picOrderCountLsb);
|
picCountClock.setPicCount(picOrderCountLsb);
|
||||||
return;
|
return;
|
||||||
|
} else if (spsData.picOrderCountType == 2) {
|
||||||
|
picCountClock.setPicCount(frameNum);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
picCountClock.setIndex(picCountClock.getIndex());
|
picCountClock.setIndex(picCountClock.getIndex());
|
||||||
}
|
}
|
||||||
|
|
@ -80,7 +83,12 @@ public class AvcChunkPeeker extends NalChunkPeeker {
|
||||||
final int spsStart = nalTypeOffset + 1;
|
final int spsStart = nalTypeOffset + 1;
|
||||||
nalTypeOffset = seekNextNal(input, spsStart);
|
nalTypeOffset = seekNextNal(input, spsStart);
|
||||||
spsData = NalUnitUtil.parseSpsNalUnitPayload(buffer, spsStart, pos);
|
spsData = NalUnitUtil.parseSpsNalUnitPayload(buffer, spsStart, pos);
|
||||||
picCountClock.setMaxPicCount(1 << (spsData.picOrderCntLsbLength));
|
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) {
|
if (spsData.pixelWidthHeightRatio != pixelWidthHeightRatio) {
|
||||||
pixelWidthHeightRatio = spsData.pixelWidthHeightRatio;
|
pixelWidthHeightRatio = spsData.pixelWidthHeightRatio;
|
||||||
formatBuilder.setPixelWidthHeightRatio(pixelWidthHeightRatio);
|
formatBuilder.setPixelWidthHeightRatio(pixelWidthHeightRatio);
|
||||||
|
|
@ -103,6 +111,7 @@ public class AvcChunkPeeker extends NalChunkPeeker {
|
||||||
case NAL_TYPE_IRD:
|
case NAL_TYPE_IRD:
|
||||||
picCountClock.syncIndexes();
|
picCountClock.syncIndexes();
|
||||||
return;
|
return;
|
||||||
|
case NAL_TYPE_AUD:
|
||||||
case NAL_TYPE_SEI:
|
case NAL_TYPE_SEI:
|
||||||
case NAL_TYPE_PPS: {
|
case NAL_TYPE_PPS: {
|
||||||
nalTypeOffset = seekNextNal(input, nalTypeOffset);
|
nalTypeOffset = seekNextNal(input, nalTypeOffset);
|
||||||
|
|
|
||||||
|
|
@ -255,15 +255,14 @@ public class AviExtractor implements Extractor {
|
||||||
builder.setFrameRate(streamHeader.getFrameRate());
|
builder.setFrameRate(streamHeader.getFrameRate());
|
||||||
builder.setSampleMimeType(mimeType);
|
builder.setSampleMimeType(mimeType);
|
||||||
|
|
||||||
|
final LinearClock clock = new LinearClock(durationUs, length);
|
||||||
|
aviTrack = new AviTrack(streamId, C.TRACK_TYPE_VIDEO, clock, trackOutput);
|
||||||
|
|
||||||
if (MimeTypes.VIDEO_H264.equals(mimeType)) {
|
if (MimeTypes.VIDEO_H264.equals(mimeType)) {
|
||||||
final AvcChunkPeeker avcChunkPeeker = new AvcChunkPeeker(builder, trackOutput, durationUs,
|
final AvcChunkPeeker avcChunkPeeker = new AvcChunkPeeker(builder, trackOutput, clock);
|
||||||
length);
|
aviTrack.setClock(avcChunkPeeker.getClock());
|
||||||
aviTrack = new AviTrack(streamId, C.TRACK_TYPE_VIDEO, avcChunkPeeker.getPicCountClock(),
|
|
||||||
trackOutput);
|
|
||||||
aviTrack.setChunkPeeker(avcChunkPeeker);
|
aviTrack.setChunkPeeker(avcChunkPeeker);
|
||||||
} else {
|
} else {
|
||||||
aviTrack = new AviTrack(streamId, C.TRACK_TYPE_VIDEO,
|
|
||||||
new LinearClock(durationUs, length), trackOutput);
|
|
||||||
if (MimeTypes.VIDEO_MP4V.equals(mimeType)) {
|
if (MimeTypes.VIDEO_MP4V.equals(mimeType)) {
|
||||||
aviTrack.setChunkPeeker(new Mp4vChunkPeeker(builder, trackOutput));
|
aviTrack.setChunkPeeker(new Mp4vChunkPeeker(builder, trackOutput));
|
||||||
}
|
}
|
||||||
|
|
@ -361,9 +360,10 @@ public class AviExtractor implements Extractor {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateAudioTiming(final int[] keyFrameCounts, final long videoDuration) {
|
void fixTimings(final int[] keyFrameCounts, final long videoDuration) {
|
||||||
for (final AviTrack aviTrack : aviTracks) {
|
for (final AviTrack aviTrack : aviTracks) {
|
||||||
if (aviTrack != null && aviTrack.isAudio()) {
|
if (aviTrack != null) {
|
||||||
|
if (aviTrack.isAudio()) {
|
||||||
final long durationUs = aviTrack.getClock().durationUs;
|
final long durationUs = aviTrack.getClock().durationUs;
|
||||||
i("Audio #" + aviTrack.id + " chunks: " + aviTrack.chunks + " us=" + durationUs +
|
i("Audio #" + aviTrack.id + " chunks: " + aviTrack.chunks + " us=" + durationUs +
|
||||||
" size=" + aviTrack.size);
|
" size=" + aviTrack.size);
|
||||||
|
|
@ -378,6 +378,13 @@ public class AviExtractor implements Extractor {
|
||||||
w("Audio is not all key frames chunks=" + aviTrack.chunks + " keyFrames=" +
|
w("Audio is not all key frames chunks=" + aviTrack.chunks + " keyFrames=" +
|
||||||
keyFrameCounts[aviTrack.id]);
|
keyFrameCounts[aviTrack.id]);
|
||||||
}
|
}
|
||||||
|
} /* else if (aviTrack.isVideo()) {
|
||||||
|
final LinearClock clock = aviTrack.getClock();
|
||||||
|
if (clock.length != aviTrack.chunks) {
|
||||||
|
w("Video #" + aviTrack.id + " chunks != length changing FPS");
|
||||||
|
clock.setLength(aviTrack.chunks);
|
||||||
|
}
|
||||||
|
}*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -464,8 +471,7 @@ public class AviExtractor implements Extractor {
|
||||||
|
|
||||||
i("Video chunks=" + videoTrack.chunks + " us=" + seekMap.getDurationUs());
|
i("Video chunks=" + videoTrack.chunks + " us=" + seekMap.getDurationUs());
|
||||||
|
|
||||||
//Needs to be called after the duration is updated
|
fixTimings(keyFrameCounts, durationUs);
|
||||||
updateAudioTiming(keyFrameCounts, durationUs);
|
|
||||||
|
|
||||||
setSeekMap(seekMap);
|
setSeekMap(seekMap);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ public class AviTrack {
|
||||||
final @C.TrackType int trackType;
|
final @C.TrackType int trackType;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
final LinearClock clock;
|
LinearClock clock;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
final TrackOutput trackOutput;
|
final TrackOutput trackOutput;
|
||||||
|
|
@ -56,7 +56,7 @@ public class AviTrack {
|
||||||
return getChunkIdLower(id) | ('w' << 16) | ('b' << 24);
|
return getChunkIdLower(id) | ('w' << 16) | ('b' << 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
AviTrack(int id, final @C.TrackType int trackType, @NonNull LinearClock clock,
|
AviTrack(int id, @C.TrackType int trackType, @NonNull LinearClock clock,
|
||||||
@NonNull TrackOutput trackOutput) {
|
@NonNull TrackOutput trackOutput) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
|
|
@ -77,10 +77,15 @@ public class AviTrack {
|
||||||
return this.chunkId == chunkId || chunkIdAlt == chunkId;
|
return this.chunkId == chunkId || chunkIdAlt == chunkId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
public LinearClock getClock() {
|
public LinearClock getClock() {
|
||||||
return clock;
|
return clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setClock(@NonNull LinearClock clock) {
|
||||||
|
this.clock = clock;
|
||||||
|
}
|
||||||
|
|
||||||
public void setChunkPeeker(ChunkPeeker chunkPeeker) {
|
public void setChunkPeeker(ChunkPeeker chunkPeeker) {
|
||||||
this.chunkPeeker = chunkPeeker;
|
this.chunkPeeker = chunkPeeker;
|
||||||
}
|
}
|
||||||
|
|
@ -144,7 +149,7 @@ public class AviTrack {
|
||||||
trackOutput.sampleMetadata(
|
trackOutput.sampleMetadata(
|
||||||
clock.getUs(), (isKeyFrame() ? C.BUFFER_FLAG_KEY_FRAME : 0), size, 0, null);
|
clock.getUs(), (isKeyFrame() ? C.BUFFER_FLAG_KEY_FRAME : 0), size, 0, null);
|
||||||
final LinearClock clock = getClock();
|
final LinearClock clock = getClock();
|
||||||
// Log.d(AviExtractor.TAG, "Frame: " + (isVideo()? 'V' : 'A') + " us=" + clock.getUs() + " size=" + size + " frame=" + clock.getIndex() + " key=" + isKeyFrame());
|
Log.d(AviExtractor.TAG, "Frame: " + (isVideo()? 'V' : 'A') + " us=" + clock.getUs() + " size=" + size + " frame=" + clock.getIndex() + " key=" + isKeyFrame());
|
||||||
clock.advance();
|
clock.advance();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,13 @@ package com.google.android.exoplayer2.extractor.avi;
|
||||||
* Properly calculates the frame time for H264 frames using PicCount
|
* Properly calculates the frame time for H264 frames using PicCount
|
||||||
*/
|
*/
|
||||||
public class PicCountClock extends LinearClock {
|
public class PicCountClock extends LinearClock {
|
||||||
//I believe this is 2 because there is a bottom pic order and a top pic order
|
|
||||||
private static final int STEP = 2;
|
|
||||||
//The frame as a calculated from the picCount
|
//The frame as a calculated from the picCount
|
||||||
private int picIndex;
|
private int picIndex;
|
||||||
private int lastPicCount;
|
private int lastPicCount;
|
||||||
//Largest picFrame, used when we hit an I frame
|
//Largest picFrame, used when we hit an I frame
|
||||||
private int maxPicIndex =-1;
|
private int maxPicIndex =-1;
|
||||||
private int maxPicCount;
|
private int maxPicCount;
|
||||||
|
private int step = 2;
|
||||||
private int posHalf;
|
private int posHalf;
|
||||||
private int negHalf;
|
private int negHalf;
|
||||||
|
|
||||||
|
|
@ -19,15 +18,15 @@ public class PicCountClock extends LinearClock {
|
||||||
super(durationUs, length);
|
super(durationUs, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMaxPicCount(int maxPicCount) {
|
public void setMaxPicCount(int maxPicCount, int step) {
|
||||||
this.maxPicCount = maxPicCount;
|
this.maxPicCount = maxPicCount;
|
||||||
posHalf = maxPicCount / STEP;
|
this.step = step;
|
||||||
|
posHalf = maxPicCount / step;
|
||||||
negHalf = -posHalf;
|
negHalf = -posHalf;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Done on seek. May cause sync issues if frame picCount != 0 (I frames are always 0)
|
* Used primarily on seek. May cause issues if not a key frame
|
||||||
* @param index
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void setIndex(int index) {
|
public void setIndex(int index) {
|
||||||
|
|
@ -42,7 +41,7 @@ public class PicCountClock extends LinearClock {
|
||||||
} else if (delta > posHalf) {
|
} else if (delta > posHalf) {
|
||||||
delta -= maxPicCount;
|
delta -= maxPicCount;
|
||||||
}
|
}
|
||||||
picIndex += delta / STEP;
|
picIndex += delta / step;
|
||||||
lastPicCount = picCount;
|
lastPicCount = picCount;
|
||||||
if (maxPicIndex < picIndex) {
|
if (maxPicIndex < picIndex) {
|
||||||
maxPicIndex = picIndex;
|
maxPicIndex = picIndex;
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ public class PicCountClockTest {
|
||||||
@Test
|
@Test
|
||||||
public void us_givenTwoStepsForward() {
|
public void us_givenTwoStepsForward() {
|
||||||
final PicCountClock picCountClock = new PicCountClock(10_000L, 100);
|
final PicCountClock picCountClock = new PicCountClock(10_000L, 100);
|
||||||
picCountClock.setMaxPicCount(16*2);
|
picCountClock.setMaxPicCount(16*2, 2);
|
||||||
picCountClock.setPicCount(2*2);
|
picCountClock.setPicCount(2*2);
|
||||||
Assert.assertEquals(2*100, picCountClock.getUs());
|
Assert.assertEquals(2*100, picCountClock.getUs());
|
||||||
}
|
}
|
||||||
|
|
@ -15,7 +15,7 @@ public class PicCountClockTest {
|
||||||
@Test
|
@Test
|
||||||
public void us_givenThreeStepsBackwards() {
|
public void us_givenThreeStepsBackwards() {
|
||||||
final PicCountClock picCountClock = new PicCountClock(10_000L, 100);
|
final PicCountClock picCountClock = new PicCountClock(10_000L, 100);
|
||||||
picCountClock.setMaxPicCount(16*2);
|
picCountClock.setMaxPicCount(16*2, 2);
|
||||||
picCountClock.setPicCount(4*2); // 400ms
|
picCountClock.setPicCount(4*2); // 400ms
|
||||||
Assert.assertEquals(400, picCountClock.getUs());
|
Assert.assertEquals(400, picCountClock.getUs());
|
||||||
picCountClock.setPicCount(1*2);
|
picCountClock.setPicCount(1*2);
|
||||||
|
|
@ -32,7 +32,7 @@ public class PicCountClockTest {
|
||||||
@Test
|
@Test
|
||||||
public void us_giveWrapBackwards() {
|
public void us_giveWrapBackwards() {
|
||||||
final PicCountClock picCountClock = new PicCountClock(10_000L, 100);
|
final PicCountClock picCountClock = new PicCountClock(10_000L, 100);
|
||||||
picCountClock.setMaxPicCount(16*2);
|
picCountClock.setMaxPicCount(16*2, 2);
|
||||||
//Need to walk up no faster than maxPicCount / 2
|
//Need to walk up no faster than maxPicCount / 2
|
||||||
picCountClock.setPicCount(7*2);
|
picCountClock.setPicCount(7*2);
|
||||||
picCountClock.setPicCount(11*2);
|
picCountClock.setPicCount(11*2);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue