private void emitAdjustedTimeCode()

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