blob: 873f0e26c8281af18f21a5f3db032eacbd7cbba4 [file] [log] [blame]
/* $Id$ */
/*
* Copyright (C) 2008-2010 Teluu Inc. (http://www.teluu.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <pjmedia/videoport.h>
#include <pjmedia/clock.h>
#include <pjmedia/converter.h>
#include <pjmedia/errno.h>
#include <pjmedia/vid_codec.h>
#include <pj/log.h>
#include <pj/pool.h>
#define THIS_FILE "videoport.c"
typedef struct vid_pasv_port vid_pasv_port;
enum role
{
ROLE_NONE,
ROLE_ACTIVE,
ROLE_PASSIVE
};
struct pjmedia_vid_port
{
pj_str_t dev_name;
pjmedia_dir dir;
pjmedia_rect_size cap_size;
pjmedia_vid_dev_stream *strm;
pjmedia_vid_cb strm_cb;
void *strm_cb_data;
enum role role,
stream_role;
vid_pasv_port *pasv_port;
pjmedia_port *client_port;
pj_bool_t destroy_client_port;
pjmedia_converter *cap_conv;
void *cap_conv_buf;
pj_size_t cap_conv_buf_size;
pjmedia_clock *enc_clock,
*dec_clock;
pjmedia_clock_src cap_clocksrc,
rend_clocksrc;
struct sync_clock_src_t
{
pjmedia_clock_src *sync_clocksrc;
pj_int32_t sync_delta;
unsigned max_sync_ticks;
unsigned nsync_frame;
unsigned nsync_progress;
} cap_sync_clocksrc, rend_sync_clocksrc;
pjmedia_frame *enc_frm_buf,
*dec_frm_buf;
pj_size_t enc_frm_buf_size,
dec_frm_buf_size;
pj_mutex_t *enc_frm_mutex,
*dec_frm_mutex;
};
struct vid_pasv_port
{
pjmedia_port base;
pjmedia_vid_port *vp;
};
static pj_status_t vidstream_cap_cb(pjmedia_vid_dev_stream *stream,
void *user_data,
pjmedia_frame *frame);
static pj_status_t vidstream_render_cb(pjmedia_vid_dev_stream *stream,
void *user_data,
pjmedia_frame *frame);
static pj_status_t vidstream_event_cb(pjmedia_vid_dev_stream *stream,
void *user_data,
pjmedia_vid_event *event);
static void enc_clock_cb(const pj_timestamp *ts, void *user_data);
static void dec_clock_cb(const pj_timestamp *ts, void *user_data);
static pj_status_t vid_pasv_port_put_frame(struct pjmedia_port *this_port,
pjmedia_frame *frame);
static pj_status_t vid_pasv_port_get_frame(struct pjmedia_port *this_port,
pjmedia_frame *frame);
PJ_DEF(void) pjmedia_vid_port_param_default(pjmedia_vid_port_param *prm)
{
pj_bzero(prm, sizeof(*prm));
prm->active = PJ_TRUE;
}
PJ_DEF(pj_status_t) pjmedia_vid_port_create( pj_pool_t *pool,
const pjmedia_vid_port_param *prm,
pjmedia_vid_port **p_vid_port)
{
pjmedia_vid_port *vp;
pjmedia_vid_dev_index dev_id = PJMEDIA_VID_INVALID_DEV;
pjmedia_vid_dev_info di;
const pjmedia_video_format_detail *vfd;
pjmedia_vid_cb vid_cb;
pj_bool_t need_frame_buf = PJ_FALSE;
pj_status_t status;
unsigned ptime_usec;
pjmedia_vid_param vparam;
PJ_ASSERT_RETURN(pool && prm && p_vid_port, PJ_EINVAL);
PJ_ASSERT_RETURN(prm->vidparam.fmt.type == PJMEDIA_TYPE_VIDEO,
PJ_EINVAL);
/* Retrieve the video format detail */
vfd = pjmedia_format_get_video_format_detail(&prm->vidparam.fmt, PJ_TRUE);
if (!vfd)
return PJ_EINVAL;
PJ_ASSERT_RETURN(vfd->fps.num, PJ_EINVAL);
/* Allocate videoport */
vp = PJ_POOL_ZALLOC_T(pool, pjmedia_vid_port);
vp->role = prm->active ? ROLE_ACTIVE : ROLE_PASSIVE;
vp->dir = prm->vidparam.dir;
vp->cap_size = vfd->size;
/* Determine the device id to get the video device info */
if ((vp->dir & PJMEDIA_DIR_CAPTURE) &&
prm->vidparam.cap_id != PJMEDIA_VID_INVALID_DEV)
{
dev_id = prm->vidparam.cap_id;
} else if ((vp->dir & PJMEDIA_DIR_RENDER) &&
prm->vidparam.rend_id != PJMEDIA_VID_INVALID_DEV)
{
dev_id = prm->vidparam.rend_id;
} else
return PJ_EINVAL;
/* Get device info */
status = pjmedia_vid_dev_get_info(dev_id, &di);
if (status != PJ_SUCCESS)
return status;
vparam = prm->vidparam;
/* Check if we need converter */
if (vp->dir & PJMEDIA_DIR_CAPTURE) {
unsigned i;
for (i = 0; i < di.fmt_cnt; ++i) {
if (prm->vidparam.fmt.id == di.fmt[i].id)
break;
}
if (i == di.fmt_cnt) {
/* Yes, we need converter */
pjmedia_conversion_param conv_param;
const pjmedia_video_format_info *vfi;
pjmedia_video_apply_fmt_param vafp;
pjmedia_format_copy(&conv_param.src, &prm->vidparam.fmt);
pjmedia_format_copy(&conv_param.dst, &prm->vidparam.fmt);
for (i = 0; i < di.fmt_cnt; ++i) {
conv_param.src.id = di.fmt[i].id;
status = pjmedia_converter_create(NULL, pool, &conv_param,
&vp->cap_conv);
if (status == PJ_SUCCESS)
break;
}
if (status != PJ_SUCCESS)
return status;
/* Update format ID for the capture device */
vparam.fmt.id = conv_param.src.id;
/* Allocate buffer for conversion */
vfi = pjmedia_get_video_format_info(NULL, conv_param.dst.id);
if (!vfi)
return PJMEDIA_EBADFMT;
pj_bzero(&vafp, sizeof(vafp));
vafp.size = vfd->size;
status = vfi->apply_fmt(vfi, &vafp);
if (status != PJ_SUCCESS)
return PJMEDIA_EBADFMT;
vp->cap_conv_buf = pj_pool_alloc(pool, vafp.framebytes);
vp->cap_conv_buf_size = vafp.framebytes;
}
}
PJ_LOG(4,(THIS_FILE, "Opening %s..", di.name));
pj_strdup2_with_null(pool, &vp->dev_name, di.name);
vp->stream_role = di.has_callback ? ROLE_ACTIVE : ROLE_PASSIVE;
ptime_usec = PJMEDIA_PTIME(&vfd->fps);
pjmedia_clock_src_init(&vp->cap_clocksrc, PJMEDIA_TYPE_VIDEO,
prm->vidparam.clock_rate, ptime_usec);
pjmedia_clock_src_init(&vp->rend_clocksrc, PJMEDIA_TYPE_VIDEO,
prm->vidparam.clock_rate, ptime_usec);
vp->cap_sync_clocksrc.max_sync_ticks =
PJMEDIA_CLOCK_SYNC_MAX_RESYNC_DURATION *
1000 / vp->cap_clocksrc.ptime_usec;
vp->rend_sync_clocksrc.max_sync_ticks =
PJMEDIA_CLOCK_SYNC_MAX_RESYNC_DURATION *
1000 / vp->rend_clocksrc.ptime_usec;
/* Create the video stream */
pj_bzero(&vid_cb, sizeof(vid_cb));
vid_cb.capture_cb = &vidstream_cap_cb;
vid_cb.render_cb = &vidstream_render_cb;
vid_cb.on_event_cb = &vidstream_event_cb;
status = pjmedia_vid_dev_stream_create(&vparam, &vid_cb, vp,
&vp->strm);
if (status != PJ_SUCCESS)
goto on_error;
if (vp->role==ROLE_ACTIVE && vp->stream_role==ROLE_PASSIVE) {
/* Active role is wanted, but our device is passive, so create
* master clocks to run the media flow.
*/
need_frame_buf = PJ_TRUE;
if (vp->dir & PJMEDIA_DIR_ENCODING) {
pjmedia_clock_param param;
param.usec_interval = PJMEDIA_PTIME(&vfd->fps);
param.clock_rate = prm->vidparam.clock_rate;
status = pjmedia_clock_create2(pool, &param,
PJMEDIA_CLOCK_NO_HIGHEST_PRIO,
&enc_clock_cb, vp, &vp->enc_clock);
if (status != PJ_SUCCESS)
goto on_error;
}
if (vp->dir & PJMEDIA_DIR_DECODING) {
pjmedia_clock_param param;
param.usec_interval = PJMEDIA_PTIME(&vfd->fps);
param.clock_rate = prm->vidparam.clock_rate;
status = pjmedia_clock_create2(pool, &param,
PJMEDIA_CLOCK_NO_HIGHEST_PRIO,
&dec_clock_cb, vp, &vp->dec_clock);
if (status != PJ_SUCCESS)
goto on_error;
}
} else if (vp->role==ROLE_PASSIVE) {
vid_pasv_port *pp;
/* Always need to create media port for passive role */
vp->pasv_port = pp = PJ_POOL_ZALLOC_T(pool, vid_pasv_port);
pp->vp = vp;
pp->base.get_frame = &vid_pasv_port_get_frame;
pp->base.put_frame = &vid_pasv_port_put_frame;
pjmedia_port_info_init2(&pp->base.info, &vp->dev_name,
PJMEDIA_PORT_SIGNATURE('v', 'i', 'd', 'p'),
prm->vidparam.dir, &prm->vidparam.fmt);
if (vp->stream_role == ROLE_ACTIVE || vp->cap_conv) {
need_frame_buf = PJ_TRUE;
}
}
if (need_frame_buf) {
const pjmedia_video_format_info *vfi;
pjmedia_video_apply_fmt_param vafp;
vfi = pjmedia_get_video_format_info(NULL, vparam.fmt.id);
if (!vfi) {
status = PJ_ENOTFOUND;
goto on_error;
}
pj_bzero(&vafp, sizeof(vafp));
vafp.size = vfd->size;
status = vfi->apply_fmt(vfi, &vafp);
if (status != PJ_SUCCESS)
goto on_error;
if (vp->dir & PJMEDIA_DIR_ENCODING) {
vp->enc_frm_buf = PJ_POOL_ZALLOC_T(pool, pjmedia_frame);
vp->enc_frm_buf_size = vafp.framebytes;
vp->enc_frm_buf->buf = pj_pool_alloc(pool, vafp.framebytes);
vp->enc_frm_buf->size = vafp.framebytes;
vp->enc_frm_buf->type = PJMEDIA_FRAME_TYPE_NONE;
status = pj_mutex_create_simple(pool, vp->dev_name.ptr,
&vp->enc_frm_mutex);
if (status != PJ_SUCCESS)
goto on_error;
}
if (vp->dir & PJMEDIA_DIR_DECODING) {
vp->dec_frm_buf = PJ_POOL_ZALLOC_T(pool, pjmedia_frame);
vp->dec_frm_buf_size = vafp.framebytes;
vp->dec_frm_buf->buf = pj_pool_alloc(pool, vafp.framebytes);
vp->dec_frm_buf->size = vafp.framebytes;
vp->dec_frm_buf->type = PJMEDIA_FRAME_TYPE_NONE;
status = pj_mutex_create_simple(pool, vp->dev_name.ptr,
&vp->dec_frm_mutex);
if (status != PJ_SUCCESS)
goto on_error;
}
}
*p_vid_port = vp;
return PJ_SUCCESS;
on_error:
pjmedia_vid_port_destroy(vp);
return status;
}
PJ_DEF(void) pjmedia_vid_port_set_cb(pjmedia_vid_port *vid_port,
const pjmedia_vid_cb *cb,
void *user_data)
{
pj_assert(vid_port && cb);
pj_memcpy(&vid_port->strm_cb, cb, sizeof(*cb));
vid_port->strm_cb_data = user_data;
}
PJ_DEF(pjmedia_vid_dev_stream*)
pjmedia_vid_port_get_stream(pjmedia_vid_port *vp)
{
PJ_ASSERT_RETURN(vp, NULL);
return vp->strm;
}
PJ_DEF(pjmedia_port*)
pjmedia_vid_port_get_passive_port(pjmedia_vid_port *vp)
{
PJ_ASSERT_RETURN(vp && vp->role==ROLE_PASSIVE, NULL);
return &vp->pasv_port->base;
}
PJ_DEF(pjmedia_clock_src *)
pjmedia_vid_port_get_clock_src( pjmedia_vid_port *vid_port,
pjmedia_dir dir )
{
return (dir == PJMEDIA_DIR_CAPTURE? &vid_port->cap_clocksrc:
&vid_port->rend_clocksrc);
}
PJ_DECL(pj_status_t)
pjmedia_vid_port_set_clock_src( pjmedia_vid_port *vid_port,
pjmedia_dir dir,
pjmedia_clock_src *clocksrc)
{
pjmedia_clock_src *vclocksrc;
struct sync_clock_src_t *sync_src;
PJ_ASSERT_RETURN(vid_port && clocksrc, PJ_EINVAL);
vclocksrc = (dir == PJMEDIA_DIR_CAPTURE? &vid_port->cap_clocksrc:
&vid_port->rend_clocksrc);
sync_src = (dir == PJMEDIA_DIR_CAPTURE? &vid_port->cap_sync_clocksrc:
&vid_port->rend_sync_clocksrc);
sync_src->sync_clocksrc = clocksrc;
sync_src->sync_delta = pjmedia_clock_src_get_time_msec(vclocksrc) -
pjmedia_clock_src_get_time_msec(clocksrc);
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t) pjmedia_vid_port_connect(pjmedia_vid_port *vp,
pjmedia_port *port,
pj_bool_t destroy)
{
PJ_ASSERT_RETURN(vp && vp->role==ROLE_ACTIVE, PJ_EINVAL);
vp->destroy_client_port = destroy;
vp->client_port = port;
return PJ_SUCCESS;
}
PJ_DEF(pj_status_t) pjmedia_vid_port_disconnect(pjmedia_vid_port *vp)
{
PJ_ASSERT_RETURN(vp && vp->role==ROLE_ACTIVE, PJ_EINVAL);
vp->client_port = NULL;
return PJ_SUCCESS;
}
PJ_DEF(pjmedia_port*)
pjmedia_vid_port_get_connected_port(pjmedia_vid_port *vp)
{
PJ_ASSERT_RETURN(vp && vp->role==ROLE_ACTIVE, NULL);
return vp->client_port;
}
PJ_DEF(pj_status_t) pjmedia_vid_port_start(pjmedia_vid_port *vp)
{
pj_status_t status;
PJ_ASSERT_RETURN(vp, PJ_EINVAL);
status = pjmedia_vid_dev_stream_start(vp->strm);
if (status != PJ_SUCCESS)
goto on_error;
if (vp->enc_clock) {
status = pjmedia_clock_start(vp->enc_clock);
if (status != PJ_SUCCESS)
goto on_error;
}
if (vp->dec_clock) {
status = pjmedia_clock_start(vp->dec_clock);
if (status != PJ_SUCCESS)
goto on_error;
}
return PJ_SUCCESS;
on_error:
pjmedia_vid_port_stop(vp);
return status;
}
PJ_DEF(pj_status_t) pjmedia_vid_port_stop(pjmedia_vid_port *vp)
{
pj_status_t status;
PJ_ASSERT_RETURN(vp, PJ_EINVAL);
status = pjmedia_vid_dev_stream_stop(vp->strm);
if (vp->enc_clock) {
status = pjmedia_clock_stop(vp->enc_clock);
}
if (vp->dec_clock) {
status = pjmedia_clock_stop(vp->dec_clock);
}
return status;
}
PJ_DEF(void) pjmedia_vid_port_destroy(pjmedia_vid_port *vp)
{
PJ_ASSERT_ON_FAIL(vp, return);
PJ_LOG(4,(THIS_FILE, "Closing %s..", vp->dev_name.ptr));
if (vp->enc_clock) {
pjmedia_clock_destroy(vp->enc_clock);
vp->enc_clock = NULL;
}
if (vp->dec_clock) {
pjmedia_clock_destroy(vp->dec_clock);
vp->dec_clock = NULL;
}
if (vp->strm) {
pjmedia_vid_dev_stream_destroy(vp->strm);
vp->strm = NULL;
}
if (vp->client_port) {
if (vp->destroy_client_port)
pjmedia_port_destroy(vp->client_port);
vp->client_port = NULL;
}
if (vp->enc_frm_mutex) {
pj_mutex_destroy(vp->enc_frm_mutex);
vp->enc_frm_mutex = NULL;
}
if (vp->dec_frm_mutex) {
pj_mutex_destroy(vp->dec_frm_mutex);
vp->dec_frm_mutex = NULL;
}
}
/*
static void save_rgb_frame(int width, int height, const pjmedia_frame *frm)
{
static int counter;
FILE *pFile;
char szFilename[32];
const pj_uint8_t *pFrame = (const pj_uint8_t*)frm->buf;
int y;
if (counter > 10)
return;
// Open file
sprintf(szFilename, "frame%02d.ppm", counter++);
pFile=fopen(szFilename, "wb");
if(pFile==NULL)
return;
// Write header
fprintf(pFile, "P6\n%d %d\n255\n", width, height);
// Write pixel data
for(y=0; y<height; y++)
fwrite(pFrame+y*width*3, 1, width*3, pFile);
// Close file
fclose(pFile);
}
*/
static pj_status_t detect_fmt_change(pjmedia_vid_port *vp,
pjmedia_frame *frame)
{
if (frame->bit_info & PJMEDIA_VID_CODEC_EVENT_FMT_CHANGED) {
const pjmedia_video_format_detail *vfd;
pjmedia_vid_event pevent;
pj_status_t status;
/* Retrieve the video format detail */
vfd = pjmedia_format_get_video_format_detail(
&vp->client_port->info.fmt, PJ_TRUE);
if (!vfd)
return PJMEDIA_EVID_BADFORMAT;
pj_assert(vfd->fps.num);
status = pjmedia_vid_dev_stream_set_cap(
vp->strm,
PJMEDIA_VID_DEV_CAP_FORMAT,
&vp->client_port->info.fmt);
if (status != PJ_SUCCESS) {
PJ_LOG(3, (THIS_FILE, "failure in changing the format of the "
"video device"));
PJ_LOG(3, (THIS_FILE, "reverting to its original format: %s",
status != PJMEDIA_EVID_ERR ? "success" :
"failure"));
pjmedia_vid_port_stop(vp);
return status;
}
if (vp->stream_role == ROLE_PASSIVE) {
pjmedia_vid_param vid_param;
pjmedia_clock_param clock_param;
/**
* Initially, dec_frm_buf was allocated the biggest
* supported size, so we do not need to re-allocate
* the buffer here.
*/
/* Adjust the clock */
pjmedia_vid_dev_stream_get_param(vp->strm, &vid_param);
clock_param.usec_interval = PJMEDIA_PTIME(&vfd->fps);
clock_param.clock_rate = vid_param.clock_rate;
pjmedia_clock_modify(vp->dec_clock, &clock_param);
}
/* Notify application of the format change. */
pevent.event_type = PJMEDIA_EVENT_FMT_CHANGED;
pj_memcpy(&pevent.event_desc.fmt_change.new_format,
&vp->client_port->info.fmt, sizeof(pjmedia_format));
if (vp->strm_cb.on_event_cb)
(*vp->strm_cb.on_event_cb)(vp->strm, vp->strm_cb_data, &pevent);
}
return PJ_SUCCESS;
}
static void enc_clock_cb(const pj_timestamp *ts, void *user_data)
{
/* We are here because user wants us to be active but the stream is
* passive. So get a frame from the stream and push it to user.
*/
pjmedia_vid_port *vp = (pjmedia_vid_port*)user_data;
pjmedia_frame frame;
pj_status_t status;
pj_assert(vp->role==ROLE_ACTIVE && vp->stream_role==ROLE_PASSIVE);
PJ_UNUSED_ARG(ts);
if (!vp->client_port)
return;
vp->enc_frm_buf->size = vp->enc_frm_buf_size;
status = pjmedia_vid_dev_stream_get_frame(vp->strm, vp->enc_frm_buf);
if (status != PJ_SUCCESS)
return;
//save_rgb_frame(vp->cap_size.w, vp->cap_size.h, vp->enc_frm_buf);
frame = *vp->enc_frm_buf;
if (vp->cap_conv) {
frame.buf = vp->cap_conv_buf;
frame.size = vp->cap_conv_buf_size;
status = pjmedia_converter_convert(vp->cap_conv,
vp->enc_frm_buf, &frame);
if (status != PJ_SUCCESS)
return;
}
status = pjmedia_port_put_frame(vp->client_port, &frame);
}
static void dec_clock_cb(const pj_timestamp *ts, void *user_data)
{
/* We are here because user wants us to be active but the stream is
* passive. So get a frame from the stream and push it to user.
*/
pjmedia_vid_port *vp = (pjmedia_vid_port*)user_data;
pj_status_t status;
unsigned frame_ts = vp->rend_clocksrc.clock_rate / 1000 *
vp->rend_clocksrc.ptime_usec / 1000;
pj_assert(vp->role==ROLE_ACTIVE && vp->stream_role==ROLE_PASSIVE);
PJ_UNUSED_ARG(ts);
if (!vp->client_port)
return;
if (vp->rend_sync_clocksrc.sync_clocksrc) {
pjmedia_clock_src *src = vp->rend_sync_clocksrc.sync_clocksrc;
pj_int32_t diff;
unsigned nsync_frame;
/* Synchronization */
/* Calculate the time difference (in ms) with the sync source */
diff = pjmedia_clock_src_get_time_msec(&vp->rend_clocksrc) -
pjmedia_clock_src_get_time_msec(src) -
vp->rend_sync_clocksrc.sync_delta;
/* Check whether sync source made a large jump */
if (diff < 0 && -diff > PJMEDIA_CLOCK_SYNC_MAX_SYNC_MSEC) {
pjmedia_clock_src_update(&vp->rend_clocksrc, NULL);
vp->rend_sync_clocksrc.sync_delta =
pjmedia_clock_src_get_time_msec(src) -
pjmedia_clock_src_get_time_msec(&vp->rend_clocksrc);
vp->rend_sync_clocksrc.nsync_frame = 0;
return;
}
/* Calculate the difference (in frames) with the sync source */
nsync_frame = abs(diff) * 1000 / vp->rend_clocksrc.ptime_usec;
if (nsync_frame == 0) {
/* Nothing to sync */
vp->rend_sync_clocksrc.nsync_frame = 0;
} else {
pj_int32_t init_sync_frame = nsync_frame;
/* Check whether it's a new sync or whether we need to reset
* the sync
*/
if (vp->rend_sync_clocksrc.nsync_frame == 0 ||
(vp->rend_sync_clocksrc.nsync_frame > 0 &&
nsync_frame > vp->rend_sync_clocksrc.nsync_frame))
{
vp->rend_sync_clocksrc.nsync_frame = nsync_frame;
vp->rend_sync_clocksrc.nsync_progress = 0;
} else {
init_sync_frame = vp->rend_sync_clocksrc.nsync_frame;
}
if (diff >= 0) {
unsigned skip_mod;
/* We are too fast */
if (vp->rend_sync_clocksrc.max_sync_ticks > 0) {
skip_mod = init_sync_frame /
vp->rend_sync_clocksrc.max_sync_ticks + 2;
} else
skip_mod = init_sync_frame + 2;
PJ_LOG(5, (THIS_FILE, "synchronization: early by %d ms",
diff));
/* We'll play a frame every skip_mod-th tick instead of
* a complete pause
*/
if (++vp->rend_sync_clocksrc.nsync_progress % skip_mod > 0) {
pjmedia_clock_src_update(&vp->rend_clocksrc, NULL);
return;
}
} else {
unsigned i, ndrop = init_sync_frame;
/* We are too late, drop the frame */
if (vp->rend_sync_clocksrc.max_sync_ticks > 0) {
ndrop /= vp->rend_sync_clocksrc.max_sync_ticks;
ndrop++;
}
PJ_LOG(5, (THIS_FILE, "synchronization: late, "
"dropping %d frame(s)", ndrop));
if (ndrop >= nsync_frame) {
vp->rend_sync_clocksrc.nsync_frame = 0;
ndrop = nsync_frame;
} else
vp->rend_sync_clocksrc.nsync_progress += ndrop;
for (i = 0; i < ndrop; i++) {
vp->dec_frm_buf->size = vp->dec_frm_buf_size;
status = pjmedia_port_get_frame(vp->client_port,
vp->dec_frm_buf);
if (status != PJ_SUCCESS) {
pjmedia_clock_src_update(&vp->rend_clocksrc, NULL);
return;
}
status = detect_fmt_change(vp, vp->dec_frm_buf);
if (status != PJ_SUCCESS)
return;
pj_add_timestamp32(&vp->rend_clocksrc.timestamp,
frame_ts);
}
}
}
}
vp->dec_frm_buf->size = vp->dec_frm_buf_size;
status = pjmedia_port_get_frame(vp->client_port, vp->dec_frm_buf);
if (status != PJ_SUCCESS) {
pjmedia_clock_src_update(&vp->rend_clocksrc, NULL);
return;
}
pj_add_timestamp32(&vp->rend_clocksrc.timestamp, frame_ts);
pjmedia_clock_src_update(&vp->rend_clocksrc, NULL);
status = detect_fmt_change(vp, vp->dec_frm_buf);
if (status != PJ_SUCCESS)
return;
status = pjmedia_vid_dev_stream_put_frame(vp->strm, vp->dec_frm_buf);
}
static void copy_frame(pjmedia_frame *dst, const pjmedia_frame *src)
{
PJ_ASSERT_ON_FAIL(dst->size >= src->size, return);
pj_memcpy(dst, src, sizeof(*src));
pj_memcpy(dst->buf, src->buf, src->size);
dst->size = src->size;
}
static pj_status_t vidstream_cap_cb(pjmedia_vid_dev_stream *stream,
void *user_data,
pjmedia_frame *frame)
{
pjmedia_vid_port *vp = (pjmedia_vid_port*)user_data;
pjmedia_frame frame_;
frame_ = *frame;
if (vp->cap_conv) {
pj_status_t status;
frame_.buf = vp->cap_conv_buf;
frame_.size = vp->cap_conv_buf_size;
status = pjmedia_converter_convert(vp->cap_conv,
frame, &frame_);
if (status != PJ_SUCCESS)
return status;
}
if (vp->role==ROLE_ACTIVE) {
if (vp->client_port)
return pjmedia_port_put_frame(vp->client_port, &frame_);
} else {
pj_mutex_lock(vp->enc_frm_mutex);
copy_frame(vp->enc_frm_buf, &frame_);
pj_mutex_unlock(vp->enc_frm_mutex);
}
if (vp->strm_cb.capture_cb)
return (*vp->strm_cb.capture_cb)(stream, vp->strm_cb_data, &frame_);
return PJ_SUCCESS;
}
static pj_status_t vidstream_render_cb(pjmedia_vid_dev_stream *stream,
void *user_data,
pjmedia_frame *frame)
{
pjmedia_vid_port *vp = (pjmedia_vid_port*)user_data;
if (vp->role==ROLE_ACTIVE) {
if (vp->client_port) {
pj_status_t status;
status = pjmedia_port_get_frame(vp->client_port, frame);
if (status != PJ_SUCCESS)
return status;
return detect_fmt_change(vp, frame);
}
} else {
pj_mutex_lock(vp->dec_frm_mutex);
copy_frame(frame, vp->dec_frm_buf);
pj_mutex_unlock(vp->dec_frm_mutex);
}
if (vp->strm_cb.render_cb)
return (*vp->strm_cb.render_cb)(stream, vp->strm_cb_data, frame);
return PJ_SUCCESS;
}
static pj_status_t vidstream_event_cb(pjmedia_vid_dev_stream *stream,
void *user_data,
pjmedia_vid_event *event)
{
pjmedia_vid_port *vp = (pjmedia_vid_port*)user_data;
if (vp->strm_cb.on_event_cb)
return (*vp->strm_cb.on_event_cb)(stream, vp->strm_cb_data, event);
return PJ_SUCCESS;
}
static pj_status_t vid_pasv_port_put_frame(struct pjmedia_port *this_port,
pjmedia_frame *frame)
{
struct vid_pasv_port *vpp = (struct vid_pasv_port*)this_port;
pjmedia_vid_port *vp = vpp->vp;
if (vp->stream_role==ROLE_PASSIVE) {
return pjmedia_vid_dev_stream_put_frame(vp->strm, frame);
} else {
pj_mutex_lock(vp->dec_frm_mutex);
copy_frame(vp->dec_frm_buf, frame);
pj_mutex_unlock(vp->dec_frm_mutex);
}
return PJ_SUCCESS;
}
static pj_status_t vid_pasv_port_get_frame(struct pjmedia_port *this_port,
pjmedia_frame *frame)
{
struct vid_pasv_port *vpp = (struct vid_pasv_port*)this_port;
pjmedia_vid_port *vp = vpp->vp;
if (vp->stream_role==ROLE_PASSIVE) {
if (vp->cap_conv) {
pj_status_t status;
vp->enc_frm_buf->size = vp->enc_frm_buf_size;
status = pjmedia_vid_dev_stream_get_frame(vp->strm, vp->enc_frm_buf);
if (status != PJ_SUCCESS)
return status;
status = pjmedia_converter_convert(vp->cap_conv,
vp->enc_frm_buf, frame);
if (status != PJ_SUCCESS)
return status;
} else {
return pjmedia_vid_dev_stream_get_frame(vp->strm, frame);
}
} else {
pj_mutex_lock(vp->enc_frm_mutex);
copy_frame(frame, vp->enc_frm_buf);
pj_mutex_unlock(vp->enc_frm_mutex);
}
return PJ_SUCCESS;
}