diff options
author | Nanang Izzuddin <nanang@teluu.com> | 2011-12-07 10:43:28 +0000 |
---|---|---|
committer | Nanang Izzuddin <nanang@teluu.com> | 2011-12-07 10:43:28 +0000 |
commit | 2ba3536e2d318130242c35ed053aaae7f771b261 (patch) | |
tree | 564da2c0e0a5b2b2fef7a50342286727eb825662 | |
parent | 3a0786774a23558b8da85fd261b2858995c2c999 (diff) |
Re #1234: Initial version of keyframe request/response via SIP INFO.
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@3901 74dad513-b988-da41-8d7b-12977e46ad98
-rw-r--r-- | pjmedia/include/pjmedia/errno.h | 5 | ||||
-rw-r--r-- | pjmedia/include/pjmedia/event.h | 24 | ||||
-rw-r--r-- | pjmedia/include/pjmedia/vid_codec.h | 47 | ||||
-rw-r--r-- | pjmedia/include/pjmedia/vid_stream.h | 23 | ||||
-rw-r--r-- | pjmedia/src/pjmedia-codec/ffmpeg_codecs.c | 205 | ||||
-rw-r--r-- | pjmedia/src/pjmedia-videodev/colorbar_dev.c | 2 | ||||
-rw-r--r-- | pjmedia/src/pjmedia-videodev/dshow_dev.c | 2 | ||||
-rw-r--r-- | pjmedia/src/pjmedia-videodev/ios_dev.m | 2 | ||||
-rw-r--r-- | pjmedia/src/pjmedia-videodev/qt_dev.m | 2 | ||||
-rw-r--r-- | pjmedia/src/pjmedia-videodev/v4l2_dev.c | 1 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/errno.c | 1 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/vid_codec.c | 8 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/vid_stream.c | 54 | ||||
-rw-r--r-- | pjmedia/src/test/vid_codec_test.c | 2 | ||||
-rw-r--r-- | pjsip-apps/src/pjsua/pjsua_app.c | 33 | ||||
-rw-r--r-- | pjsip/include/pjsua-lib/pjsua.h | 53 | ||||
-rw-r--r-- | pjsip/include/pjsua-lib/pjsua_internal.h | 4 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_call.c | 53 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_media.c | 74 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_vid.c | 29 |
20 files changed, 487 insertions, 137 deletions
diff --git a/pjmedia/include/pjmedia/errno.h b/pjmedia/include/pjmedia/errno.h index 4f3a6c3e..3c27e35f 100644 --- a/pjmedia/include/pjmedia/errno.h +++ b/pjmedia/include/pjmedia/errno.h @@ -346,6 +346,11 @@ PJ_BEGIN_DECL * Invalid mode. */ #define PJMEDIA_CODEC_EINMODE (PJMEDIA_ERRNO_START+86) /* 220086 */ +/** + * @hideinitializer + * Bad or corrupted bitstream. + */ +#define PJMEDIA_CODEC_EBADBITSTREAM (PJMEDIA_ERRNO_START+87) /* 220087 */ /************************************************************ diff --git a/pjmedia/include/pjmedia/event.h b/pjmedia/include/pjmedia/event.h index 7a779e79..ba06625c 100644 --- a/pjmedia/include/pjmedia/event.h +++ b/pjmedia/include/pjmedia/event.h @@ -70,14 +70,14 @@ typedef enum pjmedia_event_type PJMEDIA_EVENT_MOUSE_BTN_DOWN = PJMEDIA_FOURCC('M', 'S', 'D', 'N'), /** - * Video key frame has just been decoded event. + * Video keyframe has just been decoded event. */ - PJMEDIA_EVENT_KEY_FRAME_FOUND = PJMEDIA_FOURCC('I', 'F', 'R', 'F'), + PJMEDIA_EVENT_KEYFRAME_FOUND = PJMEDIA_FOURCC('I', 'F', 'R', 'F'), /** - * Video decoding error due to missing key frame event. + * Video decoding error due to missing keyframe event. */ - PJMEDIA_EVENT_KEY_FRAME_MISSING = PJMEDIA_FOURCC('I', 'F', 'R', 'M'), + PJMEDIA_EVENT_KEYFRAME_MISSING = PJMEDIA_FOURCC('I', 'F', 'R', 'M'), /** * Video orientation has been changed event. @@ -135,11 +135,11 @@ typedef pjmedia_event_dummy_data pjmedia_event_wnd_closed_data; /** Additional parameters for mouse button down event */ typedef pjmedia_event_dummy_data pjmedia_event_mouse_btn_down_data; -/** Additional parameters for key frame found event */ -typedef pjmedia_event_dummy_data pjmedia_event_key_frame_found_data; +/** Additional parameters for keyframe found event */ +typedef pjmedia_event_dummy_data pjmedia_event_keyframe_found_data; -/** Additional parameters for key frame missing event */ -typedef pjmedia_event_dummy_data pjmedia_event_key_frame_missing_data; +/** Additional parameters for keyframe missing event */ +typedef pjmedia_event_dummy_data pjmedia_event_keyframe_missing_data; /** * Maximum size of additional parameters section in pjmedia_event structure @@ -205,11 +205,11 @@ typedef struct pjmedia_event /** Mouse button down event data */ pjmedia_event_mouse_btn_down_data mouse_btn_down; - /** Key frame found event data */ - pjmedia_event_key_frame_found_data key_frm_found; + /** Keyframe found event data */ + pjmedia_event_keyframe_found_data keyframe_found; - /** Key frame missing event data */ - pjmedia_event_key_frame_missing_data key_frm_missing; + /** Keyframe missing event data */ + pjmedia_event_keyframe_missing_data keyframe_missing; /** Storage for user event data */ pjmedia_event_user_data user; diff --git a/pjmedia/include/pjmedia/vid_codec.h b/pjmedia/include/pjmedia/vid_codec.h index 9716e540..6029f91e 100644 --- a/pjmedia/include/pjmedia/vid_codec.h +++ b/pjmedia/include/pjmedia/vid_codec.h @@ -76,6 +76,37 @@ typedef enum pjmedia_vid_packing } pjmedia_vid_packing; + +/** + * Enumeration of video frame info flag for the bit_info field in the + * pjmedia_frame. + */ +typedef enum pjmedia_vid_frm_bit_info +{ + /** + * The video frame is keyframe. + */ + PJMEDIA_VID_FRM_KEYFRAME = 1 + +} pjmedia_vid_frm_bit_info; + + +/** + * Encoding option. + */ +typedef struct pjmedia_vid_encode_opt +{ + /** + * Flag to force the encoder to generate keyframe for the specified input + * frame. When this flag is set, application can verify the result by + * examining PJMEDIA_VID_FRM_KEYFRAME flag in the bit_info field of the + * output frame. + */ + pj_bool_t force_keyframe; + +} pjmedia_vid_encode_opt; + + /** * Identification used to search for codec factory that supports specific * codec specification. @@ -178,7 +209,7 @@ typedef struct pjmedia_vid_codec_op /** * See #pjmedia_vid_codec_modify(). */ - pj_status_t (*modify)(pjmedia_vid_codec *codec, + pj_status_t (*modify)(pjmedia_vid_codec *codec, const pjmedia_vid_codec_param *param); /** @@ -191,6 +222,7 @@ typedef struct pjmedia_vid_codec_op * See #pjmedia_vid_codec_encode_begin(). */ pj_status_t (*encode_begin)(pjmedia_vid_codec *codec, + const pjmedia_vid_encode_opt *opt, const pjmedia_frame *input, unsigned out_size, pjmedia_frame *output, @@ -363,15 +395,6 @@ typedef struct pjmedia_vid_codec_mgr pjmedia_vid_codec_mgr; /** - * Initialize pjmedia_vid_codec structure with default values. - * - * @param codec The codec to be initialized. - * @param sig Codec's object signature (see signatures.h) - */ -PJ_DECL(void) pjmedia_vid_codec_reset(pjmedia_vid_codec *codec, - pjmedia_obj_sig sig); - -/** * Initialize codec manager. If there is no the default video codec manager, * this function will automatically set the default video codec manager to * the new codec manager instance. Normally this function is called by pjmedia @@ -728,6 +751,7 @@ pjmedia_vid_codec_get_param(pjmedia_vid_codec *codec, * codec. * * @param codec The codec instance. + * @param opt Optional encoding options. * @param input The input frame. * @param out_size The length of buffer in the output frame. This * should be at least the same as the configured @@ -741,12 +765,13 @@ pjmedia_vid_codec_get_param(pjmedia_vid_codec *codec, */ PJ_INLINE(pj_status_t) pjmedia_vid_codec_encode_begin( pjmedia_vid_codec *codec, + const pjmedia_vid_encode_opt *opt, const pjmedia_frame *input, unsigned out_size, pjmedia_frame *output, pj_bool_t *has_more) { - return (*codec->op->encode_begin)(codec, input, out_size, output, + return (*codec->op->encode_begin)(codec, opt, input, out_size, output, has_more); } diff --git a/pjmedia/include/pjmedia/vid_stream.h b/pjmedia/include/pjmedia/vid_stream.h index bdd84079..4ce819bd 100644 --- a/pjmedia/include/pjmedia/vid_stream.h +++ b/pjmedia/include/pjmedia/vid_stream.h @@ -298,10 +298,10 @@ PJ_DECL(pj_bool_t) pjmedia_vid_stream_is_running(pjmedia_vid_stream *stream, pjmedia_dir dir); /** - * Pause the individual channel in the stream. + * Pause stream channels. * - * @param stream The video channel. - * @param dir Which direction to pause. + * @param stream The video stream. + * @param dir Which channel direction to pause. * * @return PJ_SUCCESS on success. */ @@ -309,10 +309,10 @@ PJ_DECL(pj_status_t) pjmedia_vid_stream_pause(pjmedia_vid_stream *stream, pjmedia_dir dir); /** - * Resume the individual channel in the stream. + * Resume stream channels. * - * @param stream The video channel. - * @param dir Which direction to resume. + * @param stream The video stream. + * @param dir Which channel direction to resume. * * @return PJ_SUCCESS on success; */ @@ -321,6 +321,17 @@ PJ_DECL(pj_status_t) pjmedia_vid_stream_resume(pjmedia_vid_stream *stream, /** + * Force stream to send video keyframe on the next transmission. + * + * @param stream The video stream. + * + * @return PJ_SUCCESS on success; + */ +PJ_DECL(pj_status_t) pjmedia_vid_stream_send_keyframe( + pjmedia_vid_stream *stream); + + +/** * @} */ diff --git a/pjmedia/src/pjmedia-codec/ffmpeg_codecs.c b/pjmedia/src/pjmedia-codec/ffmpeg_codecs.c index ccd0901f..96b77be1 100644 --- a/pjmedia/src/pjmedia-codec/ffmpeg_codecs.c +++ b/pjmedia/src/pjmedia-codec/ffmpeg_codecs.c @@ -77,11 +77,12 @@ static pj_status_t ffmpeg_codec_modify(pjmedia_vid_codec *codec, const pjmedia_vid_codec_param *attr ); static pj_status_t ffmpeg_codec_get_param(pjmedia_vid_codec *codec, pjmedia_vid_codec_param *param); -static pj_status_t ffmpeg_codec_encode_begin( pjmedia_vid_codec *codec, - const pjmedia_frame *input, - unsigned out_size, - pjmedia_frame *output, - pj_bool_t *has_more); +static pj_status_t ffmpeg_codec_encode_begin(pjmedia_vid_codec *codec, + const pjmedia_vid_encode_opt *opt, + const pjmedia_frame *input, + unsigned out_size, + pjmedia_frame *output, + pj_bool_t *has_more); static pj_status_t ffmpeg_codec_encode_more(pjmedia_vid_codec *codec, unsigned out_size, pjmedia_frame *output, @@ -136,8 +137,6 @@ typedef struct ffmpeg_private const ffmpeg_codec_desc *desc; pjmedia_vid_codec_param param; /**< Codec param */ pj_pool_t *pool; /**< Pool for each instance */ - pj_timestamp last_tx; /**< Timestamp of last - transmit */ /* Format info and apply format param */ const pjmedia_video_format_info *enc_vfi; @@ -149,10 +148,12 @@ typedef struct ffmpeg_private pj_bool_t whole; void *enc_buf; unsigned enc_buf_size; + pj_bool_t enc_buf_is_keyframe; unsigned enc_frame_len; unsigned enc_processed; void *dec_buf; unsigned dec_buf_size; + pj_timestamp last_dec_keyframe_ts; /* The ffmpeg codec states. */ AVCodec *enc; @@ -981,12 +982,11 @@ static pj_status_t ffmpeg_alloc_codec( pjmedia_vid_codec_factory *factory, /* Create pool for codec instance */ pool = pj_pool_create(ffmpeg_factory.pf, "ffmpeg codec", 512, 512, NULL); - codec = PJ_POOL_ALLOC_T(pool, pjmedia_vid_codec); + codec = PJ_POOL_ZALLOC_T(pool, pjmedia_vid_codec); if (!codec) { status = PJ_ENOMEM; goto on_error; } - pjmedia_vid_codec_reset(codec, PJMEDIA_SIG_VID_CODEC_FFMPEG); codec->op = &ffmpeg_op; codec->factory = factory; ff = PJ_POOL_ZALLOC_T(pool, ffmpeg_private); @@ -1361,6 +1361,7 @@ static pj_status_t ffmpeg_unpacketize(pjmedia_vid_codec *codec, * Encode frames. */ static pj_status_t ffmpeg_codec_encode_whole(pjmedia_vid_codec *codec, + const pjmedia_vid_encode_opt *opt, const pjmedia_frame *input, unsigned output_buf_len, pjmedia_frame *output) @@ -1401,22 +1402,30 @@ static pj_status_t ffmpeg_codec_encode_whole(pjmedia_vid_codec *codec, p += ff->enc_vafp.plane_bytes[i[0]]; } + /* Force keyframe */ + if (opt && opt->force_keyframe) + avframe.pict_type = AV_PICTURE_TYPE_I; + err = avcodec_encode_video(ff->enc_ctx, out_buf, out_buf_len, &avframe); if (err < 0) { print_ffmpeg_err(err); return PJMEDIA_CODEC_EFAILED; } else { output->size = err; + output->bit_info = 0; + if (ff->enc_ctx->coded_frame->key_frame) + output->bit_info |= PJMEDIA_VID_FRM_KEYFRAME; } return PJ_SUCCESS; } -static pj_status_t ffmpeg_codec_encode_begin( pjmedia_vid_codec *codec, - const pjmedia_frame *input, - unsigned out_size, - pjmedia_frame *output, - pj_bool_t *has_more) +static pj_status_t ffmpeg_codec_encode_begin(pjmedia_vid_codec *codec, + const pjmedia_vid_encode_opt *opt, + const pjmedia_frame *input, + unsigned out_size, + pjmedia_frame *output, + pj_bool_t *has_more) { ffmpeg_private *ff = (ffmpeg_private*)codec->codec_data; pj_status_t status; @@ -1424,7 +1433,8 @@ static pj_status_t ffmpeg_codec_encode_begin( pjmedia_vid_codec *codec, *has_more = PJ_FALSE; if (ff->whole) { - status = ffmpeg_codec_encode_whole(codec, input, out_size, output); + status = ffmpeg_codec_encode_whole(codec, opt, input, out_size, + output); } else { pjmedia_frame whole_frm; const pj_uint8_t *payload; @@ -1433,11 +1443,13 @@ static pj_status_t ffmpeg_codec_encode_begin( pjmedia_vid_codec *codec, pj_bzero(&whole_frm, sizeof(whole_frm)); whole_frm.buf = ff->enc_buf; whole_frm.size = ff->enc_buf_size; - status = ffmpeg_codec_encode_whole(codec, input, + status = ffmpeg_codec_encode_whole(codec, opt, input, whole_frm.size, &whole_frm); if (status != PJ_SUCCESS) return status; + ff->enc_buf_is_keyframe = (whole_frm.bit_info & + PJMEDIA_VID_FRM_KEYFRAME); ff->enc_frame_len = (unsigned)whole_frm.size; ff->enc_processed = 0; status = ffmpeg_packetize(codec, (pj_uint8_t*)whole_frm.buf, @@ -1453,6 +1465,9 @@ static pj_status_t ffmpeg_codec_encode_begin( pjmedia_vid_codec *codec, pj_memcpy(output->buf, payload, payload_len); output->size = payload_len; + if (ff->enc_buf_is_keyframe) + output->bit_info |= PJMEDIA_VID_FRM_KEYFRAME; + *has_more = (ff->enc_processed < ff->enc_frame_len); } @@ -1489,12 +1504,90 @@ static pj_status_t ffmpeg_codec_encode_more(pjmedia_vid_codec *codec, pj_memcpy(output->buf, payload, payload_len); output->size = payload_len; + if (ff->enc_buf_is_keyframe) + output->bit_info |= PJMEDIA_VID_FRM_KEYFRAME; + *has_more = (ff->enc_processed < ff->enc_frame_len); return PJ_SUCCESS; } +static pj_status_t check_decode_result(pjmedia_vid_codec *codec, + const pj_timestamp *ts, + pj_bool_t got_keyframe) +{ + ffmpeg_private *ff = (ffmpeg_private*)codec->codec_data; + pjmedia_video_apply_fmt_param *vafp = &ff->dec_vafp; + pjmedia_event event; + + /* Check for format change. + * Decoder output format is set by libavcodec, in case it is different + * to the configured param. + */ + if (ff->dec_ctx->pix_fmt != ff->expected_dec_fmt || + ff->dec_ctx->width != (int)vafp->size.w || + ff->dec_ctx->height != (int)vafp->size.h) + { + pjmedia_format_id new_fmt_id; + pj_status_t status; + + /* Get current raw format id from ffmpeg decoder context */ + status = PixelFormat_to_pjmedia_format_id(ff->dec_ctx->pix_fmt, + &new_fmt_id); + if (status != PJ_SUCCESS) + return status; + + /* Update decoder format in param */ + ff->param.dec_fmt.id = new_fmt_id; + ff->param.dec_fmt.det.vid.size.w = ff->dec_ctx->width; + ff->param.dec_fmt.det.vid.size.h = ff->dec_ctx->height; + ff->expected_dec_fmt = ff->dec_ctx->pix_fmt; + + /* Re-init format info and apply-param of decoder */ + ff->dec_vfi = pjmedia_get_video_format_info(NULL, ff->param.dec_fmt.id); + if (!ff->dec_vfi) + return PJ_ENOTSUP; + pj_bzero(&ff->dec_vafp, sizeof(ff->dec_vafp)); + ff->dec_vafp.size = ff->param.dec_fmt.det.vid.size; + ff->dec_vafp.buffer = NULL; + status = (*ff->dec_vfi->apply_fmt)(ff->dec_vfi, &ff->dec_vafp); + if (status != PJ_SUCCESS) + return status; + + /* Realloc buffer if necessary */ + if (ff->dec_vafp.framebytes > ff->dec_buf_size) { + PJ_LOG(5,(THIS_FILE, "Reallocating decoding buffer %u --> %u", + (unsigned)ff->dec_buf_size, + (unsigned)ff->dec_vafp.framebytes)); + ff->dec_buf_size = ff->dec_vafp.framebytes; + ff->dec_buf = pj_pool_alloc(ff->pool, ff->dec_buf_size); + } + + /* Broadcast format changed event */ + pjmedia_event_init(&event, PJMEDIA_EVENT_FMT_CHANGED, ts, codec); + event.data.fmt_changed.dir = PJMEDIA_DIR_DECODING; + pj_memcpy(&event.data.fmt_changed.new_fmt, &ff->param.dec_fmt, + sizeof(ff->param.dec_fmt)); + pjmedia_event_publish(NULL, codec, &event, 0); + } + + /* Check for missing/found keyframe */ + if (got_keyframe) { + pj_get_timestamp(&ff->last_dec_keyframe_ts); + + /* Broadcast keyframe event */ + pjmedia_event_init(&event, PJMEDIA_EVENT_KEYFRAME_FOUND, ts, codec); + pjmedia_event_publish(NULL, codec, &event, 0); + } else if (ff->last_dec_keyframe_ts.u64 == 0) { + /* Broadcast missing keyframe event */ + pjmedia_event_init(&event, PJMEDIA_EVENT_KEYFRAME_MISSING, ts, codec); + pjmedia_event_publish(NULL, codec, &event, 0); + } + + return PJ_SUCCESS; +} + /* * Decode frame. */ @@ -1557,69 +1650,31 @@ static pj_status_t ffmpeg_codec_decode_whole(pjmedia_vid_codec *codec, &got_picture, avpacket.data, avpacket.size); #endif if (err < 0) { + pjmedia_event event; + output->type = PJMEDIA_FRAME_TYPE_NONE; output->size = 0; print_ffmpeg_err(err); - return PJMEDIA_CODEC_EFAILED; + + /* Broadcast missing keyframe event */ + pjmedia_event_init(&event, PJMEDIA_EVENT_KEYFRAME_MISSING, + &input->timestamp, codec); + pjmedia_event_publish(NULL, codec, &event, 0); + + return PJMEDIA_CODEC_EBADBITSTREAM; } else if (got_picture) { pjmedia_video_apply_fmt_param *vafp = &ff->dec_vafp; pj_uint8_t *q = (pj_uint8_t*)output->buf; unsigned i; + pj_status_t status; - /* Decoder output format is set by libavcodec, in case it is different - * to the configured param. + /* Check decoding result, e.g: see if the format got changed, + * keyframe found/missing. */ - if (ff->dec_ctx->pix_fmt != ff->expected_dec_fmt || - ff->dec_ctx->width != (int)vafp->size.w || - ff->dec_ctx->height != (int)vafp->size.h) - { - pjmedia_format_id new_fmt_id; - pj_status_t status; - - /* Get current raw format id from ffmpeg decoder context */ - status = PixelFormat_to_pjmedia_format_id(ff->dec_ctx->pix_fmt, - &new_fmt_id); - if (status != PJ_SUCCESS) - return status; - - /* Update decoder format in param */ - ff->param.dec_fmt.id = new_fmt_id; - ff->param.dec_fmt.det.vid.size.w = ff->dec_ctx->width; - ff->param.dec_fmt.det.vid.size.h = ff->dec_ctx->height; - ff->expected_dec_fmt = ff->dec_ctx->pix_fmt; - - /* Re-init format info and apply-param of decoder */ - ff->dec_vfi = pjmedia_get_video_format_info(NULL, ff->param.dec_fmt.id); - if (!ff->dec_vfi) - return PJ_ENOTSUP; - pj_bzero(&ff->dec_vafp, sizeof(ff->dec_vafp)); - ff->dec_vafp.size = ff->param.dec_fmt.det.vid.size; - ff->dec_vafp.buffer = NULL; - status = (*ff->dec_vfi->apply_fmt)(ff->dec_vfi, &ff->dec_vafp); - if (status != PJ_SUCCESS) - return status; - - /* Realloc buffer if necessary */ - if (ff->dec_vafp.framebytes > ff->dec_buf_size) { - PJ_LOG(5,(THIS_FILE, "Reallocating decoding buffer %u --> %u", - (unsigned)ff->dec_buf_size, - (unsigned)ff->dec_vafp.framebytes)); - ff->dec_buf_size = ff->dec_vafp.framebytes; - ff->dec_buf = pj_pool_alloc(ff->pool, ff->dec_buf_size); - } - - /* Broadcast event */ - { - pjmedia_event event; - - pjmedia_event_init(&event, PJMEDIA_EVENT_FMT_CHANGED, - &input->timestamp, codec); - event.data.fmt_changed.dir = PJMEDIA_DIR_DECODING; - pj_memcpy(&event.data.fmt_changed.new_fmt, &ff->param.dec_fmt, - sizeof(ff->param.dec_fmt)); - pjmedia_event_publish(NULL, codec, &event, 0); - } - } + status = check_decode_result(codec, &input->timestamp, + avframe.key_frame); + if (status != PJ_SUCCESS) + return status; /* Check provided buffer size */ if (vafp->framebytes > output_buf_len) @@ -1649,16 +1704,6 @@ static pj_status_t ffmpeg_codec_decode_whole(pjmedia_vid_codec *codec, output->type = PJMEDIA_FRAME_TYPE_VIDEO; output->size = vafp->framebytes; - - /* Check if we got key frame */ - if (avframe.key_frame) - { - pjmedia_event event; - - pjmedia_event_init(&event, PJMEDIA_EVENT_KEY_FRAME_FOUND, - &output->timestamp, codec); - pjmedia_event_publish(NULL, codec, &event, 0); - } } else { output->type = PJMEDIA_FRAME_TYPE_NONE; output->size = 0; diff --git a/pjmedia/src/pjmedia-videodev/colorbar_dev.c b/pjmedia/src/pjmedia-videodev/colorbar_dev.c index 6fb658ef..11acf6cb 100644 --- a/pjmedia/src/pjmedia-videodev/colorbar_dev.c +++ b/pjmedia/src/pjmedia-videodev/colorbar_dev.c @@ -577,6 +577,8 @@ static pj_status_t cbar_stream_get_frame(pjmedia_vid_dev_stream *strm, { struct cbar_stream *stream = (struct cbar_stream*)strm; + frame->type = PJMEDIA_FRAME_TYPE_VIDEO; + frame->bit_info = 0; frame->timestamp = stream->ts; stream->ts.u64 += stream->ts_inc; return spectrum_run(stream, frame->buf, frame->size); diff --git a/pjmedia/src/pjmedia-videodev/dshow_dev.c b/pjmedia/src/pjmedia-videodev/dshow_dev.c index c116875f..875a0e89 100644 --- a/pjmedia/src/pjmedia-videodev/dshow_dev.c +++ b/pjmedia/src/pjmedia-videodev/dshow_dev.c @@ -577,7 +577,7 @@ static void input_cb(void *user_data, IMediaSample *pMediaSample) PJ_LOG(5,(THIS_FILE, "Capture thread started")); } - frame.type = PJMEDIA_TYPE_VIDEO; + frame.type = PJMEDIA_FRAME_TYPE_VIDEO; IMediaSample_GetPointer(pMediaSample, (BYTE **)&frame.buf); frame.size = IMediaSample_GetActualDataLength(pMediaSample); frame.bit_info = 0; diff --git a/pjmedia/src/pjmedia-videodev/ios_dev.m b/pjmedia/src/pjmedia-videodev/ios_dev.m index 93b165f1..d81be142 100644 --- a/pjmedia/src/pjmedia-videodev/ios_dev.m +++ b/pjmedia/src/pjmedia-videodev/ios_dev.m @@ -360,7 +360,7 @@ static pj_status_t ios_factory_default_param(pj_pool_t *pool, /* Lock the base address of the pixel buffer */ CVPixelBufferLockBaseAddress(imageBuffer, 0); - frame.type = PJMEDIA_TYPE_VIDEO; + frame.type = PJMEDIA_FRAME_TYPE_VIDEO; frame.buf = CVPixelBufferGetBaseAddress(imageBuffer); frame.size = stream->frame_size; frame.bit_info = 0; diff --git a/pjmedia/src/pjmedia-videodev/qt_dev.m b/pjmedia/src/pjmedia-videodev/qt_dev.m index 8c098fed..39b769d9 100644 --- a/pjmedia/src/pjmedia-videodev/qt_dev.m +++ b/pjmedia/src/pjmedia-videodev/qt_dev.m @@ -371,7 +371,7 @@ static qt_fmt_info* get_qt_format_info(pjmedia_format_id id) if (!videoFrame) return; - frame.type = PJMEDIA_TYPE_VIDEO; + frame.type = PJMEDIA_FRAME_TYPE_VIDEO; frame.buf = [sampleBuffer bytesForAllSamples]; frame.size = size; frame.bit_info = 0; diff --git a/pjmedia/src/pjmedia-videodev/v4l2_dev.c b/pjmedia/src/pjmedia-videodev/v4l2_dev.c index 9e67234f..260799ff 100644 --- a/pjmedia/src/pjmedia-videodev/v4l2_dev.c +++ b/pjmedia/src/pjmedia-videodev/v4l2_dev.c @@ -690,6 +690,7 @@ static pj_status_t vid4lin_stream_get_frame_mmap(vid4lin_stream *stream, PJ_TIME_VAL_SUB(time, stream->start_time); frame->type = PJMEDIA_FRAME_TYPE_VIDEO; + frame->bit_info = 0; frame->size = buf.bytesused; frame->timestamp.u64 = PJ_UINT64(1) * PJ_TIME_VAL_MSEC(time) * stream->param.clock_rate / PJ_UINT64(1000); diff --git a/pjmedia/src/pjmedia/errno.c b/pjmedia/src/pjmedia/errno.c index db66a398..788f7431 100644 --- a/pjmedia/src/pjmedia/errno.c +++ b/pjmedia/src/pjmedia/errno.c @@ -104,6 +104,7 @@ static const struct PJ_BUILD_ERR( PJMEDIA_CODEC_EFRMINLEN, "Invalid codec frame length" ), PJ_BUILD_ERR( PJMEDIA_CODEC_EPCMFRMINLEN, "Invalid PCM frame length" ), PJ_BUILD_ERR( PJMEDIA_CODEC_EINMODE, "Invalid codec mode (no fmtp?)" ), + PJ_BUILD_ERR( PJMEDIA_CODEC_EBADBITSTREAM, "Bad or corrupted bitstream" ), /* Media errors. */ PJ_BUILD_ERR( PJMEDIA_EINVALIDIP, "Invalid remote media (IP) address" ), diff --git a/pjmedia/src/pjmedia/vid_codec.c b/pjmedia/src/pjmedia/vid_codec.c index 2002c6d5..07387991 100644 --- a/pjmedia/src/pjmedia/vid_codec.c +++ b/pjmedia/src/pjmedia/vid_codec.c @@ -70,14 +70,6 @@ struct pjmedia_vid_codec_mgr /* Sort codecs in codec manager based on priorities */ static void sort_codecs(pjmedia_vid_codec_mgr *mgr); -/* - * Initialize pjmedia_vid_codec structure with default values. - */ -PJ_DEF(void) pjmedia_vid_codec_reset(pjmedia_vid_codec *codec, - pjmedia_obj_sig sig) -{ - pj_bzero(codec, sizeof(*codec)); -} /* * Duplicate video codec parameter. diff --git a/pjmedia/src/pjmedia/vid_stream.c b/pjmedia/src/pjmedia/vid_stream.c index 91cedf3d..8d055770 100644 --- a/pjmedia/src/pjmedia/vid_stream.c +++ b/pjmedia/src/pjmedia/vid_stream.c @@ -68,6 +68,7 @@ # define PJMEDIA_VSTREAM_INC 1000 #endif + /** * Media channel. */ @@ -123,6 +124,9 @@ struct pjmedia_vid_stream pjmedia_frame dec_frame; /**< Current decoded frame. */ pjmedia_event fmt_event; /**< Buffered fmt_changed event to avoid deadlock */ + pjmedia_event miss_keyframe_event; + /**< Buffered missing keyframe + event for delayed republish*/ unsigned frame_size; /**< Size of encoded base frame.*/ unsigned frame_ts_len; /**< Frame length in timestamp. */ @@ -131,6 +135,8 @@ struct pjmedia_vid_stream pjmedia_frame *rx_frames; /**< Temp. buffer for incoming frame assembly. */ + pj_bool_t force_keyframe;/**< Forced to encode keyframe? */ + #if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0 pj_bool_t use_ka; /**< Stream keep-alive with non- codec-VAD mechanism is @@ -351,6 +357,12 @@ static pj_status_t stream_event_cb(pjmedia_event *event, */ pj_memcpy(&stream->fmt_event, event, sizeof(*event)); return PJ_SUCCESS; + + case PJMEDIA_EVENT_KEYFRAME_MISSING: + /* Republish this event later from get_frame(). */ + pj_memcpy(&stream->miss_keyframe_event, event, sizeof(*event)); + return PJ_SUCCESS; + default: break; } @@ -763,7 +775,7 @@ static pj_status_t put_frame(pjmedia_port *port, int rtphdrlen; pj_bool_t has_more_data = PJ_FALSE; pj_size_t total_sent = 0; - + pjmedia_vid_encode_opt enc_opt; #if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA != 0 /* If the interval since last sending packet is greater than @@ -796,8 +808,18 @@ static pj_status_t put_frame(pjmedia_port *port, frame_out.buf = ((char*)channel->buf) + sizeof(pjmedia_rtp_hdr); frame_out.size = 0; + /* Init encoding option */ + pj_bzero(&enc_opt, sizeof(enc_opt)); + if (stream->force_keyframe) { + /* Force encoder to generate keyframe */ + enc_opt.force_keyframe = PJ_TRUE; + stream->force_keyframe = PJ_FALSE; + TRC_((channel->port.info.name.ptr, + "Forcing encoder to generate keyframe")); + } + /* Encode! */ - status = pjmedia_vid_codec_encode_begin(stream->codec, frame, + status = pjmedia_vid_codec_encode_begin(stream->codec, &enc_opt, frame, channel->buf_size - sizeof(pjmedia_rtp_hdr), &frame_out, @@ -1079,6 +1101,12 @@ static pj_status_t get_frame(pjmedia_port *port, stream->fmt_event.type = PJMEDIA_EVENT_NONE; } + if (stream->miss_keyframe_event.type != PJMEDIA_EVENT_NONE) { + pjmedia_event_publish(NULL, port, &stream->miss_keyframe_event, + PJMEDIA_EVENT_PUBLISH_POST_EVENT); + stream->miss_keyframe_event.type = PJMEDIA_EVENT_NONE; + } + pj_mutex_lock( stream->jb_mutex ); if (stream->dec_frame.size == 0) { @@ -1424,8 +1452,8 @@ PJ_DEF(pj_status_t) pjmedia_vid_stream_create( /* Set up jitter buffer */ - pjmedia_jbuf_set_adaptive( stream->jb, jb_init, jb_min_pre, jb_max_pre); - //pjmedia_jbuf_enable_discard(stream->jb, PJ_FALSE); + pjmedia_jbuf_set_adaptive(stream->jb, jb_init, jb_min_pre, jb_max_pre); + pjmedia_jbuf_set_discard(stream->jb, PJMEDIA_JB_DISCARD_NONE); /* Init RTCP session: */ { @@ -2090,4 +2118,22 @@ PJ_DEF(pj_status_t) pjmedia_vid_stream_info_from_sdp( return status; } + +/* + * Force stream to send video keyframe. + */ +PJ_DEF(pj_status_t) pjmedia_vid_stream_send_keyframe( + pjmedia_vid_stream *stream) +{ + PJ_ASSERT_RETURN(stream, PJ_EINVAL); + + if (!pjmedia_vid_stream_is_running(stream, PJMEDIA_DIR_ENCODING)) + return PJ_EINVALIDOP; + + stream->force_keyframe = PJ_TRUE; + + return PJ_SUCCESS; +} + + #endif /* PJMEDIA_HAS_VIDEO */ diff --git a/pjmedia/src/test/vid_codec_test.c b/pjmedia/src/test/vid_codec_test.c index 11ef93ab..3df532ab 100644 --- a/pjmedia/src/test/vid_codec_test.c +++ b/pjmedia/src/test/vid_codec_test.c @@ -94,7 +94,7 @@ static pj_status_t codec_put_frame(pjmedia_port *port, enc_frames[enc_cnt].buf = enc_buf; enc_frames[enc_cnt].size = enc_size_left; - status = pjmedia_vid_codec_encode_begin(codec, frame, enc_size_left, + status = pjmedia_vid_codec_encode_begin(codec, NULL, frame, enc_size_left, &enc_frames[enc_cnt], &has_more); if (status != PJ_SUCCESS) goto on_error; diff --git a/pjsip-apps/src/pjsua/pjsua_app.c b/pjsip-apps/src/pjsua/pjsua_app.c index e459d95c..aa810ca5 100644 --- a/pjsip-apps/src/pjsua/pjsua_app.c +++ b/pjsip-apps/src/pjsua/pjsua_app.c @@ -2633,7 +2633,30 @@ static void on_call_tsx_state(pjsua_call_id call_id, /* * Handle INFO method. */ - if (tsx->role == PJSIP_ROLE_UAC && + const pj_str_t STR_APPLICATION = { "application", 11}; + const pj_str_t STR_DTMF_RELAY = { "dtmf-relay", 10 }; + pjsip_msg_body *body = NULL; + pj_bool_t dtmf_info = PJ_FALSE; + + if (tsx->role == PJSIP_ROLE_UAC) { + if (e->body.tsx_state.type == PJSIP_EVENT_TX_MSG) + body = e->body.tsx_state.src.tdata->msg->body; + else + body = e->body.tsx_state.tsx->last_tx->msg->body; + } else { + if (e->body.tsx_state.type == PJSIP_EVENT_RX_MSG) + body = e->body.tsx_state.src.rdata->msg_info.msg->body; + } + + /* Check DTMF content in the INFO message */ + if (body && body->len && + pj_stricmp(&body->content_type.type, &STR_APPLICATION)==0 && + pj_stricmp(&body->content_type.subtype, &STR_DTMF_RELAY)==0) + { + dtmf_info = PJ_TRUE; + } + + if (dtmf_info && tsx->role == PJSIP_ROLE_UAC && (tsx->state == PJSIP_TSX_STATE_COMPLETED || (tsx->state == PJSIP_TSX_STATE_TERMINATED && e->body.tsx_state.prev_state != PJSIP_TSX_STATE_COMPLETED))) @@ -2651,7 +2674,7 @@ static void on_call_tsx_state(pjsua_call_id call_id, (int)tsx->status_text.slen, tsx->status_text.ptr)); } - } else if (tsx->role == PJSIP_ROLE_UAS && + } else if (dtmf_info && tsx->role == PJSIP_ROLE_UAS && tsx->state == PJSIP_TSX_STATE_TRYING) { /* Answer incoming INFO with 200/OK */ @@ -2882,9 +2905,9 @@ static void on_call_media_state(pjsua_call_id call_id) vid_idx = pjsua_call_get_vid_stream_idx(call_id); if (vid_idx == -1 || call_info.media[vid_idx].dir == PJMEDIA_DIR_NONE) { PJ_LOG(3,(THIS_FILE, - "Just rejected incoming video offer on call %d" - "use \"vid call add\" to enable video!", - call_id)); + "Just rejected incoming video offer on call %d, " + "use \"vid call enable %d\" or \"vid call add\" to enable video!", + call_id, vid_idx)); } } #endif diff --git a/pjsip/include/pjsua-lib/pjsua.h b/pjsip/include/pjsua-lib/pjsua.h index 31fbfca1..b043f266 100644 --- a/pjsip/include/pjsua-lib/pjsua.h +++ b/pjsip/include/pjsua-lib/pjsua.h @@ -331,6 +331,17 @@ typedef struct pjsua_msg_data pjsua_msg_data; # define PJSUA_HAS_VIDEO PJMEDIA_HAS_VIDEO #endif + +/** + * Interval between two keyframe requests, in milliseconds. + * + * Default: 500 ms + */ +#ifndef PJSUA_VID_REQ_KEYFRAME_INTERVAL +# define PJSUA_VID_REQ_KEYFRAME_INTERVAL 500 +#endif + + /** * This enumeration represents pjsua state. */ @@ -3371,18 +3382,46 @@ typedef enum pjsua_call_media_status /** + * Enumeration of video keyframe request methods. Keyframe request is + * triggered by decoder, usually when the incoming video stream cannot + * be decoded properly due to missing video keyframe. + */ +typedef enum pjsua_vid_req_keyframe_method +{ + /** + * Requesting keyframe via SIP INFO message. Note that incoming keyframe + * request via SIP INFO will always be handled even if this flag is unset. + */ + PJSUA_VID_REQ_KEYFRAME_SIP_INFO = 1, + + /** + * Requesting keyframe via Picture Loss Indication of RTCP feedback. + * This is currently not supported. + */ + PJSUA_VID_REQ_KEYFRAME_RTCP_PLI = 2 + +} pjsua_vid_req_keyframe_method; + + +/** * Call settings. */ typedef struct pjsua_call_setting { /** - * Bitmask of pjsua_call_flag constants. + * Bitmask of #pjsua_call_flag constants. * * Default: 0 */ unsigned flag; /** + * This flag controls what methods to request keyframe are allowed on + * the call. Value is bitmask of #pjsua_vid_req_keyframe_method. + */ + unsigned req_keyframe_method; + + /** * Number of simultaneous active audio streams for this call. Setting * this to zero will disable audio in this call. * @@ -3649,6 +3688,13 @@ typedef enum pjsua_call_vid_strm_op */ PJSUA_CALL_VID_STRM_STOP_TRANSMIT, + /** + * Send keyframe in the video stream. This will force the stream to + * generate and send video keyframe as soon as possible. No + * re-INVITE/UPDATE is to be transmitted to remote with this operation. + */ + PJSUA_CALL_VID_STRM_SEND_KEYFRAME + } pjsua_call_vid_strm_op; @@ -4664,6 +4710,11 @@ PJ_DECL(void) pjsua_pres_dump(pj_bool_t verbose); extern const pjsip_method pjsip_message_method; +/** + * The INFO method (defined in pjsua_call.c) + */ +extern const pjsip_method pjsip_info_method; + /** * Send instant messaging outside dialog, using the specified account for diff --git a/pjsip/include/pjsua-lib/pjsua_internal.h b/pjsip/include/pjsua-lib/pjsua_internal.h index 5c16370a..31447ddf 100644 --- a/pjsip/include/pjsua-lib/pjsua_internal.h +++ b/pjsip/include/pjsua-lib/pjsua_internal.h @@ -84,6 +84,7 @@ struct pjsua_call_media (used to update ICE default address) */ pjmedia_srtp_use rem_srtp_use; /**< Remote's SRTP usage policy. */ + pj_timestamp last_req_keyframe;/**< Last TX keyframe request. */ pjsua_med_tp_state_cb med_init_cb;/**< Media transport initialization callback. */ @@ -477,6 +478,9 @@ typedef struct pjsua_im_data void *user_data; } pjsua_im_data; +pj_status_t pjsua_media_apply_xml_control(pjsua_call_id call_id, + const pj_str_t *xml_st); + /** * Duplicate IM data. diff --git a/pjsip/src/pjsua-lib/pjsua_call.c b/pjsip/src/pjsua-lib/pjsua_call.c index 2447b80c..9c1c0500 100644 --- a/pjsip/src/pjsua-lib/pjsua_call.c +++ b/pjsip/src/pjsua-lib/pjsua_call.c @@ -34,6 +34,17 @@ */ #define LOCK_CODEC_MAX_RETRY 5 + +/* + * The INFO method. + */ +const pjsip_method pjsip_info_method = +{ + PJSIP_OTHER_METHOD, + { "INFO", 4 } +}; + + /* This callback receives notification from invite session when the * session state has changed. */ @@ -500,11 +511,8 @@ PJ_DEF(void) pjsua_call_setting_default(pjsua_call_setting *opt) #if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0) opt->video_cnt = 1; - //{ - // unsigned i; - // for (i = 0; i < PJ_ARRAY_SIZE(opt->vid_cap_dev); ++i) - // opt->vid_cap_dev[i] = PJMEDIA_VID_DEFAULT_CAPTURE_DEV; - //} + opt->req_keyframe_method = PJSUA_VID_REQ_KEYFRAME_SIP_INFO | + PJSUA_VID_REQ_KEYFRAME_RTCP_PLI; #endif } @@ -4190,6 +4198,41 @@ static void pjsua_call_on_tsx_state_changed(pjsip_inv_session *inv, PJ_LOG(3,(THIS_FILE, "Error putting call %d on hold (reason=%d)", call->index, tsx->status_code)); } + } else if (tsx->role==PJSIP_ROLE_UAS && + tsx->state==PJSIP_TSX_STATE_TRYING && + pjsip_method_cmp(&tsx->method, &pjsip_info_method)==0) + { + /* + * Incoming INFO request for media control. + */ + const pj_str_t STR_APPLICATION = { "application", 11}; + const pj_str_t STR_MEDIA_CONTROL_XML = { "media_control+xml", 17 }; + pjsip_rx_data *rdata = e->body.tsx_state.src.rdata; + pjsip_msg_body *body = rdata->msg_info.msg->body; + + if (body && body->len && + pj_stricmp(&body->content_type.type, &STR_APPLICATION)==0 && + pj_stricmp(&body->content_type.subtype, &STR_MEDIA_CONTROL_XML)==0) + { + pjsip_tx_data *tdata; + pj_str_t control_st; + pj_status_t status; + + /* Apply and answer the INFO request */ + pj_strset(&control_st, (char*)body->data, body->len); + status = pjsua_media_apply_xml_control(call->index, &control_st); + if (status == PJ_SUCCESS) { + status = pjsip_endpt_create_response(tsx->endpt, rdata, + 200, NULL, &tdata); + if (status == PJ_SUCCESS) + status = pjsip_tsx_send_msg(tsx, tdata); + } else { + status = pjsip_endpt_create_response(tsx->endpt, rdata, + 400, NULL, &tdata); + if (status == PJ_SUCCESS) + status = pjsip_tsx_send_msg(tsx, tdata); + } + } } on_return: diff --git a/pjsip/src/pjsua-lib/pjsua_media.c b/pjsip/src/pjsua-lib/pjsua_media.c index e938247f..4a413e37 100644 --- a/pjsip/src/pjsua-lib/pjsua_media.c +++ b/pjsip/src/pjsua-lib/pjsua_media.c @@ -1258,13 +1258,55 @@ pj_status_t call_media_on_event(pjmedia_event *event, { pjsua_call_media *call_med = (pjsua_call_media*)user_data; pjsua_call *call = call_med->call; + pj_status_t status = PJ_SUCCESS; + + switch(event->type) { + case PJMEDIA_EVENT_KEYFRAME_MISSING: + if (call->opt.req_keyframe_method & PJSUA_VID_REQ_KEYFRAME_SIP_INFO) + { + pj_timestamp now; + + pj_get_timestamp(&now); + if (pj_elapsed_msec(&call_med->last_req_keyframe, &now) >= + PJSUA_VID_REQ_KEYFRAME_INTERVAL) + { + pjsua_msg_data msg_data; + const pj_str_t SIP_INFO = {"INFO", 4}; + const char *BODY_TYPE = "application/media_control+xml"; + const char *BODY = + "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" + "<media_control><vc_primitive><to_encoder>" + "<picture_fast_update/>" + "</to_encoder></vc_primitive></media_control>"; + + PJ_LOG(4,(THIS_FILE, + "Sending video keyframe request via SIP INFO")); + + pjsua_msg_data_init(&msg_data); + pj_cstr(&msg_data.content_type, BODY_TYPE); + pj_cstr(&msg_data.msg_body, BODY); + status = pjsua_call_send_request(call->index, &SIP_INFO, + &msg_data); + if (status != PJ_SUCCESS) { + pj_perror(3, THIS_FILE, status, + "Failed requesting keyframe via SIP INFO"); + } else { + call_med->last_req_keyframe = now; + } + } + } + break; + + default: + break; + } if (pjsua_var.ua_cfg.cb.on_call_media_event && call) { (*pjsua_var.ua_cfg.cb.on_call_media_event)(call->index, call_med->idx, event); } - return PJ_SUCCESS; + return status; } /* Set media transport state and notify the application via the callback. */ @@ -4187,3 +4229,33 @@ PJ_DEF(pj_status_t) pjsua_codec_set_param( const pj_str_t *codec_id, } +pj_status_t pjsua_media_apply_xml_control(pjsua_call_id call_id, + const pj_str_t *xml_st) +{ + pjsua_call *call = &pjsua_var.calls[call_id]; + const pj_str_t PICT_FAST_UPDATE = {"picture_fast_update", 19}; + +#if PJMEDIA_HAS_VIDEO + if (pj_strstr(xml_st, &PICT_FAST_UPDATE)) { + unsigned i; + + PJ_LOG(4,(THIS_FILE, "Received keyframe request via SIP INFO")); + + for (i = 0; i < call->med_cnt; ++i) { + pjsua_call_media *cm = &call->media[i]; + if (cm->type != PJMEDIA_TYPE_VIDEO || !cm->strm.v.stream) + continue; + + pjmedia_vid_stream_send_keyframe(cm->strm.v.stream); + } + + return PJ_SUCCESS; + } +#endif + + /* Just to avoid compiler warning of unused var */ + PJ_UNUSED_ARG(xml_st); + + return PJ_ENOTSUP; +} + diff --git a/pjsip/src/pjsua-lib/pjsua_vid.c b/pjsip/src/pjsua-lib/pjsua_vid.c index 4e9017fa..b8768c00 100644 --- a/pjsip/src/pjsua-lib/pjsua_vid.c +++ b/pjsip/src/pjsua-lib/pjsua_vid.c @@ -2000,6 +2000,32 @@ static pj_status_t call_set_tx_video(pjsua_call *call, } +static pj_status_t call_send_vid_keyframe(pjsua_call *call, + int med_idx) +{ + pjsua_call_media *call_med; + + /* Verify and normalize media index */ + if (med_idx == -1) { + int first_active; + + call_get_vid_strm_info(call, &first_active, NULL, NULL, NULL); + if (first_active == -1) + return PJ_ENOTFOUND; + + med_idx = first_active; + } + + call_med = &call->media[med_idx]; + + /* Verify media type and stream instance. */ + if (call_med->type != PJMEDIA_TYPE_VIDEO || !call_med->strm.v.stream) + return PJ_EINVAL; + + return pjmedia_vid_stream_send_keyframe(call_med->strm.v.stream); +} + + /* * Start, stop, and/or manipulate video transmission for the specified call. */ @@ -2069,6 +2095,9 @@ PJ_DEF(pj_status_t) pjsua_call_set_vid_strm ( case PJSUA_CALL_VID_STRM_STOP_TRANSMIT: status = call_set_tx_video(call, param_.med_idx, PJ_FALSE); break; + case PJSUA_CALL_VID_STRM_SEND_KEYFRAME: + status = call_send_vid_keyframe(call, param_.med_idx); + break; default: status = PJ_EINVALIDOP; break; |