* #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
+
+