blob: 2444402a90bc12fcf122d24331759deba8ff161e [file] [log] [blame]
Alexandre Lision8af73cb2013-12-10 14:11:20 -05001/* $Id$ */
2/*
3 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19#include <pjmedia/vid_port.h>
20#include <pjmedia/clock.h>
21#include <pjmedia/converter.h>
22#include <pjmedia/errno.h>
23#include <pjmedia/event.h>
24#include <pjmedia/vid_codec.h>
25#include <pj/log.h>
26#include <pj/pool.h>
27
28
29#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
30
31
32#define SIGNATURE PJMEDIA_SIG_VID_PORT
33#define THIS_FILE "vid_port.c"
34
35typedef struct vid_pasv_port vid_pasv_port;
36
37enum role
38{
39 ROLE_NONE,
40 ROLE_ACTIVE,
41 ROLE_PASSIVE
42};
43
44struct pjmedia_vid_port
45{
46 pj_pool_t *pool;
47 pj_str_t dev_name;
48 pjmedia_dir dir;
49// pjmedia_rect_size cap_size;
50 pjmedia_vid_dev_stream *strm;
51 pjmedia_vid_dev_cb strm_cb;
52 void *strm_cb_data;
53 enum role role,
54 stream_role;
55 vid_pasv_port *pasv_port;
56 pjmedia_port *client_port;
57 pj_bool_t destroy_client_port;
58
59 struct {
60 pjmedia_converter *conv;
61 void *conv_buf;
62 pj_size_t conv_buf_size;
63 pjmedia_conversion_param conv_param;
64 unsigned usec_ctr;
65 unsigned usec_src, usec_dst;
66 } conv;
67
68 pjmedia_clock *clock;
69 pjmedia_clock_src clocksrc;
70
71 struct sync_clock_src_t
72 {
73 pjmedia_clock_src *sync_clocksrc;
74 pj_int32_t sync_delta;
75 unsigned max_sync_ticks;
76 unsigned nsync_frame;
77 unsigned nsync_progress;
78 } sync_clocksrc;
79
80 pjmedia_frame *frm_buf;
81 pj_size_t frm_buf_size;
82 pj_mutex_t *frm_mutex;
83};
84
85struct vid_pasv_port
86{
87 pjmedia_port base;
88 pjmedia_vid_port *vp;
89};
90
91static pj_status_t vidstream_cap_cb(pjmedia_vid_dev_stream *stream,
92 void *user_data,
93 pjmedia_frame *frame);
94static pj_status_t vidstream_render_cb(pjmedia_vid_dev_stream *stream,
95 void *user_data,
96 pjmedia_frame *frame);
97static pj_status_t vidstream_event_cb(pjmedia_event *event,
98 void *user_data);
99static pj_status_t client_port_event_cb(pjmedia_event *event,
100 void *user_data);
101
102static void enc_clock_cb(const pj_timestamp *ts, void *user_data);
103static void dec_clock_cb(const pj_timestamp *ts, void *user_data);
104
105static pj_status_t vid_pasv_port_put_frame(struct pjmedia_port *this_port,
106 pjmedia_frame *frame);
107
108static pj_status_t vid_pasv_port_get_frame(struct pjmedia_port *this_port,
109 pjmedia_frame *frame);
110
111
112PJ_DEF(void) pjmedia_vid_port_param_default(pjmedia_vid_port_param *prm)
113{
114 pj_bzero(prm, sizeof(*prm));
115 prm->active = PJ_TRUE;
116}
117
118static const char *vid_dir_name(pjmedia_dir dir)
119{
120 switch (dir) {
121 case PJMEDIA_DIR_CAPTURE:
122 return "capture";
123 case PJMEDIA_DIR_RENDER:
124 return "render";
125 default:
126 return "??";
127 }
128}
129
130static pj_status_t create_converter(pjmedia_vid_port *vp)
131{
132 if (vp->conv.conv) {
133 pjmedia_converter_destroy(vp->conv.conv);
134 vp->conv.conv = NULL;
135 }
136
137 /* Instantiate converter if necessary */
138 if (vp->conv.conv_param.src.id != vp->conv.conv_param.dst.id ||
139 (vp->conv.conv_param.src.det.vid.size.w !=
140 vp->conv.conv_param.dst.det.vid.size.w) ||
141 (vp->conv.conv_param.src.det.vid.size.h !=
142 vp->conv.conv_param.dst.det.vid.size.h))
143 {
144 pj_status_t status;
145
146 /* Yes, we need converter */
147 status = pjmedia_converter_create(NULL, vp->pool, &vp->conv.conv_param,
148 &vp->conv.conv);
149 if (status != PJ_SUCCESS) {
150 PJ_PERROR(4,(THIS_FILE, status, "Error creating converter"));
151 return status;
152 }
153 }
154
155 if (vp->conv.conv ||
156 (vp->role==ROLE_ACTIVE && (vp->dir & PJMEDIA_DIR_ENCODING)))
157 {
158 pj_status_t status;
159 const pjmedia_video_format_info *vfi;
160 pjmedia_video_apply_fmt_param vafp;
161
162 /* Allocate buffer for conversion */
163 vfi = pjmedia_get_video_format_info(NULL, vp->conv.conv_param.dst.id);
164 if (!vfi)
165 return PJMEDIA_EBADFMT;
166
167 pj_bzero(&vafp, sizeof(vafp));
168 vafp.size = vp->conv.conv_param.dst.det.vid.size;
169 status = vfi->apply_fmt(vfi, &vafp);
170 if (status != PJ_SUCCESS)
171 return PJMEDIA_EBADFMT;
172
173 if (vafp.framebytes > vp->conv.conv_buf_size) {
174 vp->conv.conv_buf = pj_pool_alloc(vp->pool, vafp.framebytes);
175 vp->conv.conv_buf_size = vafp.framebytes;
176 }
177 }
178
179 vp->conv.usec_ctr = 0;
180 vp->conv.usec_src = PJMEDIA_PTIME(&vp->conv.conv_param.src.det.vid.fps);
181 vp->conv.usec_dst = PJMEDIA_PTIME(&vp->conv.conv_param.dst.det.vid.fps);
182
183 return PJ_SUCCESS;
184}
185
186PJ_DEF(pj_status_t) pjmedia_vid_port_create( pj_pool_t *pool,
187 const pjmedia_vid_port_param *prm,
188 pjmedia_vid_port **p_vid_port)
189{
190 pjmedia_vid_port *vp;
191 const pjmedia_video_format_detail *vfd;
192 char dev_name[64];
193 char fmt_name[5];
194 pjmedia_vid_dev_cb vid_cb;
195 pj_bool_t need_frame_buf = PJ_FALSE;
196 pj_status_t status;
197 unsigned ptime_usec;
198 pjmedia_vid_dev_param vparam;
199 pjmedia_vid_dev_info di;
200 unsigned i;
201
202 PJ_ASSERT_RETURN(pool && prm && p_vid_port, PJ_EINVAL);
203 PJ_ASSERT_RETURN(prm->vidparam.fmt.type == PJMEDIA_TYPE_VIDEO &&
204 prm->vidparam.dir != PJMEDIA_DIR_NONE &&
205 prm->vidparam.dir != PJMEDIA_DIR_CAPTURE_RENDER,
206 PJ_EINVAL);
207
208 /* Retrieve the video format detail */
209 vfd = pjmedia_format_get_video_format_detail(&prm->vidparam.fmt, PJ_TRUE);
210 if (!vfd)
211 return PJ_EINVAL;
212
213 PJ_ASSERT_RETURN(vfd->fps.num, PJ_EINVAL);
214
215 /* Allocate videoport */
216 vp = PJ_POOL_ZALLOC_T(pool, pjmedia_vid_port);
217 vp->pool = pj_pool_create(pool->factory, "video port", 500, 500, NULL);
218 vp->role = prm->active ? ROLE_ACTIVE : ROLE_PASSIVE;
219 vp->dir = prm->vidparam.dir;
220// vp->cap_size = vfd->size;
221
222 vparam = prm->vidparam;
223 dev_name[0] = '\0';
224
225 /* Get device info */
226 if (vp->dir & PJMEDIA_DIR_CAPTURE)
227 status = pjmedia_vid_dev_get_info(prm->vidparam.cap_id, &di);
228 else
229 status = pjmedia_vid_dev_get_info(prm->vidparam.rend_id, &di);
230 if (status != PJ_SUCCESS)
231 return status;
232
233 pj_ansi_snprintf(dev_name, sizeof(dev_name), "%s [%s]",
234 di.name, di.driver);
235
236 for (i = 0; i < di.fmt_cnt; ++i) {
237 if (prm->vidparam.fmt.id == di.fmt[i].id)
238 break;
239 }
240
241 if (i == di.fmt_cnt) {
242 /* The device has no no matching format. Pick one from
243 * the supported formats, and later use converter to
244 * convert it to the required format.
245 */
246 pj_assert(di.fmt_cnt != 0);
247 vparam.fmt.id = di.fmt[0].id;
248 }
249
250 pj_strdup2_with_null(pool, &vp->dev_name, di.name);
251 vp->stream_role = di.has_callback ? ROLE_ACTIVE : ROLE_PASSIVE;
252
253 pjmedia_fourcc_name(vparam.fmt.id, fmt_name);
254
255 PJ_LOG(4,(THIS_FILE,
256 "Opening device %s for %s: format=%s, size=%dx%d @%d:%d fps",
257 dev_name,
258 vid_dir_name(prm->vidparam.dir), fmt_name,
259 vfd->size.w, vfd->size.h,
260 vfd->fps.num, vfd->fps.denum));
261
262 ptime_usec = PJMEDIA_PTIME(&vfd->fps);
263 pjmedia_clock_src_init(&vp->clocksrc, PJMEDIA_TYPE_VIDEO,
264 prm->vidparam.clock_rate, ptime_usec);
265 vp->sync_clocksrc.max_sync_ticks =
266 PJMEDIA_CLOCK_SYNC_MAX_RESYNC_DURATION *
267 1000 / vp->clocksrc.ptime_usec;
268
269 /* Create the video stream */
270 pj_bzero(&vid_cb, sizeof(vid_cb));
271 vid_cb.capture_cb = &vidstream_cap_cb;
272 vid_cb.render_cb = &vidstream_render_cb;
273
274 status = pjmedia_vid_dev_stream_create(&vparam, &vid_cb, vp,
275 &vp->strm);
276 if (status != PJ_SUCCESS)
277 goto on_error;
278
279 PJ_LOG(4,(THIS_FILE,
280 "Device %s opened: format=%s, size=%dx%d @%d:%d fps",
281 dev_name, fmt_name,
282 vparam.fmt.det.vid.size.w, vparam.fmt.det.vid.size.h,
283 vparam.fmt.det.vid.fps.num, vparam.fmt.det.vid.fps.denum));
284
285 /* Subscribe to device's events */
286 pjmedia_event_subscribe(NULL, &vidstream_event_cb,
287 vp, vp->strm);
288
289 if (vp->dir & PJMEDIA_DIR_CAPTURE) {
290 pjmedia_format_copy(&vp->conv.conv_param.src, &vparam.fmt);
291 pjmedia_format_copy(&vp->conv.conv_param.dst, &prm->vidparam.fmt);
292 } else {
293 pjmedia_format_copy(&vp->conv.conv_param.src, &prm->vidparam.fmt);
294 pjmedia_format_copy(&vp->conv.conv_param.dst, &vparam.fmt);
295 }
296
297 status = create_converter(vp);
298 if (status != PJ_SUCCESS)
299 goto on_error;
300
301 if (vp->role==ROLE_ACTIVE &&
302 ((vp->dir & PJMEDIA_DIR_ENCODING) || vp->stream_role==ROLE_PASSIVE))
303 {
304 pjmedia_clock_param param;
305
306 /* Active role is wanted, but our device is passive, so create
307 * master clocks to run the media flow. For encoding direction,
308 * we also want to create our own clock since the device's clock
309 * may run at a different rate.
310 */
311 need_frame_buf = PJ_TRUE;
312
313 param.usec_interval = PJMEDIA_PTIME(&vfd->fps);
314 param.clock_rate = prm->vidparam.clock_rate;
315 status = pjmedia_clock_create2(pool, &param,
316 PJMEDIA_CLOCK_NO_HIGHEST_PRIO,
317 (vp->dir & PJMEDIA_DIR_ENCODING) ?
318 &enc_clock_cb: &dec_clock_cb,
319 vp, &vp->clock);
320 if (status != PJ_SUCCESS)
321 goto on_error;
322
323 } else if (vp->role==ROLE_PASSIVE) {
324 vid_pasv_port *pp;
325
326 /* Always need to create media port for passive role */
327 vp->pasv_port = pp = PJ_POOL_ZALLOC_T(pool, vid_pasv_port);
328 pp->vp = vp;
329 pp->base.get_frame = &vid_pasv_port_get_frame;
330 pp->base.put_frame = &vid_pasv_port_put_frame;
331 pjmedia_port_info_init2(&pp->base.info, &vp->dev_name,
332 PJMEDIA_SIG_VID_PORT,
333 prm->vidparam.dir, &prm->vidparam.fmt);
334
335 need_frame_buf = PJ_TRUE;
336 }
337
338 if (need_frame_buf) {
339 const pjmedia_video_format_info *vfi;
340 pjmedia_video_apply_fmt_param vafp;
341
342 vfi = pjmedia_get_video_format_info(NULL, vparam.fmt.id);
343 if (!vfi) {
344 status = PJ_ENOTFOUND;
345 goto on_error;
346 }
347
348 pj_bzero(&vafp, sizeof(vafp));
349 vafp.size = vparam.fmt.det.vid.size;
350 status = vfi->apply_fmt(vfi, &vafp);
351 if (status != PJ_SUCCESS)
352 goto on_error;
353
354 vp->frm_buf = PJ_POOL_ZALLOC_T(pool, pjmedia_frame);
355 vp->frm_buf_size = vafp.framebytes;
356 vp->frm_buf->buf = pj_pool_alloc(pool, vafp.framebytes);
357 vp->frm_buf->size = vp->frm_buf_size;
358 vp->frm_buf->type = PJMEDIA_FRAME_TYPE_NONE;
359
360 status = pj_mutex_create_simple(pool, vp->dev_name.ptr,
361 &vp->frm_mutex);
362 if (status != PJ_SUCCESS)
363 goto on_error;
364 }
365
366 *p_vid_port = vp;
367
368 return PJ_SUCCESS;
369
370on_error:
371 pjmedia_vid_port_destroy(vp);
372 return status;
373}
374
375PJ_DEF(void) pjmedia_vid_port_set_cb(pjmedia_vid_port *vid_port,
376 const pjmedia_vid_dev_cb *cb,
377 void *user_data)
378{
379 pj_assert(vid_port && cb);
380 pj_memcpy(&vid_port->strm_cb, cb, sizeof(*cb));
381 vid_port->strm_cb_data = user_data;
382}
383
384PJ_DEF(pjmedia_vid_dev_stream*)
385pjmedia_vid_port_get_stream(pjmedia_vid_port *vp)
386{
387 PJ_ASSERT_RETURN(vp, NULL);
388 return vp->strm;
389}
390
391
392PJ_DEF(pjmedia_port*)
393pjmedia_vid_port_get_passive_port(pjmedia_vid_port *vp)
394{
395 PJ_ASSERT_RETURN(vp && vp->role==ROLE_PASSIVE, NULL);
396 return &vp->pasv_port->base;
397}
398
399
400PJ_DEF(pjmedia_clock_src *)
401pjmedia_vid_port_get_clock_src( pjmedia_vid_port *vid_port )
402{
403 PJ_ASSERT_RETURN(vid_port, NULL);
404 return &vid_port->clocksrc;
405}
406
407PJ_DECL(pj_status_t)
408pjmedia_vid_port_set_clock_src( pjmedia_vid_port *vid_port,
409 pjmedia_clock_src *clocksrc)
410{
411 PJ_ASSERT_RETURN(vid_port && clocksrc, PJ_EINVAL);
412
413 vid_port->sync_clocksrc.sync_clocksrc = clocksrc;
414 vid_port->sync_clocksrc.sync_delta =
415 pjmedia_clock_src_get_time_msec(&vid_port->clocksrc) -
416 pjmedia_clock_src_get_time_msec(clocksrc);
417
418 return PJ_SUCCESS;
419}
420
421
422PJ_DEF(pj_status_t) pjmedia_vid_port_connect(pjmedia_vid_port *vp,
423 pjmedia_port *port,
424 pj_bool_t destroy)
425{
426 PJ_ASSERT_RETURN(vp && vp->role==ROLE_ACTIVE, PJ_EINVAL);
427 vp->destroy_client_port = destroy;
428 vp->client_port = port;
429
430 /* Subscribe to client port's events */
431 pjmedia_event_subscribe(NULL, &client_port_event_cb, vp,
432 vp->client_port);
433
434 return PJ_SUCCESS;
435}
436
437
438PJ_DEF(pj_status_t) pjmedia_vid_port_disconnect(pjmedia_vid_port *vp)
439{
440 PJ_ASSERT_RETURN(vp && vp->role==ROLE_ACTIVE, PJ_EINVAL);
441
442 pjmedia_event_unsubscribe(NULL, &client_port_event_cb, vp,
443 vp->client_port);
444 vp->client_port = NULL;
445
446 return PJ_SUCCESS;
447}
448
449
450PJ_DEF(pjmedia_port*)
451pjmedia_vid_port_get_connected_port(pjmedia_vid_port *vp)
452{
453 PJ_ASSERT_RETURN(vp && vp->role==ROLE_ACTIVE, NULL);
454 return vp->client_port;
455}
456
457PJ_DEF(pj_status_t) pjmedia_vid_port_start(pjmedia_vid_port *vp)
458{
459 pj_status_t status;
460
461 PJ_ASSERT_RETURN(vp, PJ_EINVAL);
462
463 status = pjmedia_vid_dev_stream_start(vp->strm);
464 if (status != PJ_SUCCESS)
465 goto on_error;
466
467 if (vp->clock) {
468 status = pjmedia_clock_start(vp->clock);
469 if (status != PJ_SUCCESS)
470 goto on_error;
471 }
472
473 return PJ_SUCCESS;
474
475on_error:
476 pjmedia_vid_port_stop(vp);
477 return status;
478}
479
480PJ_DEF(pj_bool_t) pjmedia_vid_port_is_running(pjmedia_vid_port *vp)
481{
482 return pjmedia_vid_dev_stream_is_running(vp->strm);
483}
484
485PJ_DEF(pj_status_t) pjmedia_vid_port_stop(pjmedia_vid_port *vp)
486{
487 pj_status_t status;
488
489 PJ_ASSERT_RETURN(vp, PJ_EINVAL);
490
491 if (vp->clock) {
492 status = pjmedia_clock_stop(vp->clock);
493 }
494
495 status = pjmedia_vid_dev_stream_stop(vp->strm);
496
497 return status;
498}
499
500PJ_DEF(void) pjmedia_vid_port_destroy(pjmedia_vid_port *vp)
501{
502 PJ_ASSERT_ON_FAIL(vp, return);
503
504 PJ_LOG(4,(THIS_FILE, "Closing %s..", vp->dev_name.ptr));
505
506 if (vp->clock) {
507 pjmedia_clock_destroy(vp->clock);
508 vp->clock = NULL;
509 }
510 if (vp->strm) {
511 pjmedia_event_unsubscribe(NULL, &vidstream_event_cb, vp, vp->strm);
512 pjmedia_vid_dev_stream_destroy(vp->strm);
513 vp->strm = NULL;
514 }
515 if (vp->client_port) {
516 pjmedia_event_unsubscribe(NULL, &client_port_event_cb, vp,
517 vp->client_port);
518 if (vp->destroy_client_port)
519 pjmedia_port_destroy(vp->client_port);
520 vp->client_port = NULL;
521 }
522 if (vp->frm_mutex) {
523 pj_mutex_destroy(vp->frm_mutex);
524 vp->frm_mutex = NULL;
525 }
526 if (vp->conv.conv) {
527 pjmedia_converter_destroy(vp->conv.conv);
528 vp->conv.conv = NULL;
529 }
530 pj_pool_release(vp->pool);
531}
532
533/*
534static void save_rgb_frame(int width, int height, const pjmedia_frame *frm)
535{
536 static int counter;
537 FILE *pFile;
538 char szFilename[32];
539 const pj_uint8_t *pFrame = (const pj_uint8_t*)frm->buf;
540 int y;
541
542 if (counter > 10)
543 return;
544
545 // Open file
546 sprintf(szFilename, "frame%02d.ppm", counter++);
547 pFile=fopen(szFilename, "wb");
548 if(pFile==NULL)
549 return;
550
551 // Write header
552 fprintf(pFile, "P6\n%d %d\n255\n", width, height);
553
554 // Write pixel data
555 for(y=0; y<height; y++)
556 fwrite(pFrame+y*width*3, 1, width*3, pFile);
557
558 // Close file
559 fclose(pFile);
560}
561*/
562
563/* Handle event from vidstream */
564static pj_status_t vidstream_event_cb(pjmedia_event *event,
565 void *user_data)
566{
567 pjmedia_vid_port *vp = (pjmedia_vid_port*)user_data;
568
569 /* Just republish the event to our client */
570 return pjmedia_event_publish(NULL, vp, event, 0);
571}
572
573static pj_status_t client_port_event_cb(pjmedia_event *event,
574 void *user_data)
575{
576 pjmedia_vid_port *vp = (pjmedia_vid_port*)user_data;
577
578 if (event->type == PJMEDIA_EVENT_FMT_CHANGED) {
579 const pjmedia_video_format_detail *vfd;
580 pjmedia_vid_dev_param vid_param;
581 pj_status_t status;
582
583 pjmedia_vid_port_stop(vp);
584
585 /* Retrieve the video format detail */
586 vfd = pjmedia_format_get_video_format_detail(
587 &event->data.fmt_changed.new_fmt, PJ_TRUE);
588 if (!vfd || !vfd->fps.num || !vfd->fps.denum)
589 return PJMEDIA_EVID_BADFORMAT;
590
591 /* Change the destination format to the new format */
592 pjmedia_format_copy(&vp->conv.conv_param.src,
593 &event->data.fmt_changed.new_fmt);
594 /* Only copy the size here */
595 vp->conv.conv_param.dst.det.vid.size =
596 event->data.fmt_changed.new_fmt.det.vid.size;
597
598 status = create_converter(vp);
599 if (status != PJ_SUCCESS) {
600 PJ_PERROR(4,(THIS_FILE, status, "Error recreating converter"));
601 return status;
602 }
603
604 pjmedia_vid_dev_stream_get_param(vp->strm, &vid_param);
605 if (vid_param.fmt.id != vp->conv.conv_param.dst.id ||
606 (vid_param.fmt.det.vid.size.h !=
607 vp->conv.conv_param.dst.det.vid.size.h) ||
608 (vid_param.fmt.det.vid.size.w !=
609 vp->conv.conv_param.dst.det.vid.size.w))
610 {
611 status = pjmedia_vid_dev_stream_set_cap(vp->strm,
612 PJMEDIA_VID_DEV_CAP_FORMAT,
613 &vp->conv.conv_param.dst);
614 if (status != PJ_SUCCESS) {
615 PJ_LOG(3, (THIS_FILE, "failure in changing the format of the "
616 "video device"));
617 PJ_LOG(3, (THIS_FILE, "reverting to its original format: %s",
618 status != PJMEDIA_EVID_ERR ? "success" :
619 "failure"));
620 return status;
621 }
622 }
623
624 if (vp->stream_role == ROLE_PASSIVE) {
625 pjmedia_clock_param clock_param;
626
627 /**
628 * Initially, frm_buf was allocated the biggest
629 * supported size, so we do not need to re-allocate
630 * the buffer here.
631 */
632 /* Adjust the clock */
633 clock_param.usec_interval = PJMEDIA_PTIME(&vfd->fps);
634 clock_param.clock_rate = vid_param.clock_rate;
635 pjmedia_clock_modify(vp->clock, &clock_param);
636 }
637
638 pjmedia_vid_port_start(vp);
639 }
640
641 /* Republish the event, post the event to the event manager
642 * to avoid deadlock if vidport is trying to stop the clock.
643 */
644 return pjmedia_event_publish(NULL, vp, event,
645 PJMEDIA_EVENT_PUBLISH_POST_EVENT);
646}
647
648static pj_status_t convert_frame(pjmedia_vid_port *vp,
649 pjmedia_frame *src_frame,
650 pjmedia_frame *dst_frame)
651{
652 pj_status_t status = PJ_SUCCESS;
653
654 if (vp->conv.conv) {
655 if (!dst_frame->buf || dst_frame->size < vp->conv.conv_buf_size) {
656 dst_frame->buf = vp->conv.conv_buf;
657 dst_frame->size = vp->conv.conv_buf_size;
658 }
659 status = pjmedia_converter_convert(vp->conv.conv,
660 src_frame, dst_frame);
661 }
662
663 return status;
664}
665
666/* Copy frame to buffer. */
667static void copy_frame_to_buffer(pjmedia_vid_port *vp,
668 pjmedia_frame *frame)
669{
670 pj_mutex_lock(vp->frm_mutex);
671 pjmedia_frame_copy(vp->frm_buf, frame);
672 pj_mutex_unlock(vp->frm_mutex);
673}
674
675/* Get frame from buffer and convert it if necessary. */
676static pj_status_t get_frame_from_buffer(pjmedia_vid_port *vp,
677 pjmedia_frame *frame)
678{
679 pj_status_t status = PJ_SUCCESS;
680
681 pj_mutex_lock(vp->frm_mutex);
682 if (vp->conv.conv)
683 status = convert_frame(vp, vp->frm_buf, frame);
684 else
685 pjmedia_frame_copy(frame, vp->frm_buf);
686 pj_mutex_unlock(vp->frm_mutex);
687
688 return status;
689}
690
691static void enc_clock_cb(const pj_timestamp *ts, void *user_data)
692{
693 /* We are here because user wants us to be active but the stream is
694 * passive. So get a frame from the stream and push it to user.
695 */
696 pjmedia_vid_port *vp = (pjmedia_vid_port*)user_data;
697 pjmedia_frame frame_;
698 pj_status_t status = PJ_SUCCESS;
699
700 pj_assert(vp->role==ROLE_ACTIVE);
701
702 PJ_UNUSED_ARG(ts);
703
704 if (!vp->client_port)
705 return;
706
707 if (vp->stream_role == ROLE_PASSIVE) {
708 while (vp->conv.usec_ctr < vp->conv.usec_dst) {
709 vp->frm_buf->size = vp->frm_buf_size;
710 status = pjmedia_vid_dev_stream_get_frame(vp->strm, vp->frm_buf);
711 vp->conv.usec_ctr += vp->conv.usec_src;
712 }
713 vp->conv.usec_ctr -= vp->conv.usec_dst;
714 if (status != PJ_SUCCESS)
715 return;
716 }
717
718 //save_rgb_frame(vp->cap_size.w, vp->cap_size.h, vp->frm_buf);
719
720 frame_.buf = vp->conv.conv_buf;
721 frame_.size = vp->conv.conv_buf_size;
722 status = get_frame_from_buffer(vp, &frame_);
723 if (status != PJ_SUCCESS)
724 return;
725
726 status = pjmedia_port_put_frame(vp->client_port, &frame_);
727 if (status != PJ_SUCCESS)
728 return;
729}
730
731static void dec_clock_cb(const pj_timestamp *ts, void *user_data)
732{
733 /* We are here because user wants us to be active but the stream is
734 * passive. So get a frame from the stream and push it to user.
735 */
736 pjmedia_vid_port *vp = (pjmedia_vid_port*)user_data;
737 pj_status_t status;
738 pjmedia_frame frame;
739
740 pj_assert(vp->role==ROLE_ACTIVE && vp->stream_role==ROLE_PASSIVE);
741
742 PJ_UNUSED_ARG(ts);
743
744 if (!vp->client_port)
745 return;
746
747 status = vidstream_render_cb(vp->strm, vp, &frame);
748 if (status != PJ_SUCCESS)
749 return;
750
751 if (frame.size > 0)
752 status = pjmedia_vid_dev_stream_put_frame(vp->strm, &frame);
753}
754
755static pj_status_t vidstream_cap_cb(pjmedia_vid_dev_stream *stream,
756 void *user_data,
757 pjmedia_frame *frame)
758{
759 pjmedia_vid_port *vp = (pjmedia_vid_port*)user_data;
760
761 /* We just store the frame in the buffer. For active role, we let
762 * video port's clock to push the frame buffer to the user.
763 * The decoding counterpart for passive role and active stream is
764 * located in vid_pasv_port_put_frame()
765 */
766 copy_frame_to_buffer(vp, frame);
767
768 /* This is tricky since the frame is still in its original unconverted
769 * format, which may not be what the application expects.
770 */
771 if (vp->strm_cb.capture_cb)
772 return (*vp->strm_cb.capture_cb)(stream, vp->strm_cb_data, frame);
773 return PJ_SUCCESS;
774}
775
776static pj_status_t vidstream_render_cb(pjmedia_vid_dev_stream *stream,
777 void *user_data,
778 pjmedia_frame *frame)
779{
780 pjmedia_vid_port *vp = (pjmedia_vid_port*)user_data;
781 pj_status_t status = PJ_SUCCESS;
782
783 pj_bzero(frame, sizeof(pjmedia_frame));
784 if (vp->role==ROLE_ACTIVE) {
785 unsigned frame_ts = vp->clocksrc.clock_rate / 1000 *
786 vp->clocksrc.ptime_usec / 1000;
787
788 if (!vp->client_port)
789 return status;
790
791 if (vp->sync_clocksrc.sync_clocksrc) {
792 pjmedia_clock_src *src = vp->sync_clocksrc.sync_clocksrc;
793 pj_int32_t diff;
794 unsigned nsync_frame;
795
796 /* Synchronization */
797 /* Calculate the time difference (in ms) with the sync source */
798 diff = pjmedia_clock_src_get_time_msec(&vp->clocksrc) -
799 pjmedia_clock_src_get_time_msec(src) -
800 vp->sync_clocksrc.sync_delta;
801
802 /* Check whether sync source made a large jump */
803 if (diff < 0 && -diff > PJMEDIA_CLOCK_SYNC_MAX_SYNC_MSEC) {
804 pjmedia_clock_src_update(&vp->clocksrc, NULL);
805 vp->sync_clocksrc.sync_delta =
806 pjmedia_clock_src_get_time_msec(src) -
807 pjmedia_clock_src_get_time_msec(&vp->clocksrc);
808 vp->sync_clocksrc.nsync_frame = 0;
809 return status;
810 }
811
812 /* Calculate the difference (in frames) with the sync source */
813 nsync_frame = abs(diff) * 1000 / vp->clocksrc.ptime_usec;
814 if (nsync_frame == 0) {
815 /* Nothing to sync */
816 vp->sync_clocksrc.nsync_frame = 0;
817 } else {
818 pj_int32_t init_sync_frame = nsync_frame;
819
820 /* Check whether it's a new sync or whether we need to reset
821 * the sync
822 */
823 if (vp->sync_clocksrc.nsync_frame == 0 ||
824 (vp->sync_clocksrc.nsync_frame > 0 &&
825 nsync_frame > vp->sync_clocksrc.nsync_frame))
826 {
827 vp->sync_clocksrc.nsync_frame = nsync_frame;
828 vp->sync_clocksrc.nsync_progress = 0;
829 } else {
830 init_sync_frame = vp->sync_clocksrc.nsync_frame;
831 }
832
833 if (diff >= 0) {
834 unsigned skip_mod;
835
836 /* We are too fast */
837 if (vp->sync_clocksrc.max_sync_ticks > 0) {
838 skip_mod = init_sync_frame /
839 vp->sync_clocksrc.max_sync_ticks + 2;
840 } else
841 skip_mod = init_sync_frame + 2;
842
843 PJ_LOG(5, (THIS_FILE, "synchronization: early by %d ms",
844 diff));
845 /* We'll play a frame every skip_mod-th tick instead of
846 * a complete pause
847 */
848 if (++vp->sync_clocksrc.nsync_progress % skip_mod > 0) {
849 pjmedia_clock_src_update(&vp->clocksrc, NULL);
850 return status;
851 }
852 } else {
853 unsigned i, ndrop = init_sync_frame;
854
855 /* We are too late, drop the frame */
856 if (vp->sync_clocksrc.max_sync_ticks > 0) {
857 ndrop /= vp->sync_clocksrc.max_sync_ticks;
858 ndrop++;
859 }
860 PJ_LOG(5, (THIS_FILE, "synchronization: late, "
861 "dropping %d frame(s)", ndrop));
862
863 if (ndrop >= nsync_frame) {
864 vp->sync_clocksrc.nsync_frame = 0;
865 ndrop = nsync_frame;
866 } else
867 vp->sync_clocksrc.nsync_progress += ndrop;
868
869 for (i = 0; i < ndrop; i++) {
870 vp->frm_buf->size = vp->frm_buf_size;
871 status = pjmedia_port_get_frame(vp->client_port,
872 vp->frm_buf);
873 if (status != PJ_SUCCESS) {
874 pjmedia_clock_src_update(&vp->clocksrc, NULL);
875 return status;
876 }
877
878 pj_add_timestamp32(&vp->clocksrc.timestamp,
879 frame_ts);
880 }
881 }
882 }
883 }
884
885 vp->frm_buf->size = vp->frm_buf_size;
886 status = pjmedia_port_get_frame(vp->client_port, vp->frm_buf);
887 if (status != PJ_SUCCESS) {
888 pjmedia_clock_src_update(&vp->clocksrc, NULL);
889 return status;
890 }
891 pj_add_timestamp32(&vp->clocksrc.timestamp, frame_ts);
892 pjmedia_clock_src_update(&vp->clocksrc, NULL);
893
894 status = convert_frame(vp, vp->frm_buf, frame);
895 if (status != PJ_SUCCESS)
896 return status;
897
898 if (!vp->conv.conv)
899 pj_memcpy(frame, vp->frm_buf, sizeof(*frame));
900 } else {
901 /* The stream is active while we are passive so we need to get the
902 * frame from the buffer.
903 * The encoding counterpart is located in vid_pasv_port_get_frame()
904 */
905 get_frame_from_buffer(vp, frame);
906 }
907 if (vp->strm_cb.render_cb)
908 return (*vp->strm_cb.render_cb)(stream, vp->strm_cb_data, frame);
909 return PJ_SUCCESS;
910}
911
912static pj_status_t vid_pasv_port_put_frame(struct pjmedia_port *this_port,
913 pjmedia_frame *frame)
914{
915 struct vid_pasv_port *vpp = (struct vid_pasv_port*)this_port;
916 pjmedia_vid_port *vp = vpp->vp;
917
918 if (vp->stream_role==ROLE_PASSIVE) {
919 /* We are passive and the stream is passive.
920 * The encoding counterpart is in vid_pasv_port_get_frame().
921 */
922 pj_status_t status;
923 pjmedia_frame frame_;
924
925 pj_bzero(&frame_, sizeof(frame_));
926 status = convert_frame(vp, frame, &frame_);
927 if (status != PJ_SUCCESS)
928 return status;
929
930 return pjmedia_vid_dev_stream_put_frame(vp->strm, (vp->conv.conv?
931 &frame_: frame));
932 } else {
933 /* We are passive while the stream is active so we just store the
934 * frame in the buffer.
935 * The encoding counterpart is located in vidstream_cap_cb()
936 */
937 copy_frame_to_buffer(vp, frame);
938 }
939
940 return PJ_SUCCESS;
941}
942
943static pj_status_t vid_pasv_port_get_frame(struct pjmedia_port *this_port,
944 pjmedia_frame *frame)
945{
946 struct vid_pasv_port *vpp = (struct vid_pasv_port*)this_port;
947 pjmedia_vid_port *vp = vpp->vp;
948 pj_status_t status = PJ_SUCCESS;
949
950 if (vp->stream_role==ROLE_PASSIVE) {
951 /* We are passive and the stream is passive.
952 * The decoding counterpart is in vid_pasv_port_put_frame().
953 */
954 status = pjmedia_vid_dev_stream_get_frame(vp->strm, (vp->conv.conv?
955 vp->frm_buf: frame));
956 if (status != PJ_SUCCESS)
957 return status;
958
959 status = convert_frame(vp, vp->frm_buf, frame);
960 } else {
961 /* The stream is active while we are passive so we need to get the
962 * frame from the buffer.
963 * The decoding counterpart is located in vidstream_rend_cb()
964 */
965 get_frame_from_buffer(vp, frame);
966 }
967
968 return status;
969}
970
971
972#endif /* PJMEDIA_HAS_VIDEO */