Skip to content

Commit 314ed94

Browse files
examples/user_timestamped_video
1 parent d69ec99 commit 314ed94

4 files changed

Lines changed: 159 additions & 120 deletions

File tree

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,44 @@
11
# UserTimestampedVideo
22

3-
This example is split into two executables:
3+
This example is split into two executables and can demonstrate all four
4+
producer/consumer combinations:
45

56
- `UserTimestampedVideoProducer` publishes a synthetic camera track and stamps
67
each frame with `VideoCaptureOptions::metadata.user_timestamp_us`.
78
- `UserTimestampedVideoConsumer` subscribes to remote camera frames with
8-
`Room::setOnVideoFrameEventCallback` and logs the received user timestamp
9-
metadata.
9+
either the rich or legacy callback path.
1010

1111
Run them in the same room with different participant identities:
1212

1313
```sh
1414
LIVEKIT_URL=ws://localhost:7880 LIVEKIT_TOKEN=<producer-token> ./UserTimestampedVideoProducer
1515
LIVEKIT_URL=ws://localhost:7880 LIVEKIT_TOKEN=<consumer-token> ./UserTimestampedVideoConsumer
1616
```
17+
18+
Flags:
19+
20+
- Producer default: sends user timestamps
21+
- Producer `--without-user-timestamp`: does not send user timestamps
22+
- Consumer default: reads user timestamps through `setOnVideoFrameEventCallback`
23+
- Consumer `--ignore-user-timestamp`: ignores metadata through the legacy
24+
`setOnVideoFrameCallback`
25+
26+
Matrix:
27+
28+
```sh
29+
# 1. Producer sends, consumer reads
30+
./UserTimestampedVideoProducer
31+
./UserTimestampedVideoConsumer
32+
33+
# 2. Producer sends, consumer ignores
34+
./UserTimestampedVideoProducer
35+
./UserTimestampedVideoConsumer --ignore-user-timestamp
36+
37+
# 3. Producer does not send, consumer ignores
38+
./UserTimestampedVideoProducer --without-user-timestamp
39+
./UserTimestampedVideoConsumer --ignore-user-timestamp
40+
41+
# 4. Producer does not send, consumer reads
42+
./UserTimestampedVideoProducer --without-user-timestamp
43+
./UserTimestampedVideoConsumer
44+
```

examples/user_timestamped_video/consumer.cpp

Lines changed: 73 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include <string>
3131
#include <thread>
3232
#include <unordered_set>
33+
#include <vector>
3334

3435
#include "livekit/livekit.h"
3536

@@ -57,14 +58,49 @@ formatUserTimestamp(const std::optional<VideoFrameMetadata> &metadata) {
5758

5859
void printUsage(const char *program) {
5960
std::cerr << "Usage:\n"
60-
<< " " << program << " <ws-url> <token>\n"
61+
<< " " << program << " <ws-url> <token> [--ignore-user-timestamp]\n"
6162
<< "or:\n"
62-
<< " LIVEKIT_URL=... LIVEKIT_TOKEN=... " << program << "\n";
63+
<< " LIVEKIT_URL=... LIVEKIT_TOKEN=... " << program
64+
<< " [--ignore-user-timestamp]\n";
65+
}
66+
67+
bool parseArgs(int argc, char *argv[], std::string &url, std::string &token,
68+
bool &read_user_timestamp) {
69+
read_user_timestamp = true;
70+
std::vector<std::string> positional;
71+
72+
for (int i = 1; i < argc; ++i) {
73+
const std::string arg = argv[i];
74+
if (arg == "-h" || arg == "--help") {
75+
return false;
76+
}
77+
if (arg == "--ignore-user-timestamp") {
78+
read_user_timestamp = false;
79+
continue;
80+
}
81+
if (arg == "--read-user-timestamp") {
82+
read_user_timestamp = true;
83+
continue;
84+
}
85+
86+
positional.push_back(arg);
87+
}
88+
89+
url = getenvOrEmpty("LIVEKIT_URL");
90+
token = getenvOrEmpty("LIVEKIT_TOKEN");
91+
92+
if (positional.size() >= 2) {
93+
url = positional[0];
94+
token = positional[1];
95+
}
96+
97+
return !(url.empty() || token.empty());
6398
}
6499

65100
class UserTimestampedVideoConsumerDelegate : public RoomDelegate {
66101
public:
67-
explicit UserTimestampedVideoConsumerDelegate(Room &room) : room_(room) {}
102+
UserTimestampedVideoConsumerDelegate(Room &room, bool read_user_timestamp)
103+
: room_(room), read_user_timestamp_(read_user_timestamp) {}
68104

69105
void registerExistingParticipants() {
70106
for (const auto &participant : room_.remoteParticipants()) {
@@ -114,39 +150,50 @@ class UserTimestampedVideoConsumerDelegate : public RoomDelegate {
114150
VideoStream::Options stream_options;
115151
stream_options.format = VideoBufferType::RGBA;
116152

117-
room_.setOnVideoFrameEventCallback(
118-
identity, TrackSource::SOURCE_CAMERA,
119-
[identity](const VideoFrameEvent &event) {
120-
std::cout << "[consumer] from=" << identity
121-
<< " size=" << event.frame.width() << "x"
122-
<< event.frame.height()
123-
<< " capture_ts_us=" << event.timestamp_us
124-
<< " user_ts_us=" << formatUserTimestamp(event.metadata)
125-
<< " rotation=" << static_cast<int>(event.rotation) << "\n";
126-
},
127-
stream_options);
153+
if (read_user_timestamp_) {
154+
room_.setOnVideoFrameEventCallback(
155+
identity, TrackSource::SOURCE_CAMERA,
156+
[identity](const VideoFrameEvent &event) {
157+
std::cout << "[consumer] from=" << identity
158+
<< " size=" << event.frame.width() << "x"
159+
<< event.frame.height()
160+
<< " capture_ts_us=" << event.timestamp_us
161+
<< " user_ts_us=" << formatUserTimestamp(event.metadata)
162+
<< " rotation=" << static_cast<int>(event.rotation)
163+
<< "\n";
164+
},
165+
stream_options);
166+
} else {
167+
room_.setOnVideoFrameCallback(
168+
identity, TrackSource::SOURCE_CAMERA,
169+
[identity](const VideoFrame &frame, std::int64_t timestamp_us) {
170+
std::cout << "[consumer] from=" << identity
171+
<< " size=" << frame.width() << "x" << frame.height()
172+
<< " capture_ts_us=" << timestamp_us
173+
<< " user_ts_us=ignored\n";
174+
},
175+
stream_options);
176+
}
128177

129178
std::cout << "[consumer] listening for camera frames from " << identity
130-
<< "\n";
179+
<< " with user timestamp "
180+
<< (read_user_timestamp_ ? "enabled" : "ignored") << "\n";
131181
}
132182

133183
Room &room_;
184+
bool read_user_timestamp_;
134185
std::mutex mutex_;
135186
std::unordered_set<std::string> registered_identities_;
136187
};
137188

138189
} // namespace
139190

140191
int main(int argc, char *argv[]) {
141-
std::string url = getenvOrEmpty("LIVEKIT_URL");
142-
std::string token = getenvOrEmpty("LIVEKIT_TOKEN");
143-
144-
if (argc >= 3) {
145-
url = argv[1];
146-
token = argv[2];
147-
}
192+
std::string url;
193+
std::string token;
194+
bool read_user_timestamp = true;
148195

149-
if (url.empty() || token.empty()) {
196+
if (!parseArgs(argc, argv, url, token, read_user_timestamp)) {
150197
printUsage(argv[0]);
151198
return 1;
152199
}
@@ -165,7 +212,7 @@ int main(int argc, char *argv[]) {
165212
options.auto_subscribe = true;
166213
options.dynacast = false;
167214

168-
UserTimestampedVideoConsumerDelegate delegate(room);
215+
UserTimestampedVideoConsumerDelegate delegate(room, read_user_timestamp);
169216
room.setDelegate(&delegate);
170217

171218
std::cout << "[consumer] connecting to " << url << "\n";
@@ -175,7 +222,8 @@ int main(int argc, char *argv[]) {
175222
} else {
176223
std::cout << "[consumer] connected as "
177224
<< room.localParticipant()->identity() << " to room '"
178-
<< room.room_info().name << "'\n";
225+
<< room.room_info().name << "' with user timestamp "
226+
<< (read_user_timestamp ? "enabled" : "ignored") << "\n";
179227

180228
delegate.registerExistingParticipants();
181229

examples/user_timestamped_video/producer.cpp

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include <memory>
3131
#include <string>
3232
#include <thread>
33+
#include <vector>
3334

3435
#include "livekit/livekit.h"
3536

@@ -74,23 +75,54 @@ void fillFrame(VideoFrame &frame, std::uint32_t frame_index) {
7475

7576
void printUsage(const char *program) {
7677
std::cerr << "Usage:\n"
77-
<< " " << program << " <ws-url> <token>\n"
78+
<< " " << program
79+
<< " <ws-url> <token> [--without-user-timestamp]\n"
7880
<< "or:\n"
79-
<< " LIVEKIT_URL=... LIVEKIT_TOKEN=... " << program << "\n";
81+
<< " LIVEKIT_URL=... LIVEKIT_TOKEN=... " << program
82+
<< " [--without-user-timestamp]\n";
8083
}
8184

82-
} // namespace
85+
bool parseArgs(int argc, char *argv[], std::string &url, std::string &token,
86+
bool &send_user_timestamp) {
87+
send_user_timestamp = true;
88+
std::vector<std::string> positional;
8389

84-
int main(int argc, char *argv[]) {
85-
std::string url = getenvOrEmpty("LIVEKIT_URL");
86-
std::string token = getenvOrEmpty("LIVEKIT_TOKEN");
90+
for (int i = 1; i < argc; ++i) {
91+
const std::string arg = argv[i];
92+
if (arg == "-h" || arg == "--help") {
93+
return false;
94+
}
95+
if (arg == "--without-user-timestamp") {
96+
send_user_timestamp = false;
97+
continue;
98+
}
99+
if (arg == "--with-user-timestamp") {
100+
send_user_timestamp = true;
101+
continue;
102+
}
103+
104+
positional.push_back(arg);
105+
}
106+
107+
url = getenvOrEmpty("LIVEKIT_URL");
108+
token = getenvOrEmpty("LIVEKIT_TOKEN");
87109

88-
if (argc >= 3) {
89-
url = argv[1];
90-
token = argv[2];
110+
if (positional.size() >= 2) {
111+
url = positional[0];
112+
token = positional[1];
91113
}
92114

93-
if (url.empty() || token.empty()) {
115+
return !(url.empty() || token.empty());
116+
}
117+
118+
} // namespace
119+
120+
int main(int argc, char *argv[]) {
121+
std::string url;
122+
std::string token;
123+
bool send_user_timestamp = true;
124+
125+
if (!parseArgs(argc, argv, url, token, send_user_timestamp)) {
94126
printUsage(argv[0]);
95127
return 1;
96128
}
@@ -125,11 +157,12 @@ int main(int argc, char *argv[]) {
125157
try {
126158
TrackPublishOptions publish_options;
127159
publish_options.source = TrackSource::SOURCE_CAMERA;
128-
publish_options.packet_trailer_features.user_timestamp = true;
160+
publish_options.packet_trailer_features.user_timestamp =
161+
send_user_timestamp;
129162

130163
room.localParticipant()->publishTrack(track, publish_options);
131-
std::cout << "[producer] published camera track with "
132-
"packet_trailer_features.user_timestamp enabled\n";
164+
std::cout << "[producer] published camera track with user timestamp "
165+
<< (send_user_timestamp ? "enabled" : "disabled") << "\n";
133166

134167
VideoFrame frame = VideoFrame::create(kFrameWidth, kFrameHeight,
135168
VideoBufferType::BGRA);
@@ -146,16 +179,22 @@ int main(int argc, char *argv[]) {
146179
std::chrono::steady_clock::now() - capture_start)
147180
.count());
148181
capture_options.rotation = VideoRotation::VIDEO_ROTATION_0;
149-
capture_options.metadata = VideoFrameMetadata{};
150-
capture_options.metadata->user_timestamp_us = nowEpochUs();
182+
if (send_user_timestamp) {
183+
capture_options.metadata = VideoFrameMetadata{};
184+
capture_options.metadata->user_timestamp_us = nowEpochUs();
185+
}
151186

152187
source->captureFrame(frame, capture_options);
153188

154189
if (frame_index % 5 == 0) {
155190
std::cout << "[producer] frame=" << frame_index
156191
<< " capture_ts_us=" << capture_options.timestamp_us
157192
<< " user_ts_us="
158-
<< *capture_options.metadata->user_timestamp_us << "\n";
193+
<< (send_user_timestamp
194+
? std::to_string(
195+
*capture_options.metadata->user_timestamp_us)
196+
: std::string("disabled"))
197+
<< "\n";
159198
}
160199

161200
++frame_index;

0 commit comments

Comments
 (0)