diff options
author | zoff99 <zoff@zoff.cc> | 2018-01-19 22:59:42 +0100 |
---|---|---|
committer | iphydf <iphydf@users.noreply.github.com> | 2018-02-11 23:31:46 +0000 |
commit | 721358208b6650c62aa654be922867f10a5d6f38 (patch) | |
tree | 988aef376cc8c74b19b5e605133072bdf3d23e27 | |
parent | 0647c2c5bc8c871dbcaed64de40eb252d13d303c (diff) |
Improve video key frame sending.
This change does not include the addition of VP9. We do that in a
separate pull request.
Changes:
* fix the video bug (video frames larger than 65KBytes) by sending full
frame length in alternate header field
* improve video frame reconstruction logic with slots
* configure video encoder and decoder to be multihtreaded
* set error resilience flags on video codec
* change encoder and decoder softdeadline
-rw-r--r-- | testing/BUILD.bazel | 4 | ||||
-rw-r--r-- | toxav/BUILD.bazel | 2 | ||||
-rw-r--r-- | toxav/bwcontroller.c | 99 | ||||
-rw-r--r-- | toxav/bwcontroller.h | 2 | ||||
-rw-r--r-- | toxav/rtp.c | 874 | ||||
-rw-r--r-- | toxav/rtp.h | 63 | ||||
-rw-r--r-- | toxav/rtp_test.cpp | 2 | ||||
-rw-r--r-- | toxav/toxav.c | 90 | ||||
-rw-r--r-- | toxav/video.c | 292 | ||||
-rw-r--r-- | toxav/video.h | 5 | ||||
-rw-r--r-- | toxcore/BUILD.bazel | 4 |
11 files changed, 1031 insertions, 406 deletions
diff --git a/testing/BUILD.bazel b/testing/BUILD.bazel index afb1a8aa..6cc7018e 100644 --- a/testing/BUILD.bazel +++ b/testing/BUILD.bazel | |||
@@ -34,9 +34,9 @@ cc_binary( | |||
34 | "//c-toxcore/toxav", | 34 | "//c-toxcore/toxav", |
35 | "//c-toxcore/toxav:monolith", | 35 | "//c-toxcore/toxav:monolith", |
36 | "//c-toxcore/toxcore", | 36 | "//c-toxcore/toxcore", |
37 | "@portaudio", | ||
38 | "@sndfile", | ||
39 | "@opencv//:core", | 37 | "@opencv//:core", |
40 | "@opencv//:highgui", | 38 | "@opencv//:highgui", |
39 | "@portaudio", | ||
40 | "@sndfile", | ||
41 | ], | 41 | ], |
42 | ) | 42 | ) |
diff --git a/toxav/BUILD.bazel b/toxav/BUILD.bazel index ff775fdd..cf905910 100644 --- a/toxav/BUILD.bazel +++ b/toxav/BUILD.bazel | |||
@@ -41,7 +41,7 @@ cc_test( | |||
41 | deps = [ | 41 | deps = [ |
42 | ":rtp", | 42 | ":rtp", |
43 | "//c-toxcore/toxcore:crypto_core", | 43 | "//c-toxcore/toxcore:crypto_core", |
44 | "@gtest", | 44 | "@com_google_googletest//:gtest_main", |
45 | ], | 45 | ], |
46 | ) | 46 | ) |
47 | 47 | ||
diff --git a/toxav/bwcontroller.c b/toxav/bwcontroller.c index 9d1ad9ce..defb3fde 100644 --- a/toxav/bwcontroller.c +++ b/toxav/bwcontroller.c | |||
@@ -32,9 +32,10 @@ | |||
32 | #include <errno.h> | 32 | #include <errno.h> |
33 | 33 | ||
34 | #define BWC_PACKET_ID 196 | 34 | #define BWC_PACKET_ID 196 |
35 | #define BWC_SEND_INTERVAL_MS 1000 | 35 | #define BWC_SEND_INTERVAL_MS 950 /* 0.95s */ |
36 | #define BWC_REFRESH_INTERVAL_MS 10000 | 36 | #define BWC_REFRESH_INTERVAL_MS 2000 /* 2.00s */ |
37 | #define BWC_AVG_PKT_COUNT 20 | 37 | #define BWC_AVG_PKT_COUNT 20 |
38 | #define BWC_AVG_LOSS_OVER_CYCLES_COUNT 30 | ||
38 | 39 | ||
39 | struct BWController_s { | 40 | struct BWController_s { |
40 | void (*mcb)(BWController *, uint32_t, float, void *); | 41 | void (*mcb)(BWController *, uint32_t, float, void *); |
@@ -56,6 +57,8 @@ struct BWController_s { | |||
56 | uint32_t packet_length_array[BWC_AVG_PKT_COUNT]; | 57 | uint32_t packet_length_array[BWC_AVG_PKT_COUNT]; |
57 | RingBuffer *rb; | 58 | RingBuffer *rb; |
58 | } rcvpkt; /* To calculate average received packet (this means split parts, not the full message!) */ | 59 | } rcvpkt; /* To calculate average received packet (this means split parts, not the full message!) */ |
60 | |||
61 | uint32_t packet_loss_counted_cycles; | ||
59 | }; | 62 | }; |
60 | 63 | ||
61 | struct BWCMessage { | 64 | struct BWCMessage { |
@@ -71,23 +74,23 @@ BWController *bwc_new(Messenger *m, uint32_t friendnumber, | |||
71 | void *udata) | 74 | void *udata) |
72 | { | 75 | { |
73 | BWController *retu = (BWController *)calloc(sizeof(struct BWController_s), 1); | 76 | BWController *retu = (BWController *)calloc(sizeof(struct BWController_s), 1); |
74 | 77 | LOGGER_DEBUG(m->log, "Creating bandwidth controller"); | |
75 | retu->mcb = mcb; | 78 | retu->mcb = mcb; |
76 | retu->mcb_data = udata; | 79 | retu->mcb_data = udata; |
77 | retu->m = m; | 80 | retu->m = m; |
78 | retu->friend_number = friendnumber; | 81 | retu->friend_number = friendnumber; |
79 | retu->cycle.last_sent_timestamp = retu->cycle.last_refresh_timestamp = current_time_monotonic(); | 82 | retu->cycle.last_sent_timestamp = retu->cycle.last_refresh_timestamp = current_time_monotonic(); |
80 | retu->rcvpkt.rb = rb_new(BWC_AVG_PKT_COUNT); | 83 | retu->rcvpkt.rb = rb_new(BWC_AVG_PKT_COUNT); |
84 | retu->cycle.lost = 0; | ||
85 | retu->cycle.recv = 0; | ||
86 | retu->packet_loss_counted_cycles = 0; | ||
81 | 87 | ||
82 | /* Fill with zeros */ | 88 | /* Fill with zeros */ |
83 | int i = 0; | 89 | for (int i = 0; i < BWC_AVG_PKT_COUNT; i++) { |
84 | 90 | rb_write(retu->rcvpkt.rb, &retu->rcvpkt.packet_length_array[i]); | |
85 | for (; i < BWC_AVG_PKT_COUNT; i ++) { | ||
86 | rb_write(retu->rcvpkt.rb, retu->rcvpkt.packet_length_array + i); | ||
87 | } | 91 | } |
88 | 92 | ||
89 | m_callback_rtp_packet(m, friendnumber, BWC_PACKET_ID, bwc_handle_data, retu); | 93 | m_callback_rtp_packet(m, friendnumber, BWC_PACKET_ID, bwc_handle_data, retu); |
90 | |||
91 | return retu; | 94 | return retu; |
92 | } | 95 | } |
93 | 96 | ||
@@ -98,47 +101,21 @@ void bwc_kill(BWController *bwc) | |||
98 | } | 101 | } |
99 | 102 | ||
100 | m_callback_rtp_packet(bwc->m, bwc->friend_number, BWC_PACKET_ID, nullptr, nullptr); | 103 | m_callback_rtp_packet(bwc->m, bwc->friend_number, BWC_PACKET_ID, nullptr, nullptr); |
101 | |||
102 | rb_kill(bwc->rcvpkt.rb); | 104 | rb_kill(bwc->rcvpkt.rb); |
103 | free(bwc); | 105 | free(bwc); |
104 | } | 106 | } |
105 | 107 | ||
106 | void bwc_feed_avg(BWController *bwc, uint32_t bytes) | ||
107 | { | ||
108 | uint32_t *p; | ||
109 | |||
110 | rb_read(bwc->rcvpkt.rb, (void **) &p); | ||
111 | rb_write(bwc->rcvpkt.rb, p); | ||
112 | |||
113 | *p = bytes; | ||
114 | } | ||
115 | |||
116 | void bwc_add_lost(BWController *bwc, uint32_t bytes_lost) | 108 | void bwc_add_lost(BWController *bwc, uint32_t bytes_lost) |
117 | { | 109 | { |
118 | if (!bwc) { | 110 | if (!bwc) { |
119 | return; | 111 | return; |
120 | } | 112 | } |
121 | 113 | ||
122 | if (!bytes_lost) { | 114 | if (bytes_lost > 0) { |
123 | uint32_t *t_avg[BWC_AVG_PKT_COUNT], c = 1; | 115 | LOGGER_DEBUG(bwc->m->log, "BWC lost(1): %d", (int)bytes_lost); |
124 | 116 | bwc->cycle.lost += bytes_lost; | |
125 | rb_data(bwc->rcvpkt.rb, (void **) t_avg); | 117 | send_update(bwc); |
126 | |||
127 | int i = 0; | ||
128 | |||
129 | for (; i < BWC_AVG_PKT_COUNT; i ++) { | ||
130 | bytes_lost += *(t_avg[i]); | ||
131 | |||
132 | if (*(t_avg[i])) { | ||
133 | c++; | ||
134 | } | ||
135 | } | ||
136 | |||
137 | bytes_lost /= c; | ||
138 | } | 118 | } |
139 | |||
140 | bwc->cycle.lost += bytes_lost; | ||
141 | send_update(bwc); | ||
142 | } | 119 | } |
143 | 120 | ||
144 | void bwc_add_recv(BWController *bwc, uint32_t recv_bytes) | 121 | void bwc_add_recv(BWController *bwc, uint32_t recv_bytes) |
@@ -147,37 +124,35 @@ void bwc_add_recv(BWController *bwc, uint32_t recv_bytes) | |||
147 | return; | 124 | return; |
148 | } | 125 | } |
149 | 126 | ||
127 | bwc->packet_loss_counted_cycles++; | ||
150 | bwc->cycle.recv += recv_bytes; | 128 | bwc->cycle.recv += recv_bytes; |
151 | send_update(bwc); | 129 | send_update(bwc); |
152 | } | 130 | } |
153 | 131 | ||
154 | |||
155 | void send_update(BWController *bwc) | 132 | void send_update(BWController *bwc) |
156 | { | 133 | { |
157 | if (current_time_monotonic() - bwc->cycle.last_refresh_timestamp > BWC_REFRESH_INTERVAL_MS) { | 134 | if (bwc->packet_loss_counted_cycles > BWC_AVG_LOSS_OVER_CYCLES_COUNT && |
158 | 135 | current_time_monotonic() - bwc->cycle.last_sent_timestamp > BWC_SEND_INTERVAL_MS) { | |
159 | bwc->cycle.lost /= 10; | 136 | bwc->packet_loss_counted_cycles = 0; |
160 | bwc->cycle.recv /= 10; | ||
161 | bwc->cycle.last_refresh_timestamp = current_time_monotonic(); | ||
162 | } else if (current_time_monotonic() - bwc->cycle.last_sent_timestamp > BWC_SEND_INTERVAL_MS) { | ||
163 | 137 | ||
164 | if (bwc->cycle.lost) { | 138 | if (bwc->cycle.lost) { |
165 | LOGGER_DEBUG(bwc->m->log, "%p Sent update rcv: %u lost: %u", | 139 | LOGGER_DEBUG(bwc->m->log, "%p Sent update rcv: %u lost: %u percent: %f %%", |
166 | bwc, bwc->cycle.recv, bwc->cycle.lost); | 140 | bwc, bwc->cycle.recv, bwc->cycle.lost, |
167 | 141 | (((float) bwc->cycle.lost / (bwc->cycle.recv + bwc->cycle.lost)) * 100.0f)); | |
168 | uint8_t p_msg[sizeof(struct BWCMessage) + 1]; | 142 | uint8_t bwc_packet[sizeof(struct BWCMessage) + 1]; |
169 | struct BWCMessage *b_msg = (struct BWCMessage *)(p_msg + 1); | 143 | struct BWCMessage *msg = (struct BWCMessage *)(bwc_packet + 1); |
170 | 144 | bwc_packet[0] = BWC_PACKET_ID; // set packet ID | |
171 | p_msg[0] = BWC_PACKET_ID; | 145 | msg->lost = net_htonl(bwc->cycle.lost); |
172 | b_msg->lost = net_htonl(bwc->cycle.lost); | 146 | msg->recv = net_htonl(bwc->cycle.recv); |
173 | b_msg->recv = net_htonl(bwc->cycle.recv); | 147 | |
174 | 148 | if (-1 == m_send_custom_lossy_packet(bwc->m, bwc->friend_number, bwc_packet, sizeof(bwc_packet))) { | |
175 | if (-1 == m_send_custom_lossy_packet(bwc->m, bwc->friend_number, p_msg, sizeof(p_msg))) { | 149 | LOGGER_WARNING(bwc->m->log, "BWC send failed (len: %d)! std error: %s", sizeof(bwc_packet), strerror(errno)); |
176 | LOGGER_WARNING(bwc->m->log, "BWC send failed (len: %d)! std error: %s", sizeof(p_msg), strerror(errno)); | ||
177 | } | 150 | } |
178 | } | 151 | } |
179 | 152 | ||
180 | bwc->cycle.last_sent_timestamp = current_time_monotonic(); | 153 | bwc->cycle.last_sent_timestamp = current_time_monotonic(); |
154 | bwc->cycle.lost = 0; | ||
155 | bwc->cycle.recv = 0; | ||
181 | } | 156 | } |
182 | } | 157 | } |
183 | 158 | ||
@@ -185,9 +160,9 @@ static int on_update(BWController *bwc, const struct BWCMessage *msg) | |||
185 | { | 160 | { |
186 | LOGGER_DEBUG(bwc->m->log, "%p Got update from peer", bwc); | 161 | LOGGER_DEBUG(bwc->m->log, "%p Got update from peer", bwc); |
187 | 162 | ||
188 | /* Peer must respect time boundary */ | 163 | /* Peers sent update too soon */ |
189 | if (current_time_monotonic() < bwc->cycle.last_recv_timestamp + BWC_SEND_INTERVAL_MS) { | 164 | if (bwc->cycle.last_recv_timestamp + BWC_SEND_INTERVAL_MS > current_time_monotonic()) { |
190 | LOGGER_DEBUG(bwc->m->log, "%p Rejecting extra update", bwc); | 165 | LOGGER_INFO(bwc->m->log, "%p Rejecting extra update", bwc); |
191 | return -1; | 166 | return -1; |
192 | } | 167 | } |
193 | 168 | ||
@@ -196,9 +171,9 @@ static int on_update(BWController *bwc, const struct BWCMessage *msg) | |||
196 | uint32_t recv = net_ntohl(msg->recv); | 171 | uint32_t recv = net_ntohl(msg->recv); |
197 | uint32_t lost = net_ntohl(msg->lost); | 172 | uint32_t lost = net_ntohl(msg->lost); |
198 | 173 | ||
199 | LOGGER_DEBUG(bwc->m->log, "recved: %u lost: %u", recv, lost); | ||
200 | |||
201 | if (lost && bwc->mcb) { | 174 | if (lost && bwc->mcb) { |
175 | LOGGER_DEBUG(bwc->m->log, "recved: %u lost: %u percentage: %f %%", recv, lost, | ||
176 | (((float) lost / (recv + lost)) * 100.0f)); | ||
202 | bwc->mcb(bwc, bwc->friend_number, | 177 | bwc->mcb(bwc, bwc->friend_number, |
203 | ((float) lost / (recv + lost)), | 178 | ((float) lost / (recv + lost)), |
204 | bwc->mcb_data); | 179 | bwc->mcb_data); |
diff --git a/toxav/bwcontroller.h b/toxav/bwcontroller.h index 43475252..be5eb191 100644 --- a/toxav/bwcontroller.h +++ b/toxav/bwcontroller.h | |||
@@ -27,9 +27,9 @@ typedef struct BWController_s BWController; | |||
27 | BWController *bwc_new(Messenger *m, uint32_t friendnumber, | 27 | BWController *bwc_new(Messenger *m, uint32_t friendnumber, |
28 | void (*mcb)(BWController *, uint32_t, float, void *), | 28 | void (*mcb)(BWController *, uint32_t, float, void *), |
29 | void *udata); | 29 | void *udata); |
30 | |||
30 | void bwc_kill(BWController *bwc); | 31 | void bwc_kill(BWController *bwc); |
31 | 32 | ||
32 | void bwc_feed_avg(BWController *bwc, uint32_t bytes); | ||
33 | void bwc_add_lost(BWController *bwc, uint32_t bytes); | 33 | void bwc_add_lost(BWController *bwc, uint32_t bytes); |
34 | void bwc_add_recv(BWController *bwc, uint32_t bytes); | 34 | void bwc_add_recv(BWController *bwc, uint32_t bytes); |
35 | 35 | ||
diff --git a/toxav/rtp.c b/toxav/rtp.c index 06bc6df5..c5d940e2 100644 --- a/toxav/rtp.c +++ b/toxav/rtp.c | |||
@@ -33,11 +33,557 @@ | |||
33 | #include <errno.h> | 33 | #include <errno.h> |
34 | #include <stdlib.h> | 34 | #include <stdlib.h> |
35 | 35 | ||
36 | enum { | ||
37 | /** | ||
38 | * The number of milliseconds we want to keep a keyframe in the buffer for, | ||
39 | * even though there are no free slots for incoming frames. | ||
40 | */ | ||
41 | VIDEO_KEEP_KEYFRAME_IN_BUFFER_FOR_MS = 15, | ||
42 | }; | ||
43 | |||
44 | // allocate_len is NOT including header! | ||
45 | static struct RTPMessage *new_message(const struct RTPHeader *header, size_t allocate_len, const uint8_t *data, | ||
46 | uint16_t data_length) | ||
47 | { | ||
48 | assert(allocate_len >= data_length); | ||
49 | struct RTPMessage *msg = (struct RTPMessage *)calloc(1, sizeof(struct RTPMessage) + allocate_len); | ||
50 | |||
51 | if (msg == nullptr) { | ||
52 | return nullptr; | ||
53 | } | ||
54 | |||
55 | msg->len = data_length; // result without header | ||
56 | msg->header = *header; | ||
57 | memcpy(msg->data, data, msg->len); | ||
58 | return msg; | ||
59 | } | ||
60 | |||
61 | enum { | ||
62 | /** | ||
63 | * Instruct the caller to clear slot 0. | ||
64 | */ | ||
65 | GET_SLOT_RESULT_DROP_OLDEST_SLOT = -1, | ||
66 | /** | ||
67 | * Instruct the caller to drop the incoming packet. | ||
68 | */ | ||
69 | GET_SLOT_RESULT_DROP_INCOMING = -2, | ||
70 | }; | ||
71 | |||
72 | /** | ||
73 | * Find the next free slot in work_buffer for the incoming data packet. | ||
74 | * | ||
75 | * - If the data packet belongs to a frame thats already in the work_buffer then | ||
76 | * use that slot. | ||
77 | * - If there is no free slot return GET_SLOT_RESULT_DROP_OLDEST_SLOT. | ||
78 | * - If the data packet is too old return GET_SLOT_RESULT_DROP_INCOMING. | ||
79 | * | ||
80 | * If there is a keyframe beeing assembled in slot 0, keep it a bit longer and | ||
81 | * do not kick it out right away if all slots are full instead kick out the new | ||
82 | * incoming interframe. | ||
83 | */ | ||
84 | static int8_t get_slot(Logger *log, struct RTPWorkBufferList *wkbl, bool is_keyframe, | ||
85 | const struct RTPHeader *header, bool is_multipart) | ||
86 | { | ||
87 | if (is_multipart) { | ||
88 | // This RTP message is part of a multipart frame, so we try to find an | ||
89 | // existing slot with the previous parts of the frame in it. | ||
90 | for (uint8_t i = 0; i < wkbl->next_free_entry; i++) { | ||
91 | const struct RTPWorkBuffer *slot = &wkbl->work_buffer[i]; | ||
92 | |||
93 | if ((slot->buf->header.sequnum == header->sequnum) && (slot->buf->header.timestamp == header->timestamp)) { | ||
94 | // Sequence number and timestamp match, so this slot belongs to | ||
95 | // the same frame. | ||
96 | // | ||
97 | // In reality, these will almost certainly either both match or | ||
98 | // both not match. Only if somehow there were 65535 frames | ||
99 | // between, the timestamp will matter. | ||
100 | return i; | ||
101 | } | ||
102 | } | ||
103 | } | ||
104 | |||
105 | // The message may or may not be part of a multipart frame. | ||
106 | // | ||
107 | // If it is part of a multipart frame, then this is an entirely new frame | ||
108 | // for which we did not have a slot *or* the frame is so old that its slot | ||
109 | // has been evicted by now. | ||
110 | // | ||
111 | // |----------- time -----------> | ||
112 | // _________________ | ||
113 | // slot 0 | | | ||
114 | // ----------------- | ||
115 | // _________________ | ||
116 | // slot 1 | | | ||
117 | // ----------------- | ||
118 | // ____________ | ||
119 | // slot 2 | | -> frame too old, drop | ||
120 | // ------------ | ||
121 | // | ||
122 | // | ||
123 | // | ||
124 | // |----------- time -----------> | ||
125 | // _________________ | ||
126 | // slot 0 | | | ||
127 | // ----------------- | ||
128 | // _________________ | ||
129 | // slot 1 | | | ||
130 | // ----------------- | ||
131 | // ____________ | ||
132 | // slot 2 | | -> ok, start filling in a new slot | ||
133 | // ------------ | ||
134 | |||
135 | // If there is a free slot: | ||
136 | if (wkbl->next_free_entry < USED_RTP_WORKBUFFER_COUNT) { | ||
137 | // If there is at least one filled slot: | ||
138 | if (wkbl->next_free_entry > 0) { | ||
139 | // Get the most recently filled slot. | ||
140 | const struct RTPWorkBuffer *slot = &wkbl->work_buffer[wkbl->next_free_entry - 1]; | ||
141 | |||
142 | // If the incoming packet is older than our newest slot, drop it. | ||
143 | // This is the first situation in the above diagram. | ||
144 | if (slot->buf->header.timestamp > header->timestamp) { | ||
145 | LOGGER_DEBUG(log, "workbuffer:2:timestamp too old"); | ||
146 | return GET_SLOT_RESULT_DROP_INCOMING; | ||
147 | } | ||
148 | } | ||
149 | |||
150 | // Not all slots are filled, and the packet is newer than our most | ||
151 | // recent slot, so it's a new frame we want to start assembling. This is | ||
152 | // the second situation in the above diagram. | ||
153 | return wkbl->next_free_entry; | ||
154 | } | ||
155 | |||
156 | // If the incoming frame is a key frame, then stop assembling the oldest | ||
157 | // slot, regardless of whether there was a keyframe in that or not. | ||
158 | if (is_keyframe) { | ||
159 | return GET_SLOT_RESULT_DROP_OLDEST_SLOT; | ||
160 | } | ||
161 | |||
162 | // The incoming slot is not a key frame, so we look at slot 0 to see what to | ||
163 | // do next. | ||
164 | const struct RTPWorkBuffer *slot = &wkbl->work_buffer[0]; | ||
165 | |||
166 | // The incoming frame is not a key frame, but the existing slot 0 is also | ||
167 | // not a keyframe, so we stop assembling the existing frame and make space | ||
168 | // for the new one. | ||
169 | if (!slot->is_keyframe) { | ||
170 | return GET_SLOT_RESULT_DROP_OLDEST_SLOT; | ||
171 | } | ||
172 | |||
173 | // If this key frame is fully received, we also stop assembling and clear | ||
174 | // slot 0. This also means sending the frame to the decoder. | ||
175 | if (slot->received_len == slot->buf->header.data_length_full) { | ||
176 | return GET_SLOT_RESULT_DROP_OLDEST_SLOT; | ||
177 | } | ||
178 | |||
179 | // This is a key frame, not fully received yet, but it's already much older | ||
180 | // than the incoming frame, so we stop assembling it and send whatever part | ||
181 | // we did receive to the decoder. | ||
182 | if (slot->buf->header.timestamp + VIDEO_KEEP_KEYFRAME_IN_BUFFER_FOR_MS <= header->timestamp) { | ||
183 | return GET_SLOT_RESULT_DROP_OLDEST_SLOT; | ||
184 | } | ||
185 | |||
186 | // This is a key frame, it's not too old yet, so we keep it in its slot for | ||
187 | // a little longer. | ||
188 | LOGGER_INFO(log, "keep KEYFRAME in workbuffer"); | ||
189 | return GET_SLOT_RESULT_DROP_INCOMING; | ||
190 | } | ||
191 | |||
192 | /** | ||
193 | * Returns an assembled frame (as much data as we currently have for this frame, | ||
194 | * some pieces may be missing) | ||
195 | * | ||
196 | * If there are no frames ready, we return NULL. If this function returns | ||
197 | * non-NULL, it transfers ownership of the message to the caller, i.e. the | ||
198 | * caller is responsible for storing it elsewhere or calling free(). | ||
199 | */ | ||
200 | static struct RTPMessage *process_frame(Logger *log, struct RTPWorkBufferList *wkbl, uint8_t slot_id) | ||
201 | { | ||
202 | assert(wkbl->next_free_entry >= 0); | ||
203 | |||
204 | if (wkbl->next_free_entry == 0) { | ||
205 | // There are no frames in any slot. | ||
206 | return nullptr; | ||
207 | } | ||
208 | |||
209 | // Slot 0 contains a key frame, slot_id points at an interframe that is | ||
210 | // relative to that key frame, so we don't use it yet. | ||
211 | if (wkbl->work_buffer[0].is_keyframe && slot_id != 0) { | ||
212 | LOGGER_DEBUG(log, "process_frame:KEYFRAME waiting in slot 0"); | ||
213 | return nullptr; | ||
214 | } | ||
215 | |||
216 | // Either slot_id is 0 and slot 0 is a key frame, or there is no key frame | ||
217 | // in slot 0 (and slot_id is anything). | ||
218 | struct RTPWorkBuffer *const slot = &wkbl->work_buffer[slot_id]; | ||
219 | |||
220 | // Move ownership of the frame out of the slot into m_new. | ||
221 | struct RTPMessage *const m_new = slot->buf; | ||
222 | slot->buf = nullptr; | ||
223 | |||
224 | assert(wkbl->next_free_entry >= 1); | ||
225 | |||
226 | if (slot_id != wkbl->next_free_entry - 1) { | ||
227 | // The slot is not the last slot, so we created a gap. We move all the | ||
228 | // entries after it one step up. | ||
229 | for (uint8_t i = slot_id; i < wkbl->next_free_entry - 1; i++) { | ||
230 | // Move entry (i+1) into entry (i). | ||
231 | wkbl->work_buffer[i] = wkbl->work_buffer[i + 1]; | ||
232 | } | ||
233 | } | ||
234 | |||
235 | // We now have a free entry at the end of the array. | ||
236 | wkbl->next_free_entry--; | ||
237 | |||
238 | // Clear the newly freed entry. | ||
239 | const struct RTPWorkBuffer empty = {0}; | ||
240 | wkbl->work_buffer[wkbl->next_free_entry] = empty; | ||
241 | |||
242 | // Move ownership of the frame to the caller. | ||
243 | return m_new; | ||
244 | } | ||
245 | |||
246 | /** | ||
247 | * @param log A logger. | ||
248 | * @param wkbl The list of in-progress frames, i.e. all the slots. | ||
249 | * @param slot_id The slot we want to fill the data into. | ||
250 | * @param is_keyframe Whether the data is part of a key frame. | ||
251 | * @param header The RTP header from the incoming packet. | ||
252 | * @param incoming_data The pure payload without header. | ||
253 | * @param incoming_data_length The length in bytes of the incoming data payload. | ||
254 | */ | ||
255 | static bool fill_data_into_slot(Logger *log, struct RTPWorkBufferList *wkbl, const uint8_t slot_id, bool is_keyframe, | ||
256 | const struct RTPHeader *header, const uint8_t *incoming_data, uint16_t incoming_data_length) | ||
257 | { | ||
258 | // We're either filling the data into an existing slot, or in a new one that | ||
259 | // is the next free entry. | ||
260 | assert(slot_id <= wkbl->next_free_entry); | ||
261 | struct RTPWorkBuffer *const slot = &wkbl->work_buffer[slot_id]; | ||
262 | |||
263 | assert(header != nullptr); | ||
264 | assert(is_keyframe == (bool)(header->flags & RTP_KEY_FRAME)); | ||
265 | |||
266 | if (slot->received_len == 0) { | ||
267 | assert(slot->buf == nullptr); | ||
268 | |||
269 | // No data for this slot has been received, yet, so we create a new | ||
270 | // message for it with enough memory for the entire frame. | ||
271 | struct RTPMessage *msg = (struct RTPMessage *)calloc(1, sizeof(struct RTPMessage) + header->data_length_full); | ||
272 | |||
273 | if (msg == nullptr) { | ||
274 | LOGGER_ERROR(log, "Out of memory while trying to allocate for frame of size %u\n", | ||
275 | (unsigned)header->data_length_full); | ||
276 | // Out of memory: throw away the incoming data. | ||
277 | return false; | ||
278 | } | ||
279 | |||
280 | // Unused in the new video receiving code, as it's 16 bit and can't hold | ||
281 | // the full length of large frames. Instead, we use slot->received_len. | ||
282 | msg->len = 0; | ||
283 | msg->header = *header; | ||
284 | |||
285 | slot->buf = msg; | ||
286 | slot->is_keyframe = is_keyframe; | ||
287 | slot->received_len = 0; | ||
288 | |||
289 | assert(wkbl->next_free_entry < USED_RTP_WORKBUFFER_COUNT); | ||
290 | wkbl->next_free_entry++; | ||
291 | } | ||
292 | |||
293 | // We already checked this when we received the packet, but we rely on it | ||
294 | // here, so assert again. | ||
295 | assert(header->offset_full < header->data_length_full); | ||
296 | |||
297 | // Copy the incoming chunk of data into the correct position in the full | ||
298 | // frame data array. | ||
299 | memcpy( | ||
300 | slot->buf->data + header->offset_full, | ||
301 | incoming_data, | ||
302 | incoming_data_length | ||
303 | ); | ||
304 | |||
305 | // Update the total received length of this slot. | ||
306 | slot->received_len += incoming_data_length; | ||
307 | |||
308 | // Update received length also in the header of the message, for later use. | ||
309 | slot->buf->header.received_length_full = slot->received_len; | ||
310 | |||
311 | return slot->received_len == header->data_length_full; | ||
312 | } | ||
313 | |||
314 | static void update_bwc_values(Logger *log, RTPSession *session, const struct RTPMessage *msg) | ||
315 | { | ||
316 | if (session->first_packets_counter < DISMISS_FIRST_LOST_VIDEO_PACKET_COUNT) { | ||
317 | session->first_packets_counter++; | ||
318 | } else { | ||
319 | uint32_t data_length_full = msg->header.data_length_full; // without header | ||
320 | uint32_t received_length_full = msg->header.received_length_full; // without header | ||
321 | bwc_add_recv(session->bwc, data_length_full); | ||
322 | |||
323 | if (received_length_full < data_length_full) { | ||
324 | LOGGER_DEBUG(log, "BWC: full length=%u received length=%d", data_length_full, received_length_full); | ||
325 | bwc_add_lost(session->bwc, (data_length_full - received_length_full)); | ||
326 | } | ||
327 | } | ||
328 | } | ||
329 | |||
330 | /** | ||
331 | * Handle a single RTP video packet. | ||
332 | * | ||
333 | * The packet may or may not be part of a multipart frame. This function will | ||
334 | * find out and handle it appropriately. | ||
335 | * | ||
336 | * @param session The current RTP session with: | ||
337 | * session->mcb == vc_queue_message() // this function is called from here | ||
338 | * session->mp == struct RTPMessage * | ||
339 | * session->cs == call->video.second // == VCSession created by vc_new() call | ||
340 | * @param header The RTP header deserialised from the packet. | ||
341 | * @param incoming_data The packet data *not* header, i.e. this is the actual | ||
342 | * payload. | ||
343 | * @param incoming_data_length The packet length *not* including header, i.e. | ||
344 | * this is the actual payload length. | ||
345 | * @param log A logger. | ||
346 | * | ||
347 | * @return -1 on error, 0 on success. | ||
348 | */ | ||
349 | static int handle_video_packet(RTPSession *session, const struct RTPHeader *header, | ||
350 | const uint8_t *incoming_data, uint16_t incoming_data_length, Logger *log) | ||
351 | { | ||
352 | // Full frame length in bytes. The frame may be split into multiple packets, | ||
353 | // but this value is the complete assembled frame size. | ||
354 | const uint32_t full_frame_length = header->data_length_full; | ||
355 | |||
356 | // Current offset in the frame. If this is the first packet of a multipart | ||
357 | // frame or it's not a multipart frame, then this value is 0. | ||
358 | const uint32_t offset = header->offset_full; // without header | ||
359 | |||
360 | // The sender tells us whether this is a key frame. | ||
361 | const bool is_keyframe = (header->flags & RTP_KEY_FRAME) != 0; | ||
362 | |||
363 | LOGGER_DEBUG(log, "-- handle_video_packet -- full lens=%u len=%u offset=%u is_keyframe=%s", | ||
364 | (unsigned)incoming_data_length, (unsigned)full_frame_length, (unsigned)offset, is_keyframe ? "K" : "."); | ||
365 | LOGGER_DEBUG(log, "wkbl->next_free_entry:003=%d", session->work_buffer_list->next_free_entry); | ||
366 | |||
367 | const bool is_multipart = full_frame_length != incoming_data_length; | ||
368 | |||
369 | /* The message was sent in single part */ | ||
370 | int8_t slot_id = get_slot(log, session->work_buffer_list, is_keyframe, header, is_multipart); | ||
371 | LOGGER_DEBUG(log, "slot num=%d", slot_id); | ||
372 | |||
373 | // get_slot told us to drop the packet, so we ignore it. | ||
374 | if (slot_id == GET_SLOT_RESULT_DROP_INCOMING) { | ||
375 | return -1; | ||
376 | } | ||
377 | |||
378 | // get_slot said there is no free slot. | ||
379 | if (slot_id == GET_SLOT_RESULT_DROP_OLDEST_SLOT) { | ||
380 | LOGGER_DEBUG(log, "there was no free slot, so we process the oldest frame"); | ||
381 | // We now own the frame. | ||
382 | struct RTPMessage *m_new = process_frame(log, session->work_buffer_list, 0); | ||
383 | |||
384 | // The process_frame function returns NULL if there is no slot 0, i.e. | ||
385 | // the work buffer list is completely empty. It can't be empty, because | ||
386 | // get_slot just told us it's full, so process_frame must return non-null. | ||
387 | assert(m_new != nullptr); | ||
388 | |||
389 | LOGGER_DEBUG(log, "-- handle_video_packet -- CALLBACK-001a b0=%d b1=%d", (int)m_new->data[0], (int)m_new->data[1]); | ||
390 | update_bwc_values(log, session, m_new); | ||
391 | // Pass ownership of m_new to the callback. | ||
392 | session->mcb(session->cs, m_new); | ||
393 | // Now we no longer own m_new. | ||
394 | m_new = nullptr; | ||
395 | |||
396 | // Now we must have a free slot, so we either get that slot, i.e. >= 0, | ||
397 | // or get told to drop the incoming packet if it's too old. | ||
398 | slot_id = get_slot(log, session->work_buffer_list, is_keyframe, header, /* is_multipart */false); | ||
399 | |||
400 | if (slot_id == GET_SLOT_RESULT_DROP_INCOMING) { | ||
401 | // The incoming frame is too old, so we drop it. | ||
402 | return -1; | ||
403 | } | ||
404 | } | ||
405 | |||
406 | // We must have a valid slot here. | ||
407 | assert(slot_id >= 0); | ||
408 | |||
409 | LOGGER_DEBUG(log, "fill_data_into_slot.1"); | ||
410 | |||
411 | // fill in this part into the slot buffer at the correct offset | ||
412 | if (!fill_data_into_slot( | ||
413 | log, | ||
414 | session->work_buffer_list, | ||
415 | slot_id, | ||
416 | is_keyframe, | ||
417 | header, | ||
418 | incoming_data, | ||
419 | incoming_data_length)) { | ||
420 | // Memory allocation failed. Return error. | ||
421 | return -1; | ||
422 | } | ||
423 | |||
424 | struct RTPMessage *m_new = process_frame(log, session->work_buffer_list, slot_id); | ||
425 | |||
426 | if (m_new) { | ||
427 | LOGGER_DEBUG(log, "-- handle_video_packet -- CALLBACK-003a b0=%d b1=%d", (int)m_new->data[0], (int)m_new->data[1]); | ||
428 | update_bwc_values(log, session, m_new); | ||
429 | session->mcb(session->cs, m_new); | ||
430 | |||
431 | m_new = nullptr; | ||
432 | } | ||
433 | |||
434 | return 0; | ||
435 | } | ||
436 | |||
437 | /** | ||
438 | * @return -1 on error, 0 on success. | ||
439 | */ | ||
440 | static int handle_rtp_packet(Messenger *m, uint32_t friendnumber, const uint8_t *data, uint16_t length, void *object) | ||
441 | { | ||
442 | RTPSession *session = (RTPSession *)object; | ||
443 | |||
444 | if (!session || length < RTP_HEADER_SIZE + 1) { | ||
445 | LOGGER_WARNING(m->log, "No session or invalid length of received buffer!"); | ||
446 | return -1; | ||
447 | } | ||
448 | |||
449 | // Get the packet type. | ||
450 | const uint8_t packet_type = data[0]; | ||
451 | ++data; | ||
452 | --length; | ||
453 | |||
454 | // Unpack the header. | ||
455 | struct RTPHeader header; | ||
456 | rtp_header_unpack(data, &header); | ||
457 | |||
458 | if (header.pt != packet_type % 128) { | ||
459 | LOGGER_WARNING(m->log, "RTPHeader packet type and Tox protocol packet type did not agree: %d != %d", | ||
460 | header.pt, packet_type % 128); | ||
461 | return -1; | ||
462 | } | ||
463 | |||
464 | if (header.pt != session->payload_type % 128) { | ||
465 | LOGGER_WARNING(m->log, "RTPHeader packet type does not match this session's payload type: %d != %d", | ||
466 | header.pt, session->payload_type % 128); | ||
467 | return -1; | ||
468 | } | ||
469 | |||
470 | if (header.offset_full >= header.data_length_full) { | ||
471 | LOGGER_ERROR(m->log, "Invalid video packet: frame offset (%u) >= full frame length (%u)", | ||
472 | (unsigned)header.offset_full, (unsigned)header.data_length_full); | ||
473 | return -1; | ||
474 | } | ||
475 | |||
476 | if (header.offset_lower >= header.data_length_lower) { | ||
477 | LOGGER_ERROR(m->log, "Invalid old protocol video packet: frame offset (%u) >= full frame length (%u)", | ||
478 | (unsigned)header.offset_lower, (unsigned)header.data_length_lower); | ||
479 | return -1; | ||
480 | } | ||
481 | |||
482 | LOGGER_DEBUG(m->log, "header.pt %d, video %d", (uint8_t)header.pt, (rtp_TypeVideo % 128)); | ||
483 | |||
484 | // The sender uses the new large-frame capable protocol and is sending a | ||
485 | // video packet. | ||
486 | if ((header.flags & RTP_LARGE_FRAME) && header.pt == (rtp_TypeVideo % 128)) { | ||
487 | return handle_video_packet(session, &header, data + RTP_HEADER_SIZE, length - RTP_HEADER_SIZE, m->log); | ||
488 | } | ||
489 | |||
490 | // everything below here is for the old 16 bit protocol ------------------ | ||
491 | |||
492 | if (header.data_length_lower == length - RTP_HEADER_SIZE) { | ||
493 | /* The message is sent in single part */ | ||
494 | |||
495 | /* Message is not late; pick up the latest parameters */ | ||
496 | session->rsequnum = header.sequnum; | ||
497 | session->rtimestamp = header.timestamp; | ||
498 | bwc_add_recv(session->bwc, length); | ||
499 | |||
500 | /* Invoke processing of active multiparted message */ | ||
501 | if (session->mp) { | ||
502 | session->mcb(session->cs, session->mp); | ||
503 | session->mp = nullptr; | ||
504 | } | ||
505 | |||
506 | /* The message came in the allowed time; | ||
507 | */ | ||
508 | |||
509 | return session->mcb(session->cs, new_message(&header, length - RTP_HEADER_SIZE, data + RTP_HEADER_SIZE, | ||
510 | length - RTP_HEADER_SIZE)); | ||
511 | } | ||
512 | |||
513 | /* The message is sent in multiple parts */ | ||
514 | |||
515 | if (session->mp) { | ||
516 | /* There are 2 possible situations in this case: | ||
517 | * 1) being that we got the part of already processing message. | ||
518 | * 2) being that we got the part of a new/old message. | ||
519 | * | ||
520 | * We handle them differently as we only allow a single multiparted | ||
521 | * processing message | ||
522 | */ | ||
523 | if (session->mp->header.sequnum == header.sequnum && | ||
524 | session->mp->header.timestamp == header.timestamp) { | ||
525 | /* First case */ | ||
526 | |||
527 | /* Make sure we have enough allocated memory */ | ||
528 | if (session->mp->header.data_length_lower - session->mp->len < length - RTP_HEADER_SIZE || | ||
529 | session->mp->header.data_length_lower <= header.offset_lower) { | ||
530 | /* There happened to be some corruption on the stream; | ||
531 | * continue wihtout this part | ||
532 | */ | ||
533 | return 0; | ||
534 | } | ||
535 | |||
536 | memcpy(session->mp->data + header.offset_lower, data + RTP_HEADER_SIZE, | ||
537 | length - RTP_HEADER_SIZE); | ||
538 | session->mp->len += length - RTP_HEADER_SIZE; | ||
539 | bwc_add_recv(session->bwc, length); | ||
540 | |||
541 | if (session->mp->len == session->mp->header.data_length_lower) { | ||
542 | /* Received a full message; now push it for the further | ||
543 | * processing. | ||
544 | */ | ||
545 | session->mcb(session->cs, session->mp); | ||
546 | session->mp = nullptr; | ||
547 | } | ||
548 | } else { | ||
549 | /* Second case */ | ||
550 | if (session->mp->header.timestamp > header.timestamp) { | ||
551 | /* The received message part is from the old message; | ||
552 | * discard it. | ||
553 | */ | ||
554 | return 0; | ||
555 | } | ||
556 | |||
557 | /* Push the previous message for processing */ | ||
558 | session->mcb(session->cs, session->mp); | ||
559 | |||
560 | session->mp = nullptr; | ||
561 | goto NEW_MULTIPARTED; | ||
562 | } | ||
563 | } else { | ||
564 | /* In this case threat the message as if it was received in order | ||
565 | */ | ||
566 | /* This is also a point for new multiparted messages */ | ||
567 | NEW_MULTIPARTED: | ||
568 | |||
569 | /* Message is not late; pick up the latest parameters */ | ||
570 | session->rsequnum = header.sequnum; | ||
571 | session->rtimestamp = header.timestamp; | ||
572 | bwc_add_recv(session->bwc, length); | ||
573 | |||
574 | /* Store message. | ||
575 | */ | ||
576 | session->mp = new_message(&header, header.data_length_lower, data + RTP_HEADER_SIZE, length - RTP_HEADER_SIZE); | ||
577 | memmove(session->mp->data + header.offset_lower, session->mp->data, session->mp->len); | ||
578 | } | ||
579 | |||
580 | return 0; | ||
581 | } | ||
36 | 582 | ||
37 | size_t rtp_header_pack(uint8_t *const rdata, const struct RTPHeader *header) | 583 | size_t rtp_header_pack(uint8_t *const rdata, const struct RTPHeader *header) |
38 | { | 584 | { |
39 | uint8_t *p = rdata; | 585 | uint8_t *p = rdata; |
40 | *p++ = (header->protocol_version & 3) << 6 | 586 | *p++ = (header->ve & 3) << 6 |
41 | | (header->pe & 1) << 5 | 587 | | (header->pe & 1) << 5 |
42 | | (header->xe & 1) << 4 | 588 | | (header->xe & 1) << 4 |
43 | | (header->cc & 0xf); | 589 | | (header->cc & 0xf); |
@@ -62,11 +608,10 @@ size_t rtp_header_pack(uint8_t *const rdata, const struct RTPHeader *header) | |||
62 | return p - rdata; | 608 | return p - rdata; |
63 | } | 609 | } |
64 | 610 | ||
65 | |||
66 | size_t rtp_header_unpack(const uint8_t *data, struct RTPHeader *header) | 611 | size_t rtp_header_unpack(const uint8_t *data, struct RTPHeader *header) |
67 | { | 612 | { |
68 | const uint8_t *p = data; | 613 | const uint8_t *p = data; |
69 | header->protocol_version = (*p >> 6) & 3; | 614 | header->ve = (*p >> 6) & 3; |
70 | header->pe = (*p >> 5) & 1; | 615 | header->pe = (*p >> 5) & 1; |
71 | header->xe = (*p >> 4) & 1; | 616 | header->xe = (*p >> 4) & 1; |
72 | header->cc = *p & 0xf; | 617 | header->cc = *p & 0xf; |
@@ -93,44 +638,56 @@ size_t rtp_header_unpack(const uint8_t *data, struct RTPHeader *header) | |||
93 | } | 638 | } |
94 | 639 | ||
95 | 640 | ||
96 | int handle_rtp_packet(Messenger *m, uint32_t friendnumber, const uint8_t *data, uint16_t length, void *object); | ||
97 | |||
98 | |||
99 | RTPSession *rtp_new(int payload_type, Messenger *m, uint32_t friendnumber, | 641 | RTPSession *rtp_new(int payload_type, Messenger *m, uint32_t friendnumber, |
100 | BWController *bwc, void *cs, | 642 | BWController *bwc, void *cs, |
101 | int (*mcb)(void *, struct RTPMessage *)) | 643 | int (*mcb)(void *, struct RTPMessage *)) |
102 | { | 644 | { |
103 | assert(mcb); | 645 | assert(mcb != nullptr); |
104 | assert(cs); | 646 | assert(cs != nullptr); |
105 | assert(m); | 647 | assert(m != nullptr); |
106 | 648 | ||
107 | RTPSession *retu = (RTPSession *)calloc(1, sizeof(RTPSession)); | 649 | RTPSession *session = (RTPSession *)calloc(1, sizeof(RTPSession)); |
108 | 650 | ||
109 | if (!retu) { | 651 | if (!session) { |
110 | LOGGER_WARNING(m->log, "Alloc failed! Program might misbehave!"); | 652 | LOGGER_WARNING(m->log, "Alloc failed! Program might misbehave!"); |
111 | return nullptr; | 653 | return nullptr; |
112 | } | 654 | } |
113 | 655 | ||
114 | retu->ssrc = random_u32(); | 656 | session->work_buffer_list = (struct RTPWorkBufferList *)calloc(1, sizeof(struct RTPWorkBufferList)); |
115 | retu->payload_type = payload_type; | ||
116 | 657 | ||
117 | retu->m = m; | 658 | if (session->work_buffer_list == nullptr) { |
118 | retu->friend_number = friendnumber; | 659 | LOGGER_ERROR(m->log, "out of memory while allocating work buffer list"); |
660 | free(session); | ||
661 | return nullptr; | ||
662 | } | ||
119 | 663 | ||
120 | /* Also set payload type as prefix */ | 664 | // First entry is free. |
665 | session->work_buffer_list->next_free_entry = 0; | ||
666 | |||
667 | session->ssrc = payload_type == rtp_TypeVideo ? 0 : random_u32(); | ||
668 | session->payload_type = payload_type; | ||
669 | session->m = m; | ||
670 | session->friend_number = friendnumber; | ||
121 | 671 | ||
122 | retu->bwc = bwc; | 672 | // set NULL just in case |
123 | retu->cs = cs; | 673 | session->mp = nullptr; |
124 | retu->mcb = mcb; | 674 | session->first_packets_counter = 1; |
125 | 675 | ||
126 | if (-1 == rtp_allow_receiving(retu)) { | 676 | /* Also set payload type as prefix */ |
677 | session->bwc = bwc; | ||
678 | session->cs = cs; | ||
679 | session->mcb = mcb; | ||
680 | |||
681 | if (-1 == rtp_allow_receiving(session)) { | ||
127 | LOGGER_WARNING(m->log, "Failed to start rtp receiving mode"); | 682 | LOGGER_WARNING(m->log, "Failed to start rtp receiving mode"); |
128 | free(retu); | 683 | free(session->work_buffer_list); |
684 | free(session); | ||
129 | return nullptr; | 685 | return nullptr; |
130 | } | 686 | } |
131 | 687 | ||
132 | return retu; | 688 | return session; |
133 | } | 689 | } |
690 | |||
134 | void rtp_kill(RTPSession *session) | 691 | void rtp_kill(RTPSession *session) |
135 | { | 692 | { |
136 | if (!session) { | 693 | if (!session) { |
@@ -138,10 +695,15 @@ void rtp_kill(RTPSession *session) | |||
138 | } | 695 | } |
139 | 696 | ||
140 | LOGGER_DEBUG(session->m->log, "Terminated RTP session: %p", session); | 697 | LOGGER_DEBUG(session->m->log, "Terminated RTP session: %p", session); |
141 | |||
142 | rtp_stop_receiving(session); | 698 | rtp_stop_receiving(session); |
699 | |||
700 | LOGGER_DEBUG(session->m->log, "Terminated RTP session V3 work_buffer_list->next_free_entry: %d", | ||
701 | (int)session->work_buffer_list->next_free_entry); | ||
702 | |||
703 | free(session->work_buffer_list); | ||
143 | free(session); | 704 | free(session); |
144 | } | 705 | } |
706 | |||
145 | int rtp_allow_receiving(RTPSession *session) | 707 | int rtp_allow_receiving(RTPSession *session) |
146 | { | 708 | { |
147 | if (session == nullptr) { | 709 | if (session == nullptr) { |
@@ -157,6 +719,7 @@ int rtp_allow_receiving(RTPSession *session) | |||
157 | LOGGER_DEBUG(session->m->log, "Started receiving on session: %p", session); | 719 | LOGGER_DEBUG(session->m->log, "Started receiving on session: %p", session); |
158 | return 0; | 720 | return 0; |
159 | } | 721 | } |
722 | |||
160 | int rtp_stop_receiving(RTPSession *session) | 723 | int rtp_stop_receiving(RTPSession *session) |
161 | { | 724 | { |
162 | if (session == nullptr) { | 725 | if (session == nullptr) { |
@@ -168,42 +731,76 @@ int rtp_stop_receiving(RTPSession *session) | |||
168 | LOGGER_DEBUG(session->m->log, "Stopped receiving on session: %p", session); | 731 | LOGGER_DEBUG(session->m->log, "Stopped receiving on session: %p", session); |
169 | return 0; | 732 | return 0; |
170 | } | 733 | } |
171 | int rtp_send_data(RTPSession *session, const uint8_t *data, uint16_t length, Logger *log) | 734 | |
735 | /** | ||
736 | * @param input is raw vpx data. | ||
737 | * @param length is the length of the raw data. | ||
738 | */ | ||
739 | int rtp_send_data(RTPSession *session, const uint8_t *data, uint32_t length, | ||
740 | bool is_keyframe, Logger *log) | ||
172 | { | 741 | { |
173 | if (!session) { | 742 | if (!session) { |
174 | LOGGER_ERROR(log, "No session!"); | 743 | LOGGER_ERROR(log, "No session!"); |
175 | return -1; | 744 | return -1; |
176 | } | 745 | } |
177 | 746 | ||
178 | VLA(uint8_t, rdata, length + RTP_HEADER_SIZE + 1); | 747 | uint8_t is_video_payload = 0; |
179 | memset(rdata, 0, SIZEOF_VLA(rdata)); | ||
180 | 748 | ||
181 | rdata[0] = session->payload_type; | 749 | if (session->payload_type == rtp_TypeVideo) { |
750 | is_video_payload = 1; | ||
751 | } | ||
182 | 752 | ||
183 | struct RTPHeader header = {0}; | 753 | struct RTPHeader header = {0}; |
184 | 754 | ||
185 | header.protocol_version = 2; | 755 | header.ve = 2; // this is unused in toxav |
756 | |||
186 | header.pe = 0; | 757 | header.pe = 0; |
758 | |||
187 | header.xe = 0; | 759 | header.xe = 0; |
760 | |||
188 | header.cc = 0; | 761 | header.cc = 0; |
189 | 762 | ||
190 | header.ma = 0; | 763 | header.ma = 0; |
764 | |||
191 | header.pt = session->payload_type % 128; | 765 | header.pt = session->payload_type % 128; |
192 | 766 | ||
193 | header.sequnum = session->sequnum; | 767 | header.sequnum = session->sequnum; |
768 | |||
194 | header.timestamp = current_time_monotonic(); | 769 | header.timestamp = current_time_monotonic(); |
770 | |||
195 | header.ssrc = session->ssrc; | 771 | header.ssrc = session->ssrc; |
196 | 772 | ||
197 | header.offset_lower = 0; | 773 | header.offset_lower = 0; |
774 | |||
775 | // here the highest bits gets stripped anyway, no need to do keyframe bit magic here! | ||
198 | header.data_length_lower = length; | 776 | header.data_length_lower = length; |
199 | 777 | ||
200 | if (MAX_CRYPTO_DATA_SIZE > length + RTP_HEADER_SIZE + 1) { | 778 | header.flags = RTP_LARGE_FRAME; |
201 | 779 | ||
780 | uint16_t length_safe = (uint16_t)length; | ||
781 | |||
782 | if (length > UINT16_MAX) { | ||
783 | length_safe = UINT16_MAX; | ||
784 | } | ||
785 | |||
786 | header.data_length_lower = length_safe; | ||
787 | header.data_length_full = length; // without header | ||
788 | header.offset_lower = 0; | ||
789 | header.offset_full = 0; | ||
790 | |||
791 | if (is_keyframe) { | ||
792 | header.flags |= RTP_KEY_FRAME; | ||
793 | } | ||
794 | |||
795 | VLA(uint8_t, rdata, length + RTP_HEADER_SIZE + 1); | ||
796 | memset(rdata, 0, SIZEOF_VLA(rdata)); | ||
797 | rdata[0] = session->payload_type; // packet id == payload_type | ||
798 | |||
799 | if (MAX_CRYPTO_DATA_SIZE > (length + RTP_HEADER_SIZE + 1)) { | ||
202 | /** | 800 | /** |
203 | * The length is lesser than the maximum allowed length (including header) | 801 | * The length is lesser than the maximum allowed length (including header) |
204 | * Send the packet in single piece. | 802 | * Send the packet in single piece. |
205 | */ | 803 | */ |
206 | |||
207 | rtp_header_pack(rdata + 1, &header); | 804 | rtp_header_pack(rdata + 1, &header); |
208 | memcpy(rdata + 1 + RTP_HEADER_SIZE, data, length); | 805 | memcpy(rdata + 1 + RTP_HEADER_SIZE, data, length); |
209 | 806 | ||
@@ -211,13 +808,11 @@ int rtp_send_data(RTPSession *session, const uint8_t *data, uint16_t length, Log | |||
211 | LOGGER_WARNING(session->m->log, "RTP send failed (len: %d)! std error: %s", SIZEOF_VLA(rdata), strerror(errno)); | 808 | LOGGER_WARNING(session->m->log, "RTP send failed (len: %d)! std error: %s", SIZEOF_VLA(rdata), strerror(errno)); |
212 | } | 809 | } |
213 | } else { | 810 | } else { |
214 | |||
215 | /** | 811 | /** |
216 | * The length is greater than the maximum allowed length (including header) | 812 | * The length is greater than the maximum allowed length (including header) |
217 | * Send the packet in multiple pieces. | 813 | * Send the packet in multiple pieces. |
218 | */ | 814 | */ |
219 | 815 | uint32_t sent = 0; | |
220 | uint16_t sent = 0; | ||
221 | uint16_t piece = MAX_CRYPTO_DATA_SIZE - (RTP_HEADER_SIZE + 1); | 816 | uint16_t piece = MAX_CRYPTO_DATA_SIZE - (RTP_HEADER_SIZE + 1); |
222 | 817 | ||
223 | while ((length - sent) + RTP_HEADER_SIZE + 1 > MAX_CRYPTO_DATA_SIZE) { | 818 | while ((length - sent) + RTP_HEADER_SIZE + 1 > MAX_CRYPTO_DATA_SIZE) { |
@@ -232,6 +827,7 @@ int rtp_send_data(RTPSession *session, const uint8_t *data, uint16_t length, Log | |||
232 | 827 | ||
233 | sent += piece; | 828 | sent += piece; |
234 | header.offset_lower = sent; | 829 | header.offset_lower = sent; |
830 | header.offset_full = sent; // raw data offset, without any header | ||
235 | } | 831 | } |
236 | 832 | ||
237 | /* Send remaining */ | 833 | /* Send remaining */ |
@@ -252,215 +848,3 @@ int rtp_send_data(RTPSession *session, const uint8_t *data, uint16_t length, Log | |||
252 | session->sequnum ++; | 848 | session->sequnum ++; |
253 | return 0; | 849 | return 0; |
254 | } | 850 | } |
255 | |||
256 | |||
257 | static bool chloss(const RTPSession *session, const struct RTPHeader *header) | ||
258 | { | ||
259 | if (header->timestamp < session->rtimestamp) { | ||
260 | uint16_t hosq, lost = 0; | ||
261 | |||
262 | hosq = header->sequnum; | ||
263 | |||
264 | lost = (hosq > session->rsequnum) ? | ||
265 | (session->rsequnum + 65535) - hosq : | ||
266 | session->rsequnum - hosq; | ||
267 | |||
268 | fprintf(stderr, "Lost packet\n"); | ||
269 | |||
270 | while (lost --) { | ||
271 | bwc_add_lost(session->bwc, 0); | ||
272 | } | ||
273 | |||
274 | return true; | ||
275 | } | ||
276 | |||
277 | return false; | ||
278 | } | ||
279 | static struct RTPMessage *new_message(size_t allocate_len, const uint8_t *data, uint16_t data_length) | ||
280 | { | ||
281 | assert(allocate_len >= data_length); | ||
282 | |||
283 | struct RTPMessage *msg = (struct RTPMessage *)calloc(sizeof(struct RTPMessage) + | ||
284 | (allocate_len - RTP_HEADER_SIZE), 1); | ||
285 | |||
286 | if (msg == nullptr) { | ||
287 | return nullptr; | ||
288 | } | ||
289 | |||
290 | msg->len = data_length - RTP_HEADER_SIZE; | ||
291 | rtp_header_unpack(data, &msg->header); | ||
292 | memcpy(msg->data, data + RTP_HEADER_SIZE, allocate_len - RTP_HEADER_SIZE); | ||
293 | |||
294 | return msg; | ||
295 | } | ||
296 | |||
297 | int handle_rtp_packet(Messenger *m, uint32_t friendnumber, const uint8_t *data, uint16_t length, void *object) | ||
298 | { | ||
299 | (void) m; | ||
300 | (void) friendnumber; | ||
301 | |||
302 | RTPSession *session = (RTPSession *)object; | ||
303 | |||
304 | data ++; | ||
305 | length--; | ||
306 | |||
307 | if (!session || length < RTP_HEADER_SIZE) { | ||
308 | LOGGER_WARNING(m->log, "No session or invalid length of received buffer!"); | ||
309 | return -1; | ||
310 | } | ||
311 | |||
312 | struct RTPHeader header; | ||
313 | |||
314 | rtp_header_unpack(data, &header); | ||
315 | |||
316 | if (header.pt != session->payload_type % 128) { | ||
317 | LOGGER_WARNING(m->log, "Invalid payload type with the session"); | ||
318 | return -1; | ||
319 | } | ||
320 | |||
321 | if (header.offset_lower >= header.data_length_lower) { | ||
322 | /* Never allow this case to happen */ | ||
323 | return -1; | ||
324 | } | ||
325 | |||
326 | bwc_feed_avg(session->bwc, length); | ||
327 | |||
328 | if (header.data_length_lower == length - RTP_HEADER_SIZE) { | ||
329 | /* The message is sent in single part */ | ||
330 | |||
331 | /* Only allow messages which have arrived in order; | ||
332 | * drop late messages | ||
333 | */ | ||
334 | if (chloss(session, &header)) { | ||
335 | return 0; | ||
336 | } | ||
337 | |||
338 | /* Message is not late; pick up the latest parameters */ | ||
339 | session->rsequnum = header.sequnum; | ||
340 | session->rtimestamp = header.timestamp; | ||
341 | |||
342 | bwc_add_recv(session->bwc, length); | ||
343 | |||
344 | /* Invoke processing of active multiparted message */ | ||
345 | if (session->mp) { | ||
346 | if (session->mcb) { | ||
347 | session->mcb(session->cs, session->mp); | ||
348 | } else { | ||
349 | free(session->mp); | ||
350 | } | ||
351 | |||
352 | session->mp = nullptr; | ||
353 | } | ||
354 | |||
355 | /* The message came in the allowed time; | ||
356 | * process it only if handler for the session is present. | ||
357 | */ | ||
358 | |||
359 | if (!session->mcb) { | ||
360 | return 0; | ||
361 | } | ||
362 | |||
363 | return session->mcb(session->cs, new_message(length, data, length)); | ||
364 | } | ||
365 | |||
366 | /* The message is sent in multiple parts */ | ||
367 | |||
368 | if (session->mp) { | ||
369 | /* There are 2 possible situations in this case: | ||
370 | * 1) being that we got the part of already processing message. | ||
371 | * 2) being that we got the part of a new/old message. | ||
372 | * | ||
373 | * We handle them differently as we only allow a single multiparted | ||
374 | * processing message | ||
375 | */ | ||
376 | |||
377 | if (session->mp->header.sequnum == header.sequnum && | ||
378 | session->mp->header.timestamp == header.timestamp) { | ||
379 | /* First case */ | ||
380 | |||
381 | /* Make sure we have enough allocated memory */ | ||
382 | if (session->mp->header.data_length_lower - session->mp->len < length - RTP_HEADER_SIZE || | ||
383 | session->mp->header.data_length_lower <= header.offset_lower) { | ||
384 | /* There happened to be some corruption on the stream; | ||
385 | * continue wihtout this part | ||
386 | */ | ||
387 | return 0; | ||
388 | } | ||
389 | |||
390 | memcpy(session->mp->data + header.offset_lower, data + RTP_HEADER_SIZE, | ||
391 | length - RTP_HEADER_SIZE); | ||
392 | |||
393 | session->mp->len += length - RTP_HEADER_SIZE; | ||
394 | |||
395 | bwc_add_recv(session->bwc, length); | ||
396 | |||
397 | if (session->mp->len == session->mp->header.data_length_lower) { | ||
398 | /* Received a full message; now push it for the further | ||
399 | * processing. | ||
400 | */ | ||
401 | if (session->mcb) { | ||
402 | session->mcb(session->cs, session->mp); | ||
403 | } else { | ||
404 | free(session->mp); | ||
405 | } | ||
406 | |||
407 | session->mp = nullptr; | ||
408 | } | ||
409 | } else { | ||
410 | /* Second case */ | ||
411 | |||
412 | if (session->mp->header.timestamp > header.timestamp) { | ||
413 | /* The received message part is from the old message; | ||
414 | * discard it. | ||
415 | */ | ||
416 | return 0; | ||
417 | } | ||
418 | |||
419 | /* Measure missing parts of the old message */ | ||
420 | bwc_add_lost(session->bwc, | ||
421 | (session->mp->header.data_length_lower - session->mp->len) + | ||
422 | |||
423 | /* Must account sizes of rtp headers too */ | ||
424 | ((session->mp->header.data_length_lower - session->mp->len) / | ||
425 | MAX_CRYPTO_DATA_SIZE) * RTP_HEADER_SIZE); | ||
426 | |||
427 | /* Push the previous message for processing */ | ||
428 | if (session->mcb) { | ||
429 | session->mcb(session->cs, session->mp); | ||
430 | } else { | ||
431 | free(session->mp); | ||
432 | } | ||
433 | |||
434 | session->mp = nullptr; | ||
435 | goto NEW_MULTIPARTED; | ||
436 | } | ||
437 | } else { | ||
438 | /* In this case threat the message as if it was received in order | ||
439 | */ | ||
440 | |||
441 | /* This is also a point for new multiparted messages */ | ||
442 | NEW_MULTIPARTED: | ||
443 | |||
444 | /* Only allow messages which have arrived in order; | ||
445 | * drop late messages | ||
446 | */ | ||
447 | if (chloss(session, &header)) { | ||
448 | return 0; | ||
449 | } | ||
450 | |||
451 | /* Message is not late; pick up the latest parameters */ | ||
452 | session->rsequnum = header.sequnum; | ||
453 | session->rtimestamp = header.timestamp; | ||
454 | |||
455 | bwc_add_recv(session->bwc, length); | ||
456 | |||
457 | /* Again, only store message if handler is present | ||
458 | */ | ||
459 | if (session->mcb) { | ||
460 | session->mp = new_message(header.data_length_lower + RTP_HEADER_SIZE, data, length); | ||
461 | memmove(session->mp->data + header.offset_lower, session->mp->data, session->mp->len); | ||
462 | } | ||
463 | } | ||
464 | |||
465 | return 0; | ||
466 | } | ||
diff --git a/toxav/rtp.h b/toxav/rtp.h index c8af08d7..a310d58a 100644 --- a/toxav/rtp.h +++ b/toxav/rtp.h | |||
@@ -66,9 +66,10 @@ enum RTPFlags { | |||
66 | RTP_KEY_FRAME = 1 << 1, | 66 | RTP_KEY_FRAME = 1 << 1, |
67 | }; | 67 | }; |
68 | 68 | ||
69 | |||
69 | struct RTPHeader { | 70 | struct RTPHeader { |
70 | /* Standard RTP header */ | 71 | /* Standard RTP header */ |
71 | unsigned protocol_version: 2; /* Version has only 2 bits! */ | 72 | unsigned ve: 2; /* Version has only 2 bits! */ |
72 | unsigned pe: 1; /* Padding */ | 73 | unsigned pe: 1; /* Padding */ |
73 | unsigned xe: 1; /* Extra header */ | 74 | unsigned xe: 1; /* Extra header */ |
74 | unsigned cc: 4; /* Contributing sources count */ | 75 | unsigned cc: 4; /* Contributing sources count */ |
@@ -113,33 +114,71 @@ struct RTPHeader { | |||
113 | uint16_t data_length_lower; | 114 | uint16_t data_length_lower; |
114 | }; | 115 | }; |
115 | 116 | ||
117 | |||
116 | struct RTPMessage { | 118 | struct RTPMessage { |
119 | /** | ||
120 | * This is used in the old code that doesn't deal with large frames, i.e. | ||
121 | * the audio code or receiving code for old 16 bit messages. We use it to | ||
122 | * record the number of bytes received so far in a multi-part message. The | ||
123 | * multi-part message in the old code is stored in \ref RTPSession::mp. | ||
124 | */ | ||
117 | uint16_t len; | 125 | uint16_t len; |
118 | 126 | ||
119 | struct RTPHeader header; | 127 | struct RTPHeader header; |
120 | uint8_t data[]; | 128 | uint8_t data[]; |
121 | }; | 129 | }; |
122 | 130 | ||
131 | #define USED_RTP_WORKBUFFER_COUNT 3 | ||
132 | |||
133 | /** | ||
134 | * One slot in the work buffer list. Represents one frame that is currently | ||
135 | * being assembled. | ||
136 | */ | ||
137 | struct RTPWorkBuffer { | ||
138 | /** | ||
139 | * Whether this slot contains a key frame. This is true iff | ||
140 | * buf->header.flags & RTP_KEY_FRAME. | ||
141 | */ | ||
142 | bool is_keyframe; | ||
143 | /** | ||
144 | * The number of bytes received so far, regardless of which pieces. I.e. we | ||
145 | * could have received the first 1000 bytes and the last 1000 bytes with | ||
146 | * 4000 bytes in the middle still to come, and this number would be 2000. | ||
147 | */ | ||
148 | uint32_t received_len; | ||
149 | /** | ||
150 | * The message currently being assembled. | ||
151 | */ | ||
152 | struct RTPMessage *buf; | ||
153 | }; | ||
154 | |||
155 | struct RTPWorkBufferList { | ||
156 | int8_t next_free_entry; | ||
157 | struct RTPWorkBuffer work_buffer[USED_RTP_WORKBUFFER_COUNT]; | ||
158 | }; | ||
159 | |||
160 | #define DISMISS_FIRST_LOST_VIDEO_PACKET_COUNT 10 | ||
161 | |||
123 | /** | 162 | /** |
124 | * RTP control session. | 163 | * RTP control session. |
125 | */ | 164 | */ |
126 | typedef struct { | 165 | typedef struct RTPSession { |
127 | uint8_t payload_type; | 166 | uint8_t payload_type; |
128 | uint16_t sequnum; /* Sending sequence number */ | 167 | uint16_t sequnum; /* Sending sequence number */ |
129 | uint16_t rsequnum; /* Receiving sequence number */ | 168 | uint16_t rsequnum; /* Receiving sequence number */ |
130 | uint32_t rtimestamp; | 169 | uint32_t rtimestamp; |
131 | uint32_t ssrc; | 170 | uint32_t ssrc; // this seems to be unused!? |
132 | |||
133 | struct RTPMessage *mp; /* Expected parted message */ | 171 | struct RTPMessage *mp; /* Expected parted message */ |
134 | 172 | struct RTPWorkBufferList *work_buffer_list; | |
173 | uint8_t first_packets_counter; /* dismiss first few lost video packets */ | ||
135 | Messenger *m; | 174 | Messenger *m; |
136 | uint32_t friend_number; | 175 | uint32_t friend_number; |
137 | |||
138 | BWController *bwc; | 176 | BWController *bwc; |
139 | void *cs; | 177 | void *cs; |
140 | int (*mcb)(void *, struct RTPMessage *msg); | 178 | int (*mcb)(void *, struct RTPMessage *msg); |
141 | } RTPSession; | 179 | } RTPSession; |
142 | 180 | ||
181 | |||
143 | /** | 182 | /** |
144 | * Serialise an RTPHeader to bytes to be sent over the network. | 183 | * Serialise an RTPHeader to bytes to be sent over the network. |
145 | * | 184 | * |
@@ -164,7 +203,17 @@ RTPSession *rtp_new(int payload_type, Messenger *m, uint32_t friendnumber, | |||
164 | void rtp_kill(RTPSession *session); | 203 | void rtp_kill(RTPSession *session); |
165 | int rtp_allow_receiving(RTPSession *session); | 204 | int rtp_allow_receiving(RTPSession *session); |
166 | int rtp_stop_receiving(RTPSession *session); | 205 | int rtp_stop_receiving(RTPSession *session); |
167 | int rtp_send_data(RTPSession *session, const uint8_t *data, uint16_t length, Logger *log); | 206 | /** |
207 | * Send a frame of audio or video data, chunked in \ref RTPMessage instances. | ||
208 | * | ||
209 | * @param session The A/V session to send the data for. | ||
210 | * @param data A byte array of length \p length. | ||
211 | * @param length The number of bytes to send from @p data. | ||
212 | * @param is_keyframe Whether this video frame is a key frame. If it is an | ||
213 | * audio frame, this parameter is ignored. | ||
214 | */ | ||
215 | int rtp_send_data(RTPSession *session, const uint8_t *data, uint32_t length, | ||
216 | bool is_keyframe, Logger *log); | ||
168 | 217 | ||
169 | #ifdef __cplusplus | 218 | #ifdef __cplusplus |
170 | } // extern "C" | 219 | } // extern "C" |
diff --git a/toxav/rtp_test.cpp b/toxav/rtp_test.cpp index 60534b07..d6717a28 100644 --- a/toxav/rtp_test.cpp +++ b/toxav/rtp_test.cpp | |||
@@ -38,7 +38,7 @@ TEST(Rtp, Deserialisation) | |||
38 | RTPHeader unpacked = {0}; | 38 | RTPHeader unpacked = {0}; |
39 | EXPECT_EQ(rtp_header_unpack(rdata, &unpacked), RTP_HEADER_SIZE); | 39 | EXPECT_EQ(rtp_header_unpack(rdata, &unpacked), RTP_HEADER_SIZE); |
40 | 40 | ||
41 | EXPECT_EQ(header.protocol_version, unpacked.protocol_version); | 41 | EXPECT_EQ(header.ve, unpacked.ve); |
42 | EXPECT_EQ(header.pe, unpacked.pe); | 42 | EXPECT_EQ(header.pe, unpacked.pe); |
43 | EXPECT_EQ(header.xe, unpacked.xe); | 43 | EXPECT_EQ(header.xe, unpacked.xe); |
44 | EXPECT_EQ(header.cc, unpacked.cc); | 44 | EXPECT_EQ(header.cc, unpacked.cc); |
diff --git a/toxav/toxav.c b/toxav/toxav.c index c48fc192..4e45b747 100644 --- a/toxav/toxav.c +++ b/toxav/toxav.c | |||
@@ -36,7 +36,18 @@ | |||
36 | #include <stdlib.h> | 36 | #include <stdlib.h> |
37 | #include <string.h> | 37 | #include <string.h> |
38 | 38 | ||
39 | #define MAX_ENCODE_TIME_US ((1000 / 24) * 1000) | 39 | // TODO: don't hardcode this, let the application choose it |
40 | // VPX Info: Time to spend encoding, in microseconds (it's a *soft* deadline) | ||
41 | #define WANTED_MAX_ENCODER_FPS (40) | ||
42 | #define MAX_ENCODE_TIME_US (1000000 / WANTED_MAX_ENCODER_FPS) // to allow x fps | ||
43 | |||
44 | #define VIDEO_SEND_X_KEYFRAMES_FIRST 7 // force the first n frames to be keyframes! | ||
45 | |||
46 | /* | ||
47 | VPX_DL_REALTIME (1) deadline parameter analogous to VPx REALTIME mode. | ||
48 | VPX_DL_GOOD_QUALITY (1000000) deadline parameter analogous to VPx GOOD QUALITY mode. | ||
49 | VPX_DL_BEST_QUALITY (0) deadline parameter analogous to VPx BEST QUALITY mode. | ||
50 | */ | ||
40 | 51 | ||
41 | typedef struct ToxAVCall_s { | 52 | typedef struct ToxAVCall_s { |
42 | ToxAV *av; | 53 | ToxAV *av; |
@@ -752,13 +763,12 @@ bool toxav_audio_send_frame(ToxAV *av, uint32_t friend_number, const int16_t *pc | |||
752 | goto END; | 763 | goto END; |
753 | } | 764 | } |
754 | 765 | ||
755 | if (rtp_send_data(call->audio.first, dest, vrc + sizeof(sampling_rate), av->m->log) != 0) { | 766 | if (rtp_send_data(call->audio.first, dest, vrc + sizeof(sampling_rate), false, av->m->log) != 0) { |
756 | LOGGER_WARNING(av->m->log, "Failed to send audio packet"); | 767 | LOGGER_WARNING(av->m->log, "Failed to send audio packet"); |
757 | rc = TOXAV_ERR_SEND_FRAME_RTP_FAILED; | 768 | rc = TOXAV_ERR_SEND_FRAME_RTP_FAILED; |
758 | } | 769 | } |
759 | } | 770 | } |
760 | 771 | ||
761 | |||
762 | pthread_mutex_unlock(call->mutex_audio); | 772 | pthread_mutex_unlock(call->mutex_audio); |
763 | 773 | ||
764 | END: | 774 | END: |
@@ -769,12 +779,15 @@ END: | |||
769 | 779 | ||
770 | return rc == TOXAV_ERR_SEND_FRAME_OK; | 780 | return rc == TOXAV_ERR_SEND_FRAME_OK; |
771 | } | 781 | } |
782 | |||
772 | bool toxav_video_send_frame(ToxAV *av, uint32_t friend_number, uint16_t width, uint16_t height, const uint8_t *y, | 783 | bool toxav_video_send_frame(ToxAV *av, uint32_t friend_number, uint16_t width, uint16_t height, const uint8_t *y, |
773 | const uint8_t *u, const uint8_t *v, TOXAV_ERR_SEND_FRAME *error) | 784 | const uint8_t *u, const uint8_t *v, TOXAV_ERR_SEND_FRAME *error) |
774 | { | 785 | { |
775 | TOXAV_ERR_SEND_FRAME rc = TOXAV_ERR_SEND_FRAME_OK; | 786 | TOXAV_ERR_SEND_FRAME rc = TOXAV_ERR_SEND_FRAME_OK; |
776 | ToxAVCall *call; | 787 | ToxAVCall *call; |
777 | 788 | ||
789 | int vpx_encode_flags = 0; | ||
790 | |||
778 | if (m_friend_exists(av->m, friend_number) == 0) { | 791 | if (m_friend_exists(av->m, friend_number) == 0) { |
779 | rc = TOXAV_ERR_SEND_FRAME_FRIEND_NOT_FOUND; | 792 | rc = TOXAV_ERR_SEND_FRAME_FRIEND_NOT_FOUND; |
780 | goto END; | 793 | goto END; |
@@ -810,12 +823,28 @@ bool toxav_video_send_frame(ToxAV *av, uint32_t friend_number, uint16_t width, u | |||
810 | goto END; | 823 | goto END; |
811 | } | 824 | } |
812 | 825 | ||
813 | if (vc_reconfigure_encoder(call->video.second, call->video_bit_rate * 1000, width, height) != 0) { | 826 | if (vc_reconfigure_encoder(call->video.second, call->video_bit_rate * 1000, width, height, -1) != 0) { |
814 | pthread_mutex_unlock(call->mutex_video); | 827 | pthread_mutex_unlock(call->mutex_video); |
815 | rc = TOXAV_ERR_SEND_FRAME_INVALID; | 828 | rc = TOXAV_ERR_SEND_FRAME_INVALID; |
816 | goto END; | 829 | goto END; |
817 | } | 830 | } |
818 | 831 | ||
832 | if (call->video.first->ssrc < VIDEO_SEND_X_KEYFRAMES_FIRST) { | ||
833 | // Key frame flag for first frames | ||
834 | vpx_encode_flags = VPX_EFLAG_FORCE_KF; | ||
835 | LOGGER_INFO(av->m->log, "I_FRAME_FLAG:%d only-i-frame mode", call->video.first->ssrc); | ||
836 | |||
837 | call->video.first->ssrc++; | ||
838 | } else if (call->video.first->ssrc == VIDEO_SEND_X_KEYFRAMES_FIRST) { | ||
839 | // normal keyframe placement | ||
840 | vpx_encode_flags = 0; | ||
841 | LOGGER_INFO(av->m->log, "I_FRAME_FLAG:%d normal mode", call->video.first->ssrc); | ||
842 | |||
843 | call->video.first->ssrc++; | ||
844 | } | ||
845 | |||
846 | // we start with I-frames (full frames) and then switch to normal mode later | ||
847 | |||
819 | { /* Encode */ | 848 | { /* Encode */ |
820 | vpx_image_t img; | 849 | vpx_image_t img; |
821 | img.w = img.h = img.d_w = img.d_h = 0; | 850 | img.w = img.h = img.d_w = img.d_h = 0; |
@@ -829,7 +858,7 @@ bool toxav_video_send_frame(ToxAV *av, uint32_t friend_number, uint16_t width, u | |||
829 | memcpy(img.planes[VPX_PLANE_V], v, (width / 2) * (height / 2)); | 858 | memcpy(img.planes[VPX_PLANE_V], v, (width / 2) * (height / 2)); |
830 | 859 | ||
831 | vpx_codec_err_t vrc = vpx_codec_encode(call->video.second->encoder, &img, | 860 | vpx_codec_err_t vrc = vpx_codec_encode(call->video.second->encoder, &img, |
832 | call->video.second->frame_counter, 1, 0, MAX_ENCODE_TIME_US); | 861 | call->video.second->frame_counter, 1, vpx_encode_flags, MAX_ENCODE_TIME_US); |
833 | 862 | ||
834 | vpx_img_free(&img); | 863 | vpx_img_free(&img); |
835 | 864 | ||
@@ -847,22 +876,31 @@ bool toxav_video_send_frame(ToxAV *av, uint32_t friend_number, uint16_t width, u | |||
847 | vpx_codec_iter_t iter = nullptr; | 876 | vpx_codec_iter_t iter = nullptr; |
848 | const vpx_codec_cx_pkt_t *pkt; | 877 | const vpx_codec_cx_pkt_t *pkt; |
849 | 878 | ||
850 | while ((pkt = vpx_codec_get_cx_data(call->video.second->encoder, &iter))) { | 879 | while ((pkt = vpx_codec_get_cx_data(call->video.second->encoder, &iter)) != nullptr) { |
851 | if (pkt->kind == VPX_CODEC_CX_FRAME_PKT) { | 880 | if (pkt->kind == VPX_CODEC_CX_FRAME_PKT) { |
852 | const uint8_t *buf = (const uint8_t *)pkt->data.frame.buf; | 881 | const bool is_keyframe = (pkt->data.frame.flags & VPX_FRAME_IS_KEY) != 0; |
853 | const uint8_t *end = buf + pkt->data.frame.sz; | 882 | |
854 | 883 | // https://www.webmproject.org/docs/webm-sdk/structvpx__codec__cx__pkt.html | |
855 | while (buf < end) { | 884 | // pkt->data.frame.sz -> size_t |
856 | uint16_t size = MIN(UINT16_MAX, end - buf); | 885 | const uint32_t frame_length_in_bytes = pkt->data.frame.sz; |
857 | 886 | ||
858 | if (rtp_send_data(call->video.first, buf, size, av->m->log) < 0) { | 887 | const int res = rtp_send_data( |
859 | pthread_mutex_unlock(call->mutex_video); | 888 | call->video.first, |
860 | LOGGER_WARNING(av->m->log, "Could not send video frame: %s\n", strerror(errno)); | 889 | (const uint8_t *)pkt->data.frame.buf, |
861 | rc = TOXAV_ERR_SEND_FRAME_RTP_FAILED; | 890 | frame_length_in_bytes, |
862 | goto END; | 891 | is_keyframe, |
863 | } | 892 | av->m->log); |
864 | 893 | ||
865 | buf += size; | 894 | LOGGER_DEBUG(av->m->log, "+ _sending_FRAME_TYPE_==%s bytes=%d frame_len=%d", is_keyframe ? "K" : ".", |
895 | (int)pkt->data.frame.sz, (int)frame_length_in_bytes); | ||
896 | LOGGER_DEBUG(av->m->log, "+ _sending_FRAME_ b0=%d b1=%d", ((const uint8_t *)pkt->data.frame.buf)[0], | ||
897 | ((const uint8_t *)pkt->data.frame.buf)[1]); | ||
898 | |||
899 | if (res < 0) { | ||
900 | pthread_mutex_unlock(call->mutex_video); | ||
901 | LOGGER_WARNING(av->m->log, "Could not send video frame: %s", strerror(errno)); | ||
902 | rc = TOXAV_ERR_SEND_FRAME_RTP_FAILED; | ||
903 | goto END; | ||
866 | } | 904 | } |
867 | } | 905 | } |
868 | } | 906 | } |
@@ -878,6 +916,7 @@ END: | |||
878 | 916 | ||
879 | return rc == TOXAV_ERR_SEND_FRAME_OK; | 917 | return rc == TOXAV_ERR_SEND_FRAME_OK; |
880 | } | 918 | } |
919 | |||
881 | void toxav_callback_audio_receive_frame(ToxAV *av, toxav_audio_receive_frame_cb *callback, void *user_data) | 920 | void toxav_callback_audio_receive_frame(ToxAV *av, toxav_audio_receive_frame_cb *callback, void *user_data) |
882 | { | 921 | { |
883 | pthread_mutex_lock(av->mutex); | 922 | pthread_mutex_lock(av->mutex); |
@@ -885,6 +924,7 @@ void toxav_callback_audio_receive_frame(ToxAV *av, toxav_audio_receive_frame_cb | |||
885 | av->acb.second = user_data; | 924 | av->acb.second = user_data; |
886 | pthread_mutex_unlock(av->mutex); | 925 | pthread_mutex_unlock(av->mutex); |
887 | } | 926 | } |
927 | |||
888 | void toxav_callback_video_receive_frame(ToxAV *av, toxav_video_receive_frame_cb *callback, void *user_data) | 928 | void toxav_callback_video_receive_frame(ToxAV *av, toxav_video_receive_frame_cb *callback, void *user_data) |
889 | { | 929 | { |
890 | pthread_mutex_lock(av->mutex); | 930 | pthread_mutex_lock(av->mutex); |
@@ -893,7 +933,6 @@ void toxav_callback_video_receive_frame(ToxAV *av, toxav_video_receive_frame_cb | |||
893 | pthread_mutex_unlock(av->mutex); | 933 | pthread_mutex_unlock(av->mutex); |
894 | } | 934 | } |
895 | 935 | ||
896 | |||
897 | /******************************************************************************* | 936 | /******************************************************************************* |
898 | * | 937 | * |
899 | * :: Internal | 938 | * :: Internal |
@@ -913,7 +952,8 @@ void callback_bwc(BWController *bwc, uint32_t friend_number, float loss, void *u | |||
913 | 952 | ||
914 | LOGGER_DEBUG(call->av->m->log, "Reported loss of %f%%", loss * 100); | 953 | LOGGER_DEBUG(call->av->m->log, "Reported loss of %f%%", loss * 100); |
915 | 954 | ||
916 | if (loss < .01f) { | 955 | /* if less than 10% data loss we do nothing! */ |
956 | if (loss < 0.1f) { | ||
917 | return; | 957 | return; |
918 | } | 958 | } |
919 | 959 | ||
@@ -1079,6 +1119,7 @@ bool invoke_call_state_callback(ToxAV *av, uint32_t friend_number, uint32_t stat | |||
1079 | 1119 | ||
1080 | return true; | 1120 | return true; |
1081 | } | 1121 | } |
1122 | |||
1082 | ToxAVCall *call_new(ToxAV *av, uint32_t friend_number, TOXAV_ERR_CALL *error) | 1123 | ToxAVCall *call_new(ToxAV *av, uint32_t friend_number, TOXAV_ERR_CALL *error) |
1083 | { | 1124 | { |
1084 | /* Assumes mutex locked */ | 1125 | /* Assumes mutex locked */ |
@@ -1100,7 +1141,6 @@ ToxAVCall *call_new(ToxAV *av, uint32_t friend_number, TOXAV_ERR_CALL *error) | |||
1100 | goto END; | 1141 | goto END; |
1101 | } | 1142 | } |
1102 | 1143 | ||
1103 | |||
1104 | call = (ToxAVCall *)calloc(sizeof(ToxAVCall), 1); | 1144 | call = (ToxAVCall *)calloc(sizeof(ToxAVCall), 1); |
1105 | 1145 | ||
1106 | if (call == nullptr) { | 1146 | if (call == nullptr) { |
@@ -1161,6 +1201,7 @@ END: | |||
1161 | 1201 | ||
1162 | return call; | 1202 | return call; |
1163 | } | 1203 | } |
1204 | |||
1164 | ToxAVCall *call_get(ToxAV *av, uint32_t friend_number) | 1205 | ToxAVCall *call_get(ToxAV *av, uint32_t friend_number) |
1165 | { | 1206 | { |
1166 | /* Assumes mutex locked */ | 1207 | /* Assumes mutex locked */ |
@@ -1170,6 +1211,7 @@ ToxAVCall *call_get(ToxAV *av, uint32_t friend_number) | |||
1170 | 1211 | ||
1171 | return av->calls[friend_number]; | 1212 | return av->calls[friend_number]; |
1172 | } | 1213 | } |
1214 | |||
1173 | ToxAVCall *call_remove(ToxAVCall *call) | 1215 | ToxAVCall *call_remove(ToxAVCall *call) |
1174 | { | 1216 | { |
1175 | if (call == nullptr) { | 1217 | if (call == nullptr) { |
@@ -1217,6 +1259,7 @@ CLEAR: | |||
1217 | 1259 | ||
1218 | return nullptr; | 1260 | return nullptr; |
1219 | } | 1261 | } |
1262 | |||
1220 | bool call_prepare_transmission(ToxAVCall *call) | 1263 | bool call_prepare_transmission(ToxAVCall *call) |
1221 | { | 1264 | { |
1222 | /* Assumes mutex locked */ | 1265 | /* Assumes mutex locked */ |
@@ -1305,6 +1348,7 @@ FAILURE_3: | |||
1305 | pthread_mutex_destroy(call->mutex_audio); | 1348 | pthread_mutex_destroy(call->mutex_audio); |
1306 | return false; | 1349 | return false; |
1307 | } | 1350 | } |
1351 | |||
1308 | void call_kill_transmission(ToxAVCall *call) | 1352 | void call_kill_transmission(ToxAVCall *call) |
1309 | { | 1353 | { |
1310 | if (call == nullptr || call->active == 0) { | 1354 | if (call == nullptr || call->active == 0) { |
diff --git a/toxav/video.c b/toxav/video.c index eee542a2..0014dbb6 100644 --- a/toxav/video.c +++ b/toxav/video.c | |||
@@ -33,8 +33,124 @@ | |||
33 | #include <assert.h> | 33 | #include <assert.h> |
34 | #include <stdlib.h> | 34 | #include <stdlib.h> |
35 | 35 | ||
36 | #define MAX_DECODE_TIME_US 0 /* Good quality encode. */ | 36 | /** |
37 | #define VIDEO_DECODE_BUFFER_SIZE 20 | 37 | * Soft deadline the decoder should attempt to meet, in "us" (microseconds). |
38 | * Set to zero for unlimited. | ||
39 | * | ||
40 | * By convention, the value 1 is used to mean "return as fast as possible." | ||
41 | */ | ||
42 | // TODO: don't hardcode this, let the application choose it | ||
43 | #define WANTED_MAX_DECODER_FPS 40 | ||
44 | |||
45 | /** | ||
46 | * VPX_DL_REALTIME (1) | ||
47 | * deadline parameter analogous to VPx REALTIME mode. | ||
48 | * | ||
49 | * VPX_DL_GOOD_QUALITY (1000000) | ||
50 | * deadline parameter analogous to VPx GOOD QUALITY mode. | ||
51 | * | ||
52 | * VPX_DL_BEST_QUALITY (0) | ||
53 | * deadline parameter analogous to VPx BEST QUALITY mode. | ||
54 | */ | ||
55 | #define MAX_DECODE_TIME_US (1000000 / WANTED_MAX_DECODER_FPS) // to allow x fps | ||
56 | |||
57 | /** | ||
58 | * Codec control function to set encoder internal speed settings. Changes in | ||
59 | * this value influences, among others, the encoder's selection of motion | ||
60 | * estimation methods. Values greater than 0 will increase encoder speed at the | ||
61 | * expense of quality. | ||
62 | * | ||
63 | * Note Valid range for VP8: -16..16 | ||
64 | */ | ||
65 | #define VP8E_SET_CPUUSED_VALUE 16 | ||
66 | |||
67 | /** | ||
68 | * Initialize encoder with this value. Target bandwidth to use for this stream, in kilobits per second. | ||
69 | */ | ||
70 | #define VIDEO_BITRATE_INITIAL_VALUE 5000 | ||
71 | #define VIDEO_DECODE_BUFFER_SIZE 5 // this buffer has normally max. 1 entry | ||
72 | |||
73 | #define VIDEO_CODEC_DECODER_INTERFACE (vpx_codec_vp8_dx()) | ||
74 | #define VIDEO_CODEC_ENCODER_INTERFACE (vpx_codec_vp8_cx()) | ||
75 | |||
76 | #define VIDEO_CODEC_DECODER_MAX_WIDTH 800 // its a dummy value, because the struct needs a value there | ||
77 | #define VIDEO_CODEC_DECODER_MAX_HEIGHT 600 // its a dummy value, because the struct needs a value there | ||
78 | |||
79 | #define VPX_MAX_DIST_NORMAL 40 | ||
80 | #define VPX_MAX_DIST_START 40 | ||
81 | |||
82 | #define VPX_MAX_ENCODER_THREADS 4 | ||
83 | #define VPX_MAX_DECODER_THREADS 4 | ||
84 | #define VIDEO__VP8_DECODER_POST_PROCESSING_ENABLED 0 | ||
85 | |||
86 | void vc_init_encoder_cfg(Logger *log, vpx_codec_enc_cfg_t *cfg, int16_t kf_max_dist) | ||
87 | { | ||
88 | vpx_codec_err_t rc = vpx_codec_enc_config_default(VIDEO_CODEC_ENCODER_INTERFACE, cfg, 0); | ||
89 | |||
90 | if (rc != VPX_CODEC_OK) { | ||
91 | LOGGER_ERROR(log, "vc_init_encoder_cfg:Failed to get config: %s", vpx_codec_err_to_string(rc)); | ||
92 | } | ||
93 | |||
94 | /* Target bandwidth to use for this stream, in kilobits per second */ | ||
95 | cfg->rc_target_bitrate = VIDEO_BITRATE_INITIAL_VALUE; | ||
96 | cfg->g_w = VIDEO_CODEC_DECODER_MAX_WIDTH; | ||
97 | cfg->g_h = VIDEO_CODEC_DECODER_MAX_HEIGHT; | ||
98 | cfg->g_pass = VPX_RC_ONE_PASS; | ||
99 | cfg->g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT | VPX_ERROR_RESILIENT_PARTITIONS; | ||
100 | cfg->g_lag_in_frames = 0; | ||
101 | |||
102 | /* Allow lagged encoding | ||
103 | * | ||
104 | * If set, this value allows the encoder to consume a number of input | ||
105 | * frames before producing output frames. This allows the encoder to | ||
106 | * base decisions for the current frame on future frames. This does | ||
107 | * increase the latency of the encoding pipeline, so it is not appropriate | ||
108 | * in all situations (ex: realtime encoding). | ||
109 | * | ||
110 | * Note that this is a maximum value -- the encoder may produce frames | ||
111 | * sooner than the given limit. Set this value to 0 to disable this | ||
112 | * feature. | ||
113 | */ | ||
114 | cfg->kf_min_dist = 0; | ||
115 | cfg->kf_mode = VPX_KF_AUTO; // Encoder determines optimal placement automatically | ||
116 | cfg->rc_end_usage = VPX_VBR; // what quality mode? | ||
117 | |||
118 | /* | ||
119 | * VPX_VBR Variable Bit Rate (VBR) mode | ||
120 | * VPX_CBR Constant Bit Rate (CBR) mode | ||
121 | * VPX_CQ Constrained Quality (CQ) mode -> give codec a hint that we may be on low bandwidth connection | ||
122 | * VPX_Q Constant Quality (Q) mode | ||
123 | */ | ||
124 | if (kf_max_dist > 1) { | ||
125 | cfg->kf_max_dist = kf_max_dist; // a full frame every x frames minimum (can be more often, codec decides automatically) | ||
126 | LOGGER_DEBUG(log, "kf_max_dist=%d (1)", cfg->kf_max_dist); | ||
127 | } else { | ||
128 | cfg->kf_max_dist = VPX_MAX_DIST_START; | ||
129 | LOGGER_DEBUG(log, "kf_max_dist=%d (2)", cfg->kf_max_dist); | ||
130 | } | ||
131 | |||
132 | cfg->g_threads = VPX_MAX_ENCODER_THREADS; // Maximum number of threads to use | ||
133 | /* TODO: set these to something reasonable */ | ||
134 | // cfg->g_timebase.num = 1; | ||
135 | // cfg->g_timebase.den = 60; // 60 fps | ||
136 | cfg->rc_resize_allowed = 1; // allow encoder to resize to smaller resolution | ||
137 | cfg->rc_resize_up_thresh = 40; | ||
138 | cfg->rc_resize_down_thresh = 5; | ||
139 | |||
140 | /* TODO: make quality setting an API call, but start with normal quality */ | ||
141 | #if 0 | ||
142 | /* Highest-resolution encoder settings */ | ||
143 | cfg->rc_dropframe_thresh = 0; | ||
144 | cfg->rc_resize_allowed = 0; | ||
145 | cfg->rc_min_quantizer = 2; | ||
146 | cfg->rc_max_quantizer = 56; | ||
147 | cfg->rc_undershoot_pct = 100; | ||
148 | cfg->rc_overshoot_pct = 15; | ||
149 | cfg->rc_buf_initial_sz = 500; | ||
150 | cfg->rc_buf_optimal_sz = 600; | ||
151 | cfg->rc_buf_sz = 1000; | ||
152 | #endif | ||
153 | } | ||
38 | 154 | ||
39 | VCSession *vc_new(Logger *log, ToxAV *av, uint32_t friend_number, toxav_video_receive_frame_cb *cb, void *cb_data) | 155 | VCSession *vc_new(Logger *log, ToxAV *av, uint32_t friend_number, toxav_video_receive_frame_cb *cb, void *cb_data) |
40 | { | 156 | { |
@@ -52,49 +168,72 @@ VCSession *vc_new(Logger *log, ToxAV *av, uint32_t friend_number, toxav_video_re | |||
52 | return nullptr; | 168 | return nullptr; |
53 | } | 169 | } |
54 | 170 | ||
171 | int cpu_used_value = VP8E_SET_CPUUSED_VALUE; | ||
172 | |||
55 | if (!(vc->vbuf_raw = rb_new(VIDEO_DECODE_BUFFER_SIZE))) { | 173 | if (!(vc->vbuf_raw = rb_new(VIDEO_DECODE_BUFFER_SIZE))) { |
56 | goto BASE_CLEANUP; | 174 | goto BASE_CLEANUP; |
57 | } | 175 | } |
58 | 176 | ||
59 | rc = vpx_codec_dec_init(vc->decoder, VIDEO_CODEC_DECODER_INTERFACE, nullptr, 0); | 177 | /* |
178 | * VPX_CODEC_USE_FRAME_THREADING | ||
179 | * Enable frame-based multi-threading | ||
180 | * | ||
181 | * VPX_CODEC_USE_ERROR_CONCEALMENT | ||
182 | * Conceal errors in decoded frames | ||
183 | */ | ||
184 | vpx_codec_dec_cfg_t dec_cfg; | ||
185 | dec_cfg.threads = VPX_MAX_DECODER_THREADS; // Maximum number of threads to use | ||
186 | dec_cfg.w = VIDEO_CODEC_DECODER_MAX_WIDTH; | ||
187 | dec_cfg.h = VIDEO_CODEC_DECODER_MAX_HEIGHT; | ||
188 | |||
189 | LOGGER_DEBUG(log, "Using VP8 codec for decoder (0)"); | ||
190 | rc = vpx_codec_dec_init(vc->decoder, VIDEO_CODEC_DECODER_INTERFACE, &dec_cfg, | ||
191 | VPX_CODEC_USE_FRAME_THREADING | VPX_CODEC_USE_POSTPROC); | ||
192 | |||
193 | if (rc == VPX_CODEC_INCAPABLE) { | ||
194 | LOGGER_WARNING(log, "Postproc not supported by this decoder (0)"); | ||
195 | rc = vpx_codec_dec_init(vc->decoder, VIDEO_CODEC_DECODER_INTERFACE, &dec_cfg, VPX_CODEC_USE_FRAME_THREADING); | ||
196 | } | ||
60 | 197 | ||
61 | if (rc != VPX_CODEC_OK) { | 198 | if (rc != VPX_CODEC_OK) { |
62 | LOGGER_ERROR(log, "Init video_decoder failed: %s", vpx_codec_err_to_string(rc)); | 199 | LOGGER_ERROR(log, "Init video_decoder failed: %s", vpx_codec_err_to_string(rc)); |
63 | goto BASE_CLEANUP; | 200 | goto BASE_CLEANUP; |
64 | } | 201 | } |
65 | 202 | ||
66 | /* Set encoder to some initial values | 203 | if (VIDEO__VP8_DECODER_POST_PROCESSING_ENABLED == 1) { |
67 | */ | 204 | vp8_postproc_cfg_t pp = {VP8_DEBLOCK, 1, 0}; |
68 | vpx_codec_enc_cfg_t cfg; | 205 | vpx_codec_err_t cc_res = vpx_codec_control(vc->decoder, VP8_SET_POSTPROC, &pp); |
69 | rc = vpx_codec_enc_config_default(VIDEO_CODEC_ENCODER_INTERFACE, &cfg, 0); | ||
70 | 206 | ||
71 | if (rc != VPX_CODEC_OK) { | 207 | if (cc_res != VPX_CODEC_OK) { |
72 | LOGGER_ERROR(log, "Failed to get config: %s", vpx_codec_err_to_string(rc)); | 208 | LOGGER_WARNING(log, "Failed to turn on postproc"); |
73 | goto BASE_CLEANUP_1; | 209 | } else { |
210 | LOGGER_DEBUG(log, "turn on postproc: OK"); | ||
211 | } | ||
212 | } else { | ||
213 | vp8_postproc_cfg_t pp = {0, 0, 0}; | ||
214 | vpx_codec_err_t cc_res = vpx_codec_control(vc->decoder, VP8_SET_POSTPROC, &pp); | ||
215 | |||
216 | if (cc_res != VPX_CODEC_OK) { | ||
217 | LOGGER_WARNING(log, "Failed to turn OFF postproc"); | ||
218 | } else { | ||
219 | LOGGER_DEBUG(log, "Disable postproc: OK"); | ||
220 | } | ||
74 | } | 221 | } |
75 | 222 | ||
76 | cfg.rc_target_bitrate = 500000; | 223 | /* Set encoder to some initial values |
77 | cfg.g_w = 800; | 224 | */ |
78 | cfg.g_h = 600; | 225 | vpx_codec_enc_cfg_t cfg; |
79 | cfg.g_pass = VPX_RC_ONE_PASS; | 226 | vc_init_encoder_cfg(log, &cfg, 1); |
80 | /* TODO(mannol): If we set error resilience the app will crash due to bug in vp8. | ||
81 | Perhaps vp9 has solved it?*/ | ||
82 | #if 0 | ||
83 | cfg.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT | VPX_ERROR_RESILIENT_PARTITIONS; | ||
84 | #endif | ||
85 | cfg.g_lag_in_frames = 0; | ||
86 | cfg.kf_min_dist = 0; | ||
87 | cfg.kf_max_dist = 48; | ||
88 | cfg.kf_mode = VPX_KF_AUTO; | ||
89 | 227 | ||
90 | rc = vpx_codec_enc_init(vc->encoder, VIDEO_CODEC_ENCODER_INTERFACE, &cfg, 0); | 228 | LOGGER_DEBUG(log, "Using VP8 codec for encoder (0.1)"); |
229 | rc = vpx_codec_enc_init(vc->encoder, VIDEO_CODEC_ENCODER_INTERFACE, &cfg, VPX_CODEC_USE_FRAME_THREADING); | ||
91 | 230 | ||
92 | if (rc != VPX_CODEC_OK) { | 231 | if (rc != VPX_CODEC_OK) { |
93 | LOGGER_ERROR(log, "Failed to initialize encoder: %s", vpx_codec_err_to_string(rc)); | 232 | LOGGER_ERROR(log, "Failed to initialize encoder: %s", vpx_codec_err_to_string(rc)); |
94 | goto BASE_CLEANUP_1; | 233 | goto BASE_CLEANUP_1; |
95 | } | 234 | } |
96 | 235 | ||
97 | rc = vpx_codec_control(vc->encoder, VP8E_SET_CPUUSED, 8); | 236 | rc = vpx_codec_control(vc->encoder, VP8E_SET_CPUUSED, cpu_used_value); |
98 | 237 | ||
99 | if (rc != VPX_CODEC_OK) { | 238 | if (rc != VPX_CODEC_OK) { |
100 | LOGGER_ERROR(log, "Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc)); | 239 | LOGGER_ERROR(log, "Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc)); |
@@ -102,6 +241,20 @@ VCSession *vc_new(Logger *log, ToxAV *av, uint32_t friend_number, toxav_video_re | |||
102 | goto BASE_CLEANUP_1; | 241 | goto BASE_CLEANUP_1; |
103 | } | 242 | } |
104 | 243 | ||
244 | /* | ||
245 | VPX_CTRL_USE_TYPE(VP8E_SET_NOISE_SENSITIVITY, unsigned int) | ||
246 | control function to set noise sensitivity | ||
247 | 0: off, 1: OnYOnly, 2: OnYUV, 3: OnYUVAggressive, 4: Adaptive | ||
248 | */ | ||
249 | /* | ||
250 | rc = vpx_codec_control(vc->encoder, VP8E_SET_NOISE_SENSITIVITY, 2); | ||
251 | |||
252 | if (rc != VPX_CODEC_OK) { | ||
253 | LOGGER_ERROR(log, "Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc)); | ||
254 | vpx_codec_destroy(vc->encoder); | ||
255 | goto BASE_CLEANUP_1; | ||
256 | } | ||
257 | */ | ||
105 | vc->linfts = current_time_monotonic(); | 258 | vc->linfts = current_time_monotonic(); |
106 | vc->lcfd = 60; | 259 | vc->lcfd = 60; |
107 | vc->vcb.first = cb; | 260 | vc->vcb.first = cb; |
@@ -109,9 +262,7 @@ VCSession *vc_new(Logger *log, ToxAV *av, uint32_t friend_number, toxav_video_re | |||
109 | vc->friend_number = friend_number; | 262 | vc->friend_number = friend_number; |
110 | vc->av = av; | 263 | vc->av = av; |
111 | vc->log = log; | 264 | vc->log = log; |
112 | |||
113 | return vc; | 265 | return vc; |
114 | |||
115 | BASE_CLEANUP_1: | 266 | BASE_CLEANUP_1: |
116 | vpx_codec_destroy(vc->decoder); | 267 | vpx_codec_destroy(vc->decoder); |
117 | BASE_CLEANUP: | 268 | BASE_CLEANUP: |
@@ -120,6 +271,7 @@ BASE_CLEANUP: | |||
120 | free(vc); | 271 | free(vc); |
121 | return nullptr; | 272 | return nullptr; |
122 | } | 273 | } |
274 | |||
123 | void vc_kill(VCSession *vc) | 275 | void vc_kill(VCSession *vc) |
124 | { | 276 | { |
125 | if (!vc) { | 277 | if (!vc) { |
@@ -128,7 +280,6 @@ void vc_kill(VCSession *vc) | |||
128 | 280 | ||
129 | vpx_codec_destroy(vc->encoder); | 281 | vpx_codec_destroy(vc->encoder); |
130 | vpx_codec_destroy(vc->decoder); | 282 | vpx_codec_destroy(vc->decoder); |
131 | |||
132 | void *p; | 283 | void *p; |
133 | 284 | ||
134 | while (rb_read((RingBuffer *)vc->vbuf_raw, &p)) { | 285 | while (rb_read((RingBuffer *)vc->vbuf_raw, &p)) { |
@@ -136,12 +287,11 @@ void vc_kill(VCSession *vc) | |||
136 | } | 287 | } |
137 | 288 | ||
138 | rb_kill((RingBuffer *)vc->vbuf_raw); | 289 | rb_kill((RingBuffer *)vc->vbuf_raw); |
139 | |||
140 | pthread_mutex_destroy(vc->queue_mutex); | 290 | pthread_mutex_destroy(vc->queue_mutex); |
141 | |||
142 | LOGGER_DEBUG(vc->log, "Terminated video handler: %p", vc); | 291 | LOGGER_DEBUG(vc->log, "Terminated video handler: %p", vc); |
143 | free(vc); | 292 | free(vc); |
144 | } | 293 | } |
294 | |||
145 | void vc_iterate(VCSession *vc) | 295 | void vc_iterate(VCSession *vc) |
146 | { | 296 | { |
147 | if (!vc) { | 297 | if (!vc) { |
@@ -154,45 +304,63 @@ void vc_iterate(VCSession *vc) | |||
154 | 304 | ||
155 | pthread_mutex_lock(vc->queue_mutex); | 305 | pthread_mutex_lock(vc->queue_mutex); |
156 | 306 | ||
307 | uint32_t full_data_len; | ||
308 | |||
157 | if (rb_read((RingBuffer *)vc->vbuf_raw, (void **)&p)) { | 309 | if (rb_read((RingBuffer *)vc->vbuf_raw, (void **)&p)) { |
158 | pthread_mutex_unlock(vc->queue_mutex); | 310 | pthread_mutex_unlock(vc->queue_mutex); |
311 | const struct RTPHeader *const header = &p->header; | ||
312 | |||
313 | if (header->flags & RTP_LARGE_FRAME) { | ||
314 | full_data_len = header->data_length_full; | ||
315 | LOGGER_DEBUG(vc->log, "vc_iterate:001:full_data_len=%d", (int)full_data_len); | ||
316 | } else { | ||
317 | full_data_len = p->len; | ||
318 | LOGGER_DEBUG(vc->log, "vc_iterate:002"); | ||
319 | } | ||
159 | 320 | ||
160 | rc = vpx_codec_decode(vc->decoder, p->data, p->len, nullptr, MAX_DECODE_TIME_US); | 321 | LOGGER_DEBUG(vc->log, "vc_iterate: rb_read p->len=%d p->header.xe=%d", (int)full_data_len, p->header.xe); |
322 | LOGGER_DEBUG(vc->log, "vc_iterate: rb_read rb size=%d", (int)rb_size((RingBuffer *)vc->vbuf_raw)); | ||
323 | rc = vpx_codec_decode(vc->decoder, p->data, full_data_len, nullptr, MAX_DECODE_TIME_US); | ||
161 | free(p); | 324 | free(p); |
162 | 325 | ||
163 | if (rc != VPX_CODEC_OK) { | 326 | if (rc != VPX_CODEC_OK) { |
164 | LOGGER_ERROR(vc->log, "Error decoding video: %s", vpx_codec_err_to_string(rc)); | 327 | LOGGER_ERROR(vc->log, "Error decoding video: %d %s", (int)rc, vpx_codec_err_to_string(rc)); |
165 | } else { | 328 | } else { |
329 | /* Play decoded images */ | ||
166 | vpx_codec_iter_t iter = nullptr; | 330 | vpx_codec_iter_t iter = nullptr; |
167 | vpx_image_t *dest = vpx_codec_get_frame(vc->decoder, &iter); | 331 | vpx_image_t *dest = nullptr; |
168 | 332 | ||
169 | /* Play decoded images */ | 333 | while ((dest = vpx_codec_get_frame(vc->decoder, &iter)) != nullptr) { |
170 | for (; dest; dest = vpx_codec_get_frame(vc->decoder, &iter)) { | ||
171 | if (vc->vcb.first) { | 334 | if (vc->vcb.first) { |
172 | vc->vcb.first(vc->av, vc->friend_number, dest->d_w, dest->d_h, | 335 | vc->vcb.first(vc->av, vc->friend_number, dest->d_w, dest->d_h, |
173 | (const uint8_t *)dest->planes[0], (const uint8_t *)dest->planes[1], (const uint8_t *)dest->planes[2], | 336 | (const uint8_t *)dest->planes[0], (const uint8_t *)dest->planes[1], (const uint8_t *)dest->planes[2], |
174 | dest->stride[0], dest->stride[1], dest->stride[2], vc->vcb.second); | 337 | dest->stride[0], dest->stride[1], dest->stride[2], vc->vcb.second); |
175 | } | 338 | } |
176 | 339 | ||
177 | vpx_img_free(dest); | 340 | vpx_img_free(dest); // is this needed? none of the VPx examples show that |
178 | } | 341 | } |
179 | } | 342 | } |
180 | 343 | ||
181 | return; | 344 | return; |
345 | } else { | ||
346 | LOGGER_TRACE(vc->log, "no Video frame data available"); | ||
182 | } | 347 | } |
183 | 348 | ||
184 | pthread_mutex_unlock(vc->queue_mutex); | 349 | pthread_mutex_unlock(vc->queue_mutex); |
185 | } | 350 | } |
351 | |||
186 | int vc_queue_message(void *vcp, struct RTPMessage *msg) | 352 | int vc_queue_message(void *vcp, struct RTPMessage *msg) |
187 | { | 353 | { |
188 | /* This function does the reconstruction of video packets. | 354 | /* This function is called with complete messages |
189 | * See more info about video splitting in docs | 355 | * they have already been assembled. |
356 | * this function gets called from handle_rtp_packet() and handle_rtp_packet_v3() | ||
190 | */ | 357 | */ |
191 | if (!vcp || !msg) { | 358 | if (!vcp || !msg) { |
192 | return -1; | 359 | return -1; |
193 | } | 360 | } |
194 | 361 | ||
195 | VCSession *vc = (VCSession *)vcp; | 362 | VCSession *vc = (VCSession *)vcp; |
363 | const struct RTPHeader *const header = &msg->header; | ||
196 | 364 | ||
197 | if (msg->header.pt == (rtp_TypeVideo + 2) % 128) { | 365 | if (msg->header.pt == (rtp_TypeVideo + 2) % 128) { |
198 | LOGGER_WARNING(vc->log, "Got dummy!"); | 366 | LOGGER_WARNING(vc->log, "Got dummy!"); |
@@ -201,41 +369,45 @@ int vc_queue_message(void *vcp, struct RTPMessage *msg) | |||
201 | } | 369 | } |
202 | 370 | ||
203 | if (msg->header.pt != rtp_TypeVideo % 128) { | 371 | if (msg->header.pt != rtp_TypeVideo % 128) { |
204 | LOGGER_WARNING(vc->log, "Invalid payload type!"); | 372 | LOGGER_WARNING(vc->log, "Invalid payload type! pt=%d", (int)msg->header.pt); |
205 | free(msg); | 373 | free(msg); |
206 | return -1; | 374 | return -1; |
207 | } | 375 | } |
208 | 376 | ||
209 | pthread_mutex_lock(vc->queue_mutex); | 377 | pthread_mutex_lock(vc->queue_mutex); |
210 | free(rb_write((RingBuffer *)vc->vbuf_raw, msg)); | 378 | |
211 | { | 379 | if ((header->flags & RTP_LARGE_FRAME) && header->pt == rtp_TypeVideo % 128) { |
212 | /* Calculate time took for peer to send us this frame */ | 380 | LOGGER_DEBUG(vc->log, "rb_write msg->len=%d b0=%d b1=%d", (int)msg->len, (int)msg->data[0], (int)msg->data[1]); |
213 | uint32_t t_lcfd = current_time_monotonic() - vc->linfts; | ||
214 | vc->lcfd = t_lcfd > 100 ? vc->lcfd : t_lcfd; | ||
215 | vc->linfts = current_time_monotonic(); | ||
216 | } | 381 | } |
217 | pthread_mutex_unlock(vc->queue_mutex); | ||
218 | 382 | ||
383 | free(rb_write((RingBuffer *)vc->vbuf_raw, msg)); | ||
384 | |||
385 | /* Calculate time it took for peer to send us this frame */ | ||
386 | uint32_t t_lcfd = current_time_monotonic() - vc->linfts; | ||
387 | vc->lcfd = t_lcfd > 100 ? vc->lcfd : t_lcfd; | ||
388 | vc->linfts = current_time_monotonic(); | ||
389 | pthread_mutex_unlock(vc->queue_mutex); | ||
219 | return 0; | 390 | return 0; |
220 | } | 391 | } |
221 | int vc_reconfigure_encoder(VCSession *vc, uint32_t bit_rate, uint16_t width, uint16_t height) | 392 | |
393 | int vc_reconfigure_encoder(VCSession *vc, uint32_t bit_rate, uint16_t width, uint16_t height, int16_t kf_max_dist) | ||
222 | { | 394 | { |
223 | if (!vc) { | 395 | if (!vc) { |
224 | return -1; | 396 | return -1; |
225 | } | 397 | } |
226 | 398 | ||
227 | vpx_codec_enc_cfg_t cfg = *vc->encoder->config.enc; | 399 | vpx_codec_enc_cfg_t cfg2 = *vc->encoder->config.enc; |
228 | vpx_codec_err_t rc; | 400 | vpx_codec_err_t rc; |
229 | 401 | ||
230 | if (cfg.rc_target_bitrate == bit_rate && cfg.g_w == width && cfg.g_h == height) { | 402 | if (cfg2.rc_target_bitrate == bit_rate && cfg2.g_w == width && cfg2.g_h == height && kf_max_dist == -1) { |
231 | return 0; /* Nothing changed */ | 403 | return 0; /* Nothing changed */ |
232 | } | 404 | } |
233 | 405 | ||
234 | if (cfg.g_w == width && cfg.g_h == height) { | 406 | if (cfg2.g_w == width && cfg2.g_h == height && kf_max_dist == -1) { |
235 | /* Only bit rate changed */ | 407 | /* Only bit rate changed */ |
236 | cfg.rc_target_bitrate = bit_rate; | 408 | LOGGER_INFO(vc->log, "bitrate change from: %u to: %u", (uint32_t)cfg2.rc_target_bitrate, (uint32_t)bit_rate); |
237 | 409 | cfg2.rc_target_bitrate = bit_rate; | |
238 | rc = vpx_codec_enc_config_set(vc->encoder, &cfg); | 410 | rc = vpx_codec_enc_config_set(vc->encoder, &cfg2); |
239 | 411 | ||
240 | if (rc != VPX_CODEC_OK) { | 412 | if (rc != VPX_CODEC_OK) { |
241 | LOGGER_ERROR(vc->log, "Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc)); | 413 | LOGGER_ERROR(vc->log, "Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc)); |
@@ -245,23 +417,25 @@ int vc_reconfigure_encoder(VCSession *vc, uint32_t bit_rate, uint16_t width, uin | |||
245 | /* Resolution is changed, must reinitialize encoder since libvpx v1.4 doesn't support | 417 | /* Resolution is changed, must reinitialize encoder since libvpx v1.4 doesn't support |
246 | * reconfiguring encoder to use resolutions greater than initially set. | 418 | * reconfiguring encoder to use resolutions greater than initially set. |
247 | */ | 419 | */ |
248 | |||
249 | LOGGER_DEBUG(vc->log, "Have to reinitialize vpx encoder on session %p", vc); | 420 | LOGGER_DEBUG(vc->log, "Have to reinitialize vpx encoder on session %p", vc); |
250 | 421 | vpx_codec_ctx_t new_c; | |
422 | vpx_codec_enc_cfg_t cfg; | ||
423 | vc_init_encoder_cfg(vc->log, &cfg, kf_max_dist); | ||
251 | cfg.rc_target_bitrate = bit_rate; | 424 | cfg.rc_target_bitrate = bit_rate; |
252 | cfg.g_w = width; | 425 | cfg.g_w = width; |
253 | cfg.g_h = height; | 426 | cfg.g_h = height; |
254 | 427 | ||
255 | vpx_codec_ctx_t new_c; | 428 | LOGGER_DEBUG(vc->log, "Using VP8 codec for encoder"); |
256 | 429 | rc = vpx_codec_enc_init(&new_c, VIDEO_CODEC_ENCODER_INTERFACE, &cfg, VPX_CODEC_USE_FRAME_THREADING); | |
257 | rc = vpx_codec_enc_init(&new_c, VIDEO_CODEC_ENCODER_INTERFACE, &cfg, 0); | ||
258 | 430 | ||
259 | if (rc != VPX_CODEC_OK) { | 431 | if (rc != VPX_CODEC_OK) { |
260 | LOGGER_ERROR(vc->log, "Failed to initialize encoder: %s", vpx_codec_err_to_string(rc)); | 432 | LOGGER_ERROR(vc->log, "Failed to initialize encoder: %s", vpx_codec_err_to_string(rc)); |
261 | return -1; | 433 | return -1; |
262 | } | 434 | } |
263 | 435 | ||
264 | rc = vpx_codec_control(&new_c, VP8E_SET_CPUUSED, 8); | 436 | int cpu_used_value = VP8E_SET_CPUUSED_VALUE; |
437 | |||
438 | rc = vpx_codec_control(&new_c, VP8E_SET_CPUUSED, cpu_used_value); | ||
265 | 439 | ||
266 | if (rc != VPX_CODEC_OK) { | 440 | if (rc != VPX_CODEC_OK) { |
267 | LOGGER_ERROR(vc->log, "Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc)); | 441 | LOGGER_ERROR(vc->log, "Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc)); |
diff --git a/toxav/video.h b/toxav/video.h index 02670e0a..85ad3129 100644 --- a/toxav/video.h +++ b/toxav/video.h | |||
@@ -31,8 +31,7 @@ | |||
31 | 31 | ||
32 | #include <vpx/vp8cx.h> | 32 | #include <vpx/vp8cx.h> |
33 | #include <vpx/vp8dx.h> | 33 | #include <vpx/vp8dx.h> |
34 | #define VIDEO_CODEC_DECODER_INTERFACE (vpx_codec_vp8_dx()) | 34 | |
35 | #define VIDEO_CODEC_ENCODER_INTERFACE (vpx_codec_vp8_cx()) | ||
36 | 35 | ||
37 | #include <pthread.h> | 36 | #include <pthread.h> |
38 | 37 | ||
@@ -64,6 +63,6 @@ VCSession *vc_new(Logger *log, ToxAV *av, uint32_t friend_number, toxav_video_re | |||
64 | void vc_kill(VCSession *vc); | 63 | void vc_kill(VCSession *vc); |
65 | void vc_iterate(VCSession *vc); | 64 | void vc_iterate(VCSession *vc); |
66 | int vc_queue_message(void *vcp, struct RTPMessage *msg); | 65 | int vc_queue_message(void *vcp, struct RTPMessage *msg); |
67 | int vc_reconfigure_encoder(VCSession *vc, uint32_t bit_rate, uint16_t width, uint16_t height); | 66 | int vc_reconfigure_encoder(VCSession *vc, uint32_t bit_rate, uint16_t width, uint16_t height, int16_t kf_max_dist); |
68 | 67 | ||
69 | #endif /* VIDEO_H */ | 68 | #endif /* VIDEO_H */ |
diff --git a/toxcore/BUILD.bazel b/toxcore/BUILD.bazel index 934ff16c..d6c3693e 100644 --- a/toxcore/BUILD.bazel +++ b/toxcore/BUILD.bazel | |||
@@ -33,7 +33,7 @@ cc_test( | |||
33 | srcs = ["crypto_core_test.cpp"], | 33 | srcs = ["crypto_core_test.cpp"], |
34 | deps = [ | 34 | deps = [ |
35 | ":crypto_core", | 35 | ":crypto_core", |
36 | "@gtest", | 36 | "@com_google_googletest//:gtest_main", |
37 | ], | 37 | ], |
38 | ) | 38 | ) |
39 | 39 | ||
@@ -77,7 +77,7 @@ cc_test( | |||
77 | srcs = ["util_test.cpp"], | 77 | srcs = ["util_test.cpp"], |
78 | deps = [ | 78 | deps = [ |
79 | ":network", | 79 | ":network", |
80 | "@gtest", | 80 | "@com_google_googletest//:gtest_main", |
81 | ], | 81 | ], |
82 | ) | 82 | ) |
83 | 83 | ||