summaryrefslogtreecommitdiff
path: root/toxav/video.c
diff options
context:
space:
mode:
Diffstat (limited to 'toxav/video.c')
-rw-r--r--toxav/video.c375
1 files changed, 375 insertions, 0 deletions
diff --git a/toxav/video.c b/toxav/video.c
new file mode 100644
index 00000000..ee49c0a1
--- /dev/null
+++ b/toxav/video.c
@@ -0,0 +1,375 @@
1/** video.c
2 *
3 * Copyright (C) 2013-2015 Tox project All Rights Reserved.
4 *
5 * This file is part of Tox.
6 *
7 * Tox is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * Tox is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with Tox. If not, see <http://www.gnu.org/licenses/>.
19 *
20 */
21
22#include <stdlib.h>
23#include <assert.h>
24
25#include "video.h"
26#include "msi.h"
27#include "rtp.h"
28
29#include "../toxcore/logger.h"
30#include "../toxcore/network.h"
31
32/* Good quality encode. */
33#define MAX_DECODE_TIME_US 0
34
35#define MAX_VIDEOFRAME_SIZE 0x40000 /* 256KiB */
36#define VIDEOFRAME_HEADER_SIZE 0x2
37
38#define VIDEO_DECODE_BUFFER_SIZE 20
39
40typedef struct { uint16_t size; uint8_t data[]; } Payload;
41
42bool create_video_encoder (vpx_codec_ctx_t* dest, int32_t bit_rate);
43
44
45VCSession* vc_new(ToxAV* av, uint32_t friend_number, toxav_video_receive_frame_cb* cb, void* cb_data, uint32_t mvfpsz)
46{
47 VCSession *vc = calloc(sizeof(VCSession), 1);
48
49 if (!vc) {
50 LOGGER_WARNING("Allocation failed! Application might misbehave!");
51 return NULL;
52 }
53
54 if (create_recursive_mutex(vc->queue_mutex) != 0) {
55 LOGGER_WARNING("Failed to create recursive mutex!");
56 free(vc);
57 return NULL;
58 }
59
60 if ( !(vc->frame_buf = calloc(MAX_VIDEOFRAME_SIZE, 1)) )
61 goto BASE_CLEANUP;
62 if ( !(vc->split_video_frame = calloc(VIDEOFRAME_PIECE_SIZE + VIDEOFRAME_HEADER_SIZE, 1)) )
63 goto BASE_CLEANUP;
64 if ( !(vc->vbuf_raw = rb_new(VIDEO_DECODE_BUFFER_SIZE)) )
65 goto BASE_CLEANUP;
66
67 int rc = vpx_codec_dec_init_ver(vc->decoder, VIDEO_CODEC_DECODER_INTERFACE,
68 NULL, 0, VPX_DECODER_ABI_VERSION);
69 if ( rc != VPX_CODEC_OK) {
70 LOGGER_ERROR("Init video_decoder failed: %s", vpx_codec_err_to_string(rc));
71 goto BASE_CLEANUP;
72 }
73
74 if (!create_video_encoder(vc->encoder, 500000)) {
75 vpx_codec_destroy(vc->decoder);
76 goto BASE_CLEANUP;
77 }
78 if (!create_video_encoder(vc->test_encoder, 500000)) {
79 vpx_codec_destroy(vc->encoder);
80 vpx_codec_destroy(vc->decoder);
81 goto BASE_CLEANUP;
82 }
83
84 vc->linfts = current_time_monotonic();
85 vc->lcfd = 60;
86 vc->vcb.first = cb;
87 vc->vcb.second = cb_data;
88 vc->friend_number = friend_number;
89 vc->peer_video_frame_piece_size = mvfpsz;
90 vc->av = av;
91
92 return vc;
93
94BASE_CLEANUP:
95 pthread_mutex_destroy(vc->queue_mutex);
96 rb_free(vc->vbuf_raw);
97 free(vc->split_video_frame);
98 free(vc->frame_buf);
99 free(vc);
100 return NULL;
101}
102void vc_kill(VCSession* vc)
103{
104 if (!vc)
105 return;
106
107 vpx_codec_destroy(vc->encoder);
108 vpx_codec_destroy(vc->test_encoder);
109 vpx_codec_destroy(vc->decoder);
110 rb_free(vc->vbuf_raw);
111 free(vc->split_video_frame);
112 free(vc->frame_buf);
113
114 pthread_mutex_destroy(vc->queue_mutex);
115
116 LOGGER_DEBUG("Terminated video handler: %p", vc);
117 free(vc);
118}
119void vc_do(VCSession* vc)
120{
121 if (!vc)
122 return;
123
124 Payload *p;
125 int rc;
126
127 pthread_mutex_lock(vc->queue_mutex);
128 if (rb_read(vc->vbuf_raw, (void**)&p)) {
129 pthread_mutex_unlock(vc->queue_mutex);
130
131 rc = vpx_codec_decode(vc->decoder, p->data, p->size, NULL, MAX_DECODE_TIME_US);
132 free(p);
133
134 if (rc != VPX_CODEC_OK) {
135 LOGGER_ERROR("Error decoding video: %s", vpx_codec_err_to_string(rc));
136 } else {
137 vpx_codec_iter_t iter = NULL;
138 vpx_image_t *dest = vpx_codec_get_frame(vc->decoder, &iter);
139
140 /* Play decoded images */
141 for (; dest; dest = vpx_codec_get_frame(vc->decoder, &iter)) {
142 if (vc->vcb.first)
143 vc->vcb.first(vc->av, vc->friend_number, dest->d_w, dest->d_h,
144 (const uint8_t*)dest->planes[0], (const uint8_t*)dest->planes[1], (const uint8_t*)dest->planes[2],
145 dest->stride[0], dest->stride[1], dest->stride[2], vc->vcb.second);
146
147 vpx_img_free(dest);
148 }
149 }
150
151 return;
152 }
153 pthread_mutex_unlock(vc->queue_mutex);
154}
155void vc_init_video_splitter_cycle(VCSession* vc)
156{
157 if (!vc)
158 return;
159
160 vc->split_video_frame[0] = vc->frameid_out++;
161 vc->split_video_frame[1] = 0;
162}
163int vc_update_video_splitter_cycle(VCSession* vc, const uint8_t* payload, uint16_t length)
164{
165 if (!vc)
166 return 0;
167
168 vc->processing_video_frame = payload;
169 vc->processing_video_frame_size = length;
170
171 return ((length - 1) / VIDEOFRAME_PIECE_SIZE) + 1;
172}
173const uint8_t* vc_iterate_split_video_frame(VCSession* vc, uint16_t* size)
174{
175 if (!vc || !size)
176 return NULL;
177
178 if (vc->processing_video_frame_size > VIDEOFRAME_PIECE_SIZE) {
179 memcpy(vc->split_video_frame + VIDEOFRAME_HEADER_SIZE,
180 vc->processing_video_frame,
181 VIDEOFRAME_PIECE_SIZE);
182
183 vc->processing_video_frame += VIDEOFRAME_PIECE_SIZE;
184 vc->processing_video_frame_size -= VIDEOFRAME_PIECE_SIZE;
185
186 *size = VIDEOFRAME_PIECE_SIZE + VIDEOFRAME_HEADER_SIZE;
187 } else {
188 memcpy(vc->split_video_frame + VIDEOFRAME_HEADER_SIZE,
189 vc->processing_video_frame,
190 vc->processing_video_frame_size);
191
192 *size = vc->processing_video_frame_size + VIDEOFRAME_HEADER_SIZE;
193 }
194
195 vc->split_video_frame[1]++;
196
197 return vc->split_video_frame;
198}
199int vc_queue_message(void* vcp, struct RTPMessage_s *msg)
200{
201 /* This function does the reconstruction of video packets.
202 * See more info about video splitting in docs
203 */
204 if (!vcp || !msg)
205 return -1;
206
207 if ((msg->header->marker_payloadt & 0x7f) == (rtp_TypeVideo + 2) % 128) {
208 LOGGER_WARNING("Got dummy!");
209 rtp_free_msg(msg);
210 return 0;
211 }
212
213 if ((msg->header->marker_payloadt & 0x7f) != rtp_TypeVideo % 128) {
214 LOGGER_WARNING("Invalid payload type!");
215 rtp_free_msg(msg);
216 return -1;
217 }
218
219 VCSession* vc = vcp;
220
221 uint8_t *packet = msg->data;
222 uint32_t packet_size = msg->length;
223
224 if (packet_size < VIDEOFRAME_HEADER_SIZE)
225 goto end;
226
227 uint8_t diff = packet[0] - vc->frameid_in;
228
229 if (diff != 0) {
230 if (diff < 225) { /* New frame */
231 /* Flush last frames' data and get ready for this frame */
232 Payload *p = malloc(sizeof(Payload) + vc->frame_size);
233
234 if (p) {
235 pthread_mutex_lock(vc->queue_mutex);
236
237 if (rb_full(vc->vbuf_raw)) {
238 LOGGER_DEBUG("Dropped video frame");
239 Payload *tp;
240 rb_read(vc->vbuf_raw, (void**)&tp);
241 free(tp);
242 } else {
243 p->size = vc->frame_size;
244 memcpy(p->data, vc->frame_buf, vc->frame_size);
245 }
246
247 /* Calculate time took for peer to send us this frame */
248 uint32_t t_lcfd = current_time_monotonic() - vc->linfts;
249 vc->lcfd = t_lcfd > 100 ? vc->lcfd : t_lcfd;
250 vc->linfts = current_time_monotonic();
251
252 rb_write(vc->vbuf_raw, p);
253 pthread_mutex_unlock(vc->queue_mutex);
254 } else {
255 LOGGER_WARNING("Allocation failed! Program might misbehave!");
256 goto end;
257 }
258
259 vc->frameid_in = packet[0];
260 memset(vc->frame_buf, 0, vc->frame_size);
261 vc->frame_size = 0;
262
263 } else { /* Old frame; drop */
264 LOGGER_DEBUG("Old packet: %u", packet[0]);
265 goto end;
266 }
267 }
268
269 uint8_t piece_number = packet[1];
270
271 uint32_t length_before_piece = ((piece_number - 1) * vc->peer_video_frame_piece_size);
272 uint32_t framebuf_new_length = length_before_piece + (packet_size - VIDEOFRAME_HEADER_SIZE);
273
274 if (framebuf_new_length > MAX_VIDEOFRAME_SIZE)
275 goto end;
276
277
278 /* Otherwise it's part of the frame so just process */
279 /* LOGGER_DEBUG("Video Packet: %u %u", packet[0], packet[1]); */
280
281 memcpy(vc->frame_buf + length_before_piece,
282 packet + VIDEOFRAME_HEADER_SIZE,
283 packet_size - VIDEOFRAME_HEADER_SIZE);
284
285 if (framebuf_new_length > vc->frame_size)
286 vc->frame_size = framebuf_new_length;
287
288end:
289 rtp_free_msg(msg);
290 return 0;
291}
292int vc_reconfigure_encoder(VCSession* vc, int32_t bit_rate, uint16_t width, uint16_t height)
293{
294 if (!vc)
295 return -1;
296
297 vpx_codec_enc_cfg_t cfg = *vc->encoder->config.enc;
298 if (cfg.rc_target_bitrate == (uint32_t) bit_rate && cfg.g_w == width && cfg.g_h == height)
299 return 0; /* Nothing changed */
300
301 cfg.rc_target_bitrate = bit_rate;
302 cfg.g_w = width;
303 cfg.g_h = height;
304
305 int rc = vpx_codec_enc_config_set(vc->encoder, &cfg);
306 if ( rc != VPX_CODEC_OK) {
307 LOGGER_ERROR("Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc));
308 return -1;
309 }
310
311 return 0;
312}
313int vc_reconfigure_test_encoder(VCSession* vc, int32_t bit_rate, uint16_t width, uint16_t height)
314{
315 if (!vc)
316 return -1;
317
318 vpx_codec_enc_cfg_t cfg = *vc->test_encoder->config.enc;
319 if (cfg.rc_target_bitrate == (uint32_t) bit_rate && cfg.g_w == width && cfg.g_h == height)
320 return 0; /* Nothing changed */
321
322 cfg.rc_target_bitrate = bit_rate;
323 cfg.g_w = width;
324 cfg.g_h = height;
325
326 int rc = vpx_codec_enc_config_set(vc->test_encoder, &cfg);
327 if ( rc != VPX_CODEC_OK) {
328 LOGGER_ERROR("Failed to set test encoder control setting: %s", vpx_codec_err_to_string(rc));
329 return -1;
330 }
331
332 return 0;
333}
334
335
336
337bool create_video_encoder (vpx_codec_ctx_t* dest, int32_t bit_rate)
338{
339 assert(dest);
340
341 vpx_codec_enc_cfg_t cfg;
342 int rc = vpx_codec_enc_config_default(VIDEO_CODEC_ENCODER_INTERFACE, &cfg, 0);
343
344 if (rc != VPX_CODEC_OK) {
345 LOGGER_ERROR("Failed to get config: %s", vpx_codec_err_to_string(rc));
346 return false;
347 }
348
349 rc = vpx_codec_enc_init_ver(dest, VIDEO_CODEC_ENCODER_INTERFACE, &cfg, 0,
350 VPX_ENCODER_ABI_VERSION);
351
352 if ( rc != VPX_CODEC_OK) {
353 LOGGER_ERROR("Failed to initialize encoder: %s", vpx_codec_err_to_string(rc));
354 return false;
355 }
356
357 cfg.rc_target_bitrate = bit_rate;
358 cfg.g_w = 800;
359 cfg.g_h = 600;
360 cfg.g_pass = VPX_RC_ONE_PASS;
361 cfg.g_error_resilient = VPX_ERROR_RESILIENT_DEFAULT | VPX_ERROR_RESILIENT_PARTITIONS;
362 cfg.g_lag_in_frames = 0;
363 cfg.kf_min_dist = 0;
364 cfg.kf_max_dist = 48;
365 cfg.kf_mode = VPX_KF_AUTO;
366
367 rc = vpx_codec_control(dest, VP8E_SET_CPUUSED, 8);
368
369 if ( rc != VPX_CODEC_OK) {
370 LOGGER_ERROR("Failed to set encoder control setting: %s", vpx_codec_err_to_string(rc));
371 vpx_codec_destroy(dest);
372 }
373
374 return true;
375} \ No newline at end of file