summaryrefslogtreecommitdiff
path: root/pjmedia/src/test
diff options
context:
space:
mode:
authorNanang Izzuddin <nanang@teluu.com>2011-07-19 03:42:28 +0000
committerNanang Izzuddin <nanang@teluu.com>2011-07-19 03:42:28 +0000
commitcd283c8825c9a94400f27735acb1c9385e90ffc8 (patch)
tree56d5722310fa8957ce5d1ba7cbd137cf8802dcc7 /pjmedia/src/test
parented8f8d08abba9040f769e922aa0c1adbde86fbbc (diff)
Re #1326: Initial code integration from branch 2.0-dev to trunk as "2.0-pre-alpha-svn".
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@3664 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjmedia/src/test')
-rw-r--r--pjmedia/src/test/codec_vectors.c26
-rw-r--r--pjmedia/src/test/main.c10
-rw-r--r--pjmedia/src/test/mips_test.c32
-rw-r--r--pjmedia/src/test/test.c23
-rw-r--r--pjmedia/src/test/test.h6
-rw-r--r--pjmedia/src/test/vid_codec_test.c467
-rw-r--r--pjmedia/src/test/vid_dev_test.c292
-rw-r--r--pjmedia/src/test/vid_port_test.c241
8 files changed, 1065 insertions, 32 deletions
diff --git a/pjmedia/src/test/codec_vectors.c b/pjmedia/src/test/codec_vectors.c
index b53850b6..5257b5a7 100644
--- a/pjmedia/src/test/codec_vectors.c
+++ b/pjmedia/src/test/codec_vectors.c
@@ -73,13 +73,13 @@ static int codec_test_encode(pjmedia_codec_mgr *mgr,
codec_param.info.avg_bps = bitrate;
codec_param.setting.vad = 0;
- status = codec->op->init(codec, pool);
+ status = pjmedia_codec_init(codec, pool);
if (status != PJ_SUCCESS) {
rc = -60;
goto on_return;
}
- status = codec->op->open(codec, &codec_param);
+ status = pjmedia_codec_open(codec, &codec_param);
if (status != PJ_SUCCESS) {
rc = -70;
goto on_return;
@@ -117,7 +117,7 @@ static int codec_test_encode(pjmedia_codec_mgr *mgr,
break;
out_frame.size = samples_per_frame;
- status = codec->op->encode(codec, &in_frame, samples_per_frame,
+ status = pjmedia_codec_encode(codec, &in_frame, samples_per_frame,
&out_frame);
if (status != PJ_SUCCESS) {
rc = -95;
@@ -188,7 +188,7 @@ on_return:
fclose(fref);
if (codec) {
- codec->op->close(codec);
+ pjmedia_codec_close(codec);
pjmedia_codec_mgr_dealloc_codec(mgr, codec);
}
@@ -326,13 +326,13 @@ static int codec_test_decode(pjmedia_codec_mgr *mgr,
codec_param.info.avg_bps = bitrate;
codec_param.setting.vad = 0;
- status = codec->op->init(codec, pool);
+ status = pjmedia_codec_init(codec, pool);
if (status != PJ_SUCCESS) {
rc = -60;
goto on_return;
}
- status = codec->op->open(codec, &codec_param);
+ status = pjmedia_codec_open(codec, &codec_param);
if (status != PJ_SUCCESS) {
rc = -70;
goto on_return;
@@ -387,8 +387,8 @@ static int codec_test_decode(pjmedia_codec_mgr *mgr,
if (has_frame) {
count = 2;
- if (codec->op->parse(codec, pkt, encoded_len, &ts,
- &count, in_frame) != PJ_SUCCESS)
+ if (pjmedia_codec_parse(codec, pkt, encoded_len, &ts,
+ &count, in_frame) != PJ_SUCCESS)
{
rc = -100;
goto on_return;
@@ -399,15 +399,15 @@ static int codec_test_decode(pjmedia_codec_mgr *mgr,
goto on_return;
}
- if (codec->op->decode(codec, &in_frame[0], samples_per_frame*2,
- &out_frame) != PJ_SUCCESS)
+ if (pjmedia_codec_decode(codec, &in_frame[0], samples_per_frame*2,
+ &out_frame) != PJ_SUCCESS)
{
rc = -120;
goto on_return;
}
} else {
- if (codec->op->recover(codec, samples_per_frame*2,
- &out_frame) != PJ_SUCCESS)
+ if (pjmedia_codec_recover(codec, samples_per_frame*2,
+ &out_frame) != PJ_SUCCESS)
{
rc = -125;
goto on_return;
@@ -483,7 +483,7 @@ on_return:
fclose(input);
if (codec) {
- codec->op->close(codec);
+ pjmedia_codec_close(codec);
pjmedia_codec_mgr_dealloc_codec(mgr, codec);
}
diff --git a/pjmedia/src/test/main.c b/pjmedia/src/test/main.c
index b48f7d77..6ea1595c 100644
--- a/pjmedia/src/test/main.c
+++ b/pjmedia/src/test/main.c
@@ -17,6 +17,8 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
+#include <pj/os.h>
+
#include "test.h"
@@ -29,8 +31,7 @@
# include "../../../pjlib/include/rtems-network-config.h"
#endif
-
-int main(int argc, char *argv[])
+static int main_func(int argc, char *argv[])
{
int rc;
char s[10];
@@ -46,4 +47,7 @@ int main(int argc, char *argv[])
return rc;
}
-
+int main(int argc, char *argv[])
+{
+ return pj_run_app(&main_func, argc, argv, 0);
+}
diff --git a/pjmedia/src/test/mips_test.c b/pjmedia/src/test/mips_test.c
index d5ece9c7..31a33ed1 100644
--- a/pjmedia/src/test/mips_test.c
+++ b/pjmedia/src/test/mips_test.c
@@ -684,7 +684,7 @@ struct codec_port
static pj_status_t codec_put_frame(struct pjmedia_port *this_port,
- const pjmedia_frame *frame)
+ pjmedia_frame *frame)
{
struct codec_port *cp = (struct codec_port*)this_port;
pjmedia_frame out_frame;
@@ -692,8 +692,8 @@ static pj_status_t codec_put_frame(struct pjmedia_port *this_port,
out_frame.buf = cp->pkt;
out_frame.size = sizeof(cp->pkt);
- status = cp->codec->op->encode(cp->codec, frame, sizeof(cp->pkt),
- &out_frame);
+ status = pjmedia_codec_encode(cp->codec, frame, sizeof(cp->pkt),
+ &out_frame);
pj_assert(status == PJ_SUCCESS);
if (out_frame.size != 0) {
@@ -701,16 +701,16 @@ static pj_status_t codec_put_frame(struct pjmedia_port *this_port,
unsigned frame_cnt = PJ_ARRAY_SIZE(parsed_frm);
unsigned i;
- status = cp->codec->op->parse(cp->codec, out_frame.buf,
- out_frame.size, &out_frame.timestamp,
- &frame_cnt, parsed_frm);
+ status = pjmedia_codec_parse(cp->codec, out_frame.buf,
+ out_frame.size, &out_frame.timestamp,
+ &frame_cnt, parsed_frm);
pj_assert(status == PJ_SUCCESS);
for (i=0; i<frame_cnt; ++i) {
pcm_frm.buf = cp->pcm;
pcm_frm.size = sizeof(cp->pkt);
- status = cp->codec->op->decode(cp->codec, &parsed_frm[i],
- sizeof(cp->pcm), &pcm_frm);
+ status = pjmedia_codec_decode(cp->codec, &parsed_frm[i],
+ sizeof(cp->pcm), &pcm_frm);
pj_assert(status == PJ_SUCCESS);
}
}
@@ -722,7 +722,7 @@ static pj_status_t codec_on_destroy(struct pjmedia_port *this_port)
{
struct codec_port *cp = (struct codec_port*)this_port;
- cp->codec->op->close(cp->codec);
+ pjmedia_codec_close(cp->codec);
pjmedia_codec_mgr_dealloc_codec(pjmedia_endpt_get_codec_mgr(cp->endpt),
cp->codec);
cp->codec_deinit();
@@ -782,11 +782,11 @@ static pjmedia_port* codec_encode_decode( pj_pool_t *pool,
if (status != PJ_SUCCESS)
return NULL;
- status = (*cp->codec->op->init)(cp->codec, pool);
+ status = pjmedia_codec_init(cp->codec, pool);
if (status != PJ_SUCCESS)
return NULL;
- status = cp->codec->op->open(cp->codec, &codec_param);
+ status = pjmedia_codec_open(cp->codec, &codec_param);
if (status != PJ_SUCCESS)
return NULL;
@@ -1131,13 +1131,13 @@ static pj_status_t wsola_discard_get_frame(struct pjmedia_port *this_port,
pj_status_t status;
while (pjmedia_circ_buf_get_len(wp->circbuf) <
- wp->base.info.samples_per_frame * (CIRC_BUF_FRAME_CNT-1))
+ PJMEDIA_PIA_SPF(&wp->base.info) * (CIRC_BUF_FRAME_CNT-1))
{
status = pjmedia_port_get_frame(wp->gen_port, frame);
pj_assert(status==PJ_SUCCESS);
status = pjmedia_circ_buf_write(wp->circbuf, (short*)frame->buf,
- wp->base.info.samples_per_frame);
+ PJMEDIA_PIA_SPF(&wp->base.info));
pj_assert(status==PJ_SUCCESS);
}
@@ -1149,7 +1149,7 @@ static pj_status_t wsola_discard_get_frame(struct pjmedia_port *this_port,
pjmedia_circ_buf_get_read_regions(wp->circbuf, &reg1, &reg1_len,
&reg2, &reg2_len);
- del_cnt = wp->base.info.samples_per_frame;
+ del_cnt = PJMEDIA_PIA_SPF(&wp->base.info);
status = pjmedia_wsola_discard(wp->wsola, reg1, reg1_len, reg2,
reg2_len, &del_cnt);
pj_assert(status==PJ_SUCCESS);
@@ -2010,7 +2010,7 @@ static pj_status_t delaybuf_get_frame(struct pjmedia_port *this_port,
}
static pj_status_t delaybuf_put_frame(struct pjmedia_port *this_port,
- const pjmedia_frame *frame)
+ pjmedia_frame *frame)
{
struct delaybuf_port *dp = (struct delaybuf_port*)this_port;
pj_status_t status;
@@ -2219,7 +2219,7 @@ static pj_timestamp run_entry(unsigned clock_rate, struct test_entry *e)
}
/* Port may decide to use different ptime (e.g. iLBC) */
- samples_per_frame = port->info.samples_per_frame;
+ samples_per_frame = PJMEDIA_PIA_SPF(&port->info);
gen_port = create_gen_port(pool, clock_rate, 1,
samples_per_frame, 100);
diff --git a/pjmedia/src/test/test.c b/pjmedia/src/test/test.c
index ca53d732..d243ed8a 100644
--- a/pjmedia/src/test/test.c
+++ b/pjmedia/src/test/test.c
@@ -47,15 +47,33 @@ int test_main(void)
{
int rc = 0;
pj_caching_pool caching_pool;
+ pj_pool_t *pool;
pj_init();
pj_caching_pool_init(&caching_pool, &pj_pool_factory_default_policy, 0);
+ pool = pj_pool_create(&caching_pool.factory, "test", 1000, 512, NULL);
pj_log_set_decor(PJ_LOG_HAS_NEWLINE);
pj_log_set_level(3);
mem = &caching_pool.factory;
+ pjmedia_video_format_mgr_create(pool, 64, 0, NULL);
+ pjmedia_converter_mgr_create(pool, NULL);
+ pjmedia_vid_codec_mgr_create(pool, NULL);
+
+#if HAS_VID_PORT_TEST
+ DO_TEST(vid_port_test());
+#endif
+
+#if HAS_VID_DEV_TEST
+ DO_TEST(vid_dev_test());
+#endif
+
+#if HAS_VID_CODEC_TEST
+ DO_TEST(vid_codec_test());
+#endif
+
#if HAS_SDP_NEG_TEST
DO_TEST(sdp_neg_test());
#endif
@@ -81,6 +99,11 @@ on_return:
PJ_LOG(3,(THIS_FILE,"Looks like everything is okay!"));
}
+ pjmedia_video_format_mgr_destroy(pjmedia_video_format_mgr_instance());
+ pjmedia_converter_mgr_destroy(pjmedia_converter_mgr_instance());
+ pjmedia_vid_codec_mgr_destroy(pjmedia_vid_codec_mgr_instance());
+
+ pj_pool_release(pool);
pj_caching_pool_destroy(&caching_pool);
return rc;
diff --git a/pjmedia/src/test/test.h b/pjmedia/src/test/test.h
index 74279c38..abd7d0bd 100644
--- a/pjmedia/src/test/test.h
+++ b/pjmedia/src/test/test.h
@@ -23,6 +23,9 @@
#include <pjmedia.h>
#include <pjlib.h>
+#define HAS_VID_DEV_TEST 1
+#define HAS_VID_PORT_TEST 0
+#define HAS_VID_CODEC_TEST 1
#define HAS_SDP_NEG_TEST 1
#define HAS_JBUF_TEST 1
#define HAS_MIPS_TEST 1
@@ -35,6 +38,9 @@ int jbuf_main(void);
int sdp_neg_test(void);
int mips_test(void);
int codec_test_vectors(void);
+int vid_codec_test(void);
+int vid_dev_test(void);
+int vid_port_test(void);
extern pj_pool_factory *mem;
void app_perror(pj_status_t status, const char *title);
diff --git a/pjmedia/src/test/vid_codec_test.c b/pjmedia/src/test/vid_codec_test.c
new file mode 100644
index 00000000..36ca19ac
--- /dev/null
+++ b/pjmedia/src/test/vid_codec_test.c
@@ -0,0 +1,467 @@
+#include "test.h"
+#include <pjmedia-codec/ffmpeg_codecs.h>
+#include <pjmedia-videodev/videodev.h>
+#include <pjmedia/vid_codec.h>
+#include <pjmedia/port.h>
+
+#define THIS_FILE "vid_codec.c"
+
+#define BYPASS_CODEC 0
+#define BYPASS_PACKETIZER 0
+
+/*
+ * Capture device setting:
+ * -1 = colorbar,
+ * -2 = any non-colorbar capture device (first found)
+ * x = specified capture device id
+ */
+#define CAPTURE_DEV -1
+
+
+typedef struct codec_port_data_t
+{
+ pjmedia_vid_codec *codec;
+ pjmedia_vid_port *rdr_port;
+ pj_uint8_t *enc_buf;
+ pj_size_t enc_buf_size;
+ pj_uint8_t *pack_buf;
+ pj_size_t pack_buf_size;
+} codec_port_data_t;
+
+static pj_status_t codec_on_event(pjmedia_event_subscription *esub,
+ pjmedia_event *event)
+{
+ codec_port_data_t *port_data = (codec_port_data_t*)esub->user_data;
+
+ if (event->type == PJMEDIA_EVENT_FMT_CHANGED) {
+ pjmedia_vid_codec *codec = port_data->codec;
+ pjmedia_vid_codec_param codec_param;
+ pj_status_t status;
+
+ ++event->proc_cnt;
+
+ status = pjmedia_vid_codec_get_param(codec, &codec_param);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ status = pjmedia_vid_dev_stream_set_cap(
+ pjmedia_vid_port_get_stream(port_data->rdr_port),
+ PJMEDIA_VID_DEV_CAP_FORMAT,
+ &codec_param.dec_fmt);
+ if (status != PJ_SUCCESS)
+ return status;
+ }
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t codec_put_frame(pjmedia_port *port,
+ pjmedia_frame *frame)
+{
+ codec_port_data_t *port_data = (codec_port_data_t*)port->port_data.pdata;
+ pj_status_t status;
+
+#if !BYPASS_CODEC
+ {
+ pjmedia_vid_codec *codec = port_data->codec;
+ pjmedia_frame enc_frame;
+
+ enc_frame.buf = port_data->enc_buf;
+ enc_frame.size = port_data->enc_buf_size;
+
+ status = pjmedia_vid_codec_encode(codec, frame, enc_frame.size,
+ &enc_frame);
+ if (status != PJ_SUCCESS) goto on_error;
+
+#if !BYPASS_PACKETIZER
+ if (enc_frame.size) {
+ unsigned pos = 0;
+ pj_bool_t packetized = PJ_FALSE;
+ unsigned unpack_pos = 0;
+
+ while (pos < enc_frame.size) {
+ pj_uint8_t *payload;
+ pj_size_t payload_len;
+
+ status = pjmedia_vid_codec_packetize(
+ codec,
+ (pj_uint8_t*)enc_frame.buf,
+ enc_frame.size, &pos,
+ (const pj_uint8_t**)&payload,
+ &payload_len);
+ if (status == PJ_ENOTSUP)
+ break;
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ status = pjmedia_vid_codec_unpacketize(
+ codec, payload, payload_len,
+ port_data->pack_buf,
+ port_data->pack_buf_size,
+ &unpack_pos);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+
+ // what happen if the bitstream is broken?
+ //if (i++ != 1) unpack_pos -= 10;
+
+ packetized = PJ_TRUE;
+ }
+
+ if (packetized) {
+ enc_frame.buf = port_data->pack_buf;
+ enc_frame.size = unpack_pos;
+ }
+ }
+#endif
+
+ status = pjmedia_vid_codec_decode(codec, &enc_frame,
+ frame->size, frame);
+ if (status != PJ_SUCCESS) goto on_error;
+ }
+#endif
+
+ status = pjmedia_port_put_frame(
+ pjmedia_vid_port_get_passive_port(port_data->rdr_port),
+ frame);
+ if (status != PJ_SUCCESS) goto on_error;
+
+ return PJ_SUCCESS;
+
+on_error:
+ pj_perror(3, THIS_FILE, status, "codec_put_frame() error");
+ return status;
+}
+
+static const char* dump_codec_info(const pjmedia_vid_codec_info *info)
+{
+ static char str[80];
+ unsigned i;
+ char *p = str;
+
+ /* Raw format ids */
+ for (i=0; (i<info->dec_fmt_id_cnt) && (p-str+5<sizeof(str)); ++i) {
+ pj_memcpy(p, &info->dec_fmt_id[i], 4);
+ p += 4;
+ *p++ = ' ';
+ }
+ *p = '\0';
+
+ return str;
+}
+
+static int enum_codecs()
+{
+ unsigned i, cnt;
+ pjmedia_vid_codec_info info[PJMEDIA_CODEC_MGR_MAX_CODECS];
+ pj_status_t status;
+
+ PJ_LOG(3, (THIS_FILE, " codec enums"));
+ cnt = PJ_ARRAY_SIZE(info);
+ status = pjmedia_vid_codec_mgr_enum_codecs(NULL, &cnt, info, NULL);
+ if (status != PJ_SUCCESS)
+ return 100;
+
+ for (i = 0; i < cnt; ++i) {
+ PJ_LOG(3, (THIS_FILE, " %-16.*s %c%c %s",
+ info[i].encoding_name.slen, info[i].encoding_name.ptr,
+ (info[i].dir & PJMEDIA_DIR_ENCODING? 'E' : ' '),
+ (info[i].dir & PJMEDIA_DIR_DECODING? 'D' : ' '),
+ dump_codec_info(&info[i])));
+ }
+
+ return PJ_SUCCESS;
+}
+
+static int encode_decode_test(pj_pool_t *pool, const char *codec_id)
+{
+ const pj_str_t port_name = {"codec", 5};
+
+ pjmedia_vid_codec *codec=NULL;
+ pjmedia_port codec_port;
+ codec_port_data_t codec_port_data;
+ pjmedia_vid_codec_param codec_param;
+ const pjmedia_vid_codec_info *codec_info;
+
+ pjmedia_vid_dev_index cap_idx, rdr_idx;
+ pjmedia_vid_port *capture=NULL, *renderer=NULL;
+ pjmedia_vid_port_param vport_param;
+ pjmedia_video_format_detail *vfd;
+ pjmedia_event_subscription esub;
+ pj_status_t status;
+ int rc = 0;
+
+ PJ_LOG(3, (THIS_FILE, " encode decode test"));
+
+ /* Lookup codec */
+ {
+ pj_str_t codec_id_st;
+ unsigned info_cnt = 1;
+
+ /* Lookup codec */
+ pj_cstr(&codec_id_st, codec_id);
+ status = pjmedia_vid_codec_mgr_find_codecs_by_id(NULL, &codec_id_st,
+ &info_cnt,
+ &codec_info, NULL);
+ if (status != PJ_SUCCESS) {
+ rc = 205; goto on_return;
+ }
+ }
+
+
+#if CAPTURE_DEV == -1
+ /* Lookup colorbar source */
+ status = pjmedia_vid_dev_lookup("Colorbar", "Colorbar generator", &cap_idx);
+ if (status != PJ_SUCCESS) {
+ rc = 206; goto on_return;
+ }
+#elif CAPTURE_DEV == -2
+ /* Lookup any first non-colorbar source */
+ {
+ unsigned i, cnt;
+ pjmedia_vid_dev_info info;
+
+ cap_idx = -1;
+ cnt = pjmedia_vid_dev_count();
+ for (i = 0; i < cnt; ++i) {
+ status = pjmedia_vid_dev_get_info(i, &info);
+ if (status != PJ_SUCCESS) {
+ rc = 206; goto on_return;
+ }
+ if (info.dir & PJMEDIA_DIR_CAPTURE &&
+ pj_ansi_stricmp(info.driver, "Colorbar"))
+ {
+ cap_idx = i;
+ break;
+ }
+ }
+
+ if (cap_idx == -1) {
+ status = PJ_ENOTFOUND;
+ rc = 206; goto on_return;
+ }
+ }
+#else
+ cap_idx = CAPTURE_DEV;
+#endif
+
+ /* Lookup SDL renderer */
+ status = pjmedia_vid_dev_lookup("SDL", "SDL renderer", &rdr_idx);
+ if (status != PJ_SUCCESS) {
+ rc = 207; goto on_return;
+ }
+
+ /* Prepare codec */
+ {
+ pj_str_t codec_id_st;
+ unsigned info_cnt = 1;
+ const pjmedia_vid_codec_info *codec_info;
+
+ /* Lookup codec */
+ pj_cstr(&codec_id_st, codec_id);
+ status = pjmedia_vid_codec_mgr_find_codecs_by_id(NULL, &codec_id_st,
+ &info_cnt,
+ &codec_info, NULL);
+ if (status != PJ_SUCCESS) {
+ rc = 245; goto on_return;
+ }
+ status = pjmedia_vid_codec_mgr_get_default_param(NULL, codec_info,
+ &codec_param);
+ if (status != PJ_SUCCESS) {
+ rc = 246; goto on_return;
+ }
+
+#if !BYPASS_CODEC
+
+ /* Open codec */
+ status = pjmedia_vid_codec_mgr_alloc_codec(NULL, codec_info,
+ &codec);
+ if (status != PJ_SUCCESS) {
+ rc = 250; goto on_return;
+ }
+
+ status = pjmedia_vid_codec_init(codec, pool);
+ if (status != PJ_SUCCESS) {
+ rc = 251; goto on_return;
+ }
+
+ status = pjmedia_vid_codec_open(codec, &codec_param);
+ if (status != PJ_SUCCESS) {
+ rc = 252; goto on_return;
+ }
+
+ /* After opened, codec will update the param, let's sync encoder &
+ * decoder format detail.
+ */
+ codec_param.dec_fmt.det = codec_param.enc_fmt.det;
+
+ /* Subscribe to codec events */
+ pjmedia_event_subscription_init(&esub, &codec_on_event,
+ &codec_port_data);
+ pjmedia_event_subscribe(&codec->epub, &esub);
+#endif /* !BYPASS_CODEC */
+ }
+
+ pjmedia_vid_port_param_default(&vport_param);
+
+ /* Create capture, set it to active (master) */
+ status = pjmedia_vid_dev_default_param(pool, cap_idx,
+ &vport_param.vidparam);
+ if (status != PJ_SUCCESS) {
+ rc = 220; goto on_return;
+ }
+ pjmedia_format_copy(&vport_param.vidparam.fmt, &codec_param.dec_fmt);
+ vport_param.vidparam.dir = PJMEDIA_DIR_CAPTURE;
+ vport_param.active = PJ_TRUE;
+
+ if (vport_param.vidparam.fmt.detail_type != PJMEDIA_FORMAT_DETAIL_VIDEO) {
+ rc = 221; goto on_return;
+ }
+
+ vfd = pjmedia_format_get_video_format_detail(&vport_param.vidparam.fmt,
+ PJ_TRUE);
+ if (vfd == NULL) {
+ rc = 225; goto on_return;
+ }
+
+ status = pjmedia_vid_port_create(pool, &vport_param, &capture);
+ if (status != PJ_SUCCESS) {
+ rc = 226; goto on_return;
+ }
+
+ /* Create renderer, set it to passive (slave) */
+ vport_param.active = PJ_FALSE;
+ vport_param.vidparam.dir = PJMEDIA_DIR_RENDER;
+ vport_param.vidparam.rend_id = rdr_idx;
+ vport_param.vidparam.disp_size = vfd->size;
+
+ status = pjmedia_vid_port_create(pool, &vport_param, &renderer);
+ if (status != PJ_SUCCESS) {
+ rc = 230; goto on_return;
+ }
+
+ /* Init codec port */
+ pj_bzero(&codec_port, sizeof(codec_port));
+ status = pjmedia_port_info_init2(&codec_port.info, &port_name, 0x1234,
+ PJMEDIA_DIR_ENCODING,
+ &codec_param.dec_fmt);
+ if (status != PJ_SUCCESS) {
+ rc = 260; goto on_return;
+ }
+
+ codec_port_data.codec = codec;
+ codec_port_data.rdr_port = renderer;
+ codec_port_data.enc_buf_size = codec_param.dec_fmt.det.vid.size.w *
+ codec_param.dec_fmt.det.vid.size.h * 4;
+ codec_port_data.enc_buf = pj_pool_alloc(pool,
+ codec_port_data.enc_buf_size);
+ codec_port_data.pack_buf_size = codec_port_data.enc_buf_size;
+ codec_port_data.pack_buf = pj_pool_alloc(pool,
+ codec_port_data.pack_buf_size);
+
+ codec_port.put_frame = &codec_put_frame;
+ codec_port.port_data.pdata = &codec_port_data;
+
+ /* Connect capture to codec port */
+ status = pjmedia_vid_port_connect(capture,
+ &codec_port,
+ PJ_FALSE);
+ if (status != PJ_SUCCESS) {
+ rc = 270; goto on_return;
+ }
+
+#if BYPASS_CODEC
+ PJ_LOG(3, (THIS_FILE, " starting loopback test: %c%c%c%c %dx%d",
+ ((codec_param.dec_fmt.id & 0x000000FF) >> 0),
+ ((codec_param.dec_fmt.id & 0x0000FF00) >> 8),
+ ((codec_param.dec_fmt.id & 0x00FF0000) >> 16),
+ ((codec_param.dec_fmt.id & 0xFF000000) >> 24),
+ codec_param.dec_fmt.det.vid.size.w,
+ codec_param.dec_fmt.det.vid.size.h
+ ));
+#else
+ PJ_LOG(3, (THIS_FILE, " starting codec test: %c%c%c%c<->%.*s %dx%d",
+ ((codec_param.dec_fmt.id & 0x000000FF) >> 0),
+ ((codec_param.dec_fmt.id & 0x0000FF00) >> 8),
+ ((codec_param.dec_fmt.id & 0x00FF0000) >> 16),
+ ((codec_param.dec_fmt.id & 0xFF000000) >> 24),
+ codec_info->encoding_name.slen,
+ codec_info->encoding_name.ptr,
+ codec_param.dec_fmt.det.vid.size.w,
+ codec_param.dec_fmt.det.vid.size.h
+ ));
+#endif
+
+ /* Start streaming.. */
+ status = pjmedia_vid_port_start(renderer);
+ if (status != PJ_SUCCESS) {
+ rc = 275; goto on_return;
+ }
+ status = pjmedia_vid_port_start(capture);
+ if (status != PJ_SUCCESS) {
+ rc = 280; goto on_return;
+ }
+
+ /* Sleep while the video is being displayed... */
+ pj_thread_sleep(10000);
+
+on_return:
+ if (status != PJ_SUCCESS) {
+ PJ_PERROR(3, (THIS_FILE, status, " error"));
+ }
+ if (capture) {
+ pjmedia_vid_port_stop(capture);
+ pjmedia_vid_port_destroy(capture);
+ }
+ if (renderer) {
+ pjmedia_vid_port_stop(renderer);
+ pjmedia_vid_port_destroy(renderer);
+ }
+ if (codec) {
+ pjmedia_vid_codec_close(codec);
+ pjmedia_vid_codec_mgr_dealloc_codec(NULL, codec);
+ }
+
+ return rc;
+}
+
+int vid_codec_test(void)
+{
+ pj_pool_t *pool;
+ int rc = 0;
+ pj_status_t status;
+ int orig_log_level;
+
+ orig_log_level = pj_log_get_level();
+ pj_log_set_level(6);
+
+ PJ_LOG(3, (THIS_FILE, "Performing video codec tests.."));
+
+ pool = pj_pool_create(mem, "Vid codec test", 256, 256, 0);
+
+ status = pjmedia_vid_dev_subsys_init(mem);
+ if (status != PJ_SUCCESS)
+ return -10;
+
+ status = pjmedia_codec_ffmpeg_init(NULL, mem);
+ if (status != PJ_SUCCESS)
+ return -20;
+
+ rc = enum_codecs();
+ if (rc != 0)
+ goto on_return;
+
+ rc = encode_decode_test(pool, "h263-1998");
+ if (rc != 0)
+ goto on_return;
+
+on_return:
+ pjmedia_codec_ffmpeg_deinit();
+ pjmedia_vid_dev_subsys_shutdown();
+ pj_pool_release(pool);
+ pj_log_set_level(orig_log_level);
+
+ return rc;
+}
+
+
diff --git a/pjmedia/src/test/vid_dev_test.c b/pjmedia/src/test/vid_dev_test.c
new file mode 100644
index 00000000..36966047
--- /dev/null
+++ b/pjmedia/src/test/vid_dev_test.c
@@ -0,0 +1,292 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 "test.h"
+#include <pjmedia-audiodev/audiodev.h>
+#include <pjmedia-codec/ffmpeg_codecs.h>
+#include <pjmedia/vid_codec.h>
+#include <pjmedia_videodev.h>
+
+#define THIS_FILE "vid_dev_test.c"
+#define LOOP_DURATION 10
+
+static pj_bool_t is_quitting = PJ_FALSE;
+
+static const char *vid_dir_name(pjmedia_dir dir)
+{
+ switch (dir) {
+ case PJMEDIA_DIR_CAPTURE:
+ return "capture";
+ case PJMEDIA_DIR_RENDER:
+ return "render";
+ case PJMEDIA_DIR_CAPTURE_RENDER:
+ return "capture & render";
+ default:
+ return "unknown";
+ }
+}
+
+static int enum_devs(void)
+{
+ unsigned i, dev_cnt;
+ pj_status_t status;
+
+ PJ_LOG(3, (THIS_FILE, " Enum video devices:"));
+ dev_cnt = pjmedia_vid_dev_count();
+ for (i = 0; i < dev_cnt; ++i) {
+ pjmedia_vid_dev_info di;
+ status = pjmedia_vid_dev_get_info(i, &di);
+ if (status == PJ_SUCCESS) {
+ unsigned j;
+
+ PJ_LOG(3, (THIS_FILE, " %3d: %s (%s) - %s", i, di.name, di.driver,
+ vid_dir_name(di.dir)));
+
+ PJ_LOG(3,(THIS_FILE, " Supported formats:"));
+ for (j=0; j<di.fmt_cnt; ++j) {
+ const pjmedia_video_format_info *vfi;
+
+ vfi = pjmedia_get_video_format_info(NULL, di.fmt[j].id);
+ PJ_LOG(3,(THIS_FILE, " %s",
+ (vfi ? vfi->name : "unknown")));
+ }
+ }
+ }
+
+ return PJ_SUCCESS;
+}
+
+static pj_status_t vid_event_cb(pjmedia_event_subscription *esub,
+ pjmedia_event *event)
+{
+ if (event->type == PJMEDIA_EVENT_WND_CLOSED)
+ is_quitting = PJ_TRUE;
+
+ return PJ_SUCCESS;
+}
+
+static int capture_render_loopback(int cap_dev_id, int rend_dev_id,
+ const pjmedia_format *fmt)
+{
+ pj_pool_t *pool;
+ pjmedia_vid_port *capture=NULL, *renderer=NULL;
+ pjmedia_vid_dev_info cdi, rdi;
+ pjmedia_vid_port_param param;
+ pjmedia_video_format_detail *vfd;
+ pjmedia_event_subscription esub;
+ pj_status_t status;
+ int rc = 0, i;
+
+ pool = pj_pool_create(mem, "vidloop", 1000, 1000, NULL);
+
+ status = pjmedia_vid_dev_get_info(cap_dev_id, &cdi);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ status = pjmedia_vid_dev_get_info(rend_dev_id, &rdi);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ PJ_LOG(3,(THIS_FILE,
+ " %s (%s) ===> %s (%s)\t%s\t%dx%d\t@%d:%d fps",
+ cdi.name, cdi.driver, rdi.name, rdi.driver,
+ pjmedia_get_video_format_info(NULL, fmt->id)->name,
+ fmt->det.vid.size.w, fmt->det.vid.size.h,
+ fmt->det.vid.fps.num, fmt->det.vid.fps.denum));
+
+ pjmedia_vid_port_param_default(&param);
+
+ /* Create capture, set it to active (master) */
+ status = pjmedia_vid_dev_default_param(pool, cap_dev_id,
+ &param.vidparam);
+ if (status != PJ_SUCCESS) {
+ rc = 100; goto on_return;
+ }
+ param.vidparam.dir = PJMEDIA_DIR_CAPTURE;
+ param.vidparam.fmt = *fmt;
+ param.active = PJ_TRUE;
+
+ if (param.vidparam.fmt.detail_type != PJMEDIA_FORMAT_DETAIL_VIDEO) {
+ rc = 103; goto on_return;
+ }
+
+ vfd = pjmedia_format_get_video_format_detail(&param.vidparam.fmt, PJ_TRUE);
+ if (vfd == NULL) {
+ rc = 105; goto on_return;
+ }
+
+ status = pjmedia_vid_port_create(pool, &param, &capture);
+ if (status != PJ_SUCCESS) {
+ rc = 110; goto on_return;
+ }
+
+ /* Create renderer, set it to passive (slave) */
+ status = pjmedia_vid_dev_default_param(pool, rend_dev_id,
+ &param.vidparam);
+ if (status != PJ_SUCCESS) {
+ rc = 120; goto on_return;
+ }
+
+ param.active = PJ_FALSE;
+ param.vidparam.dir = PJMEDIA_DIR_RENDER;
+ param.vidparam.rend_id = rend_dev_id;
+ param.vidparam.fmt = *fmt;
+ param.vidparam.disp_size = vfd->size;
+
+ status = pjmedia_vid_port_create(pool, &param, &renderer);
+ if (status != PJ_SUCCESS) {
+ rc = 130; goto on_return;
+ }
+
+ /* Set event handler */
+ pjmedia_event_subscription_init(&esub, &vid_event_cb, NULL);
+ pjmedia_event_subscribe(
+ pjmedia_vid_port_get_event_publisher(renderer),
+ &esub);
+
+ /* Connect capture to renderer */
+ status = pjmedia_vid_port_connect(
+ capture,
+ pjmedia_vid_port_get_passive_port(renderer),
+ PJ_FALSE);
+ if (status != PJ_SUCCESS) {
+ rc = 140; goto on_return;
+ }
+
+ /* Start streaming.. */
+ status = pjmedia_vid_port_start(renderer);
+ if (status != PJ_SUCCESS) {
+ rc = 150; goto on_return;
+ }
+ status = pjmedia_vid_port_start(capture);
+ if (status != PJ_SUCCESS) {
+ rc = 160; goto on_return;
+ }
+
+ /* Sleep while the webcam is being displayed... */
+ for (i = 0; i < LOOP_DURATION*10 && (!is_quitting); i++) {
+ pj_thread_sleep(100);
+ }
+
+on_return:
+ if (status != PJ_SUCCESS)
+ PJ_PERROR(3, (THIS_FILE, status, " error"));
+
+ if (capture)
+ pjmedia_vid_port_destroy(capture);
+ if (renderer)
+ pjmedia_vid_port_destroy(renderer);
+
+ pj_pool_release(pool);
+ return rc;
+}
+
+static int loopback_test(void)
+{
+ unsigned count, i;
+ pjmedia_format_id test_fmts[] = {
+ PJMEDIA_FORMAT_YUY2
+ };
+ pjmedia_rect_size test_sizes[] = {
+ {176,144}, /* QCIF */
+ {352,288}, /* CIF */
+ {704,576} /* 4CIF */
+ };
+ pjmedia_ratio test_fpses[] = {
+ {25, 1},
+ {30, 1},
+ };
+ pj_status_t status;
+
+ PJ_LOG(3, (THIS_FILE, " Loopback tests (prepare you webcams):"));
+
+ count = pjmedia_vid_dev_count();
+ for (i=0; i<count; ++i) {
+ pjmedia_vid_dev_info cdi;
+ unsigned j;
+
+ status = pjmedia_vid_dev_get_info(i, &cdi);
+ if (status != PJ_SUCCESS)
+ return -300;
+
+ /* Only interested with capture device */
+ if ((cdi.dir & PJMEDIA_DIR_CAPTURE) == 0)
+ continue;
+
+ for (j=i+1; j<count; ++j) {
+ pjmedia_vid_dev_info rdi;
+ unsigned k;
+
+ status = pjmedia_vid_dev_get_info(j, &rdi);
+ if (status != PJ_SUCCESS)
+ return -310;
+
+ /* Only interested with render device */
+ if ((rdi.dir & PJMEDIA_DIR_RENDER) == 0)
+ continue;
+
+ /* Test with the format, size, and fps combinations */
+ for (k=0; k<PJ_ARRAY_SIZE(test_fmts); ++k) {
+ unsigned l;
+
+ for (l=0; l<PJ_ARRAY_SIZE(test_sizes); ++l) {
+ unsigned m;
+
+ for (m=0; m<PJ_ARRAY_SIZE(test_fpses); ++m) {
+ pjmedia_format fmt;
+
+ pjmedia_format_init_video(&fmt, test_fmts[k],
+ test_sizes[l].w,
+ test_sizes[l].h,
+ test_fpses[m].num,
+ test_fpses[m].denum);
+
+ capture_render_loopback(i, j, &fmt);
+ }
+ }
+ } /* k */
+
+ }
+ }
+
+ return 0;
+}
+
+int vid_dev_test(void)
+{
+ int rc = 0;
+ pj_status_t status;
+
+ status = pjmedia_vid_dev_subsys_init(mem);
+ if (status != PJ_SUCCESS)
+ return -10;
+
+ rc = enum_devs();
+ if (rc != 0)
+ goto on_return;
+
+ rc = loopback_test();
+ if (rc != 0)
+ goto on_return;
+
+on_return:
+ pjmedia_vid_dev_subsys_shutdown();
+
+ return rc;
+}
diff --git a/pjmedia/src/test/vid_port_test.c b/pjmedia/src/test/vid_port_test.c
new file mode 100644
index 00000000..cd7acd8d
--- /dev/null
+++ b/pjmedia/src/test/vid_port_test.c
@@ -0,0 +1,241 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+ * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
+ *
+ * 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 "test.h"
+#include <pjmedia-audiodev/audiodev.h>
+#include <pjmedia-codec/ffmpeg_codecs.h>
+#include <pjmedia/vid_codec.h>
+#include <pjmedia_videodev.h>
+
+#define THIS_FILE "vid_dev_test.c"
+#define LOOP_DURATION 6
+
+static pj_bool_t is_quitting = PJ_FALSE;
+
+static pj_status_t vid_event_cb(pjmedia_event_subscription *esub,
+ pjmedia_event *event)
+{
+ if (event->type == PJMEDIA_EVENT_WND_CLOSED)
+ is_quitting = PJ_TRUE;
+
+ return PJ_SUCCESS;
+}
+
+static int capture_render_loopback(pj_bool_t active,
+ int cap_dev_id, int rend_dev_id,
+ const pjmedia_format *fmt)
+{
+ pj_pool_t *pool;
+ pjmedia_vid_port *capture=NULL, *renderer=NULL;
+ pjmedia_vid_dev_info cdi, rdi;
+ pjmedia_vid_port_param param;
+ pjmedia_video_format_detail *vfd;
+ pjmedia_vid_dev_cb cb;
+ pjmedia_event_subscription esub;
+ pj_status_t status;
+ int rc = 0, i;
+
+ pool = pj_pool_create(mem, "vidportloop", 1000, 1000, NULL);
+
+ status = pjmedia_vid_dev_get_info(cap_dev_id, &cdi);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ status = pjmedia_vid_dev_get_info(rend_dev_id, &rdi);
+ if (status != PJ_SUCCESS)
+ goto on_return;
+
+ PJ_LOG(3,(THIS_FILE,
+ " %s (%s) ===> %s (%s)\t%s\t%dx%d\t@%d:%d fps",
+ cdi.name, cdi.driver, rdi.name, rdi.driver,
+ pjmedia_get_video_format_info(NULL, fmt->id)->name,
+ fmt->det.vid.size.w, fmt->det.vid.size.h,
+ fmt->det.vid.fps.num, fmt->det.vid.fps.denum));
+
+ pjmedia_vid_port_param_default(&param);
+
+ /* Create capture, set it to active (master) */
+ status = pjmedia_vid_dev_default_param(pool, cap_dev_id,
+ &param.vidparam);
+ if (status != PJ_SUCCESS) {
+ rc = 100; goto on_return;
+ }
+ param.vidparam.dir = PJMEDIA_DIR_CAPTURE;
+ param.vidparam.fmt = *fmt;
+ param.active = (active? PJ_TRUE: PJ_FALSE);
+
+ if (param.vidparam.fmt.detail_type != PJMEDIA_FORMAT_DETAIL_VIDEO) {
+ rc = 103; goto on_return;
+ }
+
+ vfd = pjmedia_format_get_video_format_detail(&param.vidparam.fmt, PJ_TRUE);
+ if (vfd == NULL) {
+ rc = 105; goto on_return;
+ }
+
+ status = pjmedia_vid_port_create(pool, &param, &capture);
+ if (status != PJ_SUCCESS) {
+ rc = 110; goto on_return;
+ }
+
+ /* Create renderer, set it to passive (slave) */
+ status = pjmedia_vid_dev_default_param(pool, rend_dev_id,
+ &param.vidparam);
+ if (status != PJ_SUCCESS) {
+ rc = 120; goto on_return;
+ }
+
+ param.active = (active? PJ_FALSE: PJ_TRUE);
+ param.vidparam.dir = PJMEDIA_DIR_RENDER;
+ param.vidparam.rend_id = rend_dev_id;
+ param.vidparam.fmt = *fmt;
+ param.vidparam.disp_size = vfd->size;
+
+ status = pjmedia_vid_port_create(pool, &param, &renderer);
+ if (status != PJ_SUCCESS) {
+ rc = 130; goto on_return;
+ }
+
+ /* Set event handler */
+ pjmedia_event_subscription_init(&esub, &vid_event_cb, NULL);
+ pjmedia_event_subscribe(
+ pjmedia_vid_port_get_event_publisher(renderer),
+ &esub);
+
+ /* Connect capture to renderer */
+ status = pjmedia_vid_port_connect(
+ (active? capture: renderer),
+ pjmedia_vid_port_get_passive_port(active? renderer: capture),
+ PJ_FALSE);
+ if (status != PJ_SUCCESS) {
+ rc = 140; goto on_return;
+ }
+
+ /* Start streaming.. */
+ status = pjmedia_vid_port_start(renderer);
+ if (status != PJ_SUCCESS) {
+ rc = 150; goto on_return;
+ }
+ status = pjmedia_vid_port_start(capture);
+ if (status != PJ_SUCCESS) {
+ rc = 160; goto on_return;
+ }
+
+ /* Sleep while the webcam is being displayed... */
+ for (i = 0; i < LOOP_DURATION*10 && (!is_quitting); i++) {
+ pj_thread_sleep(100);
+ }
+
+on_return:
+ if (status != PJ_SUCCESS)
+ PJ_PERROR(3, (THIS_FILE, status, " error"));
+
+ if (capture)
+ pjmedia_vid_port_destroy(capture);
+ if (renderer)
+ pjmedia_vid_port_destroy(renderer);
+
+ pj_pool_release(pool);
+ return rc;
+}
+
+static int find_device(pjmedia_dir dir,
+ pj_bool_t has_callback)
+{
+ unsigned i, count = pjmedia_vid_dev_count();
+
+ for (i = 0; i < count; ++i) {
+ pjmedia_vid_dev_info cdi;
+
+ if (pjmedia_vid_dev_get_info(i, &cdi) != PJ_SUCCESS)
+ continue;
+ if ((cdi.dir & dir) != 0 && cdi.has_callback == has_callback)
+ return i;
+ }
+
+ return -999;
+}
+
+static int vidport_test(void)
+{
+ int i, j, k, l;
+ int cap_id, rend_id;
+ pjmedia_format_id test_fmts[] = {
+ PJMEDIA_FORMAT_RGBA,
+ PJMEDIA_FORMAT_I420
+ };
+ pj_status_t status;
+
+ PJ_LOG(3, (THIS_FILE, " Video port tests:"));
+
+ /* Capturer's role: active/passive. */
+ for (i = 1; i >= 0; i--) {
+ /* Capturer's device has_callback: TRUE/FALSE. */
+ for (j = 1; j >= 0; j--) {
+ cap_id = find_device(PJMEDIA_DIR_CAPTURE, j);
+ if (cap_id < 0)
+ continue;
+
+ /* Renderer's device has callback: TRUE/FALSE. */
+ for (k = 1; k >= 0; k--) {
+ rend_id = find_device(PJMEDIA_DIR_RENDER, k);
+ if (rend_id < 0)
+ continue;
+
+ /* Check various formats to test format conversion. */
+ for (l = 0; l < PJ_ARRAY_SIZE(test_fmts); ++l) {
+ pjmedia_format fmt;
+
+ PJ_LOG(3,(THIS_FILE,
+ "capturer %s (stream: %s) ===> "
+ "renderer %s (stream: %s)",
+ (i? "active": "passive"),
+ (j? "active": "passive"),
+ (i? "passive": "active"),
+ (k? "active": "passive")));
+
+ pjmedia_format_init_video(&fmt, test_fmts[l],
+ 640, 480, 25, 1);
+ capture_render_loopback(i, cap_id, rend_id, &fmt);
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+int vid_port_test(void)
+{
+ int rc = 0;
+ pj_status_t status;
+
+ status = pjmedia_vid_dev_subsys_init(mem);
+ if (status != PJ_SUCCESS)
+ return -10;
+
+ rc = vidport_test();
+ if (rc != 0)
+ goto on_return;
+
+on_return:
+ pjmedia_vid_dev_subsys_shutdown();
+
+ return rc;
+}