Skip to content

Commit 0869f0c

Browse files
authored
Make the frame timecodes consistent (#217)
* Make the frame timecodes consistent * Remove unused import * Use constant instead of 1M * Avoid negative sleep values when ahead of schedule
1 parent dbb0606 commit 0869f0c

File tree

3 files changed

+31
-10
lines changed

3 files changed

+31
-10
lines changed

src/main/java/com/amazonaws/kinesisvideo/java/mediasource/file/ImageFileMediaSourceConfiguration.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public class ImageFileMediaSourceConfiguration implements MediaSourceConfigurati
1414
private final int endFileIndex;
1515
private final String contentType;
1616
private final boolean allowStreamCreation;
17+
private final long startTimeMs;
1718

1819
public ImageFileMediaSourceConfiguration(final Builder builder) {
1920
this.fps = builder.fps;
@@ -23,6 +24,7 @@ public ImageFileMediaSourceConfiguration(final Builder builder) {
2324
this.endFileIndex = builder.endFileIndex;
2425
this.contentType = builder.contentType;
2526
this.allowStreamCreation = builder.allowStreamCreation;
27+
this.startTimeMs = builder.startTimeMs;
2628
}
2729

2830
public int getFps() {
@@ -49,6 +51,8 @@ public String getContentType() {
4951
return contentType;
5052
}
5153

54+
public long getStartTimeMs() { return startTimeMs; }
55+
5256
@Override
5357
public String getMediaSourceType() {
5458
return null;
@@ -72,6 +76,7 @@ public static class Builder implements MediaSourceConfiguration.Builder<ImageFil
7276
private int endFileIndex;
7377
private String contentType = VIDEO_CONTENT_TYPE;
7478
private boolean allowStreamCreation;
79+
private long startTimeMs = System.currentTimeMillis();
7580

7681
public Builder fps(final int fps) {
7782
this.fps = fps;
@@ -111,6 +116,11 @@ public Builder allowStreamCreation(final Boolean allowStreamCreation) {
111116
return this;
112117
}
113118

119+
public Builder startTimeMs(final long startTimeMs) {
120+
this.startTimeMs = startTimeMs;
121+
return this;
122+
}
123+
114124
@Override
115125
public ImageFileMediaSourceConfiguration build() {
116126
return new ImageFileMediaSourceConfiguration(this);

src/main/java/com/amazonaws/kinesisvideo/java/mediasource/file/ImageFrameSource.java

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import static com.amazonaws.kinesisvideo.producer.FrameFlags.FRAME_FLAG_KEY_FRAME;
2222
import static com.amazonaws.kinesisvideo.producer.FrameFlags.FRAME_FLAG_NONE;
2323
import static com.amazonaws.kinesisvideo.producer.Time.HUNDREDS_OF_NANOS_IN_A_MILLISECOND;
24+
import static com.amazonaws.kinesisvideo.producer.Time.NANOS_IN_A_MILLISECOND;
2425

2526
/**
2627
* Frame source backed by local image files.
@@ -40,11 +41,13 @@ public class ImageFrameSource {
4041
private final Log log = LogFactory.getLog(ImageFrameSource.class);
4142
private final String metadataName = "ImageLoop";
4243
private int metadataCount = 0;
44+
private long currentFrameTimestampMs;
4345

4446
public ImageFrameSource(final ImageFileMediaSourceConfiguration configuration) {
4547
this.configuration = configuration;
4648
this.totalFiles = getTotalFiles(configuration.getStartFileIndex(), configuration.getEndFileIndex());
4749
this.fps = configuration.getFps();
50+
this.currentFrameTimestampMs = configuration.getStartTimeMs();
4851
}
4952

5053
private int getTotalFiles(final int startIndex, final int endIndex) {
@@ -84,20 +87,29 @@ public void run() {
8487
}
8588

8689
private void generateFrameAndNotifyListener() throws KinesisVideoException {
90+
final double frameDurationMs = (double) Duration.ofSeconds(1L).toMillis() / fps;
91+
long nextFrameTimeNs = System.nanoTime(); // to prevent time drift
92+
8793
while (isRunning) {
8894
if (mkvDataAvailableCallback != null) {
89-
mkvDataAvailableCallback.onFrameDataAvailable(createKinesisVideoFrameFromImage(frameCounter));
95+
mkvDataAvailableCallback.onFrameDataAvailable(createKinesisVideoFrameFromImage(frameCounter, currentFrameTimestampMs));
9096
if (isMetadataReady()) {
9197
mkvDataAvailableCallback.onFragmentMetadataAvailable(metadataName + metadataCount,
9298
Integer.toString(metadataCount++), false);
9399
}
94100
}
95101

96102
frameCounter++;
97-
try {
98-
Thread.sleep(Duration.ofSeconds(1L).toMillis() / fps);
99-
} catch (final InterruptedException e) {
100-
log.error("Frame interval wait interrupted by Exception ", e);
103+
currentFrameTimestampMs = configuration.getStartTimeMs() + Math.round(frameCounter * frameDurationMs);
104+
nextFrameTimeNs += (long)(frameDurationMs * NANOS_IN_A_MILLISECOND);
105+
106+
long sleepTimeMs = (nextFrameTimeNs - System.nanoTime()) / NANOS_IN_A_MILLISECOND; // Convert to Ms
107+
if (sleepTimeMs > 0) {
108+
try {
109+
Thread.sleep(sleepTimeMs);
110+
} catch (final InterruptedException e) {
111+
log.error("Frame interval wait interrupted by Exception ", e);
112+
}
101113
}
102114
}
103115
}
@@ -106,12 +118,11 @@ private boolean isMetadataReady() {
106118
return frameCounter % METADATA_INTERVAL == 0;
107119
}
108120

109-
private KinesisVideoFrame createKinesisVideoFrameFromImage(final long index) {
121+
private KinesisVideoFrame createKinesisVideoFrameFromImage(final long index, final long timestampMs) {
110122
final String filename = String.format(
111123
configuration.getFilenameFormat(),
112124
index % totalFiles + configuration.getStartFileIndex());
113125
final Path path = Paths.get(configuration.getDir() + filename);
114-
final long currentTimeMs = System.currentTimeMillis();
115126

116127
final int flags = isKeyFrame() ? FRAME_FLAG_KEY_FRAME : FRAME_FLAG_NONE;
117128

@@ -120,8 +131,8 @@ private KinesisVideoFrame createKinesisVideoFrameFromImage(final long index) {
120131
return new KinesisVideoFrame(
121132
frameCounter,
122133
flags,
123-
currentTimeMs * HUNDREDS_OF_NANOS_IN_A_MILLISECOND,
124-
currentTimeMs * HUNDREDS_OF_NANOS_IN_A_MILLISECOND,
134+
timestampMs * HUNDREDS_OF_NANOS_IN_A_MILLISECOND,
135+
timestampMs * HUNDREDS_OF_NANOS_IN_A_MILLISECOND,
125136
FRAME_DURATION_20_MS * HUNDREDS_OF_NANOS_IN_A_MILLISECOND,
126137
ByteBuffer.wrap(data));
127138
} catch (final IOException e) {

src/main/java/com/amazonaws/kinesisvideo/producer/Time.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public class Time {
1919
public static final long HUNDREDS_OF_NANOS_IN_A_SECOND = 1000 * HUNDREDS_OF_NANOS_IN_A_MILLISECOND;
2020
public static final long HUNDREDS_OF_NANOS_IN_A_MINUTE = 60 * HUNDREDS_OF_NANOS_IN_A_SECOND;
2121
public static final long HUNDREDS_OF_NANOS_IN_AN_HOUR = 60 * HUNDREDS_OF_NANOS_IN_A_MINUTE;
22-
public static final long NANOS_IN_A_MILLISECOND = 1000000;
22+
public static final long NANOS_IN_A_MILLISECOND = 1_000_000;
2323

2424
/**
2525
* Gets the current time in producer time units

0 commit comments

Comments
 (0)