summaryrefslogtreecommitdiff
path: root/pjmedia/src/pjmedia-codec
diff options
context:
space:
mode:
Diffstat (limited to 'pjmedia/src/pjmedia-codec')
-rw-r--r--pjmedia/src/pjmedia-codec/audio_codecs.c112
-rw-r--r--pjmedia/src/pjmedia-codec/ffmpeg_codecs.c1492
-rw-r--r--pjmedia/src/pjmedia-codec/g722.c3
-rw-r--r--pjmedia/src/pjmedia-codec/g7221.c3
-rw-r--r--pjmedia/src/pjmedia-codec/gsm.c3
-rw-r--r--pjmedia/src/pjmedia-codec/h263_packetizer.c287
-rw-r--r--pjmedia/src/pjmedia-codec/h264_packetizer.c530
-rw-r--r--pjmedia/src/pjmedia-codec/ilbc.c3
-rw-r--r--pjmedia/src/pjmedia-codec/ipp_codecs.c3
-rw-r--r--pjmedia/src/pjmedia-codec/l16.c3
-rw-r--r--pjmedia/src/pjmedia-codec/passthrough.c3
-rw-r--r--pjmedia/src/pjmedia-codec/speex_codec.c3
12 files changed, 2437 insertions, 8 deletions
diff --git a/pjmedia/src/pjmedia-codec/audio_codecs.c b/pjmedia/src/pjmedia-codec/audio_codecs.c
new file mode 100644
index 00000000..a9e0700a
--- /dev/null
+++ b/pjmedia/src/pjmedia-codec/audio_codecs.c
@@ -0,0 +1,112 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2011-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.h>
+#include <pjmedia/g711.h>
+
+PJ_DEF(void) pjmedia_audio_codec_config_default(pjmedia_audio_codec_config*cfg)
+{
+ pj_bzero(cfg, sizeof(*cfg));
+ cfg->speex.option = 0;
+ cfg->speex.quality = PJMEDIA_CODEC_SPEEX_DEFAULT_QUALITY;
+ cfg->speex.complexity = PJMEDIA_CODEC_SPEEX_DEFAULT_COMPLEXITY;
+ cfg->ilbc.mode = 30;
+ cfg->passthrough.setting.ilbc_mode = cfg->ilbc.mode;
+}
+
+PJ_DEF(pj_status_t)
+pjmedia_codec_register_audio_codecs(pjmedia_endpt *endpt,
+ const pjmedia_audio_codec_config *c)
+{
+ pjmedia_audio_codec_config default_cfg;
+ pj_status_t status;
+
+ PJ_ASSERT_RETURN(endpt, PJ_EINVAL);
+ if (!c) {
+ pjmedia_audio_codec_config_default(&default_cfg);
+ c = &default_cfg;
+ }
+
+ PJ_ASSERT_RETURN(c->ilbc.mode==20 || c->ilbc.mode==30, PJ_EINVAL);
+
+#if PJMEDIA_HAS_PASSTHROUGH_CODECS
+ status = pjmedia_codec_passthrough_init2(endpt, &c->passthough.ilbc);
+ if (status != PJ_SUCCESS)
+ return status;
+#endif
+
+#if PJMEDIA_HAS_SPEEX_CODEC
+ /* Register speex. */
+ status = pjmedia_codec_speex_init(endpt, c->speex.option,
+ c->speex.quality,
+ c->speex.complexity);
+ if (status != PJ_SUCCESS)
+ return status;
+#endif
+
+#if PJMEDIA_HAS_ILBC_CODEC
+ /* Register iLBC. */
+ status = pjmedia_codec_ilbc_init( endpt, c->ilbc.mode);
+ if (status != PJ_SUCCESS)
+ return status;
+#endif /* PJMEDIA_HAS_ILBC_CODEC */
+
+#if PJMEDIA_HAS_GSM_CODEC
+ /* Register GSM */
+ status = pjmedia_codec_gsm_init(endpt);
+ if (status != PJ_SUCCESS)
+ return status;
+#endif /* PJMEDIA_HAS_GSM_CODEC */
+
+#if PJMEDIA_HAS_G711_CODEC
+ /* Register PCMA and PCMU */
+ status = pjmedia_codec_g711_init(endpt);
+ if (status != PJ_SUCCESS)
+ return status;
+#endif /* PJMEDIA_HAS_G711_CODEC */
+
+#if PJMEDIA_HAS_G722_CODEC
+ status = pjmedia_codec_g722_init(endpt );
+ if (status != PJ_SUCCESS)
+ return status;
+#endif /* PJMEDIA_HAS_G722_CODEC */
+
+#if PJMEDIA_HAS_INTEL_IPP
+ /* Register IPP codecs */
+ status = pjmedia_codec_ipp_init(endpt);
+ if (status != PJ_SUCCESS)
+ return status;
+#endif /* PJMEDIA_HAS_INTEL_IPP */
+
+#if PJMEDIA_HAS_G7221_CODEC
+ /* Register G722.1 codecs */
+ status = pjmedia_codec_g7221_init(endpt);
+ if (status != PJ_SUCCESS)
+ return status;
+#endif /* PJMEDIA_HAS_G7221_CODEC */
+
+#if PJMEDIA_HAS_L16_CODEC
+ /* Register L16 family codecs */
+ status = pjmedia_codec_l16_init(endpt, 0);
+ if (status != PJ_SUCCESS)
+ return status;
+#endif /* PJMEDIA_HAS_L16_CODEC */
+
+ return PJ_SUCCESS;
+}
+
diff --git a/pjmedia/src/pjmedia-codec/ffmpeg_codecs.c b/pjmedia/src/pjmedia-codec/ffmpeg_codecs.c
new file mode 100644
index 00000000..ddf02c1a
--- /dev/null
+++ b/pjmedia/src/pjmedia-codec/ffmpeg_codecs.c
@@ -0,0 +1,1492 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2010 Teluu Inc. (http://www.teluu.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+#include <pjmedia-codec/ffmpeg_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_CODEC != 0
+ */
+#if defined(PJMEDIA_HAS_FFMPEG_CODEC) && PJMEDIA_HAS_FFMPEG_CODEC != 0
+
+#define THIS_FILE "ffmpeg_codecs.c"
+
+#include "../pjmedia/ffmpeg_util.h"
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+
+
+/* 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_packetize ( pjmedia_vid_codec *codec,
+ pj_uint8_t *buf,
+ pj_size_t buf_len,
+ unsigned *pos,
+ const pj_uint8_t **payload,
+ pj_size_t *payload_len);
+static pj_status_t ffmpeg_unpacketize(pjmedia_vid_codec *codec,
+ const pj_uint8_t *payload,
+ pj_size_t payload_len,
+ pj_uint8_t *buf,
+ pj_size_t buf_len,
+ unsigned *pos);
+static pj_status_t ffmpeg_codec_encode( pjmedia_vid_codec *codec,
+ const pjmedia_frame *input,
+ unsigned output_buf_len,
+ pjmedia_frame *output);
+static pj_status_t ffmpeg_codec_decode( pjmedia_vid_codec *codec,
+ const pjmedia_frame *input,
+ unsigned output_buf_len,
+ pjmedia_frame *output);
+static pj_status_t ffmpeg_codec_recover( pjmedia_vid_codec *codec,
+ unsigned output_buf_len,
+ 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_packetize,
+ &ffmpeg_unpacketize,
+ &ffmpeg_codec_encode,
+ &ffmpeg_codec_decode,
+ NULL //&ffmpeg_codec_recover
+};
+
+/* 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 */
+ pj_timestamp last_tx; /**< Timestamp of last
+ transmit */
+
+ /* 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;
+
+ /* 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 */
+ 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;
+};
+
+
+/* H264 constants */
+#define PROFILE_H264_BASELINE 66
+#define PROFILE_H264_MAIN 77
+
+/* Codec specific functions */
+static pj_status_t h264_preopen(ffmpeg_private *ff);
+static pj_status_t h264_postopen(ffmpeg_private *ff);
+static pj_status_t h263_preopen(ffmpeg_private *ff);
+static FUNC_PACKETIZE(h264_packetize);
+static FUNC_UNPACKETIZE(h264_unpacketize);
+static FUNC_PACKETIZE(h263_packetize);
+static FUNC_UNPACKETIZE(h263_unpacketize);
+
+
+/* Internal codec info */
+ffmpeg_codec_desc codec_desc[] =
+{
+ {
+ {PJMEDIA_FORMAT_H264, PJMEDIA_RTP_PT_H264, {"H264",4},
+ {"Constrained Baseline (level=30, pack=1)", 39}},
+ 0, 128000, 1000000,
+ &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}}, } },
+ },
+ {
+ {PJMEDIA_FORMAT_H264, PJMEDIA_RTP_PT_H264_RSV1, {"H264",4},
+ {"Baseline (level=30, pack=1)", 27}},
+ PJMEDIA_FORMAT_H264, 128000, 1000000,
+ &h264_packetize, &h264_unpacketize, &h264_preopen, &h264_postopen,
+ &pjmedia_vid_codec_h264_match_sdp,
+ {2, { {{"profile-level-id",16}, {"42001e",6}},
+ {{" packetization-mode",19}, {"1",1}}, } },
+ },
+ {
+ {PJMEDIA_FORMAT_H263P, PJMEDIA_RTP_PT_H263P, {"H263-1998",9}},
+ PJMEDIA_FORMAT_H263, 1000000, 2000000,
+ &h263_packetize, &h263_unpacketize, &h263_preopen, NULL, NULL,
+ {2, { {{"CIF",3}, {"1",1}},
+ {{"QCIF",4}, {"1",1}}, } },
+ },
+ {
+ {PJMEDIA_FORMAT_H263, PJMEDIA_RTP_PT_H263, {"H263",4}},
+ PJMEDIA_FORMAT_H263, 1000000, 2000000,
+ &h263_packetize, &h263_unpacketize, &h263_preopen, NULL, NULL,
+ {2, { {{"CIF",3}, {"1",1}},
+ {{"QCIF",4}, {"1",1}}, } },
+ },
+ {
+ {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_MPEG4, 0, {"MP4V",4}},
+ },
+ {
+ {PJMEDIA_FORMAT_XVID, 0, {"XVID",4}},
+ PJMEDIA_FORMAT_MPEG4,
+ },
+};
+
+
+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 */
+ status = pjmedia_vid_codec_h264_apply_fmtp(&ff->param);
+ if (status != PJ_SUCCESS)
+ return status;
+
+ if (ff->param.dir & PJMEDIA_DIR_ENCODING) {
+ AVCodecContext *ctx = ff->enc_ctx;
+
+ /* Apply profile. Note that, for x264 backend, ffmpeg doesn't seem to
+ * use this profile param field, so let's try to apply it "manually".
+ */
+ ctx->profile = data->fmtp.profile_idc;
+ if (ctx->profile == PROFILE_H264_BASELINE) {
+ /* Baseline profile settings (the most used profile in
+ * conversational/real-time communications).
+ */
+ ctx->coder_type = FF_CODER_TYPE_VLC;
+ ctx->max_b_frames = 0;
+ ctx->flags2 &= ~(CODEC_FLAG2_WPRED | CODEC_FLAG2_8X8DCT);
+ ctx->weighted_p_pred = 0;
+ } else if (ctx->profile == PROFILE_H264_MAIN) {
+ ctx->flags2 &= ~CODEC_FLAG2_8X8DCT;
+ }
+
+ /* Apply profile constraint bits. */
+ // The x264 doesn't seem to support non-constrained (baseline) profile
+ // so this shouldn't be a problem (for now).
+ //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;
+
+ /* Libx264 rejects the "broken" ffmpeg defaults, so just change some */
+ ctx->me_range = 16;
+ ctx->max_qdiff = 4;
+ ctx->qmin = 20;
+ ctx->qmax = 32;
+ ctx->qcompress = 0.6f;
+
+ ctx->rtp_payload_size = ff->param.enc_mtu;
+ }
+
+ 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 = 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);
+}
+
+
+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 */
+ status = pjmedia_vid_codec_h263_apply_fmtp(&ff->param);
+
+ 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);
+}
+
+
+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))
+ {
+ 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_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;
+
+ avcodec_init();
+ avcodec_register_all();
+ av_log_set_level(AV_LOG_ERROR);
+
+ /* 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 ((c->encode && (desc->info.dir & PJMEDIA_DIR_ENCODING)) ||
+ (c->decode && (desc->info.dir & PJMEDIA_DIR_DECODING)))
+ {
+ continue;
+ }
+
+ /* Get raw/decoded format ids in the encoder */
+ if (c->pix_fmts && c->encode) {
+ 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;
+ }
+
+ 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 (c->encode && !desc->enc) {
+ desc->info.dir |= PJMEDIA_DIR_ENCODING;
+ desc->enc = c;
+ }
+
+ /* Get ffmpeg decoder instance */
+ if (c->decode && !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 RTP packetization support flag in the codec info */
+ desc->info.has_rtp_pack = (desc->packetize != NULL) &&
+ (desc->unpacketize != NULL);
+ }
+
+ /* 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);
+ desc->info.has_rtp_pack = (desc->packetize != NULL) &&
+ (desc->unpacketize != NULL);
+
+ 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);
+ }
+ }
+
+ /* 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_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;
+
+ 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;
+
+ 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));
+
+ /* Direction */
+ attr->dir = desc->info.dir;
+
+ /* Encoded format */
+ pjmedia_format_init_video(&attr->enc_fmt, desc->info.fmt_id,
+ 352, 288, 30000, 1001);
+
+ /* Decoded format */
+ pjmedia_format_init_video(&attr->dec_fmt, desc->info.dec_fmt_id[0],
+ //352, 288, 30000, 1001);
+ 720, 576, 30000, 1001);
+
+ /* 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;
+
+ /* MTU */
+ attr->enc_mtu = PJMEDIA_MAX_MTU;
+
+ 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_ALLOC_T(pool, pjmedia_vid_codec);
+ if (!codec) {
+ status = PJ_ENOMEM;
+ goto on_error;
+ }
+ pjmedia_vid_codec_reset(codec, PJMEDIA_SIG_VID_CODEC_FFMPEG);
+ codec->op = &ffmpeg_op;
+ codec->factory = factory;
+ ff = PJ_POOL_ZALLOC_T(pool, ffmpeg_private);
+ 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_VERSION_MAJOR > 52 || \
+ (LIBAVCODEC_VERSION_MAJOR >= 52 && LIBAVCODEC_VERSION_MINOR >= 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 enum PixelFormat dec_get_format(struct AVCodecContext *s,
+ const enum PixelFormat * fmt)
+{
+ ffmpeg_private *ff = (ffmpeg_private*)s->opaque;
+ enum PixelFormat def_fmt = *fmt;
+
+ while (*fmt != -1) {
+ if (*fmt == ff->expected_dec_fmt)
+ return *fmt;
+ ++fmt;
+ }
+
+ pj_assert(!"Inconsistency in supported formats");
+ return def_fmt;
+}
+
+
+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) {
+ ff->enc_ctx = avcodec_alloc_context();
+ if (ff->enc_ctx == NULL)
+ goto on_error;
+ }
+ if (ff->param.dir & PJMEDIA_DIR_DECODING) {
+ ff->dec_ctx = avcodec_alloc_context();
+ if (ff->dec_ctx == NULL)
+ goto on_error;
+ }
+
+ /* Let the codec apply specific settings before the codec opened */
+ if (ff->desc->preopen) {
+ status = (*ff->desc->preopen)(ff);
+ if (status != PJ_SUCCESS)
+ goto on_error;
+ }
+
+ if (ff->param.dir & PJMEDIA_DIR_ENCODING) {
+ AVCodecContext *ctx = ff->enc_ctx;
+ int err;
+
+ /* Init common settings */
+ 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_VERSION_MAJOR > 52 || \
+ (LIBAVCODEC_VERSION_MAJOR >= 52 && LIBAVCODEC_VERSION_MINOR >= 113)
+ ctx->rc_lookahead = 0;
+#endif
+
+ /* Open ffmpeg codec */
+ pj_mutex_lock(ff_mutex);
+ err = avcodec_open(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;
+ }
+
+ if (ff->param.dir & PJMEDIA_DIR_DECODING) {
+ AVCodecContext *ctx = ff->dec_ctx;
+ int err;
+
+ /* Init common settings */
+ /* 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;
+ ctx->get_format = &dec_get_format;
+
+ /* Open ffmpeg codec */
+ pj_mutex_lock(ff_mutex);
+ err = avcodec_open(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 settings 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));
+
+ /* 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;
+ }
+
+ /* 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( pjmedia_vid_codec *codec,
+ 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;
+ pj_uint8_t *out_buf = (pj_uint8_t*)output->buf;
+ int out_buf_len = output_buf_len;
+ int err;
+ //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)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]];
+ }
+
+ err = avcodec_encode_video(ff->enc_ctx, out_buf, out_buf_len, &avframe);
+ if (err < 0) {
+ print_ffmpeg_err(err);
+ return PJMEDIA_CODEC_EFAILED;
+ } else {
+ output->size = err;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Decode frame.
+ */
+static pj_status_t ffmpeg_codec_decode( 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 = 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_VERSION_MAJOR > 52 || \
+ (LIBAVCODEC_VERSION_MAJOR >= 52 && LIBAVCODEC_VERSION_MINOR >= 72)
+ //avpacket.flags = AV_PKT_FLAG_KEY;
+#else
+ avpacket.flags = 0;
+#endif
+
+#if LIBAVCODEC_VERSION_MAJOR > 52 || \
+ (LIBAVCODEC_VERSION_MAJOR >= 52 && LIBAVCODEC_VERSION_MINOR >= 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) {
+ output->type = PJMEDIA_FRAME_TYPE_NONE;
+ output->size = 0;
+ print_ffmpeg_err(err);
+ return PJMEDIA_CODEC_EFAILED;
+ } else if (got_picture) {
+ pjmedia_video_apply_fmt_param *vafp = &ff->dec_vafp;
+ pj_uint8_t *q = (pj_uint8_t*)output->buf;
+ unsigned i;
+
+ /* 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;
+
+ /* 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;
+
+ /* Broadcast event */
+ if (pjmedia_event_publisher_has_sub(&codec->epub)) {
+ pjmedia_event event;
+
+ pjmedia_event_init(&event, PJMEDIA_EVENT_FMT_CHANGED,
+ &input->timestamp, &codec->epub);
+ 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(&codec->epub, &event);
+ }
+ }
+
+ /* 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;
+
+ /* Check if we got key frame */
+ if (avframe.key_frame && pjmedia_event_publisher_has_sub(&codec->epub))
+ {
+ pjmedia_event event;
+
+ pjmedia_event_init(&event, PJMEDIA_EVENT_KEY_FRAME_FOUND,
+ &output->timestamp, &codec->epub);
+ pjmedia_event_publish(&codec->epub, &event);
+ }
+ } else {
+ output->type = PJMEDIA_FRAME_TYPE_NONE;
+ output->size = 0;
+ }
+
+ return PJ_SUCCESS;
+}
+
+/*
+ * Recover lost frame.
+ */
+static pj_status_t ffmpeg_codec_recover( pjmedia_vid_codec *codec,
+ unsigned output_buf_len,
+ pjmedia_frame *output)
+{
+ PJ_UNUSED_ARG(codec);
+ PJ_UNUSED_ARG(output_buf_len);
+ PJ_UNUSED_ARG(output);
+
+ return PJ_ENOTSUP;
+}
+
+#ifdef _MSC_VER
+# pragma comment( lib, "avcodec.lib")
+#endif
+
+#endif /* PJMEDIA_HAS_FFMPEG_CODEC */
+
diff --git a/pjmedia/src/pjmedia-codec/g722.c b/pjmedia/src/pjmedia-codec/g722.c
index 8cd332d8..acddc142 100644
--- a/pjmedia/src/pjmedia-codec/g722.c
+++ b/pjmedia/src/pjmedia-codec/g722.c
@@ -122,7 +122,8 @@ static pjmedia_codec_factory_op g722_factory_op =
&g722_default_attr,
&g722_enum_codecs,
&g722_alloc_codec,
- &g722_dealloc_codec
+ &g722_dealloc_codec,
+ &pjmedia_codec_g722_deinit
};
/* G722 factory */
diff --git a/pjmedia/src/pjmedia-codec/g7221.c b/pjmedia/src/pjmedia-codec/g7221.c
index f1aa8ab8..44ecabc9 100644
--- a/pjmedia/src/pjmedia-codec/g7221.c
+++ b/pjmedia/src/pjmedia-codec/g7221.c
@@ -115,7 +115,8 @@ static pjmedia_codec_factory_op codec_factory_op =
&default_attr,
&enum_codecs,
&alloc_codec,
- &dealloc_codec
+ &dealloc_codec,
+ &pjmedia_codec_g7221_deinit
};
diff --git a/pjmedia/src/pjmedia-codec/gsm.c b/pjmedia/src/pjmedia-codec/gsm.c
index ea4bb63a..43149b78 100644
--- a/pjmedia/src/pjmedia-codec/gsm.c
+++ b/pjmedia/src/pjmedia-codec/gsm.c
@@ -117,7 +117,8 @@ static pjmedia_codec_factory_op gsm_factory_op =
&gsm_default_attr,
&gsm_enum_codecs,
&gsm_alloc_codec,
- &gsm_dealloc_codec
+ &gsm_dealloc_codec,
+ &pjmedia_codec_gsm_deinit
};
/* GSM factory */
diff --git a/pjmedia/src/pjmedia-codec/h263_packetizer.c b/pjmedia/src/pjmedia-codec/h263_packetizer.c
new file mode 100644
index 00000000..149c2e64
--- /dev/null
+++ b/pjmedia/src/pjmedia-codec/h263_packetizer.c
@@ -0,0 +1,287 @@
+/* $Id$ */
+/*
+ * Copyright (C) 2008-2009 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/h263_packetizer.h>
+#include <pjmedia/types.h>
+#include <pj/assert.h>
+#include <pj/errno.h>
+#include <pj/string.h>
+
+#define THIS_FILE "h263_packetizer.c"
+
+
+/* H.263 packetizer definition */
+struct pjmedia_h263_packetizer {
+ /* Current settings */
+ pjmedia_h263_packetizer_cfg cfg;
+
+ /* Unpacketizer state */
+ unsigned unpack_last_sync_pos;
+ pj_bool_t unpack_prev_lost;
+};
+
+
+/*
+ * Find synchronization point (PSC, slice, GSBC, EOS, EOSBS) in H.263
+ * bitstream.
+ */
+static pj_uint8_t* find_sync_point(pj_uint8_t *data,
+ pj_size_t data_len)
+{
+ pj_uint8_t *p = data, *end = data+data_len-1;
+
+ while (p < end && (*p || *(p+1)))
+ ++p;
+
+ if (p == end)
+ return NULL;
+
+ return p;
+}
+
+
+/*
+ * Find synchronization point (PSC, slice, GSBC, EOS, EOSBS) in H.263
+ * bitstream, in reversed manner.
+ */
+static pj_uint8_t* find_sync_point_rev(pj_uint8_t *data,
+ pj_size_t data_len)
+{
+ pj_uint8_t *p = data+data_len-2;
+
+ while (p >= data && (*p || *(p+1)))
+ --p;
+
+ if (p < data)
+ return (data + data_len);
+
+ return p;
+}
+
+
+/*
+ * Create H263 packetizer.
+ */
+PJ_DEF(pj_status_t) pjmedia_h263_packetizer_create(
+ pj_pool_t *pool,
+ const pjmedia_h263_packetizer_cfg *cfg,
+ pjmedia_h263_packetizer **p)
+{
+ pjmedia_h263_packetizer *p_;
+
+ PJ_ASSERT_RETURN(pool && p, PJ_EINVAL);
+
+ if (cfg && cfg->mode != PJMEDIA_H263_PACKETIZER_MODE_RFC4629)
+ return PJ_ENOTSUP;
+
+ p_ = PJ_POOL_ZALLOC_T(pool, pjmedia_h263_packetizer);
+ if (cfg) {
+ pj_memcpy(&p_->cfg, cfg, sizeof(*cfg));
+ } else {
+ p_->cfg.mode = PJMEDIA_H263_PACKETIZER_MODE_RFC4629;
+ p_->cfg.mtu = PJMEDIA_MAX_MTU;
+ }
+
+ *p = p_;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Generate an RTP payload from H.263 frame bitstream, in-place processing.
+ */
+PJ_DEF(pj_status_t) pjmedia_h263_packetize(pjmedia_h263_packetizer *pktz,
+ pj_uint8_t *bits,
+ pj_size_t bits_len,
+ unsigned *pos,
+ const pj_uint8_t **payload,
+ pj_size_t *payload_len)
+{
+ pj_uint8_t *p, *end;
+
+ pj_assert(pktz && bits && pos && payload && payload_len);
+ pj_assert(*pos <= bits_len);
+
+ p = bits + *pos;
+ end = bits + bits_len;
+
+ /* Put two octets payload header */
+ if ((end-p > 2) && *p==0 && *(p+1)==0) {
+ /* The bitstream starts with synchronization point, just override
+ * the two zero octets (sync point mark) for payload header.
+ */
+ *p = 0x04;
+ } else {
+ /* Not started in synchronization point, we will use two octets
+ * preceeding the bitstream for payload header!
+ */
+
+ if (*pos < 2) {
+ /* Invalid H263 bitstream, it's not started with PSC */
+ return PJ_EINVAL;
+ }
+
+ p -= 2;
+ *p = 0;
+ }
+ *(p+1) = 0;
+
+ /* When bitstream truncation needed because of payload length/MTU
+ * limitation, try to use sync point for the payload boundary.
+ */
+ if (end-p > pktz->cfg.mtu) {
+ end = find_sync_point_rev(p+2, pktz->cfg.mtu-2);
+ }
+
+ *payload = p;
+ *payload_len = end-p;
+ *pos = end - bits;
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Append an RTP payload to a H.263 picture bitstream.
+ */
+PJ_DEF(pj_status_t) pjmedia_h263_unpacketize (pjmedia_h263_packetizer *pktz,
+ const pj_uint8_t *payload,
+ pj_size_t payload_len,
+ pj_uint8_t *bits,
+ pj_size_t bits_size,
+ unsigned *pos)
+{
+ pj_uint8_t P, V, PLEN;
+ const pj_uint8_t *p = payload;
+ pj_uint8_t *q;
+
+ q = bits + *pos;
+
+ /* Check if this is a missing/lost packet */
+ if (payload == NULL) {
+ pktz->unpack_prev_lost = PJ_TRUE;
+ return PJ_SUCCESS;
+ }
+
+ /* H263 payload header size is two octets */
+ if (payload_len < 2) {
+ /* Invalid bitstream, discard this payload */
+ pktz->unpack_prev_lost = PJ_TRUE;
+ return PJ_EINVAL;
+ }
+
+ /* Reset last sync point for every new picture bitstream */
+ if (*pos == 0)
+ pktz->unpack_last_sync_pos = 0;
+
+ /* Get payload header info */
+ P = *p & 0x04;
+ V = *p & 0x02;
+ PLEN = ((*p & 0x01) << 5) + ((*(p+1) & 0xF8)>>3);
+
+ /* Get start bitstream pointer */
+ p += 2; /* Skip payload header */
+ if (V)
+ p += 1; /* Skip VRC data */
+ if (PLEN)
+ p += PLEN; /* Skip extra picture header data */
+
+ /* Get bitstream length */
+ if (payload_len > (pj_size_t)(p - payload)) {
+ payload_len -= (p - payload);
+ } else {
+ /* Invalid bitstream, discard this payload */
+ pktz->unpack_prev_lost = PJ_TRUE;
+ return PJ_EINVAL;
+ }
+
+ /* Validate bitstream length */
+ if (bits_size < *pos + payload_len + 2) {
+ /* Insufficient bistream buffer, discard this payload */
+ pj_assert(!"Insufficient H.263 bitstream buffer");
+ pktz->unpack_prev_lost = PJ_TRUE;
+ return PJ_ETOOSMALL;
+ }
+
+ /* Start writing bitstream */
+
+ /* No sync point flag */
+ if (!P) {
+ if (*pos == 0) {
+ /* Previous packet must be lost */
+ pktz->unpack_prev_lost = PJ_TRUE;
+
+ /* If there is extra picture header, let's use it. */
+ if (PLEN) {
+ /* Write two zero octets for PSC */
+ *q++ = 0;
+ *q++ = 0;
+ /* Copy the picture header */
+ p -= PLEN;
+ pj_memcpy(q, p, PLEN);
+ p += PLEN;
+ q += PLEN;
+ }
+ } else if (pktz->unpack_prev_lost) {
+ /* If prev packet was lost, revert the bitstream pointer to
+ * the last sync point.
+ */
+ pj_assert(pktz->unpack_last_sync_pos <= *pos);
+ q = bits + pktz->unpack_last_sync_pos;
+ }
+
+ /* There was packet lost, see if this payload contain sync point
+ * (usable data).
+ */
+ if (pktz->unpack_prev_lost) {
+ pj_uint8_t *sync;
+ sync = find_sync_point((pj_uint8_t*)p, payload_len);
+ if (sync) {
+ /* Got sync point, update P/sync-point flag */
+ P = 1;
+ /* Skip the two zero octets */
+ sync += 2;
+ /* Update payload length and start bitstream pointer */
+ payload_len -= (sync - p);
+ p = sync;
+ } else {
+ /* No sync point in it, just discard this payload */
+ return PJ_EIGNORED;
+ }
+ }
+ }
+
+ /* Write two zero octets when payload flagged with sync point */
+ if (P) {
+ pktz->unpack_last_sync_pos = q - bits;
+ *q++ = 0;
+ *q++ = 0;
+ }
+
+ /* Write the payload to the bitstream */
+ pj_memcpy(q, p, payload_len);
+ q += payload_len;
+
+ /* Update the bitstream writing offset */
+ *pos = q - bits;
+
+ pktz->unpack_prev_lost = PJ_FALSE;
+
+ return PJ_SUCCESS;
+}
diff --git a/pjmedia/src/pjmedia-codec/h264_packetizer.c b/pjmedia/src/pjmedia-codec/h264_packetizer.c
new file mode 100644
index 00000000..8eb22d6f
--- /dev/null
+++ b/pjmedia/src/pjmedia-codec/h264_packetizer.c
@@ -0,0 +1,530 @@
+/* $Id$ */
+/*
+ * Copyright (C) 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/h264_packetizer.h>
+#include <pjmedia/types.h>
+#include <pj/assert.h>
+#include <pj/errno.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+#define THIS_FILE "h264_packetizer.c"
+
+#define DBG_PACKETIZE 0
+#define DBG_UNPACKETIZE 0
+
+
+/* H.264 packetizer definition */
+struct pjmedia_h264_packetizer
+{
+ /* Current settings */
+ pjmedia_h264_packetizer_cfg cfg;
+
+ /* Unpacketizer state */
+ unsigned unpack_last_sync_pos;
+ pj_bool_t unpack_prev_lost;
+};
+
+
+/* Enumeration of H.264 NAL unit types */
+enum
+{
+ NAL_TYPE_SINGLE_NAL_MIN = 1,
+ NAL_TYPE_SINGLE_NAL_MAX = 23,
+ NAL_TYPE_STAP_A = 24,
+ NAL_TYPE_FU_A = 28,
+};
+
+
+/*
+ * Find next NAL unit from the specified H.264 bitstream data.
+ */
+static pj_uint8_t* find_next_nal_unit(pj_uint8_t *start,
+ pj_uint8_t *end)
+{
+ pj_uint8_t *p = start;
+
+ /* Simply lookup "0x000001" pattern */
+ while (p <= end-3 && (p[0] || p[1] || p[2]!=1))
+ ++p;
+
+ if (p > end-3)
+ /* No more NAL unit in this bitstream */
+ return NULL;
+
+ /* Include 8 bits leading zero */
+ if (p>start && *(p-1)==0)
+ return (p-1);
+
+ return p;
+}
+
+
+/*
+ * Create H264 packetizer.
+ */
+PJ_DEF(pj_status_t) pjmedia_h264_packetizer_create(
+ pj_pool_t *pool,
+ const pjmedia_h264_packetizer_cfg *cfg,
+ pjmedia_h264_packetizer **p)
+{
+ pjmedia_h264_packetizer *p_;
+
+ PJ_ASSERT_RETURN(pool && p, PJ_EINVAL);
+
+ if (cfg &&
+ cfg->mode != PJMEDIA_H264_PACKETIZER_MODE_NON_INTERLEAVED &&
+ cfg->mode != PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL)
+ {
+ return PJ_ENOTSUP;
+ }
+
+ p_ = PJ_POOL_ZALLOC_T(pool, pjmedia_h264_packetizer);
+ if (cfg) {
+ pj_memcpy(&p_->cfg, cfg, sizeof(*cfg));
+ } else {
+ p_->cfg.mode = PJMEDIA_H264_PACKETIZER_MODE_NON_INTERLEAVED;
+ p_->cfg.mtu = PJMEDIA_MAX_MTU;
+ }
+
+ *p = p_;
+
+ return PJ_SUCCESS;
+}
+
+
+
+/*
+ * Generate an RTP payload from H.264 frame bitstream, in-place processing.
+ */
+PJ_DEF(pj_status_t) pjmedia_h264_packetize(pjmedia_h264_packetizer *pktz,
+ pj_uint8_t *buf,
+ pj_size_t buf_len,
+ unsigned *pos,
+ const pj_uint8_t **payload,
+ pj_size_t *payload_len)
+{
+ pj_uint8_t *nal_start = NULL, *nal_end = NULL, *nal_octet = NULL;
+ pj_uint8_t *p, *end;
+ enum {
+ HEADER_SIZE_FU_A = 2,
+ HEADER_SIZE_STAP_A = 3,
+ };
+ enum { MAX_NALS_IN_AGGR = 32 };
+
+#if DBG_PACKETIZE
+ if (*pos == 0 && buf_len) {
+ PJ_LOG(3, ("h264pack", "<< Start packing new frame >>"));
+ }
+#endif
+
+ p = buf + *pos;
+ end = buf + buf_len;
+
+ /* Find NAL unit startcode */
+ if (end-p >= 4)
+ nal_start = find_next_nal_unit(p, p+4);
+ if (nal_start) {
+ /* Get NAL unit octet pointer */
+ while (*nal_start++ == 0);
+ nal_octet = nal_start;
+ } else {
+ /* This NAL unit is being fragmented */
+ nal_start = p;
+ }
+
+ /* Get end of NAL unit */
+ p = nal_start+pktz->cfg.mtu+1;
+ if (p > end || pktz->cfg.mode==PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL)
+ p = end;
+ nal_end = find_next_nal_unit(nal_start, p);
+ if (!nal_end)
+ nal_end = p;
+
+ /* Validate MTU vs NAL length on single NAL unit packetization */
+ if ((pktz->cfg.mode==PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL) &&
+ nal_end - nal_start > pktz->cfg.mtu)
+ {
+ //pj_assert(!"MTU too small for H.264 single NAL packetization mode");
+ PJ_LOG(2,("h264_packetizer.c",
+ "MTU too small for H.264 (required=%u, MTU=%u)",
+ nal_end - nal_start, pktz->cfg.mtu));
+ return PJ_ETOOSMALL;
+ }
+
+ /* Evaluate the proper payload format structure */
+
+ /* Fragmentation (FU-A) packet */
+ if ((pktz->cfg.mode != PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL) &&
+ (!nal_octet || nal_end-nal_start > pktz->cfg.mtu))
+ {
+ pj_uint8_t NRI, TYPE;
+
+ if (nal_octet) {
+ /* We have NAL unit octet, so this is the first fragment */
+ NRI = (*nal_octet & 0x60) >> 5;
+ TYPE = *nal_octet & 0x1F;
+
+ /* Skip nal_octet in nal_start to be overriden by FU header */
+ ++nal_start;
+ } else {
+ /* Not the first fragment, get NRI and NAL unit type
+ * from the previous fragment.
+ */
+ p = nal_start - pktz->cfg.mtu;
+ NRI = (*p & 0x60) >> 5;
+ TYPE = *(p+1) & 0x1F;
+ }
+
+ /* Init FU indicator (one octet: F+NRI+TYPE) */
+ p = nal_start - HEADER_SIZE_FU_A;
+ *p = (NRI << 5) | NAL_TYPE_FU_A;
+ ++p;
+
+ /* Init FU header (one octed: S+E+R+TYPE) */
+ *p = TYPE;
+ if (nal_octet)
+ *p |= (1 << 7); /* S bit flag = start of fragmentation */
+ if (nal_end-nal_start+HEADER_SIZE_FU_A <= pktz->cfg.mtu)
+ *p |= (1 << 6); /* E bit flag = end of fragmentation */
+
+ /* Set payload, payload length */
+ *payload = nal_start - HEADER_SIZE_FU_A;
+ if (nal_end-nal_start+HEADER_SIZE_FU_A > pktz->cfg.mtu)
+ *payload_len = pktz->cfg.mtu;
+ else
+ *payload_len = nal_end - nal_start + HEADER_SIZE_FU_A;
+ *pos = *payload + *payload_len - buf;
+
+#if DBG_PACKETIZE
+ PJ_LOG(3, ("h264pack", "Packetized fragmented H264 NAL unit "
+ "(pos=%d, type=%d, NRI=%d, S=%d, E=%d, len=%d/%d)",
+ *payload-buf, TYPE, NRI, *p>>7, (*p>>6)&1, *payload_len,
+ buf_len));
+#endif
+
+ return PJ_SUCCESS;
+ }
+
+ /* Aggregation (STAP-A) packet */
+ if ((pktz->cfg.mode != PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL) &&
+ (nal_end != end) &&
+ (nal_end - nal_start + HEADER_SIZE_STAP_A) < pktz->cfg.mtu)
+ {
+ int total_size;
+ unsigned nal_cnt = 1;
+ pj_uint8_t *nal[MAX_NALS_IN_AGGR];
+ pj_size_t nal_size[MAX_NALS_IN_AGGR];
+ pj_uint8_t NRI;
+
+ pj_assert(nal_octet);
+
+ /* Init the first NAL unit in the packet */
+ nal[0] = nal_start;
+ nal_size[0] = nal_end - nal_start;
+ total_size = nal_size[0] + HEADER_SIZE_STAP_A;
+ NRI = (*nal_octet & 0x60) >> 5;
+
+ /* Populate next NAL units */
+ while (nal_cnt < MAX_NALS_IN_AGGR) {
+ pj_uint8_t *tmp_end;
+
+ /* Find start address of the next NAL unit */
+ p = nal[nal_cnt-1] + nal_size[nal_cnt-1];
+ while (*p++ == 0);
+ nal[nal_cnt] = p;
+
+ /* Find end address of the next NAL unit */
+ tmp_end = p + (pktz->cfg.mtu - total_size);
+ if (tmp_end > end)
+ tmp_end = end;
+ p = find_next_nal_unit(p+1, tmp_end);
+ if (p) {
+ nal_size[nal_cnt] = p - nal[nal_cnt];
+ } else {
+ break;
+ }
+
+ /* Update total payload size (2 octet NAL size + NAL) */
+ total_size += (2 + nal_size[nal_cnt]);
+ if (total_size <= pktz->cfg.mtu) {
+ pj_uint8_t tmp_nri;
+
+ /* Get maximum NRI of the aggregated NAL units */
+ tmp_nri = (*(nal[nal_cnt]-1) & 0x60) >> 5;
+ if (tmp_nri > NRI)
+ NRI = tmp_nri;
+ } else {
+ break;
+ }
+
+ ++nal_cnt;
+ }
+
+ /* Only use STAP-A when we found more than one NAL units */
+ if (nal_cnt > 1) {
+ unsigned i;
+
+ /* Init STAP-A NAL header (F+NRI+TYPE) */
+ p = nal[0] - HEADER_SIZE_STAP_A;
+ *p++ = (NRI << 5) | NAL_TYPE_STAP_A;
+
+ /* Append all populated NAL units into payload (SIZE+NAL) */
+ for (i = 0; i < nal_cnt; ++i) {
+ /* Put size (2 octets in network order) */
+ pj_assert(nal_size[i] <= 0xFFFF);
+ *p++ = (pj_uint8_t)(nal_size[i] >> 8);
+ *p++ = (pj_uint8_t)(nal_size[i] & 0xFF);
+
+ /* Append NAL unit, watchout memmove()-ing bitstream! */
+ if (p != nal[i])
+ pj_memmove(p, nal[i], nal_size[i]);
+ p += nal_size[i];
+ }
+
+ /* Set payload, payload length, and pos */
+ *payload = nal[0] - HEADER_SIZE_STAP_A;
+ pj_assert(*payload >= buf+*pos);
+ *payload_len = p - *payload;
+ *pos = nal[nal_cnt-1] + nal_size[nal_cnt-1] - buf;
+
+#if DBG_PACKETIZE
+ PJ_LOG(3, ("h264pack", "Packetized aggregation of "
+ "%d H264 NAL units (pos=%d, NRI=%d len=%d/%d)",
+ nal_cnt, *payload-buf, NRI, *payload_len, buf_len));
+#endif
+
+ return PJ_SUCCESS;
+ }
+ }
+
+ /* Single NAL unit packet */
+ pj_assert(nal_octet);
+
+ *payload = nal_start;
+ *payload_len = nal_end - nal_start;
+ *pos = nal_end - buf;
+
+#if DBG_PACKETIZE
+ PJ_LOG(3, ("h264pack", "Packetized single H264 NAL unit "
+ "(pos=%d, type=%d, NRI=%d, len=%d/%d)",
+ nal_start-buf, *nal_octet&0x1F, (*nal_octet&0x60)>>5,
+ *payload_len, buf_len));
+#endif
+
+ return PJ_SUCCESS;
+}
+
+
+/*
+ * Append RTP payload to a H.264 picture bitstream. Note that the only
+ * payload format that cares about packet lost is the NAL unit
+ * fragmentation format (FU-A/B), so we will only manage the "prev_lost"
+ * state for the FU-A/B packets.
+ */
+PJ_DEF(pj_status_t) pjmedia_h264_unpacketize(pjmedia_h264_packetizer *pktz,
+ const pj_uint8_t *payload,
+ pj_size_t payload_len,
+ pj_uint8_t *bits,
+ pj_size_t bits_len,
+ unsigned *bits_pos)
+{
+ const pj_uint8_t nal_start_code[3] = {0, 0, 1};
+ enum { MIN_PAYLOAD_SIZE = 2 };
+ pj_uint8_t nal_type;
+
+ PJ_UNUSED_ARG(pktz);
+
+#if DBG_UNPACKETIZE
+ if (*bits_pos == 0 && payload_len) {
+ PJ_LOG(3, ("h264unpack", ">> Start unpacking new frame <<"));
+ }
+#endif
+
+ /* Check if this is a missing/lost packet */
+ if (payload == NULL) {
+ pktz->unpack_prev_lost = PJ_TRUE;
+ return PJ_SUCCESS;
+ }
+
+ /* H264 payload size */
+ if (payload_len < MIN_PAYLOAD_SIZE) {
+ /* Invalid bitstream, discard this payload */
+ pktz->unpack_prev_lost = PJ_TRUE;
+ return PJ_EINVAL;
+ }
+
+ /* Reset last sync point for every new picture bitstream */
+ if (*bits_pos == 0)
+ pktz->unpack_last_sync_pos = 0;
+
+ nal_type = *payload & 0x1F;
+ if (nal_type >= NAL_TYPE_SINGLE_NAL_MIN &&
+ nal_type <= NAL_TYPE_SINGLE_NAL_MAX)
+ {
+ /* Single NAL unit packet */
+ pj_uint8_t *p = bits + *bits_pos;
+
+ /* Validate bitstream length */
+ if (bits_len-*bits_pos < payload_len+PJ_ARRAY_SIZE(nal_start_code)) {
+ /* Insufficient bistream buffer, discard this payload */
+ pj_assert(!"Insufficient H.263 bitstream buffer");
+ return PJ_ETOOSMALL;
+ }
+
+ /* Write NAL unit start code */
+ pj_memcpy(p, &nal_start_code, PJ_ARRAY_SIZE(nal_start_code));
+ p += PJ_ARRAY_SIZE(nal_start_code);
+
+ /* Write NAL unit */
+ pj_memcpy(p, payload, payload_len);
+ p += payload_len;
+
+ /* Update the bitstream writing offset */
+ *bits_pos = p - bits;
+ pktz->unpack_last_sync_pos = *bits_pos;
+
+#if DBG_UNPACKETIZE
+ PJ_LOG(3, ("h264unpack", "Unpacked single H264 NAL unit "
+ "(type=%d, NRI=%d, len=%d)",
+ nal_type, (*payload&0x60)>>5, payload_len));
+#endif
+
+ }
+ else if (nal_type == NAL_TYPE_STAP_A)
+ {
+ /* Aggregation packet */
+ pj_uint8_t *p, *p_end;
+ const pj_uint8_t *q, *q_end;
+ unsigned cnt = 0;
+
+ /* Validate bitstream length */
+ if (bits_len - *bits_pos < payload_len + 32) {
+ /* Insufficient bistream buffer, discard this payload */
+ pj_assert(!"Insufficient H.263 bitstream buffer");
+ return PJ_ETOOSMALL;
+ }
+
+ /* Fill bitstream */
+ p = bits + *bits_pos;
+ p_end = bits + bits_len;
+ q = payload + 1;
+ q_end = payload + payload_len;
+ while (q < q_end && p < p_end) {
+ pj_uint16_t tmp_nal_size;
+
+ /* Write NAL unit start code */
+ pj_memcpy(p, &nal_start_code, PJ_ARRAY_SIZE(nal_start_code));
+ p += PJ_ARRAY_SIZE(nal_start_code);
+
+ /* Get NAL unit size */
+ tmp_nal_size = (*q << 8) | *(q+1);
+ q += 2;
+ if (q + tmp_nal_size > q_end) {
+ /* Invalid bitstream, discard the rest of the payload */
+ return PJ_EINVAL;
+ }
+
+ /* Write NAL unit */
+ pj_memcpy(p, q, tmp_nal_size);
+ p += tmp_nal_size;
+ q += tmp_nal_size;
+ ++cnt;
+
+ /* Update the bitstream writing offset */
+ *bits_pos = p - bits;
+ pktz->unpack_last_sync_pos = *bits_pos;
+ }
+
+#if DBG_UNPACKETIZE
+ PJ_LOG(3, ("h264unpack", "Unpacked %d H264 NAL units (len=%d)",
+ cnt, payload_len));
+#endif
+
+ }
+ else if (nal_type == NAL_TYPE_FU_A)
+ {
+ /* Fragmentation packet */
+ pj_uint8_t *p;
+ const pj_uint8_t *q = payload;
+ pj_uint8_t NRI, TYPE, S, E;
+
+ p = bits + *bits_pos;
+
+ /* Validate bitstream length */
+ if (bits_len-*bits_pos < payload_len+PJ_ARRAY_SIZE(nal_start_code)) {
+ /* Insufficient bistream buffer, drop this packet */
+ pj_assert(!"Insufficient H.263 bitstream buffer");
+ pktz->unpack_prev_lost = PJ_TRUE;
+ return PJ_ETOOSMALL;
+ }
+
+ /* Get info */
+ S = *(q+1) & 0x80; /* Start bit flag */
+ E = *(q+1) & 0x40; /* End bit flag */
+ TYPE = *(q+1) & 0x1f;
+ NRI = (*q & 0x60) >> 5;
+
+ /* Fill bitstream */
+ if (S) {
+ /* This is the first part, write NAL unit start code */
+ pj_memcpy(p, &nal_start_code, PJ_ARRAY_SIZE(nal_start_code));
+ p += PJ_ARRAY_SIZE(nal_start_code);
+
+ /* Write NAL unit octet */
+ *p++ = (NRI << 5) | TYPE;
+ } else if (pktz->unpack_prev_lost) {
+ /* If prev packet was lost, revert the bitstream pointer to
+ * the last sync point.
+ */
+ pj_assert(pktz->unpack_last_sync_pos <= *bits_pos);
+ *bits_pos = pktz->unpack_last_sync_pos;
+ /* And discard this payload (and the following fragmentation
+ * payloads carrying this same NAL unit.
+ */
+ return PJ_EIGNORED;
+ }
+ q += 2;
+
+ /* Write NAL unit */
+ pj_memcpy(p, q, payload_len - 2);
+ p += (payload_len - 2);
+
+ /* Update the bitstream writing offset */
+ *bits_pos = p - bits;
+ if (E) {
+ /* Update the sync pos only if the end bit flag is set */
+ pktz->unpack_last_sync_pos = *bits_pos;
+ }
+
+#if DBG_UNPACKETIZE
+ PJ_LOG(3, ("h264unpack", "Unpacked fragmented H264 NAL unit "
+ "(type=%d, NRI=%d, len=%d)",
+ TYPE, NRI, payload_len));
+#endif
+
+ } else {
+ *bits_pos = 0;
+ return PJ_ENOTSUP;
+ }
+
+ pktz->unpack_prev_lost = PJ_FALSE;
+
+ return PJ_SUCCESS;
+}
diff --git a/pjmedia/src/pjmedia-codec/ilbc.c b/pjmedia/src/pjmedia-codec/ilbc.c
index f30b5f09..c595477f 100644
--- a/pjmedia/src/pjmedia-codec/ilbc.c
+++ b/pjmedia/src/pjmedia-codec/ilbc.c
@@ -113,7 +113,8 @@ static pjmedia_codec_factory_op ilbc_factory_op =
&ilbc_default_attr,
&ilbc_enum_codecs,
&ilbc_alloc_codec,
- &ilbc_dealloc_codec
+ &ilbc_dealloc_codec,
+ &pjmedia_codec_ilbc_deinit
};
/* iLBC factory */
diff --git a/pjmedia/src/pjmedia-codec/ipp_codecs.c b/pjmedia/src/pjmedia-codec/ipp_codecs.c
index 023cdc7a..f5690eee 100644
--- a/pjmedia/src/pjmedia-codec/ipp_codecs.c
+++ b/pjmedia/src/pjmedia-codec/ipp_codecs.c
@@ -103,7 +103,8 @@ static pjmedia_codec_factory_op ipp_factory_op =
&ipp_default_attr,
&ipp_enum_codecs,
&ipp_alloc_codec,
- &ipp_dealloc_codec
+ &ipp_dealloc_codec,
+ &pjmedia_codec_ipp_deinit
};
/* IPP codecs factory */
diff --git a/pjmedia/src/pjmedia-codec/l16.c b/pjmedia/src/pjmedia-codec/l16.c
index 3be8a1b6..71b72009 100644
--- a/pjmedia/src/pjmedia-codec/l16.c
+++ b/pjmedia/src/pjmedia-codec/l16.c
@@ -112,7 +112,8 @@ static pjmedia_codec_factory_op l16_factory_op =
&l16_default_attr,
&l16_enum_codecs,
&l16_alloc_codec,
- &l16_dealloc_codec
+ &l16_dealloc_codec,
+ &pjmedia_codec_l16_deinit
};
/* L16 factory private data */
diff --git a/pjmedia/src/pjmedia-codec/passthrough.c b/pjmedia/src/pjmedia-codec/passthrough.c
index 73eed891..8ded8e2a 100644
--- a/pjmedia/src/pjmedia-codec/passthrough.c
+++ b/pjmedia/src/pjmedia-codec/passthrough.c
@@ -98,7 +98,8 @@ static pjmedia_codec_factory_op codec_factory_op =
&default_attr,
&enum_codecs,
&alloc_codec,
- &dealloc_codec
+ &dealloc_codec,
+ &pjmedia_codec_passthrough_deinit
};
/* Passthrough codecs factory */
diff --git a/pjmedia/src/pjmedia-codec/speex_codec.c b/pjmedia/src/pjmedia-codec/speex_codec.c
index eff88235..d5cc0a57 100644
--- a/pjmedia/src/pjmedia-codec/speex_codec.c
+++ b/pjmedia/src/pjmedia-codec/speex_codec.c
@@ -99,7 +99,8 @@ static pjmedia_codec_factory_op spx_factory_op =
&spx_default_attr,
&spx_enum_codecs,
&spx_alloc_codec,
- &spx_dealloc_codec
+ &spx_dealloc_codec,
+ &pjmedia_codec_speex_deinit
};
/* Index to Speex parameter. */