* #27232: jni: added pjproject checkout as regular git content

We will remove it once the next release of pjsip (with Android support)
comes out and is merged into SFLphone.
diff --git a/jni/pjproject-android/.svn/pristine/d6/d60708deda843d0610f6a53b635d68de1af93ed5.svn-base b/jni/pjproject-android/.svn/pristine/d6/d60708deda843d0610f6a53b635d68de1af93ed5.svn-base
new file mode 100644
index 0000000..2e3f3a2
--- /dev/null
+++ b/jni/pjproject-android/.svn/pristine/d6/d60708deda843d0610f6a53b635d68de1af93ed5.svn-base
Binary files differ
diff --git a/jni/pjproject-android/.svn/pristine/d6/d68e28eaf2892497752720448fca9929719b3e98.svn-base b/jni/pjproject-android/.svn/pristine/d6/d68e28eaf2892497752720448fca9929719b3e98.svn-base
new file mode 100644
index 0000000..085e524
--- /dev/null
+++ b/jni/pjproject-android/.svn/pristine/d6/d68e28eaf2892497752720448fca9929719b3e98.svn-base
@@ -0,0 +1,71 @@
+Microsoft Developer Studio Workspace File, Format Version 6.00

+# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!

+

+###############################################################################

+

+Project: "pjlib"="..\..\pjlib\build\pjlib.dsp" - Package Owner=<4>

+

+Package=<5>

+{{{

+}}}

+

+Package=<4>

+{{{

+}}}

+

+###############################################################################

+

+Project: "pjlib_test"="..\..\pjlib\build\pjlib_test.dsp" - Package Owner=<4>

+

+Package=<5>

+{{{

+}}}

+

+Package=<4>

+{{{

+}}}

+

+###############################################################################

+

+Project: "pjlib_util"=".\pjlib_util.dsp" - Package Owner=<4>

+

+Package=<5>

+{{{

+}}}

+

+Package=<4>

+{{{

+}}}

+

+###############################################################################

+

+Project: "pjlib_util_test"=".\pjlib_util_test.dsp" - Package Owner=<4>

+

+Package=<5>

+{{{

+}}}

+

+Package=<4>

+{{{

+    Begin Project Dependency

+    Project_Dep_Name pjlib_util

+    End Project Dependency

+    Begin Project Dependency

+    Project_Dep_Name pjlib

+    End Project Dependency

+}}}

+

+###############################################################################

+

+Global:

+

+Package=<5>

+{{{

+}}}

+

+Package=<3>

+{{{

+}}}

+

+###############################################################################

+

diff --git a/jni/pjproject-android/.svn/pristine/d6/d6a48eacef4e7983667c194059f67ea32e4b6a26.svn-base b/jni/pjproject-android/.svn/pristine/d6/d6a48eacef4e7983667c194059f67ea32e4b6a26.svn-base
new file mode 100644
index 0000000..e4c3aea
--- /dev/null
+++ b/jni/pjproject-android/.svn/pristine/d6/d6a48eacef4e7983667c194059f67ea32e4b6a26.svn-base
@@ -0,0 +1,1824 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2010-2011 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-codec/ffmpeg_vid_codecs.h>
+#include <pjmedia-codec/h263_packetizer.h>
+#include <pjmedia-codec/h264_packetizer.h>
+#include <pjmedia/errno.h>
+#include <pjmedia/vid_codec_util.h>
+#include <pj/assert.h>
+#include <pj/list.h>
+#include <pj/log.h>
+#include <pj/math.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+#include <pj/os.h>
+
+
+/*
+ * Only build this file if PJMEDIA_HAS_FFMPEG_VID_CODEC != 0 and 
+ * PJMEDIA_HAS_VIDEO != 0
+ */
+#if defined(PJMEDIA_HAS_FFMPEG_VID_CODEC) && \
+            PJMEDIA_HAS_FFMPEG_VID_CODEC != 0 && \
+    defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0)
+
+#define THIS_FILE   "ffmpeg_vid_codecs.c"
+
+#define LIBAVCODEC_VER_AT_LEAST(major,minor)  (LIBAVCODEC_VERSION_MAJOR > major || \
+     					       (LIBAVCODEC_VERSION_MAJOR == major && \
+					        LIBAVCODEC_VERSION_MINOR >= minor))
+
+#include "../pjmedia/ffmpeg_util.h"
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+#if LIBAVCODEC_VER_AT_LEAST(53,20)
+  /* Needed by 264 so far, on libavcodec 53.20 */
+# include <libavutil/opt.h>
+#endif
+
+
+/* Various compatibility */
+
+#if LIBAVCODEC_VER_AT_LEAST(53,20)
+#  define AVCODEC_OPEN(ctx,c)		avcodec_open2(ctx,c,NULL)
+#else
+#  define AVCODEC_OPEN(ctx,c)		avcodec_open(ctx,c)
+#endif
+
+#if LIBAVCODEC_VER_AT_LEAST(53,61)
+#  if LIBAVCODEC_VER_AT_LEAST(54,59)
+   /* Not sure when AVCodec::encode is obsoleted/removed. */
+#      define AVCODEC_HAS_ENCODE(c)	(c->encode2)
+#  else
+   /* Not sure when AVCodec::encode2 is introduced. It appears in 
+    * libavcodec 53.61 where some codecs actually still use AVCodec::encode
+    * (e.g: H263, H264).
+    */
+#      define AVCODEC_HAS_ENCODE(c)	(c->encode || c->encode2)
+#  endif
+#  define AV_OPT_SET(obj,name,val,opt)	(av_opt_set(obj,name,val,opt)==0)
+#  define AV_OPT_SET_INT(obj,name,val)	(av_opt_set_int(obj,name,val,0)==0)
+#else
+#  define AVCODEC_HAS_ENCODE(c)		(c->encode)
+#  define AV_OPT_SET(obj,name,val,opt)	(av_set_string3(obj,name,val,opt,NULL)==0)
+#  define AV_OPT_SET_INT(obj,name,val)	(av_set_int(obj,name,val)!=NULL)
+#endif
+#define AVCODEC_HAS_DECODE(c)		(c->decode)
+
+
+/* Prototypes for FFMPEG codecs factory */
+static pj_status_t ffmpeg_test_alloc( pjmedia_vid_codec_factory *factory, 
+				      const pjmedia_vid_codec_info *id );
+static pj_status_t ffmpeg_default_attr( pjmedia_vid_codec_factory *factory, 
+				        const pjmedia_vid_codec_info *info, 
+				        pjmedia_vid_codec_param *attr );
+static pj_status_t ffmpeg_enum_codecs( pjmedia_vid_codec_factory *factory, 
+				       unsigned *count, 
+				       pjmedia_vid_codec_info codecs[]);
+static pj_status_t ffmpeg_alloc_codec( pjmedia_vid_codec_factory *factory, 
+				       const pjmedia_vid_codec_info *info, 
+				       pjmedia_vid_codec **p_codec);
+static pj_status_t ffmpeg_dealloc_codec( pjmedia_vid_codec_factory *factory, 
+				         pjmedia_vid_codec *codec );
+
+/* Prototypes for FFMPEG codecs implementation. */
+static pj_status_t  ffmpeg_codec_init( pjmedia_vid_codec *codec, 
+				       pj_pool_t *pool );
+static pj_status_t  ffmpeg_codec_open( pjmedia_vid_codec *codec, 
+				       pjmedia_vid_codec_param *attr );
+static pj_status_t  ffmpeg_codec_close( pjmedia_vid_codec *codec );
+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_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,
+					    pj_bool_t *has_more);
+static pj_status_t ffmpeg_codec_decode( pjmedia_vid_codec *codec,
+					pj_size_t pkt_count,
+					pjmedia_frame packets[],
+					unsigned out_size,
+					pjmedia_frame *output);
+
+/* Definition for FFMPEG codecs operations. */
+static pjmedia_vid_codec_op ffmpeg_op = 
+{
+    &ffmpeg_codec_init,
+    &ffmpeg_codec_open,
+    &ffmpeg_codec_close,
+    &ffmpeg_codec_modify,
+    &ffmpeg_codec_get_param,
+    &ffmpeg_codec_encode_begin,
+    &ffmpeg_codec_encode_more,
+    &ffmpeg_codec_decode,
+    NULL
+};
+
+/* Definition for FFMPEG codecs factory operations. */
+static pjmedia_vid_codec_factory_op ffmpeg_factory_op =
+{
+    &ffmpeg_test_alloc,
+    &ffmpeg_default_attr,
+    &ffmpeg_enum_codecs,
+    &ffmpeg_alloc_codec,
+    &ffmpeg_dealloc_codec
+};
+
+
+/* FFMPEG codecs factory */
+static struct ffmpeg_factory {
+    pjmedia_vid_codec_factory    base;
+    pjmedia_vid_codec_mgr	*mgr;
+    pj_pool_factory             *pf;
+    pj_pool_t		        *pool;
+    pj_mutex_t		        *mutex;
+} ffmpeg_factory;
+
+
+typedef struct ffmpeg_codec_desc ffmpeg_codec_desc;
+
+
+/* FFMPEG codecs private data. */
+typedef struct ffmpeg_private
+{
+    const ffmpeg_codec_desc	    *desc;
+    pjmedia_vid_codec_param	     param;	/**< Codec param	    */
+    pj_pool_t			    *pool;	/**< Pool for each instance */
+
+    /* Format info and apply format param */
+    const pjmedia_video_format_info *enc_vfi;
+    pjmedia_video_apply_fmt_param    enc_vafp;
+    const pjmedia_video_format_info *dec_vfi;
+    pjmedia_video_apply_fmt_param    dec_vafp;
+
+    /* Buffers, only needed for multi-packets */
+    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;
+    AVCodec			    *dec;
+    AVCodecContext		    *enc_ctx;
+    AVCodecContext		    *dec_ctx;
+
+    /* The ffmpeg decoder cannot set the output format, so format conversion
+     * may be needed for post-decoding.
+     */
+    enum PixelFormat		     expected_dec_fmt;
+						/**< Expected output format of 
+						     ffmpeg decoder	    */
+
+    void			    *data;	/**< Codec specific data    */		    
+} ffmpeg_private;
+
+
+/* Shortcuts for packetize & unpacketize function declaration,
+ * as it has long params and is reused many times!
+ */
+#define FUNC_PACKETIZE(name) \
+    pj_status_t(name)(ffmpeg_private *ff, pj_uint8_t *bits, \
+		      pj_size_t bits_len, unsigned *bits_pos, \
+		      const pj_uint8_t **payload, pj_size_t *payload_len)
+
+#define FUNC_UNPACKETIZE(name) \
+    pj_status_t(name)(ffmpeg_private *ff, const pj_uint8_t *payload, \
+		      pj_size_t payload_len, pj_uint8_t *bits, \
+		      pj_size_t bits_len, unsigned *bits_pos)
+
+#define FUNC_FMT_MATCH(name) \
+    pj_status_t(name)(pj_pool_t *pool, \
+		      pjmedia_sdp_media *offer, unsigned o_fmt_idx, \
+		      pjmedia_sdp_media *answer, unsigned a_fmt_idx, \
+		      unsigned option)
+
+
+/* Type definition of codec specific functions */
+typedef FUNC_PACKETIZE(*func_packetize);
+typedef FUNC_UNPACKETIZE(*func_unpacketize);
+typedef pj_status_t (*func_preopen)	(ffmpeg_private *ff);
+typedef pj_status_t (*func_postopen)	(ffmpeg_private *ff);
+typedef FUNC_FMT_MATCH(*func_sdp_fmt_match);
+
+
+/* FFMPEG codec info */
+struct ffmpeg_codec_desc
+{
+    /* Predefined info */
+    pjmedia_vid_codec_info       info;
+    pjmedia_format_id		 base_fmt_id;	/**< Some codecs may be exactly
+						     same or compatible with
+						     another codec, base format
+						     will tell the initializer
+						     to copy this codec desc
+						     from its base format   */
+    pjmedia_rect_size            size;
+    pjmedia_ratio                fps;
+    pj_uint32_t			 avg_bps;
+    pj_uint32_t			 max_bps;
+    func_packetize		 packetize;
+    func_unpacketize		 unpacketize;
+    func_preopen		 preopen;
+    func_preopen		 postopen;
+    func_sdp_fmt_match		 sdp_fmt_match;
+    pjmedia_codec_fmtp		 dec_fmtp;
+
+    /* Init time defined info */
+    pj_bool_t			 enabled;
+    AVCodec                     *enc;
+    AVCodec                     *dec;
+};
+
+
+#if PJMEDIA_HAS_FFMPEG_CODEC_H264 && !LIBAVCODEC_VER_AT_LEAST(53,20)
+#   error "Must use libavcodec version 53.20 or later to enable FFMPEG H264"
+#endif
+
+/* H264 constants */
+#define PROFILE_H264_BASELINE		66
+#define PROFILE_H264_MAIN		77
+
+/* Codec specific functions */
+#if PJMEDIA_HAS_FFMPEG_CODEC_H264
+static pj_status_t h264_preopen(ffmpeg_private *ff);
+static pj_status_t h264_postopen(ffmpeg_private *ff);
+static FUNC_PACKETIZE(h264_packetize);
+static FUNC_UNPACKETIZE(h264_unpacketize);
+#endif
+
+static pj_status_t h263_preopen(ffmpeg_private *ff);
+static FUNC_PACKETIZE(h263_packetize);
+static FUNC_UNPACKETIZE(h263_unpacketize);
+
+
+/* Internal codec info */
+static ffmpeg_codec_desc codec_desc[] =
+{
+#if PJMEDIA_HAS_FFMPEG_CODEC_H264
+    {
+	{PJMEDIA_FORMAT_H264, PJMEDIA_RTP_PT_H264, {"H264",4},
+	 {"Constrained Baseline (level=30, pack=1)", 39}},
+	0,
+	{720, 480},	{15, 1},	256000, 256000,
+	&h264_packetize, &h264_unpacketize, &h264_preopen, &h264_postopen,
+	&pjmedia_vid_codec_h264_match_sdp,
+	/* Leading space for better compatibility (strange indeed!) */
+	{2, { {{"profile-level-id",16},    {"42e01e",6}}, 
+	      {{" packetization-mode",19},  {"1",1}}, } },
+    },
+#endif
+
+#if PJMEDIA_HAS_FFMPEG_CODEC_H263P
+    {
+	{PJMEDIA_FORMAT_H263P, PJMEDIA_RTP_PT_H263P, {"H263-1998",9}},
+	PJMEDIA_FORMAT_H263,
+	{352, 288},	{15, 1},	256000, 256000,
+	&h263_packetize, &h263_unpacketize, &h263_preopen, NULL, NULL,
+	{2, { {{"CIF",3},   {"1",1}}, 
+	      {{"QCIF",4},  {"1",1}}, } },
+    },
+#endif
+
+    {
+	{PJMEDIA_FORMAT_H263,	PJMEDIA_RTP_PT_H263,	{"H263",4}},
+    },
+    {
+	{PJMEDIA_FORMAT_H261,	PJMEDIA_RTP_PT_H261,	{"H261",4}},
+    },
+    {
+	{PJMEDIA_FORMAT_MJPEG,	PJMEDIA_RTP_PT_JPEG,	{"JPEG",4}},
+	PJMEDIA_FORMAT_MJPEG, {640, 480}, {25, 1},
+    },
+    {
+	{PJMEDIA_FORMAT_MPEG4,	0,			{"MP4V",4}},
+	PJMEDIA_FORMAT_MPEG4, {640, 480}, {25, 1},
+    },
+};
+
+#if PJMEDIA_HAS_FFMPEG_CODEC_H264
+
+typedef struct h264_data
+{
+    pjmedia_vid_codec_h264_fmtp	 fmtp;
+    pjmedia_h264_packetizer	*pktz;
+} h264_data;
+
+
+static pj_status_t h264_preopen(ffmpeg_private *ff)
+{
+    h264_data *data;
+    pjmedia_h264_packetizer_cfg pktz_cfg;
+    pj_status_t status;
+
+    data = PJ_POOL_ZALLOC_T(ff->pool, h264_data);
+    ff->data = data;
+
+    /* Parse remote fmtp */
+    status = pjmedia_vid_codec_h264_parse_fmtp(&ff->param.enc_fmtp,
+					       &data->fmtp);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Create packetizer */
+    pktz_cfg.mtu = ff->param.enc_mtu;
+#if 0
+    if (data->fmtp.packetization_mode == 0)
+	pktz_cfg.mode = PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL;
+    else if (data->fmtp.packetization_mode == 1)
+	pktz_cfg.mode = PJMEDIA_H264_PACKETIZER_MODE_NON_INTERLEAVED;
+    else
+	return PJ_ENOTSUP;
+#else
+    if (data->fmtp.packetization_mode!=
+				PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL &&
+	data->fmtp.packetization_mode!=
+				PJMEDIA_H264_PACKETIZER_MODE_NON_INTERLEAVED)
+    {
+	return PJ_ENOTSUP;
+    }
+    /* Better always send in single NAL mode for better compatibility */
+    pktz_cfg.mode = PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL;
+#endif
+
+    status = pjmedia_h264_packetizer_create(ff->pool, &pktz_cfg, &data->pktz);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Apply SDP fmtp to format in codec param */
+    if (!ff->param.ignore_fmtp) {
+	status = pjmedia_vid_codec_h264_apply_fmtp(&ff->param);
+	if (status != PJ_SUCCESS)
+	    return status;
+    }
+
+    if (ff->param.dir & PJMEDIA_DIR_ENCODING) {
+	pjmedia_video_format_detail *vfd;
+	AVCodecContext *ctx = ff->enc_ctx;
+	const char *profile = NULL;
+
+	vfd = pjmedia_format_get_video_format_detail(&ff->param.enc_fmt, 
+						     PJ_TRUE);
+
+	/* Override generic params after applying SDP fmtp */
+	ctx->width = vfd->size.w;
+	ctx->height = vfd->size.h;
+	ctx->time_base.num = vfd->fps.denum;
+	ctx->time_base.den = vfd->fps.num;
+
+	/* Apply profile. */
+	ctx->profile  = data->fmtp.profile_idc;
+	switch (ctx->profile) {
+	case PROFILE_H264_BASELINE:
+	    profile = "baseline";
+	    break;
+	case PROFILE_H264_MAIN:
+	    profile = "main";
+	    break;
+	default:
+	    break;
+	}
+	if (profile && !AV_OPT_SET(ctx->priv_data, "profile", profile, 0))
+	{
+	    PJ_LOG(3, (THIS_FILE, "Failed to set H264 profile to '%s'",
+		       profile));
+	}
+
+	/* Apply profile constraint bits. */
+	//PJ_TODO(set_h264_constraint_bits_properly_in_ffmpeg);
+	if (data->fmtp.profile_iop) {
+#if defined(FF_PROFILE_H264_CONSTRAINED)
+	    ctx->profile |= FF_PROFILE_H264_CONSTRAINED;
+#endif
+	}
+
+	/* Apply profile level. */
+	ctx->level    = data->fmtp.level;
+
+	/* Limit NAL unit size as we prefer single NAL unit packetization */
+	if (!AV_OPT_SET_INT(ctx->priv_data, "slice-max-size", ff->param.enc_mtu))
+	{
+	    PJ_LOG(3, (THIS_FILE, "Failed to set H264 max NAL size to %d",
+		       ff->param.enc_mtu));
+	}
+
+	/* Apply intra-refresh */
+	if (!AV_OPT_SET_INT(ctx->priv_data, "intra-refresh", 1))
+	{
+	    PJ_LOG(3, (THIS_FILE, "Failed to set x264 intra-refresh"));
+	}
+
+	/* Misc x264 settings (performance, quality, latency, etc).
+	 * Let's just use the x264 predefined preset & tune.
+	 */
+	if (!AV_OPT_SET(ctx->priv_data, "preset", "veryfast", 0)) {
+	    PJ_LOG(3, (THIS_FILE, "Failed to set x264 preset 'veryfast'"));
+	}
+	if (!AV_OPT_SET(ctx->priv_data, "tune", "animation+zerolatency", 0)) {
+	    PJ_LOG(3, (THIS_FILE, "Failed to set x264 tune 'zerolatency'"));
+	}
+    }
+
+    if (ff->param.dir & PJMEDIA_DIR_DECODING) {
+	AVCodecContext *ctx = ff->dec_ctx;
+
+	/* Apply the "sprop-parameter-sets" fmtp from remote SDP to
+	 * extradata of ffmpeg codec context.
+	 */
+	if (data->fmtp.sprop_param_sets_len) {
+	    ctx->extradata_size = (int)data->fmtp.sprop_param_sets_len;
+	    ctx->extradata = data->fmtp.sprop_param_sets;
+	}
+    }
+
+    return PJ_SUCCESS;
+}
+
+static pj_status_t h264_postopen(ffmpeg_private *ff)
+{
+    h264_data *data = (h264_data*)ff->data;
+    PJ_UNUSED_ARG(data);
+    return PJ_SUCCESS;
+}
+
+static FUNC_PACKETIZE(h264_packetize)
+{
+    h264_data *data = (h264_data*)ff->data;
+    return pjmedia_h264_packetize(data->pktz, bits, bits_len, bits_pos,
+				  payload, payload_len);
+}
+
+static FUNC_UNPACKETIZE(h264_unpacketize)
+{
+    h264_data *data = (h264_data*)ff->data;
+    return pjmedia_h264_unpacketize(data->pktz, payload, payload_len,
+				    bits, bits_len, bits_pos);
+}
+
+#endif /* PJMEDIA_HAS_FFMPEG_CODEC_H264 */
+
+
+#if PJMEDIA_HAS_FFMPEG_CODEC_H263P
+
+typedef struct h263_data
+{
+    pjmedia_h263_packetizer	*pktz;
+} h263_data;
+
+/* H263 pre-open */
+static pj_status_t h263_preopen(ffmpeg_private *ff)
+{
+    h263_data *data;
+    pjmedia_h263_packetizer_cfg pktz_cfg;
+    pj_status_t status;
+
+    data = PJ_POOL_ZALLOC_T(ff->pool, h263_data);
+    ff->data = data;
+
+    /* Create packetizer */
+    pktz_cfg.mtu = ff->param.enc_mtu;
+    pktz_cfg.mode = PJMEDIA_H263_PACKETIZER_MODE_RFC4629;
+    status = pjmedia_h263_packetizer_create(ff->pool, &pktz_cfg, &data->pktz);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Apply fmtp settings to codec param */
+    if (!ff->param.ignore_fmtp) {
+	status = pjmedia_vid_codec_h263_apply_fmtp(&ff->param);
+    }
+
+    /* Override generic params after applying SDP fmtp */
+    if (ff->param.dir & PJMEDIA_DIR_ENCODING) {
+	pjmedia_video_format_detail *vfd;
+	AVCodecContext *ctx = ff->enc_ctx;
+
+	vfd = pjmedia_format_get_video_format_detail(&ff->param.enc_fmt, 
+						     PJ_TRUE);
+
+	/* Override generic params after applying SDP fmtp */
+	ctx->width = vfd->size.w;
+	ctx->height = vfd->size.h;
+	ctx->time_base.num = vfd->fps.denum;
+	ctx->time_base.den = vfd->fps.num;
+    }
+
+    return status;
+}
+
+static FUNC_PACKETIZE(h263_packetize)
+{
+    h263_data *data = (h263_data*)ff->data;
+    return pjmedia_h263_packetize(data->pktz, bits, bits_len, bits_pos,
+				  payload, payload_len);
+}
+
+static FUNC_UNPACKETIZE(h263_unpacketize)
+{
+    h263_data *data = (h263_data*)ff->data;
+    return pjmedia_h263_unpacketize(data->pktz, payload, payload_len,
+				    bits, bits_len, bits_pos);
+}
+
+#endif /* PJMEDIA_HAS_FFMPEG_CODEC_H263P */
+
+
+static const ffmpeg_codec_desc* find_codec_desc_by_info(
+			const pjmedia_vid_codec_info *info)
+{
+    int i;
+
+    for (i=0; i<PJ_ARRAY_SIZE(codec_desc); ++i) {
+	ffmpeg_codec_desc *desc = &codec_desc[i];
+
+	if (desc->enabled &&
+	    (desc->info.fmt_id == info->fmt_id) &&
+            ((desc->info.dir & info->dir) == info->dir) &&
+	    (desc->info.pt == info->pt) &&
+	    (desc->info.packings & info->packings))
+        {
+            return desc;
+        }
+    }
+
+    return NULL;
+}
+
+
+static int find_codec_idx_by_fmt_id(pjmedia_format_id fmt_id)
+{
+    int i;
+    for (i=0; i<PJ_ARRAY_SIZE(codec_desc); ++i) {
+	if (codec_desc[i].info.fmt_id == fmt_id)
+	    return i;
+    }
+
+    return -1;
+}
+
+
+/*
+ * Initialize and register FFMPEG codec factory to pjmedia endpoint.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_ffmpeg_vid_init(pjmedia_vid_codec_mgr *mgr,
+                                                  pj_pool_factory *pf)
+{
+    pj_pool_t *pool;
+    AVCodec *c;
+    pj_status_t status;
+    unsigned i;
+
+    if (ffmpeg_factory.pool != NULL) {
+	/* Already initialized. */
+	return PJ_SUCCESS;
+    }
+
+    if (!mgr) mgr = pjmedia_vid_codec_mgr_instance();
+    PJ_ASSERT_RETURN(mgr, PJ_EINVAL);
+
+    /* Create FFMPEG codec factory. */
+    ffmpeg_factory.base.op = &ffmpeg_factory_op;
+    ffmpeg_factory.base.factory_data = NULL;
+    ffmpeg_factory.mgr = mgr;
+    ffmpeg_factory.pf = pf;
+
+    pool = pj_pool_create(pf, "ffmpeg codec factory", 256, 256, NULL);
+    if (!pool)
+	return PJ_ENOMEM;
+
+    /* Create mutex. */
+    status = pj_mutex_create_simple(pool, "ffmpeg codec factory", 
+				    &ffmpeg_factory.mutex);
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
+    pjmedia_ffmpeg_add_ref();
+#if !LIBAVCODEC_VER_AT_LEAST(53,20)
+    /* avcodec_init() dissappeared between version 53.20 and 54.15, not sure
+     * exactly when 
+     */
+    avcodec_init();
+#endif
+    avcodec_register_all();
+
+    /* Enum FFMPEG codecs */
+    for (c=av_codec_next(NULL); c; c=av_codec_next(c)) {
+        ffmpeg_codec_desc *desc;
+	pjmedia_format_id fmt_id;
+	int codec_info_idx;
+        
+#if LIBAVCODEC_VERSION_MAJOR <= 52
+#   define AVMEDIA_TYPE_VIDEO	CODEC_TYPE_VIDEO
+#endif
+        if (c->type != AVMEDIA_TYPE_VIDEO)
+            continue;
+
+        /* Video encoder and decoder are usually implemented in separate
+         * AVCodec instances. While the codec attributes (e.g: raw formats,
+	 * supported fps) are in the encoder.
+         */
+
+	//PJ_LOG(3, (THIS_FILE, "%s", c->name));
+	status = CodecID_to_pjmedia_format_id(c->id, &fmt_id);
+	/* Skip if format ID is unknown */
+	if (status != PJ_SUCCESS)
+	    continue;
+
+	codec_info_idx = find_codec_idx_by_fmt_id(fmt_id);
+	/* Skip if codec is unwanted by this wrapper (not listed in 
+	 * the codec info array)
+	 */
+	if (codec_info_idx < 0)
+	    continue;
+
+	desc = &codec_desc[codec_info_idx];
+
+	/* Skip duplicated codec implementation */
+	if ((AVCODEC_HAS_ENCODE(c) && (desc->info.dir & PJMEDIA_DIR_ENCODING))
+	    ||
+	    (AVCODEC_HAS_DECODE(c) && (desc->info.dir & PJMEDIA_DIR_DECODING)))
+	{
+	    continue;
+	}
+
+	/* Get raw/decoded format ids in the encoder */
+	if (c->pix_fmts && AVCODEC_HAS_ENCODE(c)) {
+	    pjmedia_format_id raw_fmt[PJMEDIA_VID_CODEC_MAX_DEC_FMT_CNT];
+	    unsigned raw_fmt_cnt = 0;
+	    unsigned raw_fmt_cnt_should_be = 0;
+	    const enum PixelFormat *p = c->pix_fmts;
+
+	    for(;(p && *p != -1) &&
+		 (raw_fmt_cnt < PJMEDIA_VID_CODEC_MAX_DEC_FMT_CNT);
+		 ++p)
+	    {
+		pjmedia_format_id fmt_id;
+
+		raw_fmt_cnt_should_be++;
+		status = PixelFormat_to_pjmedia_format_id(*p, &fmt_id);
+		if (status != PJ_SUCCESS) {
+		    PJ_LOG(6, (THIS_FILE, "Unrecognized ffmpeg pixel "
+			       "format %d", *p));
+		    continue;
+		}
+		
+		//raw_fmt[raw_fmt_cnt++] = fmt_id;
+		/* Disable some formats due to H.264 error:
+		 * x264 [error]: baseline profile doesn't support 4:4:4
+		 */
+		if (desc->info.pt != PJMEDIA_RTP_PT_H264 ||
+		    fmt_id != PJMEDIA_FORMAT_RGB24)
+		{
+		    raw_fmt[raw_fmt_cnt++] = fmt_id;
+		}
+	    }
+
+	    if (raw_fmt_cnt == 0) {
+		PJ_LOG(5, (THIS_FILE, "No recognized raw format "
+				      "for codec [%s/%s], codec ignored",
+				      c->name, c->long_name));
+		/* Skip this encoder */
+		continue;
+	    }
+
+	    if (raw_fmt_cnt < raw_fmt_cnt_should_be) {
+		PJ_LOG(6, (THIS_FILE, "Codec [%s/%s] have %d raw formats, "
+				      "recognized only %d raw formats",
+				      c->name, c->long_name,
+				      raw_fmt_cnt_should_be, raw_fmt_cnt));
+	    }
+
+	    desc->info.dec_fmt_id_cnt = raw_fmt_cnt;
+	    pj_memcpy(desc->info.dec_fmt_id, raw_fmt, 
+		      sizeof(raw_fmt[0])*raw_fmt_cnt);
+	}
+
+	/* Get supported framerates */
+	if (c->supported_framerates) {
+	    const AVRational *fr = c->supported_framerates;
+	    while ((fr->num != 0 || fr->den != 0) && 
+		   desc->info.fps_cnt < PJMEDIA_VID_CODEC_MAX_FPS_CNT)
+	    {
+		desc->info.fps[desc->info.fps_cnt].num = fr->num;
+		desc->info.fps[desc->info.fps_cnt].denum = fr->den;
+		++desc->info.fps_cnt;
+		++fr;
+	    }
+	}
+
+	/* Get ffmpeg encoder instance */
+	if (AVCODEC_HAS_ENCODE(c) && !desc->enc) {
+            desc->info.dir |= PJMEDIA_DIR_ENCODING;
+            desc->enc = c;
+        }
+	
+	/* Get ffmpeg decoder instance */
+        if (AVCODEC_HAS_DECODE(c) && !desc->dec) {
+            desc->info.dir |= PJMEDIA_DIR_DECODING;
+            desc->dec = c;
+        }
+
+	/* Enable this codec when any ffmpeg codec instance are recognized
+	 * and the supported raw formats info has been collected.
+	 */
+	if ((desc->dec || desc->enc) && desc->info.dec_fmt_id_cnt)
+	{
+	    desc->enabled = PJ_TRUE;
+	}
+
+	/* Normalize default value of clock rate */
+	if (desc->info.clock_rate == 0)
+	    desc->info.clock_rate = 90000;
+
+	/* Set supported packings */
+	desc->info.packings |= PJMEDIA_VID_PACKING_WHOLE;
+	if (desc->packetize && desc->unpacketize)
+	    desc->info.packings |= PJMEDIA_VID_PACKING_PACKETS;
+
+    }
+
+    /* Review all codecs for applying base format, registering format match for
+     * SDP negotiation, etc.
+     */
+    for (i = 0; i < PJ_ARRAY_SIZE(codec_desc); ++i) {
+	ffmpeg_codec_desc *desc = &codec_desc[i];
+
+	/* Init encoder/decoder description from base format */
+	if (desc->base_fmt_id && (!desc->dec || !desc->enc)) {
+	    ffmpeg_codec_desc *base_desc = NULL;
+	    int base_desc_idx;
+	    pjmedia_dir copied_dir = PJMEDIA_DIR_NONE;
+
+	    base_desc_idx = find_codec_idx_by_fmt_id(desc->base_fmt_id);
+	    if (base_desc_idx != -1)
+		base_desc = &codec_desc[base_desc_idx];
+	    if (!base_desc || !base_desc->enabled)
+		continue;
+
+	    /* Copy description from base codec */
+	    if (!desc->info.dec_fmt_id_cnt) {
+		desc->info.dec_fmt_id_cnt = base_desc->info.dec_fmt_id_cnt;
+		pj_memcpy(desc->info.dec_fmt_id, base_desc->info.dec_fmt_id, 
+			  sizeof(pjmedia_format_id)*desc->info.dec_fmt_id_cnt);
+	    }
+	    if (!desc->info.fps_cnt) {
+		desc->info.fps_cnt = base_desc->info.fps_cnt;
+		pj_memcpy(desc->info.fps, base_desc->info.fps, 
+			  sizeof(desc->info.fps[0])*desc->info.fps_cnt);
+	    }
+	    if (!desc->info.clock_rate) {
+		desc->info.clock_rate = base_desc->info.clock_rate;
+	    }
+	    if (!desc->dec && base_desc->dec) {
+		copied_dir |= PJMEDIA_DIR_DECODING;
+		desc->dec = base_desc->dec;
+	    }
+	    if (!desc->enc && base_desc->enc) {
+		copied_dir |= PJMEDIA_DIR_ENCODING;
+		desc->enc = base_desc->enc;
+	    }
+
+	    desc->info.dir |= copied_dir;
+	    desc->enabled = (desc->info.dir != PJMEDIA_DIR_NONE);
+
+	    /* Set supported packings */
+	    desc->info.packings |= PJMEDIA_VID_PACKING_WHOLE;
+	    if (desc->packetize && desc->unpacketize)
+		desc->info.packings |= PJMEDIA_VID_PACKING_PACKETS;
+
+	    if (copied_dir != PJMEDIA_DIR_NONE) {
+		const char *dir_name[] = {NULL, "encoder", "decoder", "codec"};
+		PJ_LOG(5, (THIS_FILE, "The %.*s %s is using base codec (%.*s)",
+			   desc->info.encoding_name.slen,
+			   desc->info.encoding_name.ptr,
+			   dir_name[copied_dir],
+			   base_desc->info.encoding_name.slen,
+			   base_desc->info.encoding_name.ptr));
+	    }
+        }
+
+	/* Registering format match for SDP negotiation */
+	if (desc->sdp_fmt_match) {
+	    status = pjmedia_sdp_neg_register_fmt_match_cb(
+						&desc->info.encoding_name,
+						desc->sdp_fmt_match);
+	    pj_assert(status == PJ_SUCCESS);
+	}
+
+	/* Print warning about missing encoder/decoder */
+	if (!desc->enc) {
+	    PJ_LOG(4, (THIS_FILE, "Cannot find %.*s encoder in ffmpeg library",
+		       desc->info.encoding_name.slen,
+		       desc->info.encoding_name.ptr));
+	}
+	if (!desc->dec) {
+	    PJ_LOG(4, (THIS_FILE, "Cannot find %.*s decoder in ffmpeg library",
+		       desc->info.encoding_name.slen,
+		       desc->info.encoding_name.ptr));
+	}
+    }
+
+    /* Register codec factory to codec manager. */
+    status = pjmedia_vid_codec_mgr_register_factory(mgr, 
+						    &ffmpeg_factory.base);
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
+    ffmpeg_factory.pool = pool;
+
+    /* Done. */
+    return PJ_SUCCESS;
+
+on_error:
+    pj_pool_release(pool);
+    return status;
+}
+
+/*
+ * Unregister FFMPEG codecs factory from pjmedia endpoint.
+ */
+PJ_DEF(pj_status_t) pjmedia_codec_ffmpeg_vid_deinit(void)
+{
+    pj_status_t status = PJ_SUCCESS;
+
+    if (ffmpeg_factory.pool == NULL) {
+	/* Already deinitialized */
+	return PJ_SUCCESS;
+    }
+
+    pj_mutex_lock(ffmpeg_factory.mutex);
+
+    /* Unregister FFMPEG codecs factory. */
+    status = pjmedia_vid_codec_mgr_unregister_factory(ffmpeg_factory.mgr,
+						      &ffmpeg_factory.base);
+
+    /* Destroy mutex. */
+    pj_mutex_destroy(ffmpeg_factory.mutex);
+
+    /* Destroy pool. */
+    pj_pool_release(ffmpeg_factory.pool);
+    ffmpeg_factory.pool = NULL;
+
+    pjmedia_ffmpeg_dec_ref();
+
+    return status;
+}
+
+
+/* 
+ * Check if factory can allocate the specified codec. 
+ */
+static pj_status_t ffmpeg_test_alloc( pjmedia_vid_codec_factory *factory, 
+				      const pjmedia_vid_codec_info *info )
+{
+    const ffmpeg_codec_desc *desc;
+
+    PJ_ASSERT_RETURN(factory==&ffmpeg_factory.base, PJ_EINVAL);
+    PJ_ASSERT_RETURN(info, PJ_EINVAL);
+
+    desc = find_codec_desc_by_info(info);
+    if (!desc) {
+        return PJMEDIA_CODEC_EUNSUP;
+    }
+
+    return PJ_SUCCESS;
+}
+
+/*
+ * Generate default attribute.
+ */
+static pj_status_t ffmpeg_default_attr( pjmedia_vid_codec_factory *factory, 
+				        const pjmedia_vid_codec_info *info, 
+				        pjmedia_vid_codec_param *attr )
+{
+    const ffmpeg_codec_desc *desc;
+    unsigned i;
+
+    PJ_ASSERT_RETURN(factory==&ffmpeg_factory.base, PJ_EINVAL);
+    PJ_ASSERT_RETURN(info && attr, PJ_EINVAL);
+
+    desc = find_codec_desc_by_info(info);
+    if (!desc) {
+        return PJMEDIA_CODEC_EUNSUP;
+    }
+
+    pj_bzero(attr, sizeof(pjmedia_vid_codec_param));
+
+    /* Scan the requested packings and use the lowest number */
+    attr->packing = 0;
+    for (i=0; i<15; ++i) {
+	unsigned packing = (1 << i);
+	if ((desc->info.packings & info->packings) & packing) {
+	    attr->packing = (pjmedia_vid_packing)packing;
+	    break;
+	}
+    }
+    if (attr->packing == 0) {
+	/* No supported packing in info */
+	return PJMEDIA_CODEC_EUNSUP;
+    }
+
+    /* Direction */
+    attr->dir = desc->info.dir;
+
+    /* Encoded format */
+    pjmedia_format_init_video(&attr->enc_fmt, desc->info.fmt_id,
+                              desc->size.w, desc->size.h,
+			      desc->fps.num, desc->fps.denum);
+
+    /* Decoded format */
+    pjmedia_format_init_video(&attr->dec_fmt, desc->info.dec_fmt_id[0],
+                              desc->size.w, desc->size.h,
+			      desc->fps.num, desc->fps.denum);
+
+    /* Decoding fmtp */
+    attr->dec_fmtp = desc->dec_fmtp;
+
+    /* Bitrate */
+    attr->enc_fmt.det.vid.avg_bps = desc->avg_bps;
+    attr->enc_fmt.det.vid.max_bps = desc->max_bps;
+
+    /* Encoding MTU */
+    attr->enc_mtu = PJMEDIA_MAX_VID_PAYLOAD_SIZE;
+
+    return PJ_SUCCESS;
+}
+
+/*
+ * Enum codecs supported by this factory.
+ */
+static pj_status_t ffmpeg_enum_codecs( pjmedia_vid_codec_factory *factory,
+				       unsigned *count, 
+				       pjmedia_vid_codec_info codecs[])
+{
+    unsigned i, max_cnt;
+
+    PJ_ASSERT_RETURN(codecs && *count > 0, PJ_EINVAL);
+    PJ_ASSERT_RETURN(factory == &ffmpeg_factory.base, PJ_EINVAL);
+
+    max_cnt = PJ_MIN(*count, PJ_ARRAY_SIZE(codec_desc));
+    *count = 0;
+
+    for (i=0; i<max_cnt; ++i) {
+	if (codec_desc[i].enabled) {
+	    pj_memcpy(&codecs[*count], &codec_desc[i].info, 
+		      sizeof(pjmedia_vid_codec_info));
+	    (*count)++;
+	}
+    }
+
+    return PJ_SUCCESS;
+}
+
+/*
+ * Allocate a new codec instance.
+ */
+static pj_status_t ffmpeg_alloc_codec( pjmedia_vid_codec_factory *factory, 
+				       const pjmedia_vid_codec_info *info,
+				       pjmedia_vid_codec **p_codec)
+{
+    ffmpeg_private *ff;
+    const ffmpeg_codec_desc *desc;
+    pjmedia_vid_codec *codec;
+    pj_pool_t *pool = NULL;
+    pj_status_t status = PJ_SUCCESS;
+
+    PJ_ASSERT_RETURN(factory && info && p_codec, PJ_EINVAL);
+    PJ_ASSERT_RETURN(factory == &ffmpeg_factory.base, PJ_EINVAL);
+
+    desc = find_codec_desc_by_info(info);
+    if (!desc) {
+        return PJMEDIA_CODEC_EUNSUP;
+    }
+
+    /* Create pool for codec instance */
+    pool = pj_pool_create(ffmpeg_factory.pf, "ffmpeg codec", 512, 512, NULL);
+    codec = PJ_POOL_ZALLOC_T(pool, pjmedia_vid_codec);
+    if (!codec) {
+        status = PJ_ENOMEM;
+        goto on_error;
+    }
+    codec->op = &ffmpeg_op;
+    codec->factory = factory;
+    ff = PJ_POOL_ZALLOC_T(pool, ffmpeg_private);
+    if (!ff) {
+        status = PJ_ENOMEM;
+        goto on_error;
+    }
+    codec->codec_data = ff;
+    ff->pool = pool;
+    ff->enc = desc->enc;
+    ff->dec = desc->dec;
+    ff->desc = desc;
+
+    *p_codec = codec;
+    return PJ_SUCCESS;
+
+on_error:
+    if (pool)
+        pj_pool_release(pool);
+    return status;
+}
+
+/*
+ * Free codec.
+ */
+static pj_status_t ffmpeg_dealloc_codec( pjmedia_vid_codec_factory *factory, 
+				         pjmedia_vid_codec *codec )
+{
+    ffmpeg_private *ff;
+    pj_pool_t *pool;
+
+    PJ_ASSERT_RETURN(factory && codec, PJ_EINVAL);
+    PJ_ASSERT_RETURN(factory == &ffmpeg_factory.base, PJ_EINVAL);
+
+    /* Close codec, if it's not closed. */
+    ff = (ffmpeg_private*) codec->codec_data;
+    pool = ff->pool;
+    codec->codec_data = NULL;
+    pj_pool_release(pool);
+
+    return PJ_SUCCESS;
+}
+
+/*
+ * Init codec.
+ */
+static pj_status_t ffmpeg_codec_init( pjmedia_vid_codec *codec, 
+				      pj_pool_t *pool )
+{
+    PJ_UNUSED_ARG(codec);
+    PJ_UNUSED_ARG(pool);
+    return PJ_SUCCESS;
+}
+
+static void print_ffmpeg_err(int err)
+{
+#if LIBAVCODEC_VER_AT_LEAST(52,72)
+    char errbuf[512];
+    if (av_strerror(err, errbuf, sizeof(errbuf)) >= 0)
+        PJ_LOG(5, (THIS_FILE, "ffmpeg err %d: %s", err, errbuf));
+#else
+    PJ_LOG(5, (THIS_FILE, "ffmpeg err %d", err));
+#endif
+
+}
+
+static pj_status_t open_ffmpeg_codec(ffmpeg_private *ff,
+                                     pj_mutex_t *ff_mutex)
+{
+    enum PixelFormat pix_fmt;
+    pjmedia_video_format_detail *vfd;
+    pj_bool_t enc_opened = PJ_FALSE, dec_opened = PJ_FALSE;
+    pj_status_t status;
+
+    /* Get decoded pixel format */
+    status = pjmedia_format_id_to_PixelFormat(ff->param.dec_fmt.id,
+                                              &pix_fmt);
+    if (status != PJ_SUCCESS)
+        return status;
+    ff->expected_dec_fmt = pix_fmt;
+
+    /* Get video format detail for shortcut access to encoded format */
+    vfd = pjmedia_format_get_video_format_detail(&ff->param.enc_fmt, 
+						 PJ_TRUE);
+
+    /* Allocate ffmpeg codec context */
+    if (ff->param.dir & PJMEDIA_DIR_ENCODING) {
+#if LIBAVCODEC_VER_AT_LEAST(53,20)
+	ff->enc_ctx = avcodec_alloc_context3(ff->enc);
+#else
+	ff->enc_ctx = avcodec_alloc_context();
+#endif
+	if (ff->enc_ctx == NULL)
+	    goto on_error;
+    }
+    if (ff->param.dir & PJMEDIA_DIR_DECODING) {
+#if LIBAVCODEC_VER_AT_LEAST(53,20)
+	ff->dec_ctx = avcodec_alloc_context3(ff->dec);
+#else
+	ff->dec_ctx = avcodec_alloc_context();
+#endif
+	if (ff->dec_ctx == NULL)
+	    goto on_error;
+    }
+
+    /* Init generic encoder params */
+    if (ff->param.dir & PJMEDIA_DIR_ENCODING) {
+        AVCodecContext *ctx = ff->enc_ctx;
+
+        ctx->pix_fmt = pix_fmt;
+	ctx->width = vfd->size.w;
+	ctx->height = vfd->size.h;
+	ctx->time_base.num = vfd->fps.denum;
+	ctx->time_base.den = vfd->fps.num;
+	if (vfd->avg_bps) {
+	    ctx->bit_rate = vfd->avg_bps;
+	    if (vfd->max_bps > vfd->avg_bps)
+		ctx->bit_rate_tolerance = vfd->max_bps - vfd->avg_bps;
+	}
+	ctx->strict_std_compliance = FF_COMPLIANCE_STRICT;
+        ctx->workaround_bugs = FF_BUG_AUTODETECT;
+        ctx->opaque = ff;
+
+	/* Set no delay, note that this may cause some codec functionals
+	 * not working (e.g: rate control).
+	 */
+#if LIBAVCODEC_VER_AT_LEAST(52,113) && !LIBAVCODEC_VER_AT_LEAST(53,20)
+	ctx->rc_lookahead = 0;
+#endif
+    }
+
+    /* Init generic decoder params */
+    if (ff->param.dir & PJMEDIA_DIR_DECODING) {
+	AVCodecContext *ctx = ff->dec_ctx;
+
+	/* Width/height may be overriden by ffmpeg after first decoding. */
+	ctx->width  = ctx->coded_width  = ff->param.dec_fmt.det.vid.size.w;
+	ctx->height = ctx->coded_height = ff->param.dec_fmt.det.vid.size.h;
+	ctx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
+        ctx->workaround_bugs = FF_BUG_AUTODETECT;
+        ctx->opaque = ff;
+    }
+
+    /* Override generic params or apply specific params before opening
+     * the codec.
+     */
+    if (ff->desc->preopen) {
+	status = (*ff->desc->preopen)(ff);
+	if (status != PJ_SUCCESS)
+	    goto on_error;
+    }
+
+    /* Open encoder */
+    if (ff->param.dir & PJMEDIA_DIR_ENCODING) {
+	int err;
+
+	pj_mutex_lock(ff_mutex);
+	err = AVCODEC_OPEN(ff->enc_ctx, ff->enc);
+        pj_mutex_unlock(ff_mutex);
+        if (err < 0) {
+            print_ffmpeg_err(err);
+            status = PJMEDIA_CODEC_EFAILED;
+	    goto on_error;
+        }
+	enc_opened = PJ_TRUE;
+    }
+
+    /* Open decoder */
+    if (ff->param.dir & PJMEDIA_DIR_DECODING) {
+	int err;
+
+	pj_mutex_lock(ff_mutex);
+	err = AVCODEC_OPEN(ff->dec_ctx, ff->dec);
+        pj_mutex_unlock(ff_mutex);
+        if (err < 0) {
+            print_ffmpeg_err(err);
+            status = PJMEDIA_CODEC_EFAILED;
+	    goto on_error;
+        }
+	dec_opened = PJ_TRUE;
+    }
+
+    /* Let the codec apply specific params after the codec opened */
+    if (ff->desc->postopen) {
+	status = (*ff->desc->postopen)(ff);
+	if (status != PJ_SUCCESS)
+	    goto on_error;
+    }
+
+    return PJ_SUCCESS;
+
+on_error:
+    if (ff->enc_ctx) {
+	if (enc_opened)
+	    avcodec_close(ff->enc_ctx);
+	av_free(ff->enc_ctx);
+	ff->enc_ctx = NULL;
+    }
+    if (ff->dec_ctx) {
+	if (dec_opened)
+	    avcodec_close(ff->dec_ctx);
+	av_free(ff->dec_ctx);
+	ff->dec_ctx = NULL;
+    }
+    return status;
+}
+
+/*
+ * Open codec.
+ */
+static pj_status_t ffmpeg_codec_open( pjmedia_vid_codec *codec, 
+				      pjmedia_vid_codec_param *attr )
+{
+    ffmpeg_private *ff;
+    pj_status_t status;
+    pj_mutex_t *ff_mutex;
+
+    PJ_ASSERT_RETURN(codec && attr, PJ_EINVAL);
+    ff = (ffmpeg_private*)codec->codec_data;
+
+    pj_memcpy(&ff->param, attr, sizeof(*attr));
+
+    /* Normalize encoding MTU in codec param */
+    if (attr->enc_mtu > PJMEDIA_MAX_VID_PAYLOAD_SIZE)
+	attr->enc_mtu = PJMEDIA_MAX_VID_PAYLOAD_SIZE;
+
+    /* Open the codec */
+    ff_mutex = ((struct ffmpeg_factory*)codec->factory)->mutex;
+    status = open_ffmpeg_codec(ff, ff_mutex);
+    if (status != PJ_SUCCESS)
+        goto on_error;
+
+    /* 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) {
+        status = PJ_EINVAL;
+        goto on_error;
+    }
+    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) {
+        goto on_error;
+    }
+
+    /* Init format info and apply-param of encoder */
+    ff->enc_vfi = pjmedia_get_video_format_info(NULL, ff->param.dec_fmt.id);
+    if (!ff->enc_vfi) {
+        status = PJ_EINVAL;
+        goto on_error;
+    }
+    pj_bzero(&ff->enc_vafp, sizeof(ff->enc_vafp));
+    ff->enc_vafp.size = ff->param.enc_fmt.det.vid.size;
+    ff->enc_vafp.buffer = NULL;
+    status = (*ff->enc_vfi->apply_fmt)(ff->enc_vfi, &ff->enc_vafp);
+    if (status != PJ_SUCCESS) {
+        goto on_error;
+    }
+
+    /* Alloc buffers if needed */
+    ff->whole = (ff->param.packing == PJMEDIA_VID_PACKING_WHOLE);
+    if (!ff->whole) {
+	ff->enc_buf_size = (unsigned)ff->enc_vafp.framebytes;
+	ff->enc_buf = pj_pool_alloc(ff->pool, ff->enc_buf_size);
+
+	ff->dec_buf_size = (unsigned)ff->dec_vafp.framebytes;
+	ff->dec_buf = pj_pool_alloc(ff->pool, ff->dec_buf_size);
+    }
+
+    /* Update codec attributes, e.g: encoding format may be changed by
+     * SDP fmtp negotiation.
+     */
+    pj_memcpy(attr, &ff->param, sizeof(*attr));
+
+    return PJ_SUCCESS;
+
+on_error:
+    ffmpeg_codec_close(codec);
+    return status;
+}
+
+/*
+ * Close codec.
+ */
+static pj_status_t ffmpeg_codec_close( pjmedia_vid_codec *codec )
+{
+    ffmpeg_private *ff;
+    pj_mutex_t *ff_mutex;
+
+    PJ_ASSERT_RETURN(codec, PJ_EINVAL);
+    ff = (ffmpeg_private*)codec->codec_data;
+    ff_mutex = ((struct ffmpeg_factory*)codec->factory)->mutex;
+
+    pj_mutex_lock(ff_mutex);
+    if (ff->enc_ctx) {
+        avcodec_close(ff->enc_ctx);
+        av_free(ff->enc_ctx);
+    }
+    if (ff->dec_ctx && ff->dec_ctx!=ff->enc_ctx) {
+        avcodec_close(ff->dec_ctx);
+        av_free(ff->dec_ctx);
+    }
+    ff->enc_ctx = NULL;
+    ff->dec_ctx = NULL;
+    pj_mutex_unlock(ff_mutex);
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Modify codec settings.
+ */
+static pj_status_t  ffmpeg_codec_modify( pjmedia_vid_codec *codec, 
+				         const pjmedia_vid_codec_param *attr)
+{
+    ffmpeg_private *ff = (ffmpeg_private*)codec->codec_data;
+
+    PJ_UNUSED_ARG(attr);
+    PJ_UNUSED_ARG(ff);
+
+    return PJ_ENOTSUP;
+}
+
+static pj_status_t  ffmpeg_codec_get_param(pjmedia_vid_codec *codec,
+					   pjmedia_vid_codec_param *param)
+{
+    ffmpeg_private *ff;
+
+    PJ_ASSERT_RETURN(codec && param, PJ_EINVAL);
+
+    ff = (ffmpeg_private*)codec->codec_data;
+    pj_memcpy(param, &ff->param, sizeof(*param));
+
+    return PJ_SUCCESS;
+}
+
+
+static pj_status_t  ffmpeg_packetize ( pjmedia_vid_codec *codec,
+                                       pj_uint8_t *bits,
+                                       pj_size_t bits_len,
+                                       unsigned *bits_pos,
+                                       const pj_uint8_t **payload,
+                                       pj_size_t *payload_len)
+{
+    ffmpeg_private *ff = (ffmpeg_private*)codec->codec_data;
+
+    if (ff->desc->packetize) {
+	return (*ff->desc->packetize)(ff, bits, bits_len, bits_pos,
+                                      payload, payload_len);
+    }
+
+    return PJ_ENOTSUP;
+}
+
+static pj_status_t  ffmpeg_unpacketize(pjmedia_vid_codec *codec,
+                                       const pj_uint8_t *payload,
+                                       pj_size_t   payload_len,
+                                       pj_uint8_t *bits,
+                                       pj_size_t   bits_len,
+				       unsigned   *bits_pos)
+{
+    ffmpeg_private *ff = (ffmpeg_private*)codec->codec_data;
+
+    if (ff->desc->unpacketize) {
+        return (*ff->desc->unpacketize)(ff, payload, payload_len,
+                                        bits, bits_len, bits_pos);
+    }
+    
+    return PJ_ENOTSUP;
+}
+
+
+/*
+ * 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)
+{
+    ffmpeg_private *ff = (ffmpeg_private*)codec->codec_data;
+    pj_uint8_t *p = (pj_uint8_t*)input->buf;
+    AVFrame avframe;
+    AVPacket avpacket;
+    int err, got_packet;
+    //AVRational src_timebase;
+    /* For some reasons (e.g: SSE/MMX usage), the avcodec_encode_video() must
+     * have stack aligned to 16 bytes. Let's try to be safe by preparing the
+     * 16-bytes aligned stack here, in case it's not managed by the ffmpeg.
+     */
+    PJ_ALIGN_DATA(pj_uint32_t i[4], 16);
+
+    if ((long)(pj_ssize_t)i & 0xF) {
+	PJ_LOG(2,(THIS_FILE, "Stack alignment fails"));
+    }
+
+    /* Check if encoder has been opened */
+    PJ_ASSERT_RETURN(ff->enc_ctx, PJ_EINVALIDOP);
+
+    avcodec_get_frame_defaults(&avframe);
+
+    // Let ffmpeg manage the timestamps
+    /*
+    src_timebase.num = 1;
+    src_timebase.den = ff->desc->info.clock_rate;
+    avframe.pts = av_rescale_q(input->timestamp.u64, src_timebase,
+			       ff->enc_ctx->time_base);
+    */
+    
+    for (i[0] = 0; i[0] < ff->enc_vfi->plane_cnt; ++i[0]) {
+        avframe.data[i[0]] = p;
+        avframe.linesize[i[0]] = ff->enc_vafp.strides[i[0]];
+        p += ff->enc_vafp.plane_bytes[i[0]];
+    }
+
+    /* Force keyframe */
+    if (opt && opt->force_keyframe) {
+#if LIBAVCODEC_VER_AT_LEAST(53,20)
+	avframe.pict_type = AV_PICTURE_TYPE_I;
+#else
+	avframe.pict_type = FF_I_TYPE;
+#endif
+    }
+
+    av_init_packet(&avpacket);
+    avpacket.data = (pj_uint8_t*)output->buf;
+    avpacket.size = output_buf_len;
+
+#if LIBAVCODEC_VER_AT_LEAST(54,15)
+    err = avcodec_encode_video2(ff->enc_ctx, &avpacket, &avframe, &got_packet);
+    if (!err && got_packet)
+	err = avpacket.size;
+#else
+    PJ_UNUSED_ARG(got_packet);
+    err = avcodec_encode_video(ff->enc_ctx, avpacket.data, avpacket.size, &avframe);
+#endif
+
+    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_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;
+
+    *has_more = PJ_FALSE;
+
+    if (ff->whole) {
+	status = ffmpeg_codec_encode_whole(codec, opt, input, out_size,
+					   output);
+    } else {
+	pjmedia_frame whole_frm;
+        const pj_uint8_t *payload;
+        pj_size_t payload_len;
+
+	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, opt, input,
+	                                   (unsigned)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,
+                                  whole_frm.size, &ff->enc_processed,
+				  &payload, &payload_len);
+        if (status != PJ_SUCCESS)
+            return status;
+
+        if (out_size < payload_len)
+            return PJMEDIA_CODEC_EFRMTOOSHORT;
+
+        output->type = PJMEDIA_FRAME_TYPE_VIDEO;
+        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 status;
+}
+
+static pj_status_t ffmpeg_codec_encode_more(pjmedia_vid_codec *codec,
+					    unsigned out_size,
+					    pjmedia_frame *output,
+					    pj_bool_t *has_more)
+{
+    ffmpeg_private *ff = (ffmpeg_private*)codec->codec_data;
+    const pj_uint8_t *payload;
+    pj_size_t payload_len;
+    pj_status_t status;
+
+    *has_more = PJ_FALSE;
+
+    if (ff->enc_processed >= ff->enc_frame_len) {
+	/* No more frame */
+	return PJ_EEOF;
+    }
+
+    status = ffmpeg_packetize(codec, (pj_uint8_t*)ff->enc_buf,
+                              ff->enc_frame_len, &ff->enc_processed,
+                              &payload, &payload_len);
+    if (status != PJ_SUCCESS)
+        return status;
+
+    if (out_size < payload_len)
+        return PJMEDIA_CODEC_EFRMTOOSHORT;
+
+    output->type = PJMEDIA_FRAME_TYPE_VIDEO;
+    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 = (unsigned)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.
+ */
+static pj_status_t ffmpeg_codec_decode_whole(pjmedia_vid_codec *codec,
+					     const pjmedia_frame *input,
+					     unsigned output_buf_len,
+					     pjmedia_frame *output)
+{
+    ffmpeg_private *ff = (ffmpeg_private*)codec->codec_data;
+    AVFrame avframe;
+    AVPacket avpacket;
+    int err, got_picture;
+
+    /* Check if decoder has been opened */
+    PJ_ASSERT_RETURN(ff->dec_ctx, PJ_EINVALIDOP);
+
+    /* Reset output frame bit info */
+    output->bit_info = 0;
+
+    /* Validate output buffer size */
+    // Do this validation later after getting decoding result, where the real
+    // decoded size will be assured.
+    //if (ff->dec_vafp.framebytes > output_buf_len)
+	//return PJ_ETOOSMALL;
+
+    /* Init frame to receive the decoded data, the ffmpeg codec context will
+     * automatically provide the decoded buffer (single buffer used for the
+     * whole decoding session, and seems to be freed when the codec context
+     * closed).
+     */
+    avcodec_get_frame_defaults(&avframe);
+
+    /* Init packet, the container of the encoded data */
+    av_init_packet(&avpacket);
+    avpacket.data = (pj_uint8_t*)input->buf;
+    avpacket.size = (int)input->size;
+
+    /* ffmpeg warns:
+     * - input buffer padding, at least FF_INPUT_BUFFER_PADDING_SIZE
+     * - null terminated
+     * Normally, encoded buffer is allocated more than needed, so lets just
+     * bzero the input buffer end/pad, hope it will be just fine.
+     */
+    pj_bzero(avpacket.data+avpacket.size, FF_INPUT_BUFFER_PADDING_SIZE);
+
+    output->bit_info = 0;
+    output->timestamp = input->timestamp;
+
+#if LIBAVCODEC_VER_AT_LEAST(52,72)
+    //avpacket.flags = AV_PKT_FLAG_KEY;
+#else
+    avpacket.flags = 0;
+#endif
+
+#if LIBAVCODEC_VER_AT_LEAST(52,72)
+    err = avcodec_decode_video2(ff->dec_ctx, &avframe, 
+                                &got_picture, &avpacket);
+#else
+    err = avcodec_decode_video(ff->dec_ctx, &avframe,
+                               &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);
+
+	/* 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;
+
+	/* Check decoding result, e.g: see if the format got changed,
+	 * keyframe found/missing.
+	 */
+	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)
+	    return PJ_ETOOSMALL;
+
+	/* Get the decoded data */
+	for (i = 0; i < ff->dec_vfi->plane_cnt; ++i) {
+	    pj_uint8_t *p = avframe.data[i];
+
+	    /* The decoded data may contain padding */
+	    if (avframe.linesize[i]!=vafp->strides[i]) {
+		/* Padding exists, copy line by line */
+		pj_uint8_t *q_end;
+                    
+		q_end = q+vafp->plane_bytes[i];
+		while(q < q_end) {
+		    pj_memcpy(q, p, vafp->strides[i]);
+		    q += vafp->strides[i];
+		    p += avframe.linesize[i];
+		}
+	    } else {
+		/* No padding, copy the whole plane */
+		pj_memcpy(q, p, vafp->plane_bytes[i]);
+		q += vafp->plane_bytes[i];
+	    }
+	}
+
+	output->type = PJMEDIA_FRAME_TYPE_VIDEO;
+        output->size = vafp->framebytes;
+    } else {
+	output->type = PJMEDIA_FRAME_TYPE_NONE;
+	output->size = 0;
+    }
+    
+    return PJ_SUCCESS;
+}
+
+static pj_status_t ffmpeg_codec_decode( pjmedia_vid_codec *codec,
+					pj_size_t pkt_count,
+					pjmedia_frame packets[],
+					unsigned out_size,
+					pjmedia_frame *output)
+{
+    ffmpeg_private *ff = (ffmpeg_private*)codec->codec_data;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(codec && pkt_count > 0 && packets && output,
+                     PJ_EINVAL);
+
+    if (ff->whole) {
+	pj_assert(pkt_count==1);
+	return ffmpeg_codec_decode_whole(codec, &packets[0], out_size, output);
+    } else {
+	pjmedia_frame whole_frm;
+	unsigned whole_len = 0;
+	unsigned i;
+
+	for (i=0; i<pkt_count; ++i) {
+	    if (whole_len + packets[i].size > ff->dec_buf_size) {
+		PJ_LOG(5,(THIS_FILE, "Decoding buffer overflow"));
+		break;
+	    }
+
+	    status = ffmpeg_unpacketize(codec, packets[i].buf, packets[i].size,
+	                                ff->dec_buf, ff->dec_buf_size,
+	                                &whole_len);
+	    if (status != PJ_SUCCESS) {
+		PJ_PERROR(5,(THIS_FILE, status, "Unpacketize error"));
+		continue;
+	    }
+	}
+
+	whole_frm.buf = ff->dec_buf;
+	whole_frm.size = whole_len;
+	whole_frm.timestamp = output->timestamp = packets[i].timestamp;
+	whole_frm.bit_info = 0;
+
+	return ffmpeg_codec_decode_whole(codec, &whole_frm, out_size, output);
+    }
+}
+
+
+#ifdef _MSC_VER
+#   pragma comment( lib, "avcodec.lib")
+#endif
+
+#endif	/* PJMEDIA_HAS_FFMPEG_VID_CODEC */
+
diff --git a/jni/pjproject-android/.svn/pristine/d6/d6d0a202e128f1d69d023f2b586e2814d6e8968a.svn-base b/jni/pjproject-android/.svn/pristine/d6/d6d0a202e128f1d69d023f2b586e2814d6e8968a.svn-base
new file mode 100644
index 0000000..b12d097
--- /dev/null
+++ b/jni/pjproject-android/.svn/pristine/d6/d6d0a202e128f1d69d023f2b586e2814d6e8968a.svn-base
@@ -0,0 +1,151 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2008-2011 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 <pj/sock_qos.h>
+#include <pj/assert.h>
+#include <pj/errno.h>
+#include <pj/log.h>
+#include <pj/string.h>
+
+#define THIS_FILE   "sock_qos_common.c"
+#define ALL_FLAGS   (PJ_QOS_PARAM_HAS_DSCP | PJ_QOS_PARAM_HAS_SO_PRIO | \
+                     PJ_QOS_PARAM_HAS_WMM)
+
+/* "Standard" mapping between traffic type and QoS params */
+static const pj_qos_params qos_map[] = 
+{
+    /* flags	dscp  prio wmm_prio */
+    {ALL_FLAGS, 0x00, 0,    PJ_QOS_WMM_PRIO_BULK_EFFORT},   /* BE */
+    {ALL_FLAGS, 0x08, 2,    PJ_QOS_WMM_PRIO_BULK},	    /* BK */
+    {ALL_FLAGS, 0x28, 5,    PJ_QOS_WMM_PRIO_VIDEO},	    /* VI */
+    {ALL_FLAGS, 0x30, 6,    PJ_QOS_WMM_PRIO_VOICE},	    /* VO */
+    {ALL_FLAGS, 0x38, 7,    PJ_QOS_WMM_PRIO_VOICE}	    /* CO */
+};
+
+
+/* Retrieve the mapping for the specified type */
+PJ_DEF(pj_status_t) pj_qos_get_params(pj_qos_type type, 
+				      pj_qos_params *p_param)
+{
+    PJ_ASSERT_RETURN(type<=PJ_QOS_TYPE_CONTROL && p_param, PJ_EINVAL);
+    pj_memcpy(p_param, &qos_map[type], sizeof(*p_param));
+    return PJ_SUCCESS;
+}
+
+/* Get the matching traffic type */
+PJ_DEF(pj_status_t) pj_qos_get_type( const pj_qos_params *param,
+				     pj_qos_type *p_type)
+{
+    unsigned dscp_type = PJ_QOS_TYPE_BEST_EFFORT,
+	     prio_type = PJ_QOS_TYPE_BEST_EFFORT,
+	     wmm_type = PJ_QOS_TYPE_BEST_EFFORT;
+    unsigned i, count=0;
+
+    PJ_ASSERT_RETURN(param && p_type, PJ_EINVAL);
+
+    if (param->flags & PJ_QOS_PARAM_HAS_DSCP)  {
+	for (i=0; i<=PJ_QOS_TYPE_CONTROL; ++i) {
+	    if (param->dscp_val >= qos_map[i].dscp_val)
+		dscp_type = (pj_qos_type)i;
+	}
+	++count;
+    }
+
+    if (param->flags & PJ_QOS_PARAM_HAS_SO_PRIO) {
+	for (i=0; i<=PJ_QOS_TYPE_CONTROL; ++i) {
+	    if (param->so_prio >= qos_map[i].so_prio)
+		prio_type = (pj_qos_type)i;
+	}
+	++count;
+    }
+
+    if (param->flags & PJ_QOS_PARAM_HAS_WMM) {
+	for (i=0; i<=PJ_QOS_TYPE_CONTROL; ++i) {
+	    if (param->wmm_prio >= qos_map[i].wmm_prio)
+		wmm_type = (pj_qos_type)i;
+	}
+	++count;
+    }
+
+    if (count)
+	*p_type = (pj_qos_type)((dscp_type + prio_type + wmm_type) / count);
+    else
+	*p_type = PJ_QOS_TYPE_BEST_EFFORT;
+
+    return PJ_SUCCESS;
+}
+
+/* Apply QoS */
+PJ_DEF(pj_status_t) pj_sock_apply_qos( pj_sock_t sock,
+				       pj_qos_type qos_type,
+				       pj_qos_params *qos_params,
+				       unsigned log_level,
+				       const char *log_sender,
+				       const char *sock_name)
+{
+    pj_status_t qos_type_rc = PJ_SUCCESS,
+		qos_params_rc = PJ_SUCCESS;
+
+    if (!log_sender)
+	log_sender = THIS_FILE;
+    if (!sock_name)
+	sock_name = "socket";
+
+    if (qos_type != PJ_QOS_TYPE_BEST_EFFORT) {
+	qos_type_rc = pj_sock_set_qos_type(sock, qos_type);
+
+	if (qos_type_rc != PJ_SUCCESS) {
+	    pj_perror(log_level, log_sender,  qos_type_rc, 
+		      "Error setting QoS type %d to %s", 
+		      qos_type, sock_name);
+	}
+    }
+
+    if (qos_params && qos_params->flags) {
+	qos_params_rc = pj_sock_set_qos_params(sock, qos_params);
+	if (qos_params_rc != PJ_SUCCESS) {
+	    pj_perror(log_level, log_sender,  qos_params_rc, 
+		      "Error setting QoS params (flags=%d) to %s", 
+		      qos_params->flags, sock_name);
+	    if (qos_type_rc != PJ_SUCCESS)
+		return qos_params_rc;
+	}
+    } else if (qos_type_rc != PJ_SUCCESS)
+	return qos_type_rc;
+
+    return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pj_sock_apply_qos2( pj_sock_t sock,
+ 				        pj_qos_type qos_type,
+				        const pj_qos_params *qos_params,
+				        unsigned log_level,
+				        const char *log_sender,
+				        const char *sock_name)
+{
+    pj_qos_params qos_params_buf, *qos_params_copy = NULL;
+
+    if (qos_params) {
+	pj_memcpy(&qos_params_buf, qos_params, sizeof(*qos_params));
+	qos_params_copy = &qos_params_buf;
+    }
+
+    return pj_sock_apply_qos(sock, qos_type, qos_params_copy,
+			     log_level, log_sender, sock_name);
+}
diff --git a/jni/pjproject-android/.svn/pristine/d6/d6e21b5fe78e37f26e5aea068a32cff2d7d637e3.svn-base b/jni/pjproject-android/.svn/pristine/d6/d6e21b5fe78e37f26e5aea068a32cff2d7d637e3.svn-base
new file mode 100644
index 0000000..b337e1e
--- /dev/null
+++ b/jni/pjproject-android/.svn/pristine/d6/d6e21b5fe78e37f26e5aea068a32cff2d7d637e3.svn-base
@@ -0,0 +1,1189 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2008-2011 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 <pjsip/sip_auth.h>
+#include <pjsip/sip_auth_parser.h>	/* just to get pjsip_DIGEST_STR */
+#include <pjsip/sip_auth_aka.h>
+#include <pjsip/sip_transport.h>
+#include <pjsip/sip_endpoint.h>
+#include <pjsip/sip_errno.h>
+#include <pjsip/sip_util.h>
+#include <pjlib-util/md5.h>
+#include <pj/log.h>
+#include <pj/string.h>
+#include <pj/pool.h>
+#include <pj/guid.h>
+#include <pj/assert.h>
+#include <pj/ctype.h>
+
+
+
+/* A macro just to get rid of type mismatch between char and unsigned char */
+#define MD5_APPEND(pms,buf,len)	pj_md5_update(pms, (const pj_uint8_t*)buf, \
+					      (unsigned)len)
+
+/* Logging. */
+#define THIS_FILE   "sip_auth_client.c"
+#if 0
+#  define AUTH_TRACE_(expr)  PJ_LOG(3, expr)
+#else
+#  define AUTH_TRACE_(expr)
+#endif
+
+#define PASSWD_MASK	    0x000F
+#define EXT_MASK	    0x00F0
+
+
+static void dup_bin(pj_pool_t *pool, pj_str_t *dst, const pj_str_t *src)
+{
+    dst->slen = src->slen;
+
+    if (dst->slen) {
+	dst->ptr = (char*) pj_pool_alloc(pool, src->slen);
+	pj_memcpy(dst->ptr, src->ptr, src->slen);
+    } else {
+	dst->ptr = NULL;
+    }
+}
+
+PJ_DEF(void) pjsip_cred_info_dup(pj_pool_t *pool,
+				 pjsip_cred_info *dst,
+				 const pjsip_cred_info *src)
+{
+    pj_memcpy(dst, src, sizeof(pjsip_cred_info));
+
+    pj_strdup_with_null(pool, &dst->realm, &src->realm);
+    pj_strdup_with_null(pool, &dst->scheme, &src->scheme);
+    pj_strdup_with_null(pool, &dst->username, &src->username);
+    pj_strdup_with_null(pool, &dst->data, &src->data);
+
+    if ((dst->data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) {
+	dup_bin(pool, &dst->ext.aka.k, &src->ext.aka.k);
+	dup_bin(pool, &dst->ext.aka.op, &src->ext.aka.op);
+	dup_bin(pool, &dst->ext.aka.amf, &src->ext.aka.amf);
+    }
+}
+
+
+PJ_DEF(int) pjsip_cred_info_cmp(const pjsip_cred_info *cred1,
+				const pjsip_cred_info *cred2)
+{
+    int result;
+
+    result = pj_strcmp(&cred1->realm, &cred2->realm);
+    if (result) goto on_return;
+    result = pj_strcmp(&cred1->scheme, &cred2->scheme);
+    if (result) goto on_return;
+    result = pj_strcmp(&cred1->username, &cred2->username);
+    if (result) goto on_return;
+    result = pj_strcmp(&cred1->data, &cred2->data);
+    if (result) goto on_return;
+    result = (cred1->data_type != cred2->data_type);
+    if (result) goto on_return;
+
+    if ((cred1->data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) {
+	result = pj_strcmp(&cred1->ext.aka.k, &cred2->ext.aka.k);
+	if (result) goto on_return;
+	result = pj_strcmp(&cred1->ext.aka.op, &cred2->ext.aka.op);
+	if (result) goto on_return;
+	result = pj_strcmp(&cred1->ext.aka.amf, &cred2->ext.aka.amf);
+	if (result) goto on_return;
+    }
+
+on_return:
+    return result;
+}
+
+PJ_DEF(void) pjsip_auth_clt_pref_dup( pj_pool_t *pool,
+				      pjsip_auth_clt_pref *dst,
+				      const pjsip_auth_clt_pref *src)
+{
+    pj_memcpy(dst, src, sizeof(pjsip_auth_clt_pref));
+    pj_strdup_with_null(pool, &dst->algorithm, &src->algorithm);
+}
+
+
+/* Transform digest to string.
+ * output must be at least PJSIP_MD5STRLEN+1 bytes.
+ *
+ * NOTE: THE OUTPUT STRING IS NOT NULL TERMINATED!
+ */
+static void digest2str(const unsigned char digest[], char *output)
+{
+    int i;
+    for (i = 0; i<16; ++i) {
+	pj_val_to_hex_digit(digest[i], output);
+	output += 2;
+    }
+}
+
+
+/*
+ * Create response digest based on the parameters and store the
+ * digest ASCII in 'result'. 
+ */
+PJ_DEF(void) pjsip_auth_create_digest( pj_str_t *result,
+				       const pj_str_t *nonce,
+				       const pj_str_t *nc,
+				       const pj_str_t *cnonce,
+				       const pj_str_t *qop,
+				       const pj_str_t *uri,
+				       const pj_str_t *realm,
+				       const pjsip_cred_info *cred_info,
+				       const pj_str_t *method)
+{
+    char ha1[PJSIP_MD5STRLEN];
+    char ha2[PJSIP_MD5STRLEN];
+    unsigned char digest[16];
+    pj_md5_context pms;
+
+    pj_assert(result->slen >= PJSIP_MD5STRLEN);
+
+    AUTH_TRACE_((THIS_FILE, "Begin creating digest"));
+
+    if ((cred_info->data_type & PASSWD_MASK) == PJSIP_CRED_DATA_PLAIN_PASSWD) {
+	/*** 
+	 *** ha1 = MD5(username ":" realm ":" password) 
+	 ***/
+	pj_md5_init(&pms);
+	MD5_APPEND( &pms, cred_info->username.ptr, cred_info->username.slen);
+	MD5_APPEND( &pms, ":", 1);
+	MD5_APPEND( &pms, realm->ptr, realm->slen);
+	MD5_APPEND( &pms, ":", 1);
+	MD5_APPEND( &pms, cred_info->data.ptr, cred_info->data.slen);
+	pj_md5_final(&pms, digest);
+
+	digest2str(digest, ha1);
+
+    } else if ((cred_info->data_type & PASSWD_MASK) == PJSIP_CRED_DATA_DIGEST) {
+	pj_assert(cred_info->data.slen == 32);
+	pj_memcpy( ha1, cred_info->data.ptr, cred_info->data.slen );
+    } else {
+	pj_assert(!"Invalid data_type");
+    }
+
+    AUTH_TRACE_((THIS_FILE, "  ha1=%.32s", ha1));
+
+    /***
+     *** ha2 = MD5(method ":" req_uri) 
+     ***/
+    pj_md5_init(&pms);
+    MD5_APPEND( &pms, method->ptr, method->slen);
+    MD5_APPEND( &pms, ":", 1);
+    MD5_APPEND( &pms, uri->ptr, uri->slen);
+    pj_md5_final(&pms, digest);
+    digest2str(digest, ha2);
+
+    AUTH_TRACE_((THIS_FILE, "  ha2=%.32s", ha2));
+
+    /***
+     *** When qop is not used:
+     ***    response = MD5(ha1 ":" nonce ":" ha2) 
+     ***
+     *** When qop=auth is used:
+     ***    response = MD5(ha1 ":" nonce ":" nc ":" cnonce ":" qop ":" ha2)
+     ***/
+    pj_md5_init(&pms);
+    MD5_APPEND( &pms, ha1, PJSIP_MD5STRLEN);
+    MD5_APPEND( &pms, ":", 1);
+    MD5_APPEND( &pms, nonce->ptr, nonce->slen);
+    if (qop && qop->slen != 0) {
+	MD5_APPEND( &pms, ":", 1);
+	MD5_APPEND( &pms, nc->ptr, nc->slen);
+	MD5_APPEND( &pms, ":", 1);
+	MD5_APPEND( &pms, cnonce->ptr, cnonce->slen);
+	MD5_APPEND( &pms, ":", 1);
+	MD5_APPEND( &pms, qop->ptr, qop->slen);
+    }
+    MD5_APPEND( &pms, ":", 1);
+    MD5_APPEND( &pms, ha2, PJSIP_MD5STRLEN);
+
+    /* This is the final response digest. */
+    pj_md5_final(&pms, digest);
+    
+    /* Convert digest to string and store in chal->response. */
+    result->slen = PJSIP_MD5STRLEN;
+    digest2str(digest, result->ptr);
+
+    AUTH_TRACE_((THIS_FILE, "  digest=%.32s", result->ptr));
+    AUTH_TRACE_((THIS_FILE, "Digest created"));
+}
+
+/*
+ * Finds out if qop offer contains "auth" token.
+ */
+static pj_bool_t has_auth_qop( pj_pool_t *pool, const pj_str_t *qop_offer)
+{
+    pj_str_t qop;
+    char *p;
+
+    pj_strdup_with_null( pool, &qop, qop_offer);
+    p = qop.ptr;
+    while (*p) {
+	*p = (char)pj_tolower(*p);
+	++p;
+    }
+
+    p = qop.ptr;
+    while (*p) {
+	if (*p=='a' && *(p+1)=='u' && *(p+2)=='t' && *(p+3)=='h') {
+	    int e = *(p+4);
+	    if (e=='"' || e==',' || e==0)
+		return PJ_TRUE;
+	    else
+		p += 4;
+	} else {
+	    ++p;
+	}
+    }
+
+    return PJ_FALSE;
+}
+
+/*
+ * Generate response digest. 
+ * Most of the parameters to generate the digest (i.e. username, realm, uri,
+ * and nonce) are expected to be in the credential. Additional parameters (i.e.
+ * password and method param) should be supplied in the argument.
+ *
+ * The resulting digest will be stored in cred->response.
+ * The pool is used to allocate 32 bytes to store the digest in cred->response.
+ */
+static pj_status_t respond_digest( pj_pool_t *pool,
+				   pjsip_digest_credential *cred,
+				   const pjsip_digest_challenge *chal,
+				   const pj_str_t *uri,
+				   const pjsip_cred_info *cred_info,
+				   const pj_str_t *cnonce,
+				   pj_uint32_t nc,
+				   const pj_str_t *method)
+{
+    const pj_str_t pjsip_AKAv1_MD5_STR = { "AKAv1-MD5", 9 };
+
+    /* Check algorithm is supported. We support MD5 and AKAv1-MD5. */
+    if (chal->algorithm.slen==0 ||
+	(pj_stricmp(&chal->algorithm, &pjsip_MD5_STR)==0 ||
+	 pj_stricmp(&chal->algorithm, &pjsip_AKAv1_MD5_STR)==0))
+    {
+	;
+    }
+    else {
+	PJ_LOG(4,(THIS_FILE, "Unsupported digest algorithm \"%.*s\"",
+		  chal->algorithm.slen, chal->algorithm.ptr));
+	return PJSIP_EINVALIDALGORITHM;
+    }
+
+    /* Build digest credential from arguments. */
+    pj_strdup(pool, &cred->username, &cred_info->username);
+    pj_strdup(pool, &cred->realm, &chal->realm);
+    pj_strdup(pool, &cred->nonce, &chal->nonce);
+    pj_strdup(pool, &cred->uri, uri);
+    pj_strdup(pool, &cred->algorithm, &chal->algorithm);
+    pj_strdup(pool, &cred->opaque, &chal->opaque);
+
+    /* Allocate memory. */
+    cred->response.ptr = (char*) pj_pool_alloc(pool, PJSIP_MD5STRLEN);
+    cred->response.slen = PJSIP_MD5STRLEN;
+
+    if (chal->qop.slen == 0) {
+	/* Server doesn't require quality of protection. */
+
+	if ((cred_info->data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) {
+	    /* Call application callback to create the response digest */
+	    return (*cred_info->ext.aka.cb)(pool, chal, cred_info, 
+					    method, cred);
+	} 
+	else {
+	    /* Convert digest to string and store in chal->response. */
+	    pjsip_auth_create_digest( &cred->response, &cred->nonce, NULL, 
+				      NULL,  NULL, uri, &chal->realm, 
+				      cred_info, method);
+	}
+
+    } else if (has_auth_qop(pool, &chal->qop)) {
+	/* Server requires quality of protection. 
+	 * We respond with selecting "qop=auth" protection.
+	 */
+	cred->qop = pjsip_AUTH_STR;
+	cred->nc.ptr = (char*) pj_pool_alloc(pool, 16);
+	cred->nc.slen = pj_ansi_snprintf(cred->nc.ptr, 16, "%08u", nc);
+
+	if (cnonce && cnonce->slen) {
+	    pj_strdup(pool, &cred->cnonce, cnonce);
+	} else {
+	    pj_str_t dummy_cnonce = { "b39971", 6};
+	    pj_strdup(pool, &cred->cnonce, &dummy_cnonce);
+	}
+
+	if ((cred_info->data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) {
+	    /* Call application callback to create the response digest */
+	    return (*cred_info->ext.aka.cb)(pool, chal, cred_info, 
+					    method, cred);
+	}
+	else {
+	    pjsip_auth_create_digest( &cred->response, &cred->nonce, 
+				      &cred->nc, cnonce, &pjsip_AUTH_STR, 
+				      uri, &chal->realm, cred_info, method );
+	}
+
+    } else {
+	/* Server requires quality protection that we don't support. */
+	PJ_LOG(4,(THIS_FILE, "Unsupported qop offer %.*s", 
+		  chal->qop.slen, chal->qop.ptr));
+	return PJSIP_EINVALIDQOP;
+    }
+
+    return PJ_SUCCESS;
+}
+
+#if defined(PJSIP_AUTH_QOP_SUPPORT) && PJSIP_AUTH_QOP_SUPPORT!=0
+/*
+ * Update authentication session with a challenge.
+ */
+static void update_digest_session( pj_pool_t *ses_pool, 
+				   pjsip_cached_auth *cached_auth,
+				   const pjsip_www_authenticate_hdr *hdr )
+{
+    if (hdr->challenge.digest.qop.slen == 0) {
+#if PJSIP_AUTH_AUTO_SEND_NEXT!=0
+	if (!cached_auth->last_chal || pj_stricmp2(&hdr->scheme, "digest")) {
+	    cached_auth->last_chal = (pjsip_www_authenticate_hdr*)
+				     pjsip_hdr_clone(ses_pool, hdr);
+	} else {
+	    /* Only update if the new challenge is "significantly different"
+	     * than the one in the cache, to reduce memory usage.
+	     */
+	    const pjsip_digest_challenge *d1 = 
+			&cached_auth->last_chal->challenge.digest;
+	    const pjsip_digest_challenge *d2 = &hdr->challenge.digest;
+
+	    if (pj_strcmp(&d1->domain, &d2->domain) ||
+		pj_strcmp(&d1->realm, &d2->realm) ||
+		pj_strcmp(&d1->nonce, &d2->nonce) ||
+		pj_strcmp(&d1->opaque, &d2->opaque) ||
+		pj_strcmp(&d1->algorithm, &d2->algorithm) ||
+		pj_strcmp(&d1->qop, &d2->qop))
+	    {
+		cached_auth->last_chal = (pjsip_www_authenticate_hdr*)
+				         pjsip_hdr_clone(ses_pool, hdr);
+	    }
+	}
+#endif
+	return;
+    }
+
+    /* Initialize cnonce and qop if not present. */
+    if (cached_auth->cnonce.slen == 0) {
+	/* Save the whole challenge */
+	cached_auth->last_chal = (pjsip_www_authenticate_hdr*)
+				 pjsip_hdr_clone(ses_pool, hdr);
+
+	/* Create cnonce */
+	pj_create_unique_string( ses_pool, &cached_auth->cnonce );
+
+	/* Initialize nonce-count */
+	cached_auth->nc = 1;
+
+	/* Save realm. */
+	/* Note: allow empty realm (http://trac.pjsip.org/repos/ticket/1061)
+	pj_assert(cached_auth->realm.slen != 0);
+	*/
+	if (cached_auth->realm.slen == 0) {
+	    pj_strdup(ses_pool, &cached_auth->realm, 
+		      &hdr->challenge.digest.realm);
+	}
+
+    } else {
+	/* Update last_nonce and nonce-count */
+	if (!pj_strcmp(&hdr->challenge.digest.nonce, 
+		       &cached_auth->last_chal->challenge.digest.nonce)) 
+	{
+	    /* Same nonce, increment nonce-count */
+	    ++cached_auth->nc;
+	} else {
+	    /* Server gives new nonce. */
+	    pj_strdup(ses_pool, &cached_auth->last_chal->challenge.digest.nonce,
+		      &hdr->challenge.digest.nonce);
+	    /* Has the opaque changed? */
+	    if (pj_strcmp(&cached_auth->last_chal->challenge.digest.opaque,
+			  &hdr->challenge.digest.opaque)) 
+	    {
+		pj_strdup(ses_pool, 
+			  &cached_auth->last_chal->challenge.digest.opaque,
+			  &hdr->challenge.digest.opaque);
+	    }
+	    cached_auth->nc = 1;
+	}
+    }
+}
+#endif	/* PJSIP_AUTH_QOP_SUPPORT */
+
+
+/* Find cached authentication in the list for the specified realm. */
+static pjsip_cached_auth *find_cached_auth( pjsip_auth_clt_sess *sess,
+					    const pj_str_t *realm )
+{
+    pjsip_cached_auth *auth = sess->cached_auth.next;
+    while (auth != &sess->cached_auth) {
+	if (pj_stricmp(&auth->realm, realm) == 0)
+	    return auth;
+	auth = auth->next;
+    }
+
+    return NULL;
+}
+
+/* Find credential to use for the specified realm and auth scheme. */
+static const pjsip_cred_info* auth_find_cred( const pjsip_auth_clt_sess *sess,
+					      const pj_str_t *realm,
+					      const pj_str_t *auth_scheme)
+{
+    unsigned i;
+    int wildcard = -1;
+
+    PJ_UNUSED_ARG(auth_scheme);
+
+    for (i=0; i<sess->cred_cnt; ++i) {
+	if (pj_stricmp(&sess->cred_info[i].realm, realm) == 0)
+	    return &sess->cred_info[i];
+	else if (sess->cred_info[i].realm.slen == 1 &&
+		 sess->cred_info[i].realm.ptr[0] == '*')
+	{
+	    wildcard = i;
+	}
+    }
+
+    /* No matching realm. See if we have credential with wildcard ('*')
+     * as the realm.
+     */
+    if (wildcard != -1)
+	return &sess->cred_info[wildcard];
+
+    /* Nothing is suitable */
+    return NULL;
+}
+
+
+/* Init client session. */
+PJ_DEF(pj_status_t) pjsip_auth_clt_init(  pjsip_auth_clt_sess *sess,
+					  pjsip_endpoint *endpt,
+					  pj_pool_t *pool, 
+					  unsigned options)
+{
+    PJ_ASSERT_RETURN(sess && endpt && pool && (options==0), PJ_EINVAL);
+
+    sess->pool = pool;
+    sess->endpt = endpt;
+    sess->cred_cnt = 0;
+    sess->cred_info = NULL;
+    pj_list_init(&sess->cached_auth);
+
+    return PJ_SUCCESS;
+}
+
+
+/* Clone session. */
+PJ_DEF(pj_status_t) pjsip_auth_clt_clone( pj_pool_t *pool,
+					  pjsip_auth_clt_sess *sess,
+					  const pjsip_auth_clt_sess *rhs )
+{
+    unsigned i;
+
+    PJ_ASSERT_RETURN(pool && sess && rhs, PJ_EINVAL);
+
+    pjsip_auth_clt_init(sess, (pjsip_endpoint*)rhs->endpt, pool, 0);
+    
+    sess->cred_cnt = rhs->cred_cnt;
+    sess->cred_info = (pjsip_cred_info*)
+    		      pj_pool_alloc(pool, 
+				    sess->cred_cnt*sizeof(pjsip_cred_info));
+    for (i=0; i<rhs->cred_cnt; ++i) {
+	pj_strdup(pool, &sess->cred_info[i].realm, &rhs->cred_info[i].realm);
+	pj_strdup(pool, &sess->cred_info[i].scheme, &rhs->cred_info[i].scheme);
+	pj_strdup(pool, &sess->cred_info[i].username, 
+		  &rhs->cred_info[i].username);
+	sess->cred_info[i].data_type = rhs->cred_info[i].data_type;
+	pj_strdup(pool, &sess->cred_info[i].data, &rhs->cred_info[i].data);
+    }
+
+    /* TODO note:
+     * Cloning the full authentication client is quite a big task.
+     * We do only the necessary bits here, i.e. cloning the credentials.
+     * The drawback of this basic approach is, a forked dialog will have to
+     * re-authenticate itself on the next request because it has lost the
+     * cached authentication headers.
+     */
+    PJ_TODO(FULL_CLONE_OF_AUTH_CLIENT_SESSION);
+
+    return PJ_SUCCESS;
+}
+
+
+/* Set client credentials. */
+PJ_DEF(pj_status_t) pjsip_auth_clt_set_credentials( pjsip_auth_clt_sess *sess,
+						    int cred_cnt,
+						    const pjsip_cred_info *c)
+{
+    PJ_ASSERT_RETURN(sess && c, PJ_EINVAL);
+
+    if (cred_cnt == 0) {
+	sess->cred_cnt = 0;
+    } else {
+	int i;
+	sess->cred_info = (pjsip_cred_info*)
+			  pj_pool_alloc(sess->pool, cred_cnt * sizeof(*c));
+	for (i=0; i<cred_cnt; ++i) {
+	    sess->cred_info[i].data_type = c[i].data_type;
+
+	    /* When data_type is PJSIP_CRED_DATA_EXT_AKA, 
+	     * callback must be specified.
+	     */
+	    if ((c[i].data_type & EXT_MASK) == PJSIP_CRED_DATA_EXT_AKA) {
+
+#if !PJSIP_HAS_DIGEST_AKA_AUTH
+		if (!PJSIP_HAS_DIGEST_AKA_AUTH) {
+		    pj_assert(!"PJSIP_HAS_DIGEST_AKA_AUTH is not enabled");
+		    return PJSIP_EAUTHINAKACRED;
+		}
+#endif
+
+		/* Callback must be specified */
+		PJ_ASSERT_RETURN(c[i].ext.aka.cb != NULL, PJ_EINVAL);
+
+		/* Verify K len */
+		PJ_ASSERT_RETURN(c[i].ext.aka.k.slen <= PJSIP_AKA_KLEN, 
+				 PJSIP_EAUTHINAKACRED);
+
+		/* Verify OP len */
+		PJ_ASSERT_RETURN(c[i].ext.aka.op.slen <= PJSIP_AKA_OPLEN, 
+				 PJSIP_EAUTHINAKACRED);
+
+		/* Verify AMF len */
+		PJ_ASSERT_RETURN(c[i].ext.aka.amf.slen <= PJSIP_AKA_AMFLEN,
+				 PJSIP_EAUTHINAKACRED);
+
+		sess->cred_info[i].ext.aka.cb = c[i].ext.aka.cb;
+		pj_strdup(sess->pool, &sess->cred_info[i].ext.aka.k,
+			  &c[i].ext.aka.k);
+		pj_strdup(sess->pool, &sess->cred_info[i].ext.aka.op,
+			  &c[i].ext.aka.op);
+		pj_strdup(sess->pool, &sess->cred_info[i].ext.aka.amf,
+			  &c[i].ext.aka.amf);
+	    }
+
+	    pj_strdup(sess->pool, &sess->cred_info[i].scheme, &c[i].scheme);
+	    pj_strdup(sess->pool, &sess->cred_info[i].realm, &c[i].realm);
+	    pj_strdup(sess->pool, &sess->cred_info[i].username, &c[i].username);
+	    pj_strdup(sess->pool, &sess->cred_info[i].data, &c[i].data);
+	}
+	sess->cred_cnt = cred_cnt;
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Set the preference for the client authentication session.
+ */
+PJ_DEF(pj_status_t) pjsip_auth_clt_set_prefs(pjsip_auth_clt_sess *sess,
+					     const pjsip_auth_clt_pref *p)
+{
+    PJ_ASSERT_RETURN(sess && p, PJ_EINVAL);
+
+    pj_memcpy(&sess->pref, p, sizeof(*p));
+    pj_strdup(sess->pool, &sess->pref.algorithm, &p->algorithm);
+    //if (sess->pref.algorithm.slen == 0)
+    //	sess->pref.algorithm = pj_str("md5");
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Get the preference for the client authentication session.
+ */
+PJ_DEF(pj_status_t) pjsip_auth_clt_get_prefs(pjsip_auth_clt_sess *sess,
+					     pjsip_auth_clt_pref *p)
+{
+    PJ_ASSERT_RETURN(sess && p, PJ_EINVAL);
+
+    pj_memcpy(p, &sess->pref, sizeof(pjsip_auth_clt_pref));
+    return PJ_SUCCESS;
+}
+
+
+/* 
+ * Create Authorization/Proxy-Authorization response header based on the challege
+ * in WWW-Authenticate/Proxy-Authenticate header.
+ */
+static pj_status_t auth_respond( pj_pool_t *req_pool,
+				 const pjsip_www_authenticate_hdr *hdr,
+				 const pjsip_uri *uri,
+				 const pjsip_cred_info *cred_info,
+				 const pjsip_method *method,
+				 pj_pool_t *sess_pool,
+				 pjsip_cached_auth *cached_auth,
+				 pjsip_authorization_hdr **p_h_auth)
+{
+    pjsip_authorization_hdr *hauth;
+    char tmp[PJSIP_MAX_URL_SIZE];
+    pj_str_t uri_str;
+    pj_pool_t *pool;
+    pj_status_t status;
+
+    /* Verify arguments. */
+    PJ_ASSERT_RETURN(req_pool && hdr && uri && cred_info && method &&
+		     sess_pool && cached_auth && p_h_auth, PJ_EINVAL);
+
+    /* Print URL in the original request. */
+    uri_str.ptr = tmp;
+    uri_str.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, uri, tmp,sizeof(tmp));
+    if (uri_str.slen < 1) {
+	pj_assert(!"URL is too long!");
+	return PJSIP_EURITOOLONG;
+    }
+
+#   if (PJSIP_AUTH_HEADER_CACHING)
+    {
+	pool = sess_pool;
+	PJ_UNUSED_ARG(req_pool);
+    }
+#   else
+    {
+	pool = req_pool;
+	PJ_UNUSED_ARG(sess_pool);
+    }
+#   endif
+
+    if (hdr->type == PJSIP_H_WWW_AUTHENTICATE)
+	hauth = pjsip_authorization_hdr_create(pool);
+    else if (hdr->type == PJSIP_H_PROXY_AUTHENTICATE)
+	hauth = pjsip_proxy_authorization_hdr_create(pool);
+    else {
+	pj_assert(!"Invalid response header!");
+	return PJSIP_EINVALIDHDR;
+    }
+
+    /* Only support digest scheme at the moment. */
+    if (!pj_stricmp(&hdr->scheme, &pjsip_DIGEST_STR)) {
+	pj_str_t *cnonce = NULL;
+	pj_uint32_t nc = 1;
+
+	/* Update the session (nonce-count etc) if required. */
+#	if PJSIP_AUTH_QOP_SUPPORT
+	{
+	    if (cached_auth) {
+		update_digest_session( sess_pool, cached_auth, hdr );
+
+		cnonce = &cached_auth->cnonce;
+		nc = cached_auth->nc;
+	    }
+	}
+#	endif	/* PJSIP_AUTH_QOP_SUPPORT */
+
+	hauth->scheme = pjsip_DIGEST_STR;
+	status = respond_digest( pool, &hauth->credential.digest,
+				 &hdr->challenge.digest, &uri_str, cred_info,
+				 cnonce, nc, &method->name);
+	if (status != PJ_SUCCESS)
+	    return status;
+
+	/* Set qop type in auth session the first time only. */
+	if (hdr->challenge.digest.qop.slen != 0 && cached_auth) {
+	    if (cached_auth->qop_value == PJSIP_AUTH_QOP_NONE) {
+		pj_str_t *qop_val = &hauth->credential.digest.qop;
+		if (!pj_strcmp(qop_val, &pjsip_AUTH_STR)) {
+		    cached_auth->qop_value = PJSIP_AUTH_QOP_AUTH;
+		} else {
+		    cached_auth->qop_value = PJSIP_AUTH_QOP_UNKNOWN;
+		}
+	    }
+	}
+    } else {
+	return PJSIP_EINVALIDAUTHSCHEME;
+    }
+
+    /* Keep the new authorization header in the cache, only
+     * if no qop is not present.
+     */
+#   if PJSIP_AUTH_HEADER_CACHING
+    {
+	if (hauth && cached_auth && cached_auth->qop_value == PJSIP_AUTH_QOP_NONE) {
+	    pjsip_cached_auth_hdr *cached_hdr;
+
+	    /* Delete old header with the same method. */
+	    cached_hdr = cached_auth->cached_hdr.next;
+	    while (cached_hdr != &cached_auth->cached_hdr) {
+		if (pjsip_method_cmp(method, &cached_hdr->method)==0)
+		    break;
+		cached_hdr = cached_hdr->next;
+	    }
+
+	    /* Save the header to the list. */
+	    if (cached_hdr != &cached_auth->cached_hdr) {
+		cached_hdr->hdr = hauth;
+	    } else {
+		cached_hdr = pj_pool_alloc(pool, sizeof(*cached_hdr));
+		pjsip_method_copy( pool, &cached_hdr->method, method);
+		cached_hdr->hdr = hauth;
+		pj_list_insert_before( &cached_auth->cached_hdr, cached_hdr );
+	    }
+	}
+
+#	if defined(PJSIP_AUTH_AUTO_SEND_NEXT) && PJSIP_AUTH_AUTO_SEND_NEXT!=0
+	    if (hdr != cached_auth->last_chal) {
+		cached_auth->last_chal = pjsip_hdr_clone(sess_pool, hdr);
+	    }
+#	endif
+    }
+#   endif
+
+    *p_h_auth = hauth;
+    return PJ_SUCCESS;
+
+}
+
+
+#if defined(PJSIP_AUTH_AUTO_SEND_NEXT) && PJSIP_AUTH_AUTO_SEND_NEXT!=0
+static pj_status_t new_auth_for_req( pjsip_tx_data *tdata,
+				     pjsip_auth_clt_sess *sess,
+				     pjsip_cached_auth *auth,
+				     pjsip_authorization_hdr **p_h_auth)
+{
+    const pjsip_cred_info *cred;
+    pjsip_authorization_hdr *hauth;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(tdata && sess && auth, PJ_EINVAL);
+    PJ_ASSERT_RETURN(auth->last_chal != NULL, PJSIP_EAUTHNOPREVCHAL);
+
+    cred = auth_find_cred( sess, &auth->realm, &auth->last_chal->scheme );
+    if (!cred)
+	return PJSIP_ENOCREDENTIAL;
+
+    status = auth_respond( tdata->pool, auth->last_chal,
+			   tdata->msg->line.req.uri,
+			   cred, &tdata->msg->line.req.method,
+			   sess->pool, auth, &hauth);
+    if (status != PJ_SUCCESS)
+	return status;
+    
+    pjsip_msg_add_hdr( tdata->msg, (pjsip_hdr*)hauth);
+
+    if (p_h_auth)
+	*p_h_auth = hauth;
+
+    return PJ_SUCCESS;
+}
+#endif
+
+
+/* Find credential in list of (Proxy-)Authorization headers */
+static pjsip_authorization_hdr* get_header_for_realm(const pjsip_hdr *hdr_list,
+						     const pj_str_t *realm)
+{
+    pjsip_authorization_hdr *h;
+
+    h = (pjsip_authorization_hdr*)hdr_list->next;
+    while (h != (pjsip_authorization_hdr*)hdr_list) {
+	if (pj_stricmp(&h->credential.digest.realm, realm)==0)
+	    return h;
+	h = h->next;
+    }
+
+    return NULL;
+}
+
+
+/* Initialize outgoing request. */
+PJ_DEF(pj_status_t) pjsip_auth_clt_init_req( pjsip_auth_clt_sess *sess,
+					     pjsip_tx_data *tdata )
+{
+    const pjsip_method *method;
+    pjsip_cached_auth *auth;
+    pjsip_hdr added;
+
+    PJ_ASSERT_RETURN(sess && tdata, PJ_EINVAL);
+    PJ_ASSERT_RETURN(sess->pool, PJSIP_ENOTINITIALIZED);
+    PJ_ASSERT_RETURN(tdata->msg->type==PJSIP_REQUEST_MSG,
+		     PJSIP_ENOTREQUESTMSG);
+
+    /* Init list */
+    pj_list_init(&added);
+
+    /* Get the method. */
+    method = &tdata->msg->line.req.method;
+
+    auth = sess->cached_auth.next;
+    while (auth != &sess->cached_auth) {
+	/* Reset stale counter */
+	auth->stale_cnt = 0;
+
+	if (auth->qop_value == PJSIP_AUTH_QOP_NONE) {
+#	    if defined(PJSIP_AUTH_HEADER_CACHING) && \
+	       PJSIP_AUTH_HEADER_CACHING!=0
+	    {
+		pjsip_cached_auth_hdr *entry = auth->cached_hdr.next;
+		while (entry != &auth->cached_hdr) {
+		    if (pjsip_method_cmp(&entry->method, method)==0) {
+			pjsip_authorization_hdr *hauth;
+			hauth = pjsip_hdr_shallow_clone(tdata->pool, entry->hdr);
+			//pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hauth);
+			pj_list_push_back(&added, hauth);
+			break;
+		    }
+		    entry = entry->next;
+		}
+
+#		if defined(PJSIP_AUTH_AUTO_SEND_NEXT) && \
+			   PJSIP_AUTH_AUTO_SEND_NEXT!=0
+		{
+		    if (entry == &auth->cached_hdr)
+			new_auth_for_req( tdata, sess, auth, NULL);
+		}
+#		endif
+
+	    }
+#	    elif defined(PJSIP_AUTH_AUTO_SEND_NEXT) && \
+		 PJSIP_AUTH_AUTO_SEND_NEXT!=0
+	    {
+		new_auth_for_req( tdata, sess, auth, NULL);
+	    }
+#	    endif
+
+	} 
+#	if defined(PJSIP_AUTH_QOP_SUPPORT) && \
+	   defined(PJSIP_AUTH_AUTO_SEND_NEXT) && \
+	   (PJSIP_AUTH_QOP_SUPPORT && PJSIP_AUTH_AUTO_SEND_NEXT)
+	else if (auth->qop_value == PJSIP_AUTH_QOP_AUTH) {
+	    /* For qop="auth", we have to re-create the authorization header. 
+	     */
+	    const pjsip_cred_info *cred;
+	    pjsip_authorization_hdr *hauth;
+	    pj_status_t status;
+
+	    cred = auth_find_cred(sess, &auth->realm, 
+				  &auth->last_chal->scheme);
+	    if (!cred) {
+		auth = auth->next;
+		continue;
+	    }
+
+	    status = auth_respond( tdata->pool, auth->last_chal, 
+				   tdata->msg->line.req.uri, 
+				   cred,
+				   &tdata->msg->line.req.method,
+				   sess->pool, auth, &hauth);
+	    if (status != PJ_SUCCESS)
+		return status;
+	    
+	    //pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hauth);
+	    pj_list_push_back(&added, hauth);
+	}
+#	endif	/* PJSIP_AUTH_QOP_SUPPORT && PJSIP_AUTH_AUTO_SEND_NEXT */
+
+	auth = auth->next;
+    }
+
+    if (sess->pref.initial_auth == PJ_FALSE) {
+	pjsip_hdr *h;
+
+	/* Don't want to send initial empty Authorization header, so
+	 * just send whatever available in the list (maybe empty).
+	 */
+
+	h = added.next;
+	while (h != &added) {
+	    pjsip_hdr *next = h->next;
+	    pjsip_msg_add_hdr(tdata->msg, h);
+	    h = next;
+	}
+    } else {
+	/* For each realm, add either the cached authorization header
+	 * or add an empty authorization header.
+	 */
+	unsigned i;
+	pj_str_t uri;
+
+	uri.ptr = (char*)pj_pool_alloc(tdata->pool, PJSIP_MAX_URL_SIZE);
+	uri.slen = pjsip_uri_print(PJSIP_URI_IN_REQ_URI,
+	                           tdata->msg->line.req.uri,
+	                           uri.ptr, PJSIP_MAX_URL_SIZE);
+	if (uri.slen < 1 || uri.slen >= PJSIP_MAX_URL_SIZE)
+	    return PJSIP_EURITOOLONG;
+
+	for (i=0; i<sess->cred_cnt; ++i) {
+	    pjsip_cred_info *c = &sess->cred_info[i];
+	    pjsip_authorization_hdr *h;
+
+	    h = get_header_for_realm(&added, &c->realm);
+	    if (h) {
+		pj_list_erase(h);
+		pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)h);
+	    } else {
+		pjsip_authorization_hdr *hs;
+
+		hs = pjsip_authorization_hdr_create(tdata->pool);
+		pj_strdup(tdata->pool, &hs->scheme, &c->scheme);
+		pj_strdup(tdata->pool, &hs->credential.digest.username,
+			  &c->username);
+		pj_strdup(tdata->pool, &hs->credential.digest.realm,
+			  &c->realm);
+		pj_strdup(tdata->pool, &hs->credential.digest.uri, &uri);
+		pj_strdup(tdata->pool, &hs->credential.digest.algorithm,
+			  &sess->pref.algorithm);
+
+		pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hs);
+	    }
+	}
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+/* Process authorization challenge */
+static pj_status_t process_auth( pj_pool_t *req_pool,
+				 const pjsip_www_authenticate_hdr *hchal,
+				 const pjsip_uri *uri,
+				 pjsip_tx_data *tdata,
+				 pjsip_auth_clt_sess *sess,
+				 pjsip_cached_auth *cached_auth,
+				 pjsip_authorization_hdr **h_auth)
+{
+    const pjsip_cred_info *cred;
+    pjsip_authorization_hdr *sent_auth = NULL;
+    pjsip_hdr *hdr;
+    pj_status_t status;
+
+    /* See if we have sent authorization header for this realm */
+    hdr = tdata->msg->hdr.next;
+    while (hdr != &tdata->msg->hdr) {
+	if ((hchal->type == PJSIP_H_WWW_AUTHENTICATE &&
+	     hdr->type == PJSIP_H_AUTHORIZATION) ||
+	    (hchal->type == PJSIP_H_PROXY_AUTHENTICATE &&
+	     hdr->type == PJSIP_H_PROXY_AUTHORIZATION))
+	{
+	    sent_auth = (pjsip_authorization_hdr*) hdr;
+	    if (pj_stricmp(&hchal->challenge.common.realm, 
+			   &sent_auth->credential.common.realm )==0)
+	    {
+		/* If this authorization has empty response, remove it. */
+		if (pj_stricmp(&sent_auth->scheme, &pjsip_DIGEST_STR)==0 &&
+		    sent_auth->credential.digest.response.slen == 0)
+		{
+		    /* This is empty authorization, remove it. */
+		    hdr = hdr->next;
+		    pj_list_erase(sent_auth);
+		    continue;
+		} else {
+		    /* Found previous authorization attempt */
+		    break;
+		}
+	    }
+	}
+	hdr = hdr->next;
+    }
+
+    /* If we have sent, see if server rejected because of stale nonce or
+     * other causes.
+     */
+    if (hdr != &tdata->msg->hdr) {
+	pj_bool_t stale;
+
+	/* Detect "stale" state */
+	stale = hchal->challenge.digest.stale;
+	if (!stale) {
+	    /* If stale is false, check is nonce has changed. Some servers
+	     * (broken ones!) want to change nonce but they fail to set
+	     * stale to true.
+	     */
+	    stale = pj_strcmp(&hchal->challenge.digest.nonce,
+			      &sent_auth->credential.digest.nonce);
+	}
+
+	if (stale == PJ_FALSE) {
+	    /* Our credential is rejected. No point in trying to re-supply
+	     * the same credential.
+	     */
+	    PJ_LOG(4, (THIS_FILE, "Authorization failed for %.*s@%.*s: "
+		       "server rejected with stale=false",
+		       sent_auth->credential.digest.username.slen,
+		       sent_auth->credential.digest.username.ptr,
+		       sent_auth->credential.digest.realm.slen,
+		       sent_auth->credential.digest.realm.ptr));
+	    return PJSIP_EFAILEDCREDENTIAL;
+	}
+
+	cached_auth->stale_cnt++;
+	if (cached_auth->stale_cnt >= PJSIP_MAX_STALE_COUNT) {
+	    /* Our credential is rejected. No point in trying to re-supply
+	     * the same credential.
+	     */
+	    PJ_LOG(4, (THIS_FILE, "Authorization failed for %.*s@%.*s: "
+		       "maximum number of stale retries exceeded",
+		       sent_auth->credential.digest.username.slen,
+		       sent_auth->credential.digest.username.ptr,
+		       sent_auth->credential.digest.realm.slen,
+		       sent_auth->credential.digest.realm.ptr));
+	    return PJSIP_EAUTHSTALECOUNT;
+	}
+
+	/* Otherwise remove old, stale authorization header from the mesasge.
+	 * We will supply a new one.
+	 */
+	pj_list_erase(sent_auth);
+    }
+
+    /* Find credential to be used for the challenge. */
+    cred = auth_find_cred( sess, &hchal->challenge.common.realm, 
+			   &hchal->scheme);
+    if (!cred) {
+	const pj_str_t *realm = &hchal->challenge.common.realm;
+	PJ_LOG(4,(THIS_FILE, 
+		  "Unable to set auth for %s: can not find credential for %.*s/%.*s",
+		  tdata->obj_name, 
+		  realm->slen, realm->ptr,
+		  hchal->scheme.slen, hchal->scheme.ptr));
+	return PJSIP_ENOCREDENTIAL;
+    }
+
+    /* Respond to authorization challenge. */
+    status = auth_respond( req_pool, hchal, uri, cred, 
+			   &tdata->msg->line.req.method, 
+			   sess->pool, cached_auth, h_auth);
+    return status;
+}
+
+
+/* Reinitialize outgoing request after 401/407 response is received.
+ * The purpose of this function is:
+ *  - to add a Authorization/Proxy-Authorization header.
+ *  - to put the newly created Authorization/Proxy-Authorization header
+ *    in cached_list.
+ */
+PJ_DEF(pj_status_t) pjsip_auth_clt_reinit_req(	pjsip_auth_clt_sess *sess,
+						const pjsip_rx_data *rdata,
+						pjsip_tx_data *old_request,
+						pjsip_tx_data **new_request )
+{
+    pjsip_tx_data *tdata;
+    const pjsip_hdr *hdr;
+    unsigned chal_cnt;
+    pjsip_via_hdr *via;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(sess && rdata && old_request && new_request,
+		     PJ_EINVAL);
+    PJ_ASSERT_RETURN(sess->pool, PJSIP_ENOTINITIALIZED);
+    PJ_ASSERT_RETURN(rdata->msg_info.msg->type == PJSIP_RESPONSE_MSG,
+		     PJSIP_ENOTRESPONSEMSG);
+    PJ_ASSERT_RETURN(old_request->msg->type == PJSIP_REQUEST_MSG,
+		     PJSIP_ENOTREQUESTMSG);
+    PJ_ASSERT_RETURN(rdata->msg_info.msg->line.status.code == 401 ||
+		     rdata->msg_info.msg->line.status.code == 407,
+		     PJSIP_EINVALIDSTATUS);
+
+    tdata = old_request;
+    tdata->auth_retry = PJ_FALSE;
+
+    /*
+     * Respond to each authentication challenge.
+     */
+    hdr = rdata->msg_info.msg->hdr.next;
+    chal_cnt = 0;
+    while (hdr != &rdata->msg_info.msg->hdr) {
+	pjsip_cached_auth *cached_auth;
+	const pjsip_www_authenticate_hdr *hchal;
+	pjsip_authorization_hdr *hauth;
+
+	/* Find WWW-Authenticate or Proxy-Authenticate header. */
+	while (hdr != &rdata->msg_info.msg->hdr &&
+	       hdr->type != PJSIP_H_WWW_AUTHENTICATE &&
+	       hdr->type != PJSIP_H_PROXY_AUTHENTICATE)
+	{
+	    hdr = hdr->next;
+	}
+	if (hdr == &rdata->msg_info.msg->hdr)
+	    break;
+
+	hchal = (const pjsip_www_authenticate_hdr*) hdr;
+	++chal_cnt;
+
+	/* Find authentication session for this realm, create a new one
+	 * if not present.
+	 */
+	cached_auth = find_cached_auth(sess, &hchal->challenge.common.realm );
+	if (!cached_auth) {
+	    cached_auth = PJ_POOL_ZALLOC_T( sess->pool, pjsip_cached_auth);
+	    pj_strdup( sess->pool, &cached_auth->realm, &hchal->challenge.common.realm);
+	    cached_auth->is_proxy = (hchal->type == PJSIP_H_PROXY_AUTHENTICATE);
+#	    if (PJSIP_AUTH_HEADER_CACHING)
+	    {
+		pj_list_init(&cached_auth->cached_hdr);
+	    }
+#	    endif
+	    pj_list_insert_before( &sess->cached_auth, cached_auth );
+	}
+
+	/* Create authorization header for this challenge, and update
+	 * authorization session.
+	 */
+	status = process_auth( tdata->pool, hchal, tdata->msg->line.req.uri, 
+			       tdata, sess, cached_auth, &hauth);
+	if (status != PJ_SUCCESS)
+	    return status;
+
+	/* Add to the message. */
+	pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*)hauth);
+
+	/* Process next header. */
+	hdr = hdr->next;
+    }
+
+    /* Check if challenge is present */
+    if (chal_cnt == 0)
+	return PJSIP_EAUTHNOCHAL;
+
+    /* Remove branch param in Via header. */
+    via = (pjsip_via_hdr*) pjsip_msg_find_hdr(tdata->msg, PJSIP_H_VIA, NULL);
+    via->branch_param.slen = 0;
+
+    /* Restore strict route set.
+     * See http://trac.pjsip.org/repos/ticket/492
+     */
+    pjsip_restore_strict_route_set(tdata);
+
+    /* Must invalidate the message! */
+    pjsip_tx_data_invalidate_msg(tdata);
+
+    /* Retrying.. */
+    tdata->auth_retry = PJ_TRUE;
+
+    /* Increment reference counter. */
+    pjsip_tx_data_add_ref(tdata);
+
+    /* Done. */
+    *new_request = tdata;
+    return PJ_SUCCESS;
+
+}
+
diff --git a/jni/pjproject-android/.svn/pristine/d6/d6f2d2be4f68e2144e9d4174d819069ecff80a5b.svn-base b/jni/pjproject-android/.svn/pristine/d6/d6f2d2be4f68e2144e9d4174d819069ecff80a5b.svn-base
new file mode 100644
index 0000000..77f236d
--- /dev/null
+++ b/jni/pjproject-android/.svn/pristine/d6/d6f2d2be4f68e2144e9d4174d819069ecff80a5b.svn-base
@@ -0,0 +1,195 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2008-2011 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 
+ */
+#ifndef __PJMEDIA_MEM_PORT_H__
+#define __PJMEDIA_MEM_PORT_H__
+
+/**
+ * @file mem_port.h
+ * @brief Memory based media playback/capture port
+ */
+#include <pjmedia/port.h>
+
+PJ_BEGIN_DECL
+
+
+/**
+ * @defgroup PJMEDIA_MEM_PLAYER Memory/Buffer-based Playback Port
+ * @ingroup PJMEDIA_PORT
+ * @brief Media playback from a fixed size memory buffer
+ * @{
+ *
+ * A memory/buffer based playback port is used to play media from a fixed
+ * size buffer. This is useful over @ref PJMEDIA_FILE_PLAY for 
+ * situation where filesystems are not available in the target system.
+ */
+
+
+/**
+ * Memory player options.
+ */
+enum pjmedia_mem_player_option
+{
+    /**
+     * Tell the memory player to return NULL frame when the whole
+     * buffer has been played instead of rewinding the buffer back
+     * to start position.
+     */
+    PJMEDIA_MEM_NO_LOOP = 1
+};
+
+
+/**
+ * Create the buffer based playback to play the media from the specified
+ * buffer.
+ *
+ * @param pool		    Pool to allocate memory for the port structure.
+ * @param buffer	    The buffer to play the media from, which should
+ *			    be available throughout the life time of the port.
+ *			    The player plays the media directly from this
+ *			    buffer (i.e. no copying is done).
+ * @param size		    The size of the buffer, in bytes.
+ * @param clock_rate	    Sampling rate.
+ * @param channel_count	    Number of channels.
+ * @param samples_per_frame Number of samples per frame.
+ * @param bits_per_sample   Number of bits per sample.
+ * @param options	    Option flags, see #pjmedia_mem_player_option
+ * @param p_port	    Pointer to receive the port instance.
+ *
+ * @return		    PJ_SUCCESS on success, or the appropriate
+ *			    error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_mem_player_create(pj_pool_t *pool,
+					       const void *buffer,
+					       pj_size_t size,
+					       unsigned clock_rate,
+					       unsigned channel_count,
+					       unsigned samples_per_frame,
+					       unsigned bits_per_sample,
+					       unsigned options,
+					       pjmedia_port **p_port );
+
+
+/**
+ * Register a callback to be called when the buffer reading has reached the
+ * end of buffer. If the player is set to play repeatedly, then the callback
+ * will be called multiple times. Note that only one callback can be 
+ * registered for each player port.
+ *
+ * @param port		The memory player port.
+ * @param user_data	User data to be specified in the callback
+ * @param cb		Callback to be called. If the callback returns non-
+ *			PJ_SUCCESS, the playback will stop. Note that if
+ *			application destroys the player port in the callback,
+ *			it must return non-PJ_SUCCESS here.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t) 
+pjmedia_mem_player_set_eof_cb( pjmedia_port *port,
+			       void *user_data,
+			       pj_status_t (*cb)(pjmedia_port *port,
+						 void *usr_data));
+
+
+/**
+ * @}
+ */
+
+/**
+ * @defgroup PJMEDIA_MEM_CAPTURE Memory/Buffer-based Capture Port
+ * @ingroup PJMEDIA_PORT
+ * @brief Media capture to fixed size memory buffer
+ * @{
+ *
+ * A memory based capture is used to save media streams to a fixed size
+ * buffer. This is useful over @ref PJMEDIA_FILE_REC for 
+ * situation where filesystems are not available in the target system.
+ */
+
+/**
+ * Create media port to capture/record media into a fixed size buffer.
+ *
+ * @param pool		    Pool to allocate memory for the port structure.
+ * @param buffer	    The buffer to record the media to, which should
+ *			    be available throughout the life time of the port.
+ * @param size		    The maximum size of the buffer, in bytes.
+ * @param clock_rate	    Sampling rate.
+ * @param channel_count	    Number of channels.
+ * @param samples_per_frame Number of samples per frame.
+ * @param bits_per_sample   Number of bits per sample.
+ * @param options	    Option flags.
+ * @param p_port	    Pointer to receive the port instance.
+ *
+ * @return		    PJ_SUCCESS on success, or the appropriate
+ *			    error code.
+ */
+PJ_DECL(pj_status_t) pjmedia_mem_capture_create(pj_pool_t *pool,
+						void *buffer,
+						pj_size_t size,
+						unsigned clock_rate,
+						unsigned channel_count,
+						unsigned samples_per_frame,
+						unsigned bits_per_sample,
+						unsigned options,
+						pjmedia_port **p_port);
+
+
+/**
+ * Register a callback to be called when no space left in the buffer.
+ * Note that when a callback is registered, this callback will also be
+ * called when application destroys the port and the callback has not 
+ * been called before.
+ *
+ * @param port		The memory recorder port.
+ * @param user_data	User data to be specified in the callback
+ * @param cb		Callback to be called. If the callback returns non-
+ *			PJ_SUCCESS, the recording will stop. In other cases
+ *                      recording will be restarted and the rest of the frame
+ *                      will be stored starting from the beginning of the 
+ *			buffer. Note that if application destroys the capture
+ *			port in the callback, it must return non-PJ_SUCCESS 
+ *			here.
+ *
+ * @return		PJ_SUCCESS on success.
+ */
+PJ_DECL(pj_status_t)
+pjmedia_mem_capture_set_eof_cb(pjmedia_port *port,
+                               void *user_data,
+                               pj_status_t (*cb)(pjmedia_port *port,
+						 void *usr_data));
+
+/**
+ * Return the current size of the recorded data in the buffer.
+ *
+ * @param port		The memory recorder port.
+ * @return		The size of buffer data..
+ */
+PJ_DECL(pj_size_t)
+pjmedia_mem_capture_get_size(pjmedia_port *port);
+
+
+/**
+ * @}
+ */
+
+PJ_END_DECL
+
+
+#endif	/* __PJMEDIA_MEM_PORT_H__ */
diff --git a/jni/pjproject-android/.svn/pristine/d6/d6f742c72a11eb6e568975b23fb01bdcf890121a.svn-base b/jni/pjproject-android/.svn/pristine/d6/d6f742c72a11eb6e568975b23fb01bdcf890121a.svn-base
new file mode 100644
index 0000000..561b5ce
--- /dev/null
+++ b/jni/pjproject-android/.svn/pristine/d6/d6f742c72a11eb6e568975b23fb01bdcf890121a.svn-base
@@ -0,0 +1,36 @@
+/* $Id: pjsua_app_callback.h $ */
+/*
+ * Copyright (C) 2008-2011 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
+ */
+#ifndef __PJSUA_APP_CALLBACK_H__
+#define __PJSUA_APP_CALLBACK_H__
+
+class PjsuaAppCallback {
+public:
+    virtual ~PjsuaAppCallback() {}
+    virtual void onStarted(const char *msg) {}
+    virtual void onStopped(int restart) {}
+};
+
+extern "C" {
+int pjsuaStart();
+void pjsuaDestroy();
+int pjsuaRestart();
+void setCallbackObject(PjsuaAppCallback* callback);
+}
+
+#endif /* __PJSUA_APP_CALLBACK_H__ */
diff --git a/jni/pjproject-android/.svn/pristine/d6/d6f929f252791effa45233f4e7e7e6ad38077be7.svn-base b/jni/pjproject-android/.svn/pristine/d6/d6f929f252791effa45233f4e7e7e6ad38077be7.svn-base
new file mode 100644
index 0000000..176b2d4
--- /dev/null
+++ b/jni/pjproject-android/.svn/pristine/d6/d6f929f252791effa45233f4e7e7e6ad38077be7.svn-base
@@ -0,0 +1,1711 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2008-2011 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 <pjmedia/transport_srtp.h>
+#include <pjmedia/endpoint.h>
+#include <pjlib-util/base64.h>
+#include <pj/assert.h>
+#include <pj/ctype.h>
+#include <pj/lock.h>
+#include <pj/log.h>
+#include <pj/os.h>
+#include <pj/pool.h>
+
+#if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0)
+
+#include <srtp.h>
+
+#define THIS_FILE   "transport_srtp.c"
+
+/* Maximum size of outgoing packet */
+#define MAX_RTP_BUFFER_LEN	    PJMEDIA_MAX_MTU
+#define MAX_RTCP_BUFFER_LEN	    PJMEDIA_MAX_MTU
+
+/* Maximum SRTP crypto key length */
+#define MAX_KEY_LEN		    128
+
+/* Initial value of probation counter. When probation counter > 0, 
+ * it means SRTP is in probation state, and it may restart when
+ * srtp_unprotect() returns err_status_replay_*
+ */
+#define PROBATION_CNT_INIT	    100
+
+#define DEACTIVATE_MEDIA(pool, m)   pjmedia_sdp_media_deactivate(pool, m)
+
+static const pj_str_t ID_RTP_AVP  = { "RTP/AVP", 7 };
+static const pj_str_t ID_RTP_SAVP = { "RTP/SAVP", 8 };
+static const pj_str_t ID_INACTIVE = { "inactive", 8 };
+static const pj_str_t ID_CRYPTO   = { "crypto", 6 };
+
+typedef struct crypto_suite
+{
+    char		*name;
+    cipher_type_id_t	 cipher_type;
+    unsigned		 cipher_key_len;
+    auth_type_id_t	 auth_type;
+    unsigned		 auth_key_len;
+    unsigned		 srtp_auth_tag_len;
+    unsigned		 srtcp_auth_tag_len;
+    sec_serv_t		 service;
+} crypto_suite;
+
+/* Crypto suites as defined on RFC 4568 */
+static crypto_suite crypto_suites[] = {
+    /* plain RTP/RTCP (no cipher & no auth) */
+    {"NULL", NULL_CIPHER, 0, NULL_AUTH, 0, 0, 0, sec_serv_none},
+
+    /* cipher AES_CM, auth HMAC_SHA1, auth tag len = 10 octets */
+    {"AES_CM_128_HMAC_SHA1_80", AES_128_ICM, 30, HMAC_SHA1, 20, 10, 10, 
+	sec_serv_conf_and_auth},
+
+    /* cipher AES_CM, auth HMAC_SHA1, auth tag len = 4 octets */
+    {"AES_CM_128_HMAC_SHA1_32", AES_128_ICM, 30, HMAC_SHA1, 20, 4, 10,
+	sec_serv_conf_and_auth},
+
+    /* 
+     * F8_128_HMAC_SHA1_8 not supported by libsrtp?
+     * {"F8_128_HMAC_SHA1_8", NULL_CIPHER, 0, NULL_AUTH, 0, 0, 0, sec_serv_none}
+     */
+};
+
+typedef struct transport_srtp
+{
+    pjmedia_transport	 base;		    /**< Base transport interface.  */
+    pj_pool_t		*pool;		    /**< Pool for transport SRTP.   */
+    pj_lock_t		*mutex;		    /**< Mutex for libsrtp contexts.*/
+    char		 rtp_tx_buffer[MAX_RTP_BUFFER_LEN];
+    char		 rtcp_tx_buffer[MAX_RTCP_BUFFER_LEN];
+    pjmedia_srtp_setting setting;
+    unsigned		 media_option;
+
+    /* SRTP policy */
+    pj_bool_t		 session_inited;
+    pj_bool_t		 offerer_side;
+    pj_bool_t		 bypass_srtp;
+    char		 tx_key[MAX_KEY_LEN];
+    char		 rx_key[MAX_KEY_LEN];
+    pjmedia_srtp_crypto  tx_policy;
+    pjmedia_srtp_crypto  rx_policy;
+
+    /* Temporary policy for negotiation */
+    pjmedia_srtp_crypto  tx_policy_neg;
+    pjmedia_srtp_crypto  rx_policy_neg;
+
+    /* libSRTP contexts */
+    srtp_t		 srtp_tx_ctx;
+    srtp_t		 srtp_rx_ctx;
+
+    /* Stream information */
+    void		*user_data;
+    void		(*rtp_cb)( void *user_data,
+				   void *pkt,
+				   pj_ssize_t size);
+    void		(*rtcp_cb)(void *user_data,
+				   void *pkt,
+				   pj_ssize_t size);
+        
+    /* Transport information */
+    pjmedia_transport	*member_tp; /**< Underlying transport.       */
+
+    /* SRTP usage policy of peer. This field is updated when media is starting.
+     * This is useful when SRTP is in optional mode and peer is using mandatory
+     * mode, so when local is about to reinvite/update, it should offer 
+     * RTP/SAVP instead of offering RTP/AVP.
+     */
+    pjmedia_srtp_use	 peer_use;
+
+    /* When probation counter > 0, it means SRTP is in probation state, 
+     * and it may restart when srtp_unprotect() returns err_status_replay_*
+     */
+    unsigned		 probation_cnt;
+} transport_srtp;
+
+
+/*
+ * This callback is called by transport when incoming rtp is received
+ */
+static void srtp_rtp_cb( void *user_data, void *pkt, pj_ssize_t size);
+
+/*
+ * This callback is called by transport when incoming rtcp is received
+ */
+static void srtp_rtcp_cb( void *user_data, void *pkt, pj_ssize_t size);
+
+
+/*
+ * These are media transport operations.
+ */
+static pj_status_t transport_get_info (pjmedia_transport *tp,
+				       pjmedia_transport_info *info);
+static pj_status_t transport_attach   (pjmedia_transport *tp,
+				       void *user_data,
+				       const pj_sockaddr_t *rem_addr,
+				       const pj_sockaddr_t *rem_rtcp,
+				       unsigned addr_len,
+				       void (*rtp_cb)(void*,
+						      void*,
+						      pj_ssize_t),
+				       void (*rtcp_cb)(void*,
+						       void*,
+						       pj_ssize_t));
+static void	   transport_detach   (pjmedia_transport *tp,
+				       void *strm);
+static pj_status_t transport_send_rtp( pjmedia_transport *tp,
+				       const void *pkt,
+				       pj_size_t size);
+static pj_status_t transport_send_rtcp(pjmedia_transport *tp,
+				       const void *pkt,
+				       pj_size_t size);
+static pj_status_t transport_send_rtcp2(pjmedia_transport *tp,
+				       const pj_sockaddr_t *addr,
+				       unsigned addr_len,
+				       const void *pkt,
+				       pj_size_t size);
+static pj_status_t transport_media_create(pjmedia_transport *tp,
+				       pj_pool_t *sdp_pool,
+				       unsigned options,
+				       const pjmedia_sdp_session *sdp_remote,
+				       unsigned media_index);
+static pj_status_t transport_encode_sdp(pjmedia_transport *tp,
+				       pj_pool_t *sdp_pool,
+				       pjmedia_sdp_session *sdp_local,
+				       const pjmedia_sdp_session *sdp_remote,
+				       unsigned media_index);
+static pj_status_t transport_media_start (pjmedia_transport *tp,
+				       pj_pool_t *pool,
+				       const pjmedia_sdp_session *sdp_local,
+				       const pjmedia_sdp_session *sdp_remote,
+				       unsigned media_index);
+static pj_status_t transport_media_stop(pjmedia_transport *tp);
+static pj_status_t transport_simulate_lost(pjmedia_transport *tp,
+				       pjmedia_dir dir,
+				       unsigned pct_lost);
+static pj_status_t transport_destroy  (pjmedia_transport *tp);
+
+
+
+static pjmedia_transport_op transport_srtp_op = 
+{
+    &transport_get_info,
+    &transport_attach,
+    &transport_detach,
+    &transport_send_rtp,
+    &transport_send_rtcp,
+    &transport_send_rtcp2,
+    &transport_media_create,
+    &transport_encode_sdp,
+    &transport_media_start,
+    &transport_media_stop,
+    &transport_simulate_lost,
+    &transport_destroy
+};
+
+/* This function may also be used by other module, e.g: pjmedia/errno.c,
+ * it should have C compatible declaration.
+ */
+PJ_BEGIN_DECL
+    const char* get_libsrtp_errstr(int err);
+PJ_END_DECL
+
+const char* get_libsrtp_errstr(int err)
+{
+#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0)
+    static char *liberr[] = {
+	"ok",				    /* err_status_ok            = 0  */
+	"unspecified failure",		    /* err_status_fail          = 1  */
+	"unsupported parameter",	    /* err_status_bad_param     = 2  */
+	"couldn't allocate memory",	    /* err_status_alloc_fail    = 3  */
+	"couldn't deallocate properly",	    /* err_status_dealloc_fail  = 4  */
+	"couldn't initialize",		    /* err_status_init_fail     = 5  */
+	"can't process as much data as requested", 
+					    /* err_status_terminus      = 6  */
+	"authentication failure",	    /* err_status_auth_fail     = 7  */
+	"cipher failure",		    /* err_status_cipher_fail   = 8  */
+	"replay check failed (bad index)",  /* err_status_replay_fail   = 9  */
+	"replay check failed (index too old)", 
+					    /* err_status_replay_old    = 10 */
+	"algorithm failed test routine",    /* err_status_algo_fail     = 11 */
+	"unsupported operation",	    /* err_status_no_such_op    = 12 */
+	"no appropriate context found",	    /* err_status_no_ctx        = 13 */
+	"unable to perform desired validation", 
+					    /* err_status_cant_check    = 14 */
+	"can't use key any more",	    /* err_status_key_expired   = 15 */
+	"error in use of socket",	    /* err_status_socket_err    = 16 */
+	"error in use POSIX signals",	    /* err_status_signal_err    = 17 */
+	"nonce check failed",		    /* err_status_nonce_bad     = 18 */
+	"couldn't read data",		    /* err_status_read_fail     = 19 */
+	"couldn't write data",		    /* err_status_write_fail    = 20 */
+	"error pasring data",		    /* err_status_parse_err     = 21 */
+	"error encoding data",		    /* err_status_encode_err    = 22 */
+	"error while using semaphores",	    /* err_status_semaphore_err = 23 */
+	"error while using pfkey"	    /* err_status_pfkey_err     = 24 */
+    };
+    if (err >= 0 && err < (int)PJ_ARRAY_SIZE(liberr)) {
+	return liberr[err];
+    } else {
+	static char msg[32];
+	pj_ansi_snprintf(msg, sizeof(msg), "Unknown libsrtp error %d", err);
+	return msg;
+    }
+#else
+    static char msg[32];
+    pj_ansi_snprintf(msg, sizeof(msg), "libsrtp error %d", err);
+    return msg;
+#endif
+}
+
+static pj_bool_t libsrtp_initialized;
+static void pjmedia_srtp_deinit_lib(pjmedia_endpt *endpt);
+
+PJ_DEF(pj_status_t) pjmedia_srtp_init_lib(pjmedia_endpt *endpt)
+{
+    if (libsrtp_initialized == PJ_FALSE) {
+	err_status_t err;
+
+	err = srtp_init();
+	if (err != err_status_ok) { 
+	    PJ_LOG(4, (THIS_FILE, "Failed to initialize libsrtp: %s", 
+		       get_libsrtp_errstr(err)));
+	    return PJMEDIA_ERRNO_FROM_LIBSRTP(err);
+	}
+
+	if (pjmedia_endpt_atexit(endpt, pjmedia_srtp_deinit_lib) != PJ_SUCCESS)
+	{
+	    /* There will be memory leak when it fails to schedule libsrtp 
+	     * deinitialization, however the memory leak could be harmless,
+	     * since in modern OS's memory used by an application is released 
+	     * when the application terminates.
+	     */
+	    PJ_LOG(4, (THIS_FILE, "Failed to register libsrtp deinit."));
+	}
+
+	libsrtp_initialized = PJ_TRUE;
+    }
+    
+    return PJ_SUCCESS;
+}
+
+static void pjmedia_srtp_deinit_lib(pjmedia_endpt *endpt)
+{
+    err_status_t err;
+
+    /* Note that currently this SRTP init/deinit is not equipped with
+     * reference counter, it should be safe as normally there is only
+     * one single instance of media endpoint and even if it isn't, the
+     * pjmedia_transport_srtp_create() will invoke SRTP init (the only
+     * drawback should be the delay described by #788).
+     */
+
+    PJ_UNUSED_ARG(endpt);
+
+    err = srtp_deinit();
+    if (err != err_status_ok) {
+	PJ_LOG(4, (THIS_FILE, "Failed to deinitialize libsrtp: %s", 
+		   get_libsrtp_errstr(err)));
+    }
+
+    libsrtp_initialized = PJ_FALSE;
+}
+
+
+static int get_crypto_idx(const pj_str_t* crypto_name)
+{
+    int i;
+    int cs_cnt = sizeof(crypto_suites)/sizeof(crypto_suites[0]);
+    
+    /* treat unspecified crypto_name as crypto 'NULL' */
+    if (crypto_name->slen == 0)
+	return 0;
+
+    for (i=0; i<cs_cnt; ++i) {
+	if (!pj_stricmp2(crypto_name, crypto_suites[i].name))
+	    return i;
+    }
+
+    return -1;
+}
+
+
+static int srtp_crypto_cmp(const pjmedia_srtp_crypto* c1,
+			   const pjmedia_srtp_crypto* c2)
+{
+    int r;
+
+    r = pj_strcmp(&c1->key, &c2->key);
+    if (r != 0)
+	return r;
+
+    r = pj_stricmp(&c1->name, &c2->name);
+    if (r != 0)
+	return r;
+
+    return (c1->flags != c2->flags);
+}
+
+
+static pj_bool_t srtp_crypto_empty(const pjmedia_srtp_crypto* c)
+{
+    return (c->name.slen==0 || c->key.slen==0);
+}
+
+
+PJ_DEF(void) pjmedia_srtp_setting_default(pjmedia_srtp_setting *opt)
+{
+    unsigned i;
+
+    pj_assert(opt);
+
+    pj_bzero(opt, sizeof(pjmedia_srtp_setting));
+    opt->close_member_tp = PJ_TRUE;
+    opt->use = PJMEDIA_SRTP_OPTIONAL;
+
+    /* Copy default crypto-suites, but skip crypto 'NULL' */
+    opt->crypto_count = sizeof(crypto_suites)/sizeof(crypto_suites[0]) - 1;
+    for (i=0; i<opt->crypto_count; ++i)
+	opt->crypto[i].name = pj_str(crypto_suites[i+1].name);
+}
+
+
+/*
+ * Create an SRTP media transport.
+ */
+PJ_DEF(pj_status_t) pjmedia_transport_srtp_create(
+				       pjmedia_endpt *endpt,
+				       pjmedia_transport *tp,
+				       const pjmedia_srtp_setting *opt,
+				       pjmedia_transport **p_tp)
+{
+    pj_pool_t *pool;
+    transport_srtp *srtp;
+    pj_status_t status;
+    unsigned i;
+
+    PJ_ASSERT_RETURN(endpt && tp && p_tp, PJ_EINVAL);
+
+    /* Check crypto availability */
+    if (opt && opt->crypto_count == 0 && 
+	opt->use == PJMEDIA_SRTP_MANDATORY)
+	return PJMEDIA_SRTP_ESDPREQCRYPTO;
+
+    /* Check crypto */
+    if (opt && opt->use != PJMEDIA_SRTP_DISABLED) {
+	for (i=0; i < opt->crypto_count; ++i) {
+	    int cs_idx = get_crypto_idx(&opt->crypto[i].name);
+
+	    /* check crypto name */
+	    if (cs_idx == -1)
+		return PJMEDIA_SRTP_ENOTSUPCRYPTO;
+
+	    /* check key length */
+	    if (opt->crypto[i].key.slen && 
+		opt->crypto[i].key.slen < 
+		(pj_ssize_t)crypto_suites[cs_idx].cipher_key_len)
+		return PJMEDIA_SRTP_EINKEYLEN;
+	}
+    }
+
+    /* Init libsrtp. */
+    status = pjmedia_srtp_init_lib(endpt);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    pool = pjmedia_endpt_create_pool(endpt, "srtp%p", 1000, 1000);
+    srtp = PJ_POOL_ZALLOC_T(pool, transport_srtp);
+
+    srtp->pool = pool;
+    srtp->session_inited = PJ_FALSE;
+    srtp->bypass_srtp = PJ_FALSE;
+    srtp->probation_cnt = PROBATION_CNT_INIT;
+
+    if (opt) {
+	srtp->setting = *opt;
+	if (opt->use == PJMEDIA_SRTP_DISABLED)
+	    srtp->setting.crypto_count = 0;
+
+	for (i=0; i < srtp->setting.crypto_count; ++i) {
+	    int cs_idx = get_crypto_idx(&opt->crypto[i].name);
+	    pj_str_t tmp_key = opt->crypto[i].key;
+
+	    /* re-set crypto */
+	    srtp->setting.crypto[i].name = pj_str(crypto_suites[cs_idx].name);
+	    /* cut key length */
+	    if (tmp_key.slen)
+		tmp_key.slen = crypto_suites[cs_idx].cipher_key_len;
+	    pj_strdup(pool, &srtp->setting.crypto[i].key, &tmp_key);
+	}
+    } else {
+	pjmedia_srtp_setting_default(&srtp->setting);
+    }
+
+    status = pj_lock_create_recursive_mutex(pool, pool->obj_name, &srtp->mutex);
+    if (status != PJ_SUCCESS) {
+	pj_pool_release(pool);
+	return status;
+    }
+
+    /* Initialize base pjmedia_transport */
+    pj_memcpy(srtp->base.name, pool->obj_name, PJ_MAX_OBJ_NAME);
+    if (tp)
+	srtp->base.type = tp->type;
+    else
+	srtp->base.type = PJMEDIA_TRANSPORT_TYPE_UDP;
+    srtp->base.op = &transport_srtp_op;
+
+    /* Set underlying transport */
+    srtp->member_tp = tp;
+
+    /* Initialize peer's SRTP usage mode. */
+    srtp->peer_use = srtp->setting.use;
+
+    /* Done */
+    *p_tp = &srtp->base;
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Initialize and start SRTP session with the given parameters.
+ */
+PJ_DEF(pj_status_t) pjmedia_transport_srtp_start(
+			   pjmedia_transport *tp, 
+			   const pjmedia_srtp_crypto *tx,
+			   const pjmedia_srtp_crypto *rx)
+{
+    transport_srtp  *srtp = (transport_srtp*) tp;
+    srtp_policy_t    tx_;
+    srtp_policy_t    rx_;
+    err_status_t     err;
+    int		     cr_tx_idx = 0;
+    int		     au_tx_idx = 0;
+    int		     cr_rx_idx = 0;
+    int		     au_rx_idx = 0;
+    int		     crypto_suites_cnt;
+    pj_status_t	     status = PJ_SUCCESS;
+
+    PJ_ASSERT_RETURN(tp && tx && rx, PJ_EINVAL);
+
+    pj_lock_acquire(srtp->mutex);
+
+    if (srtp->session_inited) {
+	pjmedia_transport_srtp_stop(tp);
+    }
+
+    crypto_suites_cnt = sizeof(crypto_suites)/sizeof(crypto_suites[0]);
+
+    /* Get encryption and authentication method */
+    cr_tx_idx = au_tx_idx = get_crypto_idx(&tx->name);
+    if (tx->flags & PJMEDIA_SRTP_NO_ENCRYPTION)
+	cr_tx_idx = 0;
+    if (tx->flags & PJMEDIA_SRTP_NO_AUTHENTICATION)
+	au_tx_idx = 0;
+
+    cr_rx_idx = au_rx_idx = get_crypto_idx(&rx->name);
+    if (rx->flags & PJMEDIA_SRTP_NO_ENCRYPTION)
+	cr_rx_idx = 0;
+    if (rx->flags & PJMEDIA_SRTP_NO_AUTHENTICATION)
+	au_rx_idx = 0;
+
+    /* Check whether the crypto-suite requested is supported */
+    if (cr_tx_idx == -1 || cr_rx_idx == -1 || au_tx_idx == -1 || 
+	au_rx_idx == -1)
+    {
+	status = PJMEDIA_SRTP_ENOTSUPCRYPTO;
+	goto on_return;
+    }
+
+    /* If all options points to 'NULL' method, just bypass SRTP */
+    if (cr_tx_idx == 0 && cr_rx_idx == 0 && au_tx_idx == 0 && au_rx_idx == 0) {
+	srtp->bypass_srtp = PJ_TRUE;
+	goto on_return;
+    }
+
+    /* Check key length */
+    if (tx->key.slen != (pj_ssize_t)crypto_suites[cr_tx_idx].cipher_key_len ||
+        rx->key.slen != (pj_ssize_t)crypto_suites[cr_rx_idx].cipher_key_len)
+    {
+	status = PJMEDIA_SRTP_EINKEYLEN;
+	goto on_return;
+    }
+
+    /* Init transmit direction */
+    pj_bzero(&tx_, sizeof(srtp_policy_t));
+    pj_memmove(srtp->tx_key, tx->key.ptr, tx->key.slen);
+    if (cr_tx_idx && au_tx_idx)
+	tx_.rtp.sec_serv    = sec_serv_conf_and_auth;
+    else if (cr_tx_idx)
+	tx_.rtp.sec_serv    = sec_serv_conf;
+    else if (au_tx_idx)
+	tx_.rtp.sec_serv    = sec_serv_auth;
+    else
+	tx_.rtp.sec_serv    = sec_serv_none;
+    tx_.key		    = (uint8_t*)srtp->tx_key;
+    tx_.ssrc.type	    = ssrc_any_outbound;
+    tx_.ssrc.value	    = 0;
+    tx_.rtp.cipher_type	    = crypto_suites[cr_tx_idx].cipher_type;
+    tx_.rtp.cipher_key_len  = crypto_suites[cr_tx_idx].cipher_key_len;
+    tx_.rtp.auth_type	    = crypto_suites[au_tx_idx].auth_type;
+    tx_.rtp.auth_key_len    = crypto_suites[au_tx_idx].auth_key_len;
+    tx_.rtp.auth_tag_len    = crypto_suites[au_tx_idx].srtp_auth_tag_len;
+    tx_.rtcp		    = tx_.rtp;
+    tx_.rtcp.auth_tag_len   = crypto_suites[au_tx_idx].srtcp_auth_tag_len;
+    tx_.next		    = NULL;
+    err = srtp_create(&srtp->srtp_tx_ctx, &tx_);
+    if (err != err_status_ok) {
+	status = PJMEDIA_ERRNO_FROM_LIBSRTP(err);
+	goto on_return;
+    }
+    srtp->tx_policy = *tx;
+    pj_strset(&srtp->tx_policy.key,  srtp->tx_key, tx->key.slen);
+    srtp->tx_policy.name=pj_str(crypto_suites[get_crypto_idx(&tx->name)].name);
+
+
+    /* Init receive direction */
+    pj_bzero(&rx_, sizeof(srtp_policy_t));
+    pj_memmove(srtp->rx_key, rx->key.ptr, rx->key.slen);
+    if (cr_rx_idx && au_rx_idx)
+	rx_.rtp.sec_serv    = sec_serv_conf_and_auth;
+    else if (cr_rx_idx)
+	rx_.rtp.sec_serv    = sec_serv_conf;
+    else if (au_rx_idx)
+	rx_.rtp.sec_serv    = sec_serv_auth;
+    else
+	rx_.rtp.sec_serv    = sec_serv_none;
+    rx_.key		    = (uint8_t*)srtp->rx_key;
+    rx_.ssrc.type	    = ssrc_any_inbound;
+    rx_.ssrc.value	    = 0;
+    rx_.rtp.sec_serv	    = crypto_suites[cr_rx_idx].service;
+    rx_.rtp.cipher_type	    = crypto_suites[cr_rx_idx].cipher_type;
+    rx_.rtp.cipher_key_len  = crypto_suites[cr_rx_idx].cipher_key_len;
+    rx_.rtp.auth_type	    = crypto_suites[au_rx_idx].auth_type;
+    rx_.rtp.auth_key_len    = crypto_suites[au_rx_idx].auth_key_len;
+    rx_.rtp.auth_tag_len    = crypto_suites[au_rx_idx].srtp_auth_tag_len;
+    rx_.rtcp		    = rx_.rtp;
+    rx_.rtcp.auth_tag_len   = crypto_suites[au_rx_idx].srtcp_auth_tag_len;
+    rx_.next		    = NULL;
+    err = srtp_create(&srtp->srtp_rx_ctx, &rx_);
+    if (err != err_status_ok) {
+	srtp_dealloc(srtp->srtp_tx_ctx);
+	status = PJMEDIA_ERRNO_FROM_LIBSRTP(err);
+	goto on_return;
+    }
+    srtp->rx_policy = *rx;
+    pj_strset(&srtp->rx_policy.key,  srtp->rx_key, rx->key.slen);
+    srtp->rx_policy.name=pj_str(crypto_suites[get_crypto_idx(&rx->name)].name);
+
+    /* Declare SRTP session initialized */
+    srtp->session_inited = PJ_TRUE;
+
+    /* Logging stuffs */
+#if PJ_LOG_MAX_LEVEL >= 5
+    {
+	char b64[PJ_BASE256_TO_BASE64_LEN(MAX_KEY_LEN)];
+	int b64_len;
+
+	/* TX crypto and key */
+	b64_len = sizeof(b64);
+	status = pj_base64_encode((pj_uint8_t*)tx->key.ptr, tx->key.slen,
+				  b64, &b64_len);
+	if (status != PJ_SUCCESS)
+	    b64_len = pj_ansi_sprintf(b64, "--key too long--");
+	else
+	    b64[b64_len] = '\0';
+        
+	PJ_LOG(5, (srtp->pool->obj_name, "TX: %s key=%s",
+		   srtp->tx_policy.name.ptr, b64));
+	if (srtp->tx_policy.flags) {
+	    PJ_LOG(5,(srtp->pool->obj_name, "TX: disable%s%s",
+		      (cr_tx_idx?"":" enc"),
+		      (au_tx_idx?"":" auth")));
+	}
+
+	/* RX crypto and key */
+	b64_len = sizeof(b64);
+	status = pj_base64_encode((pj_uint8_t*)rx->key.ptr, rx->key.slen,
+				  b64, &b64_len);
+	if (status != PJ_SUCCESS)
+	    b64_len = pj_ansi_sprintf(b64, "--key too long--");
+	else
+	    b64[b64_len] = '\0';
+
+	PJ_LOG(5, (srtp->pool->obj_name, "RX: %s key=%s",
+		   srtp->rx_policy.name.ptr, b64));
+	if (srtp->rx_policy.flags) {
+	    PJ_LOG(5,(srtp->pool->obj_name,"RX: disable%s%s",
+		      (cr_rx_idx?"":" enc"),
+		      (au_rx_idx?"":" auth")));
+	}
+    }
+#endif
+
+on_return:
+    pj_lock_release(srtp->mutex);
+    return status;
+}
+
+/*
+ * Stop SRTP session.
+ */
+PJ_DEF(pj_status_t) pjmedia_transport_srtp_stop(pjmedia_transport *srtp)
+{
+    transport_srtp *p_srtp = (transport_srtp*) srtp;
+    err_status_t err;
+
+    PJ_ASSERT_RETURN(srtp, PJ_EINVAL);
+
+    pj_lock_acquire(p_srtp->mutex);
+
+    if (!p_srtp->session_inited) {
+	pj_lock_release(p_srtp->mutex);
+	return PJ_SUCCESS;
+    }
+
+    err = srtp_dealloc(p_srtp->srtp_rx_ctx);
+    if (err != err_status_ok) {
+	PJ_LOG(4, (p_srtp->pool->obj_name, 
+		   "Failed to dealloc RX SRTP context: %s",
+		   get_libsrtp_errstr(err)));
+    }
+    err = srtp_dealloc(p_srtp->srtp_tx_ctx);
+    if (err != err_status_ok) {
+	PJ_LOG(4, (p_srtp->pool->obj_name, 
+		   "Failed to dealloc TX SRTP context: %s",
+		   get_libsrtp_errstr(err)));
+    }
+
+    p_srtp->session_inited = PJ_FALSE;
+    pj_bzero(&p_srtp->rx_policy, sizeof(p_srtp->rx_policy));
+    pj_bzero(&p_srtp->tx_policy, sizeof(p_srtp->tx_policy));
+
+    pj_lock_release(p_srtp->mutex);
+
+    return PJ_SUCCESS;
+}
+
+PJ_DEF(pjmedia_transport *) pjmedia_transport_srtp_get_member(
+						pjmedia_transport *tp)
+{
+    transport_srtp *srtp = (transport_srtp*) tp;
+
+    PJ_ASSERT_RETURN(tp, NULL);
+
+    return srtp->member_tp;
+}
+
+
+static pj_status_t transport_get_info(pjmedia_transport *tp,
+				      pjmedia_transport_info *info)
+{
+    transport_srtp *srtp = (transport_srtp*) tp;
+    pjmedia_srtp_info srtp_info;
+    int spc_info_idx;
+
+    PJ_ASSERT_RETURN(tp && info, PJ_EINVAL);
+    PJ_ASSERT_RETURN(info->specific_info_cnt <
+		     PJMEDIA_TRANSPORT_SPECIFIC_INFO_MAXCNT, PJ_ETOOMANY);
+    PJ_ASSERT_RETURN(sizeof(pjmedia_srtp_info) <=
+		     PJMEDIA_TRANSPORT_SPECIFIC_INFO_MAXSIZE, PJ_ENOMEM);
+
+    srtp_info.active = srtp->session_inited;
+    srtp_info.rx_policy = srtp->rx_policy;
+    srtp_info.tx_policy = srtp->tx_policy;
+    srtp_info.use = srtp->setting.use;
+    srtp_info.peer_use = srtp->peer_use;
+
+    spc_info_idx = info->specific_info_cnt++;
+    info->spc_info[spc_info_idx].type = PJMEDIA_TRANSPORT_TYPE_SRTP;
+    info->spc_info[spc_info_idx].cbsize = sizeof(srtp_info);
+    pj_memcpy(&info->spc_info[spc_info_idx].buffer, &srtp_info, 
+	      sizeof(srtp_info));
+
+    return pjmedia_transport_get_info(srtp->member_tp, info);
+}
+
+static pj_status_t transport_attach(pjmedia_transport *tp,
+				    void *user_data,
+				    const pj_sockaddr_t *rem_addr,
+				    const pj_sockaddr_t *rem_rtcp,
+				    unsigned addr_len,
+				    void (*rtp_cb) (void*, void*,
+						    pj_ssize_t),
+				    void (*rtcp_cb)(void*, void*,
+						    pj_ssize_t))
+{
+    transport_srtp *srtp = (transport_srtp*) tp;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(tp && rem_addr && addr_len, PJ_EINVAL);
+
+    /* Save the callbacks */
+    pj_lock_acquire(srtp->mutex);
+    srtp->rtp_cb = rtp_cb;
+    srtp->rtcp_cb = rtcp_cb;
+    srtp->user_data = user_data;
+    pj_lock_release(srtp->mutex);
+
+    /* Attach itself to transport */
+    status = pjmedia_transport_attach(srtp->member_tp, srtp, rem_addr, 
+				      rem_rtcp, addr_len, &srtp_rtp_cb,
+				      &srtp_rtcp_cb);
+    if (status != PJ_SUCCESS) {
+	pj_lock_acquire(srtp->mutex);
+	srtp->rtp_cb = NULL;
+	srtp->rtcp_cb = NULL;
+	srtp->user_data = NULL;
+	pj_lock_release(srtp->mutex);
+	return status;
+    }
+
+    return PJ_SUCCESS;
+}
+
+static void transport_detach(pjmedia_transport *tp, void *strm)
+{
+    transport_srtp *srtp = (transport_srtp*) tp;
+
+    PJ_UNUSED_ARG(strm);
+    PJ_ASSERT_ON_FAIL(tp, return);
+
+    if (srtp->member_tp) {
+	pjmedia_transport_detach(srtp->member_tp, srtp);
+    }
+
+    /* Clear up application infos from transport */
+    pj_lock_acquire(srtp->mutex);
+    srtp->rtp_cb = NULL;
+    srtp->rtcp_cb = NULL;
+    srtp->user_data = NULL;
+    pj_lock_release(srtp->mutex);
+}
+
+static pj_status_t transport_send_rtp( pjmedia_transport *tp,
+				       const void *pkt,
+				       pj_size_t size)
+{
+    pj_status_t status;
+    transport_srtp *srtp = (transport_srtp*) tp;
+    int len = (int)size;
+    err_status_t err;
+
+    if (srtp->bypass_srtp)
+	return pjmedia_transport_send_rtp(srtp->member_tp, pkt, size);
+
+    if (size > sizeof(srtp->rtp_tx_buffer) - 10)
+	return PJ_ETOOBIG;
+
+    pj_memcpy(srtp->rtp_tx_buffer, pkt, size);
+
+    pj_lock_acquire(srtp->mutex);
+    if (!srtp->session_inited) {
+	pj_lock_release(srtp->mutex);
+	return PJ_EINVALIDOP;
+    }
+    err = srtp_protect(srtp->srtp_tx_ctx, srtp->rtp_tx_buffer, &len);
+    pj_lock_release(srtp->mutex);
+
+    if (err == err_status_ok) {
+	status = pjmedia_transport_send_rtp(srtp->member_tp, 
+					    srtp->rtp_tx_buffer, len);
+    } else {
+	status = PJMEDIA_ERRNO_FROM_LIBSRTP(err);
+    }
+    
+    return status;
+}
+
+static pj_status_t transport_send_rtcp(pjmedia_transport *tp,
+				       const void *pkt,
+				       pj_size_t size)
+{
+    return transport_send_rtcp2(tp, NULL, 0, pkt, size);
+}
+
+static pj_status_t transport_send_rtcp2(pjmedia_transport *tp,
+				        const pj_sockaddr_t *addr,
+				        unsigned addr_len,
+				        const void *pkt,
+				        pj_size_t size)
+{
+    pj_status_t status;
+    transport_srtp *srtp = (transport_srtp*) tp;
+    int len = (int)size;
+    err_status_t err;
+
+    if (srtp->bypass_srtp) {
+	return pjmedia_transport_send_rtcp2(srtp->member_tp, addr, addr_len, 
+	                                    pkt, size);
+    }
+
+    if (size > sizeof(srtp->rtcp_tx_buffer) - 10)
+	return PJ_ETOOBIG;
+
+    pj_memcpy(srtp->rtcp_tx_buffer, pkt, size);
+
+    pj_lock_acquire(srtp->mutex);
+    if (!srtp->session_inited) {
+	pj_lock_release(srtp->mutex);
+	return PJ_EINVALIDOP;
+    }
+    err = srtp_protect_rtcp(srtp->srtp_tx_ctx, srtp->rtcp_tx_buffer, &len);
+    pj_lock_release(srtp->mutex);
+
+    if (err == err_status_ok) {
+	status = pjmedia_transport_send_rtcp2(srtp->member_tp, addr, addr_len,
+					      srtp->rtcp_tx_buffer, len);
+    } else {
+	status = PJMEDIA_ERRNO_FROM_LIBSRTP(err);
+    }
+
+    return status;
+}
+
+
+static pj_status_t transport_simulate_lost(pjmedia_transport *tp,
+					   pjmedia_dir dir,
+					   unsigned pct_lost)
+{
+    transport_srtp *srtp = (transport_srtp *) tp;
+    
+    PJ_ASSERT_RETURN(tp, PJ_EINVAL);
+
+    return pjmedia_transport_simulate_lost(srtp->member_tp, dir, pct_lost);
+}
+
+static pj_status_t transport_destroy  (pjmedia_transport *tp)
+{
+    transport_srtp *srtp = (transport_srtp *) tp;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(tp, PJ_EINVAL);
+
+    if (srtp->setting.close_member_tp && srtp->member_tp) {
+	pjmedia_transport_close(srtp->member_tp);
+    }
+
+    status = pjmedia_transport_srtp_stop(tp);
+
+    /* In case mutex is being acquired by other thread */
+    pj_lock_acquire(srtp->mutex);
+    pj_lock_release(srtp->mutex);
+
+    pj_lock_destroy(srtp->mutex);
+    pj_pool_release(srtp->pool);
+
+    return status;
+}
+
+/*
+ * This callback is called by transport when incoming rtp is received
+ */
+static void srtp_rtp_cb( void *user_data, void *pkt, pj_ssize_t size)
+{
+    transport_srtp *srtp = (transport_srtp *) user_data;
+    int len = size;
+    err_status_t err;
+    void (*cb)(void*, void*, pj_ssize_t) = NULL;
+    void *cb_data = NULL;
+
+    if (srtp->bypass_srtp) {
+	srtp->rtp_cb(srtp->user_data, pkt, size);
+	return;
+    }
+
+    if (size < 0) {
+	return;
+    }
+
+    /* Make sure buffer is 32bit aligned */
+    PJ_ASSERT_ON_FAIL( (((pj_ssize_t)pkt) & 0x03)==0, return );
+
+    if (srtp->probation_cnt > 0)
+	--srtp->probation_cnt;
+
+    pj_lock_acquire(srtp->mutex);
+
+    if (!srtp->session_inited) {
+	pj_lock_release(srtp->mutex);
+	return;
+    }
+    err = srtp_unprotect(srtp->srtp_rx_ctx, (pj_uint8_t*)pkt, &len);
+    if (srtp->probation_cnt > 0 && 
+	(err == err_status_replay_old || err == err_status_replay_fail)) 
+    {
+	/* Handle such condition that stream is updated (RTP seq is reinited
+	 * & SRTP is restarted), but some old packets are still coming 
+	 * so SRTP is learning wrong RTP seq. While the newly inited RTP seq
+	 * comes, SRTP thinks the RTP seq is replayed, so srtp_unprotect() 
+	 * will return err_status_replay_*. Restarting SRTP can resolve this.
+	 */
+	pjmedia_srtp_crypto tx, rx;
+	pj_status_t status;
+
+	tx = srtp->tx_policy;
+	rx = srtp->rx_policy;
+	status = pjmedia_transport_srtp_start((pjmedia_transport*)srtp,
+					      &tx, &rx);
+	if (status != PJ_SUCCESS) {
+	    PJ_LOG(5,(srtp->pool->obj_name, "Failed to restart SRTP, err=%s", 
+		      get_libsrtp_errstr(err)));
+	} else if (!srtp->bypass_srtp) {
+	    err = srtp_unprotect(srtp->srtp_rx_ctx, (pj_uint8_t*)pkt, &len);
+	}
+    }
+
+    if (err != err_status_ok) {
+	PJ_LOG(5,(srtp->pool->obj_name, 
+		  "Failed to unprotect SRTP, pkt size=%d, err=%s", 
+		  size, get_libsrtp_errstr(err)));
+    } else {
+	cb = srtp->rtp_cb;
+	cb_data = srtp->user_data;
+    }
+
+    pj_lock_release(srtp->mutex);
+
+    if (cb) {
+	(*cb)(cb_data, pkt, len);
+    }
+}
+
+/*
+ * This callback is called by transport when incoming rtcp is received
+ */
+static void srtp_rtcp_cb( void *user_data, void *pkt, pj_ssize_t size)
+{
+    transport_srtp *srtp = (transport_srtp *) user_data;
+    int len = size;
+    err_status_t err;
+    void (*cb)(void*, void*, pj_ssize_t) = NULL;
+    void *cb_data = NULL;
+
+    if (srtp->bypass_srtp) {
+	srtp->rtcp_cb(srtp->user_data, pkt, size);
+	return;
+    }
+
+    if (size < 0) {
+	return;
+    }
+
+    /* Make sure buffer is 32bit aligned */
+    PJ_ASSERT_ON_FAIL( (((pj_ssize_t)pkt) & 0x03)==0, return );
+
+    pj_lock_acquire(srtp->mutex);
+
+    if (!srtp->session_inited) {
+	pj_lock_release(srtp->mutex);
+	return;
+    }
+    err = srtp_unprotect_rtcp(srtp->srtp_rx_ctx, (pj_uint8_t*)pkt, &len);
+    if (err != err_status_ok) {
+	PJ_LOG(5,(srtp->pool->obj_name, 
+		  "Failed to unprotect SRTCP, pkt size=%d, err=%s",
+		  size, get_libsrtp_errstr(err)));
+    } else {
+	cb = srtp->rtcp_cb;
+	cb_data = srtp->user_data;
+    }
+
+    pj_lock_release(srtp->mutex);
+
+    if (cb) {
+	(*cb)(cb_data, pkt, len);
+    }
+}
+
+/* Generate crypto attribute, including crypto key.
+ * If crypto-suite chosen is crypto NULL, just return PJ_SUCCESS,
+ * and set buffer_len = 0.
+ */
+static pj_status_t generate_crypto_attr_value(pj_pool_t *pool,
+					      char *buffer, int *buffer_len, 
+					      pjmedia_srtp_crypto *crypto,
+					      int tag)
+{
+    pj_status_t status;
+    int cs_idx = get_crypto_idx(&crypto->name);
+    char b64_key[PJ_BASE256_TO_BASE64_LEN(MAX_KEY_LEN)+1];
+    int b64_key_len = sizeof(b64_key);
+
+    if (cs_idx == -1)
+	return PJMEDIA_SRTP_ENOTSUPCRYPTO;
+
+    /* Crypto-suite NULL. */
+    if (cs_idx == 0) {
+	*buffer_len = 0;
+	return PJ_SUCCESS;
+    }
+
+    /* Generate key if not specified. */
+    if (crypto->key.slen == 0) {
+	pj_bool_t key_ok;
+	char key[MAX_KEY_LEN];
+	err_status_t err;
+	unsigned i;
+
+	PJ_ASSERT_RETURN(MAX_KEY_LEN >= crypto_suites[cs_idx].cipher_key_len,
+			 PJ_ETOOSMALL);
+
+	do {
+	    key_ok = PJ_TRUE;
+
+	    err = crypto_get_random((unsigned char*)key, 
+				     crypto_suites[cs_idx].cipher_key_len);
+	    if (err != err_status_ok) {
+		PJ_LOG(5,(THIS_FILE, "Failed generating random key: %s",
+			  get_libsrtp_errstr(err)));
+		return PJMEDIA_ERRNO_FROM_LIBSRTP(err);
+	    }
+	    for (i=0; i<crypto_suites[cs_idx].cipher_key_len && key_ok; ++i)
+		if (key[i] == 0) key_ok = PJ_FALSE;
+
+	} while (!key_ok);
+	crypto->key.ptr = (char*)
+			  pj_pool_zalloc(pool, 
+					 crypto_suites[cs_idx].cipher_key_len);
+	pj_memcpy(crypto->key.ptr, key, crypto_suites[cs_idx].cipher_key_len);
+	crypto->key.slen = crypto_suites[cs_idx].cipher_key_len;
+    }
+
+    if (crypto->key.slen != (pj_ssize_t)crypto_suites[cs_idx].cipher_key_len)
+	return PJMEDIA_SRTP_EINKEYLEN;
+
+    /* Key transmitted via SDP should be base64 encoded. */
+    status = pj_base64_encode((pj_uint8_t*)crypto->key.ptr, crypto->key.slen,
+			      b64_key, &b64_key_len);
+    if (status != PJ_SUCCESS) {
+	PJ_LOG(5,(THIS_FILE, "Failed encoding plain key to base64"));
+	return status;
+    }
+
+    b64_key[b64_key_len] = '\0';
+    
+    PJ_ASSERT_RETURN(*buffer_len >= (crypto->name.slen + \
+		     b64_key_len + 16), PJ_ETOOSMALL);
+
+    /* Print the crypto attribute value. */
+    *buffer_len = pj_ansi_snprintf(buffer, *buffer_len, "%d %s inline:%s",
+				   tag, 
+				   crypto_suites[cs_idx].name,
+				   b64_key);
+
+    return PJ_SUCCESS;
+}
+
+/* Parse crypto attribute line */
+static pj_status_t parse_attr_crypto(pj_pool_t *pool,
+				     const pjmedia_sdp_attr *attr,
+				     pjmedia_srtp_crypto *crypto,
+				     int *tag)
+{
+    pj_str_t input;
+    char *token;
+    pj_size_t token_len;
+    pj_str_t tmp;
+    pj_status_t status;
+    int itmp;
+
+    pj_bzero(crypto, sizeof(*crypto));
+    pj_strdup_with_null(pool, &input, &attr->value);
+
+    /* Tag */
+    token = strtok(input.ptr, " ");
+    if (!token) {
+	PJ_LOG(4,(THIS_FILE, "Attribute crypto expecting tag"));
+	return PJMEDIA_SDP_EINATTR;
+    }
+    token_len = pj_ansi_strlen(token);
+
+    /* Tag must not use leading zeroes. */
+    if (token_len > 1 && *token == '0')
+	return PJMEDIA_SDP_EINATTR;
+
+    /* Tag must be decimal, i.e: contains only digit '0'-'9'. */
+    for (itmp = 0; itmp < token_len; ++itmp)
+	if (!pj_isdigit(token[itmp]))
+	    return PJMEDIA_SDP_EINATTR;
+
+    /* Get tag value. */
+    *tag = atoi(token);
+
+    /* Crypto-suite */
+    token = strtok(NULL, " ");
+    if (!token) {
+	PJ_LOG(4,(THIS_FILE, "Attribute crypto expecting crypto suite"));
+	return PJMEDIA_SDP_EINATTR;
+    }
+    crypto->name = pj_str(token);
+
+    /* Key method */
+    token = strtok(NULL, ":");
+    if (!token) {
+	PJ_LOG(4,(THIS_FILE, "Attribute crypto expecting key method"));
+	return PJMEDIA_SDP_EINATTR;
+    }
+    if (pj_ansi_stricmp(token, "inline")) {
+	PJ_LOG(4,(THIS_FILE, "Attribute crypto key method '%s' not supported!",
+	          token));
+	return PJMEDIA_SDP_EINATTR;
+    }
+
+    /* Key */
+    token = strtok(NULL, "| ");
+    if (!token) {
+	PJ_LOG(4,(THIS_FILE, "Attribute crypto expecting key"));
+	return PJMEDIA_SDP_EINATTR;
+    }
+    tmp = pj_str(token);
+    if (PJ_BASE64_TO_BASE256_LEN(tmp.slen) > MAX_KEY_LEN) {
+	PJ_LOG(4,(THIS_FILE, "Key too long"));
+	return PJMEDIA_SRTP_EINKEYLEN;
+    }
+
+    /* Decode key */
+    crypto->key.ptr = (char*) pj_pool_zalloc(pool, MAX_KEY_LEN);
+    itmp = MAX_KEY_LEN;
+    status = pj_base64_decode(&tmp, (pj_uint8_t*)crypto->key.ptr, 
+			      &itmp);
+    if (status != PJ_SUCCESS) {
+	PJ_LOG(4,(THIS_FILE, "Failed decoding crypto key from base64"));
+	return status;
+    }
+    crypto->key.slen = itmp;
+
+    return PJ_SUCCESS;
+}
+
+static pj_status_t transport_media_create(pjmedia_transport *tp,
+				          pj_pool_t *sdp_pool,
+					  unsigned options,
+				          const pjmedia_sdp_session *sdp_remote,
+					  unsigned media_index)
+{
+    struct transport_srtp *srtp = (struct transport_srtp*) tp;
+    unsigned member_tp_option;
+
+    PJ_ASSERT_RETURN(tp, PJ_EINVAL);
+    
+    pj_bzero(&srtp->rx_policy_neg, sizeof(srtp->rx_policy_neg));
+    pj_bzero(&srtp->tx_policy_neg, sizeof(srtp->tx_policy_neg));
+
+    srtp->media_option = options;
+    member_tp_option = options | PJMEDIA_TPMED_NO_TRANSPORT_CHECKING;
+
+    srtp->offerer_side = sdp_remote == NULL;
+
+    /* Validations */
+    if (srtp->offerer_side) {
+
+	if (srtp->setting.use == PJMEDIA_SRTP_DISABLED)
+	    goto BYPASS_SRTP;
+
+    } else {
+
+	pjmedia_sdp_media *m_rem;
+
+	m_rem = sdp_remote->media[media_index];
+
+	/* Nothing to do on inactive media stream */
+	if (pjmedia_sdp_media_find_attr(m_rem, &ID_INACTIVE, NULL))
+	    goto BYPASS_SRTP;
+
+	/* Validate remote media transport based on SRTP usage option.
+	 */
+	switch (srtp->setting.use) {
+	    case PJMEDIA_SRTP_DISABLED:
+		if (pj_stricmp(&m_rem->desc.transport, &ID_RTP_SAVP) == 0)
+		    return PJMEDIA_SRTP_ESDPINTRANSPORT;
+		goto BYPASS_SRTP;
+	    case PJMEDIA_SRTP_OPTIONAL:
+		break;
+	    case PJMEDIA_SRTP_MANDATORY:
+		if (pj_stricmp(&m_rem->desc.transport, &ID_RTP_SAVP) != 0)
+		    return PJMEDIA_SRTP_ESDPINTRANSPORT;
+		break;
+	}
+
+    }
+    goto PROPAGATE_MEDIA_CREATE;
+
+BYPASS_SRTP:
+    srtp->bypass_srtp = PJ_TRUE;
+    member_tp_option &= ~PJMEDIA_TPMED_NO_TRANSPORT_CHECKING;
+
+PROPAGATE_MEDIA_CREATE:
+    return pjmedia_transport_media_create(srtp->member_tp, sdp_pool, 
+					  member_tp_option, sdp_remote,
+					  media_index);
+}
+
+static pj_status_t transport_encode_sdp(pjmedia_transport *tp,
+					pj_pool_t *sdp_pool,
+					pjmedia_sdp_session *sdp_local,
+					const pjmedia_sdp_session *sdp_remote,
+					unsigned media_index)
+{
+    struct transport_srtp *srtp = (struct transport_srtp*) tp;
+    pjmedia_sdp_media *m_rem, *m_loc;
+    enum { MAXLEN = 512 };
+    char buffer[MAXLEN];
+    int buffer_len;
+    pj_status_t status;
+    pjmedia_sdp_attr *attr;
+    pj_str_t attr_value;
+    unsigned i, j;
+
+    PJ_ASSERT_RETURN(tp && sdp_pool && sdp_local, PJ_EINVAL);
+    
+    pj_bzero(&srtp->rx_policy_neg, sizeof(srtp->rx_policy_neg));
+    pj_bzero(&srtp->tx_policy_neg, sizeof(srtp->tx_policy_neg));
+
+    srtp->offerer_side = sdp_remote == NULL;
+
+    m_rem = sdp_remote ? sdp_remote->media[media_index] : NULL;
+    m_loc = sdp_local->media[media_index];
+
+    /* Bypass if media transport is not RTP/AVP or RTP/SAVP */
+    if (pj_stricmp(&m_loc->desc.transport, &ID_RTP_AVP)  != 0 && 
+	pj_stricmp(&m_loc->desc.transport, &ID_RTP_SAVP) != 0)
+	goto BYPASS_SRTP;
+
+    /* If the media is inactive, do nothing. */
+    /* No, we still need to process SRTP offer/answer even if the media is
+     * marked as inactive, because the transport is still alive in this
+     * case (e.g. for keep-alive). See:
+     *   http://trac.pjsip.org/repos/ticket/1079
+     */
+    /*
+    if (pjmedia_sdp_media_find_attr(m_loc, &ID_INACTIVE, NULL) || 
+	(m_rem && pjmedia_sdp_media_find_attr(m_rem, &ID_INACTIVE, NULL)))
+	goto BYPASS_SRTP;
+    */
+
+    /* Check remote media transport & set local media transport 
+     * based on SRTP usage option.
+     */
+    if (srtp->offerer_side) {
+
+	/* Generate transport */
+	switch (srtp->setting.use) {
+	    case PJMEDIA_SRTP_DISABLED:
+		goto BYPASS_SRTP;
+	    case PJMEDIA_SRTP_OPTIONAL:
+		m_loc->desc.transport = 
+				(srtp->peer_use == PJMEDIA_SRTP_MANDATORY)?
+				ID_RTP_SAVP : ID_RTP_AVP;
+		break;
+	    case PJMEDIA_SRTP_MANDATORY:
+		m_loc->desc.transport = ID_RTP_SAVP;
+		break;
+	}
+
+	/* Generate crypto attribute if not yet */
+	if (pjmedia_sdp_media_find_attr(m_loc, &ID_CRYPTO, NULL) == NULL) {
+	    for (i=0; i<srtp->setting.crypto_count; ++i) {
+		/* Offer crypto-suites based on setting. */
+		buffer_len = MAXLEN;
+		status = generate_crypto_attr_value(srtp->pool, buffer, &buffer_len,
+						    &srtp->setting.crypto[i],
+						    i+1);
+		if (status != PJ_SUCCESS)
+		    return status;
+
+		/* If buffer_len==0, just skip the crypto attribute. */
+		if (buffer_len) {
+		    pj_strset(&attr_value, buffer, buffer_len);
+		    attr = pjmedia_sdp_attr_create(srtp->pool, ID_CRYPTO.ptr, 
+						   &attr_value);
+		    m_loc->attr[m_loc->attr_count++] = attr;
+		}
+	    }
+	}
+
+    } else {
+	/* Answerer side */
+
+	pj_assert(sdp_remote && m_rem);
+
+	/* Generate transport */
+	switch (srtp->setting.use) {
+	    case PJMEDIA_SRTP_DISABLED:
+		if (pj_stricmp(&m_rem->desc.transport, &ID_RTP_SAVP) == 0)
+		    return PJMEDIA_SRTP_ESDPINTRANSPORT;
+		goto BYPASS_SRTP;
+	    case PJMEDIA_SRTP_OPTIONAL:
+		m_loc->desc.transport = m_rem->desc.transport;
+		break;
+	    case PJMEDIA_SRTP_MANDATORY:
+		if (pj_stricmp(&m_rem->desc.transport, &ID_RTP_SAVP) != 0)
+		    return PJMEDIA_SRTP_ESDPINTRANSPORT;
+		m_loc->desc.transport = ID_RTP_SAVP;
+		break;
+	}
+
+	/* Generate crypto attribute if not yet */
+	if (pjmedia_sdp_media_find_attr(m_loc, &ID_CRYPTO, NULL) == NULL) {
+
+	    pjmedia_srtp_crypto tmp_rx_crypto;
+	    pj_bool_t has_crypto_attr = PJ_FALSE;
+	    int matched_idx = -1;
+	    int chosen_tag = 0;
+	    int tags[64]; /* assume no more than 64 crypto attrs in a media */
+	    unsigned cr_attr_count = 0;
+
+	    /* Find supported crypto-suite, get the tag, and assign policy_local */
+	    for (i=0; i<m_rem->attr_count; ++i) {
+		if (pj_stricmp(&m_rem->attr[i]->name, &ID_CRYPTO) != 0)
+		    continue;
+
+		has_crypto_attr = PJ_TRUE;
+
+		status = parse_attr_crypto(srtp->pool, m_rem->attr[i], 
+					   &tmp_rx_crypto, &tags[cr_attr_count]);
+		if (status != PJ_SUCCESS)
+		    return status;
+    	 
+		/* Check duplicated tag */
+		for (j=0; j<cr_attr_count; ++j) {
+		    if (tags[j] == tags[cr_attr_count]) {
+			DEACTIVATE_MEDIA(sdp_pool, m_loc);
+			return PJMEDIA_SRTP_ESDPDUPCRYPTOTAG;
+		    }
+		}
+
+		if (matched_idx == -1) {
+		    /* lets see if the crypto-suite offered is supported */
+		    for (j=0; j<srtp->setting.crypto_count; ++j)
+			if (pj_stricmp(&tmp_rx_crypto.name, 
+				       &srtp->setting.crypto[j].name) == 0)
+			{
+			    int cs_idx = get_crypto_idx(&tmp_rx_crypto.name);
+
+			    /* Force to use test key */
+			    /* bad keys for snom: */
+			    //char *hex_test_key = "58b29c5c8f42308120ce857e439f2d"
+			    //		     "7810a8b10ad0b1446be5470faea496";
+			    //char *hex_test_key = "20a26aac7ba062d356ff52b61e3993"
+			    //		     "ccb78078f12c64db94b9c294927fd0";
+			    //pj_str_t *test_key = &srtp->setting.crypto[j].key;
+			    //char  *raw_test_key = pj_pool_zalloc(srtp->pool, 64);
+			    //hex_string_to_octet_string(
+			    //		raw_test_key,
+			    //		hex_test_key,
+			    //		strlen(hex_test_key));
+			    //pj_strset(test_key, raw_test_key, 
+			    //	  crypto_suites[cs_idx].cipher_key_len);
+			    /* EO Force to use test key */
+
+			    if (tmp_rx_crypto.key.slen != 
+				(int)crypto_suites[cs_idx].cipher_key_len)
+				return PJMEDIA_SRTP_EINKEYLEN;
+
+			    srtp->rx_policy_neg = tmp_rx_crypto;
+			    chosen_tag = tags[cr_attr_count];
+			    matched_idx = j;
+    			    break;
+			}
+		}
+		cr_attr_count++;
+	    }
+
+	    /* Check crypto negotiation result */
+	    switch (srtp->setting.use) {
+		case PJMEDIA_SRTP_DISABLED:
+		    pj_assert(!"Should never reach here");
+		    break;
+
+		case PJMEDIA_SRTP_OPTIONAL:
+		    /* bypass SRTP when no crypto-attr and remote uses RTP/AVP */
+		    if (!has_crypto_attr && 
+			pj_stricmp(&m_rem->desc.transport, &ID_RTP_AVP) == 0)
+			goto BYPASS_SRTP;
+		    /* bypass SRTP when nothing match and remote uses RTP/AVP */
+		    else if (matched_idx == -1 && 
+			pj_stricmp(&m_rem->desc.transport, &ID_RTP_AVP) == 0)
+			goto BYPASS_SRTP;
+		    break;
+
+		case PJMEDIA_SRTP_MANDATORY:
+		    /* Do nothing, intentional */
+		    break;
+	    }
+
+	    /* No crypto attr */
+	    if (!has_crypto_attr) {
+		DEACTIVATE_MEDIA(sdp_pool, m_loc);
+		return PJMEDIA_SRTP_ESDPREQCRYPTO;
+	    }
+
+	    /* No crypto match */
+	    if (matched_idx == -1) {
+		DEACTIVATE_MEDIA(sdp_pool, m_loc);
+		return PJMEDIA_SRTP_ENOTSUPCRYPTO;
+	    }
+
+	    /* we have to generate crypto answer, 
+	     * with srtp->tx_policy_neg matched the offer
+	     * and rem_tag contains matched offer tag.
+	     */
+	    buffer_len = MAXLEN;
+	    status = generate_crypto_attr_value(srtp->pool, buffer, &buffer_len,
+						&srtp->setting.crypto[matched_idx],
+						chosen_tag);
+	    if (status != PJ_SUCCESS)
+		return status;
+
+	    srtp->tx_policy_neg = srtp->setting.crypto[matched_idx];
+	    
+	    /* If buffer_len==0, just skip the crypto attribute. */
+	    if (buffer_len) {
+		pj_strset(&attr_value, buffer, buffer_len);
+		attr = pjmedia_sdp_attr_create(sdp_pool, ID_CRYPTO.ptr, 
+					       &attr_value);
+		m_loc->attr[m_loc->attr_count++] = attr;
+	    }
+
+	    /* At this point, we get valid rx_policy_neg & tx_policy_neg. */
+	}
+	    
+    }
+    goto PROPAGATE_MEDIA_CREATE;
+
+BYPASS_SRTP:
+    /* Do not update this flag here as actually the media session hasn't been
+     * updated.
+     */
+    //srtp->bypass_srtp = PJ_TRUE;
+
+PROPAGATE_MEDIA_CREATE:
+    return pjmedia_transport_encode_sdp(srtp->member_tp, sdp_pool, 
+					sdp_local, sdp_remote, media_index);
+}
+
+
+
+static pj_status_t transport_media_start(pjmedia_transport *tp,
+				         pj_pool_t *pool,
+				         const pjmedia_sdp_session *sdp_local,
+				         const pjmedia_sdp_session *sdp_remote,
+				         unsigned media_index)
+{
+    struct transport_srtp *srtp = (struct transport_srtp*) tp;
+    pjmedia_sdp_media *m_rem, *m_loc;
+    pj_status_t status;
+    unsigned i;
+
+    PJ_ASSERT_RETURN(tp && pool && sdp_local && sdp_remote, PJ_EINVAL);
+
+    m_rem = sdp_remote->media[media_index];
+    m_loc = sdp_local->media[media_index];
+
+    if (pj_stricmp(&m_rem->desc.transport, &ID_RTP_SAVP) == 0)
+	srtp->peer_use = PJMEDIA_SRTP_MANDATORY;
+    else
+	srtp->peer_use = PJMEDIA_SRTP_OPTIONAL;
+
+    /* For answerer side, this function will just have to start SRTP */
+
+    /* Check remote media transport & set local media transport 
+     * based on SRTP usage option.
+     */
+    if (srtp->offerer_side) {
+	if (srtp->setting.use == PJMEDIA_SRTP_DISABLED) {
+	    if (pjmedia_sdp_media_find_attr(m_rem, &ID_CRYPTO, NULL)) {
+		DEACTIVATE_MEDIA(pool, m_loc);
+		return PJMEDIA_SRTP_ESDPINCRYPTO;
+	    }
+	    goto BYPASS_SRTP;
+	} else if (srtp->setting.use == PJMEDIA_SRTP_OPTIONAL) {
+	    // Regardless the answer's transport type (RTP/AVP or RTP/SAVP),
+	    // the answer must be processed through in optional mode.
+	    // Please note that at this point transport type is ensured to be 
+	    // RTP/AVP or RTP/SAVP, see transport_media_create()
+	    //if (pj_stricmp(&m_rem->desc.transport, &m_loc->desc.transport)) {
+		//DEACTIVATE_MEDIA(pool, m_loc);
+		//return PJMEDIA_SDP_EINPROTO;
+	    //}
+	} else if (srtp->setting.use == PJMEDIA_SRTP_MANDATORY) {
+	    if (pj_stricmp(&m_rem->desc.transport, &ID_RTP_SAVP)) {
+		DEACTIVATE_MEDIA(pool, m_loc);
+		return PJMEDIA_SDP_EINPROTO;
+	    }
+	}
+    }
+    
+    if (srtp->offerer_side) {
+	/* find supported crypto-suite, get the tag, and assign policy_local */
+	pjmedia_srtp_crypto tmp_tx_crypto;
+	pj_bool_t has_crypto_attr = PJ_FALSE;
+	int rem_tag;
+
+	for (i=0; i<m_rem->attr_count; ++i) {
+	    if (pj_stricmp(&m_rem->attr[i]->name, &ID_CRYPTO) != 0)
+		continue;
+
+	    /* more than one crypto attribute in media answer */
+	    if (has_crypto_attr) {
+		DEACTIVATE_MEDIA(pool, m_loc);
+		return PJMEDIA_SRTP_ESDPAMBIGUEANS;
+	    }
+
+	    has_crypto_attr = PJ_TRUE;
+
+	    status = parse_attr_crypto(srtp->pool, m_rem->attr[i], 
+				       &tmp_tx_crypto, &rem_tag);
+	    if (status != PJ_SUCCESS)
+		return status;
+
+
+	    /* our offer tag is always ordered by setting */
+	    if (rem_tag < 1 || rem_tag > (int)srtp->setting.crypto_count) {
+		DEACTIVATE_MEDIA(pool, m_loc);
+		return PJMEDIA_SRTP_ESDPINCRYPTOTAG;
+	    }
+
+	    /* match the crypto name */
+	    if (pj_stricmp(&tmp_tx_crypto.name, 
+		&srtp->setting.crypto[rem_tag-1].name) != 0)
+	    {
+		DEACTIVATE_MEDIA(pool, m_loc);
+		return PJMEDIA_SRTP_ECRYPTONOTMATCH;
+	    }
+
+	    srtp->tx_policy_neg = srtp->setting.crypto[rem_tag-1];
+	    srtp->rx_policy_neg = tmp_tx_crypto;
+	}
+
+	if (srtp->setting.use == PJMEDIA_SRTP_DISABLED) {
+	    /* should never reach here */
+	    goto BYPASS_SRTP;
+	} else if (srtp->setting.use == PJMEDIA_SRTP_OPTIONAL) {
+	    if (!has_crypto_attr)
+		goto BYPASS_SRTP;
+	} else if (srtp->setting.use == PJMEDIA_SRTP_MANDATORY) {
+	    if (!has_crypto_attr) {
+		DEACTIVATE_MEDIA(pool, m_loc);
+		return PJMEDIA_SRTP_ESDPREQCRYPTO;
+	    }
+	}
+
+	/* At this point, we get valid rx_policy_neg & tx_policy_neg. */
+    }
+
+    /* Make sure we have the SRTP policies */
+    if (srtp_crypto_empty(&srtp->tx_policy_neg) || 
+	srtp_crypto_empty(&srtp->rx_policy_neg))
+    {
+	goto BYPASS_SRTP;
+    }
+
+    /* Reset probation counts */
+    srtp->probation_cnt = PROBATION_CNT_INIT;
+
+    /* Got policy_local & policy_remote, let's initalize the SRTP */
+
+    /* Ticket #1075: media_start() is called whenever media description
+     * gets updated, e.g: call hold, however we should restart SRTP only
+     * when the SRTP policy settings are updated.
+     */
+    if (srtp_crypto_cmp(&srtp->tx_policy_neg, &srtp->tx_policy) ||
+	srtp_crypto_cmp(&srtp->rx_policy_neg, &srtp->rx_policy))
+    {
+	status = pjmedia_transport_srtp_start(tp,
+					      &srtp->tx_policy_neg,
+					      &srtp->rx_policy_neg);
+	if (status != PJ_SUCCESS)
+	    return status;
+    }
+
+    srtp->bypass_srtp = PJ_FALSE;
+
+    goto PROPAGATE_MEDIA_START;
+
+BYPASS_SRTP:
+    srtp->bypass_srtp = PJ_TRUE;
+    srtp->peer_use = PJMEDIA_SRTP_DISABLED;
+    if (srtp->session_inited) {
+	pjmedia_transport_srtp_stop(tp);
+    }
+
+PROPAGATE_MEDIA_START:
+    return pjmedia_transport_media_start(srtp->member_tp, pool, 
+					 sdp_local, sdp_remote,
+				         media_index);
+}
+
+static pj_status_t transport_media_stop(pjmedia_transport *tp)
+{
+    struct transport_srtp *srtp = (struct transport_srtp*) tp;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(tp, PJ_EINVAL);
+
+    status = pjmedia_transport_media_stop(srtp->member_tp);
+    if (status != PJ_SUCCESS)
+	PJ_LOG(4, (srtp->pool->obj_name, 
+		   "SRTP failed stop underlying media transport."));
+
+    return pjmedia_transport_srtp_stop(tp);
+}
+
+/* Utility */
+PJ_DEF(pj_status_t) pjmedia_transport_srtp_decrypt_pkt(pjmedia_transport *tp,
+						       pj_bool_t is_rtp,
+						       void *pkt,
+						       int *pkt_len)
+{
+    transport_srtp *srtp = (transport_srtp *)tp;
+    err_status_t err;
+
+    if (srtp->bypass_srtp)
+	return PJ_SUCCESS;
+
+    PJ_ASSERT_RETURN(tp && pkt && (*pkt_len>0), PJ_EINVAL);
+    PJ_ASSERT_RETURN(srtp->session_inited, PJ_EINVALIDOP);
+
+    /* Make sure buffer is 32bit aligned */
+    PJ_ASSERT_ON_FAIL( (((pj_ssize_t)pkt) & 0x03)==0, return PJ_EINVAL);
+
+    pj_lock_acquire(srtp->mutex);
+
+    if (!srtp->session_inited) {
+	pj_lock_release(srtp->mutex);
+	return PJ_EINVALIDOP;
+    }
+
+    if (is_rtp)
+	err = srtp_unprotect(srtp->srtp_rx_ctx, pkt, pkt_len);
+    else
+	err = srtp_unprotect_rtcp(srtp->srtp_rx_ctx, pkt, pkt_len);
+    
+    if (err != err_status_ok) {
+	PJ_LOG(5,(srtp->pool->obj_name, 
+		  "Failed to unprotect SRTP, pkt size=%d, err=%s", 
+		  *pkt_len, get_libsrtp_errstr(err)));
+    }
+
+    pj_lock_release(srtp->mutex);
+
+    return (err==err_status_ok) ? PJ_SUCCESS : PJMEDIA_ERRNO_FROM_LIBSRTP(err);
+}
+
+#endif
+
+