-
Notifications
You must be signed in to change notification settings - Fork 94
/
sync_sender.cc
188 lines (156 loc) · 7.88 KB
/
sync_sender.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
#include <uvgrtp/lib.hh>
#include <climits>
#include <cstring>
#include <iostream>
#include <cstring>
/* RTP Control Protocol (RTCP) can be used to synchronize audio and video streams. RTCP Sender Reports (SR)
* contain two inportant fields: NTP timestamp and RTP timestamp. These pairs can be used to sync
* audio and video streams together. See RFC 3550 6.4.1 for more info
*
* This example demonstrates using RTCP to synchronize audio and video streams. There are two
* receivers representing video and audio streams. This example also measures the latency between
* generating Sender report and receiving them. For the latency measurements to be accurate, you
* must make sure that the system clocks are synchronized! However, for only synchronizing audio and
* video, this is not crucial.
*
* This example is intended to be ran with sync_receiver example. Start sync_receiver first and then
* sync_sender.
*
*/
// Network parameters of this example
constexpr char SENDER_ADDRESS[] = "127.0.0.1";
constexpr uint16_t SENDER_VIDEO_PORT = 8888;
constexpr uint16_t SENDER_AUDIO_PORT = 8890;
constexpr char RECEIVER_ADDRESS[] = "127.0.0.1";
constexpr uint16_t RECEIVER_VIDEO_PORT = 7776;
constexpr uint16_t RECEIVER_AUDIO_PORT = 7778;
// demonstration parameters of this example
constexpr int VIDEO_PAYLOAD_SIZE = 4000;
constexpr int AUDIO_PAYLOAD_SIZE = 100;
constexpr auto EXAMPLE_RUN_TIME_S = std::chrono::seconds(55);
constexpr auto AUDIO_FRAME_INTERVAL_MS = std::chrono::milliseconds(20);
constexpr auto VIDEO_FRAME_INTERVAL_MS = std::chrono::milliseconds(1000 / 60); // 60 fps video
/* In this example, RTP timestamps in RTP packets are manually set by the user. Every packet that contains data from the
* same frame should be given the same RTP timestamp. This way the frame can be reassembled in the receiving end.
* Audio and video streams have their own RTP timestamps. Here we assign the initial timestamps. These initial values
* SHOULD be random, but for the purpose of this demonstration they are given here as constant values.
* Different streams should start from different values. */
uint32_t a_ts = 400000;
uint32_t v_ts = 200000;
// Hook for receiving RTCP receiver reports
void receiver_hook(uvgrtp::frame::rtcp_receiver_report* frame);
void sender_function(uvgrtp::session* sender_session, int flags, std::shared_ptr<std::mutex> print_mutex,
RTP_FORMAT format, uint16_t sender_port, uint16_t receiver_port, size_t payload_size,
std::chrono::milliseconds frame_interval);
void wait_until_next_frame(std::chrono::steady_clock::time_point& start, std::chrono::milliseconds interval, int frame_index);
int main(void)
{
std::cout << "Starting uvgRTP RTCP stream synchronization example" << std::endl;
std::shared_ptr<std::mutex> print_mutex = std::shared_ptr<std::mutex>(new std::mutex);
unsigned rce_dh_flags = RCE_RTCP;
unsigned rce_multistream_flags = RCE_RTCP;
std::cout << "Initializing senders" << std::endl;
uvgrtp::context sender_ctx;
std::pair<std::string, std::string> addresses_sender(SENDER_ADDRESS, RECEIVER_ADDRESS);
uvgrtp::session* sender_session = sender_ctx.create_session(addresses_sender);
// start the audio and video in their own threads
std::thread a_sender(sender_function, sender_session, rce_dh_flags, print_mutex,
RTP_FORMAT_OPUS, SENDER_AUDIO_PORT, RECEIVER_AUDIO_PORT,
AUDIO_PAYLOAD_SIZE, AUDIO_FRAME_INTERVAL_MS);
std::thread v_sender(sender_function, sender_session, rce_multistream_flags, print_mutex,
RTP_FORMAT_H265, SENDER_VIDEO_PORT, RECEIVER_VIDEO_PORT,
VIDEO_PAYLOAD_SIZE, VIDEO_FRAME_INTERVAL_MS);
// wait until all threads have ended
if (a_sender.joinable())
{
a_sender.join();
}
if (v_sender.joinable())
{
v_sender.join();
}
if (sender_session)
sender_ctx.destroy_session(sender_session);
std::cout << "RTCP stream synchronization example finished" << std::endl;
return EXIT_SUCCESS;
}
void sender_function(uvgrtp::session* sender_session, int flags, std::shared_ptr<std::mutex> print_mutex,
RTP_FORMAT format, uint16_t sender_port, uint16_t receiver_port, size_t payload_size,
std::chrono::milliseconds frame_interval)
{
print_mutex->lock();
std::cout << "Sender thread port: " << sender_port << "->" << receiver_port << std::endl;
print_mutex->unlock();
uvgrtp::media_stream* sender_audio_strm = sender_session->create_stream(sender_port,
receiver_port,
format, flags);
if (!sender_audio_strm || sender_audio_strm->get_rtcp()->install_receiver_hook(receiver_hook) != RTP_OK)
{
std::cerr << "Failed to install RTCP receiver report hook" << std::endl;
return;
}
/* This is the RTP timestamp of the frames. Check which stream we are sending and set the initial timestamp */
uint32_t ts;
if (format == RTP_FORMAT_OPUS) {
ts = a_ts;
}
else {
ts = v_ts;
}
if (sender_audio_strm)
{
auto start = std::chrono::steady_clock::now();
for (int i = 0; std::chrono::steady_clock::now() < (start + EXAMPLE_RUN_TIME_S); ++i)
{
std::unique_ptr<uint8_t[]> dummy_frame = std::unique_ptr<uint8_t[]>(new uint8_t[payload_size]);
if (format == RTP_FORMAT_H265 && payload_size >= 5)
{
memset(dummy_frame.get(), 'a', payload_size); // data
memset(dummy_frame.get(), 0, 3);
memset(dummy_frame.get() + 3, 1, 1);
memset(dummy_frame.get() + 4, 1, (19 << 1)); // Intra frame NAL type
}
/* When sending data with the push_frame() function, note the following parameters:
*
* ts: RTP timestamp defined in RFC 3550 5.1. Several consecutive RTP packets
* will have equal timestamps if they are (logically) generated at once, e.g., belong to the same video frame.
* Consecutive RTP packets MAY contain timestamps that are not monotonic if the data is not transmitted
* in the order it was sampled, as in the case of MPEG interpolated video frames. (The sequence numbers of
* the packets as transmitted will still be monotonic.)
*
* s_ts: NTP timestamp of when the frame was sampled. This should also be the same for all packets
* belonging to the same frame
*
* When you wish to synchronize multiple streams together, you SHOULD manually give both these parameters. It
* is possible to let RTP set them automatically, however that is not recommended. */
uint64_t clock_ntp = uvgrtp::clock::ntp::now();
if (sender_audio_strm->push_frame(std::move(dummy_frame), payload_size, ts, clock_ntp, RTP_NO_FLAGS) != RTP_OK)
{
std::cerr << "Failed to send frame" << std::endl;
}
ts += 1;
// wait until it is time to send the next frame. Included only for
// demostration purposes since you can use uvgRTP to send packets as fast as desired
wait_until_next_frame(start, frame_interval, i);
}
}
}
void wait_until_next_frame(std::chrono::steady_clock::time_point& start,
std::chrono::milliseconds interval, int frame_index)
{
// wait until it is time to send the next frame. Simulates a steady sending pace
// and included only for demostration purposes since you can use uvgRTP to send
// packets as fast as desired
auto time_since_start = std::chrono::steady_clock::now() - start;
auto next_frame_time = (frame_index + 1) * interval;
if (next_frame_time > time_since_start)
{
std::this_thread::sleep_for(next_frame_time - time_since_start);
}
}
void receiver_hook(uvgrtp::frame::rtcp_receiver_report* frame)
{
std::cout << "RTCP receiver report received from " << frame->ssrc << std::endl;
/* RTCP frames can be deallocated using delete */
delete frame;
}