in src/main/java/com/amazonaws/kinesisvideo/parser/utilities/OutputSegmentMerger.java [376:446]
private void emitAdjustedTimeCode(final MkvDataElement timeCodeElement) throws MkvElementVisitException {
if (configuration.packClusters) {
final int dataSize = (int) timeCodeElement.getDataSize();
final BigInteger adjustedTimeCode;
if (lastClusterTimecode.isPresent()) {
// The timecode of the cluster should be the timecode of the previous cluster plus the previous cluster duration.
// c.timecode = (c-1).timecode + (c-1).duration
// However, neither the cluster nor the frames in the cluster have an explicit duration to use as the cluster
// duration. So, we calculate the frame duration as the difference between frame timecodes, and then add
// those durations to get the cluster duration. But this does not work for the last frame since there is
// no frame after it to take the diff with. So, we just estimate the frame duration as the average of all
// the other frame durations.
// Sort cluster frame timecodes (this handles b-frames)
Collections.sort(clusterFrameTimeCodes);
// Get frame durations
final List<Integer> frameDurations = new ArrayList<>();
for (int i = 1; i < clusterFrameTimeCodes.size(); i++) {
frameDurations.add(clusterFrameTimeCodes.get(i) - clusterFrameTimeCodes.get(i -1));
}
// Get average duration and add it to the other durations to account for the last frame
final int averageFrameDuration;
if (frameDurations.isEmpty()) {
averageFrameDuration = 1;
} else {
averageFrameDuration = frameDurations.stream().mapToInt(Integer::intValue).sum() / frameDurations.size();
}
frameDurations.add(averageFrameDuration);
// Sum up the frame durations to get the cluster duration
final int clusterDuration = frameDurations.stream().mapToInt(Integer::intValue).sum();
// Add duration to the previous cluster timecode
adjustedTimeCode = lastClusterTimecode.get().add(BigInteger.valueOf(clusterDuration));
} else {
// For the first cluster set the timecode to 0
adjustedTimeCode = BigInteger.valueOf(0L);
}
// When replacing the cluster timecode value, we want to use the same size data value so that parent element
// sizes are not impacted.
final byte[] newDataBytes = adjustedTimeCode.toByteArray();
Validate.isTrue(dataSize >= newDataBytes.length,
"Adjusted timecode is not compatible with the existing data size");
final ByteBuffer newDataBuffer = ByteBuffer.allocate(dataSize);
newDataBuffer.position(dataSize - newDataBytes.length);
newDataBuffer.put(newDataBytes);
newDataBuffer.rewind();
final MkvDataElement adjustedTimeCodeElement = MkvDataElement.builder()
.idAndSizeRawBytes(timeCodeElement.getIdAndSizeRawBytes())
.elementMetaData(timeCodeElement.getElementMetaData())
.elementPath(timeCodeElement.getElementPath())
.dataSize(timeCodeElement.getDataSize())
.dataBuffer(newDataBuffer)
.build();
emit(adjustedTimeCodeElement);
lastClusterTimecode = Optional.of(adjustedTimeCode);
// Since we are at the start of a new cluster, reset the frame state from the previous cluster.
// Note: this could also be done directly on the "cluster start" event, but resetting the values here because
// they are currently only used for cluster packing, so keeping cluster packing code together.
clusterFrameTimeCodes.clear();
} else {
emit(timeCodeElement);
lastClusterTimecode = Optional.of((BigInteger) timeCodeElement.getValueCopy().getVal());
}
}