diff options
Diffstat (limited to 'pjmedia/src/pjmedia-codec')
19 files changed, 12798 insertions, 0 deletions
diff --git a/pjmedia/src/pjmedia-codec/amr_sdp_match.c b/pjmedia/src/pjmedia-codec/amr_sdp_match.c new file mode 100644 index 0000000..44b104b --- /dev/null +++ b/pjmedia/src/pjmedia-codec/amr_sdp_match.c @@ -0,0 +1,176 @@ +/* $Id: amr_sdp_match.c 3911 2011-12-15 06:45:23Z nanang $ */ +/* + * 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-codec/amr_sdp_match.h> +#include <pjmedia/errno.h> +#include <pj/pool.h> +#include <pj/string.h> + + +#define GET_FMTP_IVAL_BASE(ival, base, fmtp, param, default_val) \ + do { \ + pj_str_t s; \ + char *p; \ + p = pj_stristr(&fmtp.fmt_param, ¶m); \ + if (!p) { \ + ival = default_val; \ + break; \ + } \ + pj_strset(&s, p + param.slen, fmtp.fmt_param.slen - \ + (p - fmtp.fmt_param.ptr) - param.slen); \ + ival = pj_strtoul2(&s, NULL, base); \ + } while (0) + +#define GET_FMTP_IVAL(ival, fmtp, param, default_val) \ + GET_FMTP_IVAL_BASE(ival, 10, fmtp, param, default_val) + + +/* Toggle AMR octet-align setting in the fmtp. */ +static pj_status_t amr_toggle_octet_align(pj_pool_t *pool, + pjmedia_sdp_media *media, + unsigned fmt_idx) +{ + pjmedia_sdp_attr *attr; + pjmedia_sdp_fmtp fmtp; + const pj_str_t STR_OCTET_ALIGN = {"octet-align=", 12}; + + enum { MAX_FMTP_STR_LEN = 160 }; + + attr = pjmedia_sdp_media_find_attr2(media, "fmtp", + &media->desc.fmt[fmt_idx]); + /* Check if the AMR media format has FMTP attribute */ + if (attr) { + char *p; + pj_status_t status; + + status = pjmedia_sdp_attr_get_fmtp(attr, &fmtp); + if (status != PJ_SUCCESS) + return status; + + /* Check if the fmtp has octet-align field. */ + p = pj_stristr(&fmtp.fmt_param, &STR_OCTET_ALIGN); + if (p) { + /* It has, just toggle the value */ + unsigned octet_align; + pj_str_t s; + + pj_strset(&s, p + STR_OCTET_ALIGN.slen, fmtp.fmt_param.slen - + (p - fmtp.fmt_param.ptr) - STR_OCTET_ALIGN.slen); + octet_align = pj_strtoul(&s); + *(p + STR_OCTET_ALIGN.slen) = (char)(octet_align? '0' : '1'); + } else { + /* It doesn't, append octet-align field */ + char buf[MAX_FMTP_STR_LEN]; + + pj_ansi_snprintf(buf, MAX_FMTP_STR_LEN, "%.*s;octet-align=1", + (int)fmtp.fmt_param.slen, fmtp.fmt_param.ptr); + attr->value = pj_strdup3(pool, buf); + } + } else { + /* Add new attribute for the AMR media format with octet-align + * field set. + */ + char buf[MAX_FMTP_STR_LEN]; + + attr = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_attr); + attr->name = pj_str("fmtp"); + pj_ansi_snprintf(buf, MAX_FMTP_STR_LEN, "%.*s octet-align=1", + (int)media->desc.fmt[fmt_idx].slen, + media->desc.fmt[fmt_idx].ptr); + attr->value = pj_strdup3(pool, buf); + media->attr[media->attr_count++] = attr; + } + + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) pjmedia_codec_amr_match_sdp( pj_pool_t *pool, + pjmedia_sdp_media *offer, + unsigned o_fmt_idx, + pjmedia_sdp_media *answer, + unsigned a_fmt_idx, + unsigned option) +{ + /* Negotiated format-param field-names constants. */ + const pj_str_t STR_OCTET_ALIGN = {"octet-align=", 12}; + const pj_str_t STR_CRC = {"crc=", 4}; + const pj_str_t STR_ROBUST_SORTING = {"robust-sorting=", 15}; + const pj_str_t STR_INTERLEAVING = {"interleaving=", 13}; + + /* Negotiated params and their default values. */ + unsigned a_octet_align = 0, o_octet_align = 0; + unsigned a_crc = 0, o_crc = 0; + unsigned a_robust_sorting = 0, o_robust_sorting = 0; + unsigned a_interleaving = 0, o_interleaving = 0; + + const pjmedia_sdp_attr *attr_ans; + const pjmedia_sdp_attr *attr_ofr; + pjmedia_sdp_fmtp fmtp; + pj_status_t status; + + /* Parse offerer FMTP */ + attr_ofr = pjmedia_sdp_media_find_attr2(offer, "fmtp", + &offer->desc.fmt[o_fmt_idx]); + if (attr_ofr) { + status = pjmedia_sdp_attr_get_fmtp(attr_ofr, &fmtp); + if (status != PJ_SUCCESS) + return status; + + GET_FMTP_IVAL(o_octet_align, fmtp, STR_OCTET_ALIGN, 0); + GET_FMTP_IVAL(o_crc, fmtp, STR_CRC, 0); + GET_FMTP_IVAL(o_robust_sorting, fmtp, STR_ROBUST_SORTING, 0); + GET_FMTP_IVAL(o_interleaving, fmtp, STR_INTERLEAVING, 0); + } + + /* Parse answerer FMTP */ + attr_ans = pjmedia_sdp_media_find_attr2(answer, "fmtp", + &answer->desc.fmt[a_fmt_idx]); + if (attr_ans) { + status = pjmedia_sdp_attr_get_fmtp(attr_ans, &fmtp); + if (status != PJ_SUCCESS) + return status; + + GET_FMTP_IVAL(a_octet_align, fmtp, STR_OCTET_ALIGN, 0); + GET_FMTP_IVAL(a_crc, fmtp, STR_CRC, 0); + GET_FMTP_IVAL(a_robust_sorting, fmtp, STR_ROBUST_SORTING, 0); + GET_FMTP_IVAL(a_interleaving, fmtp, STR_INTERLEAVING, 0); + } + + /* First, match crc, robust-sorting, interleaving settings */ + if (a_crc != o_crc || + a_robust_sorting != o_robust_sorting || + a_interleaving != o_interleaving) + { + return PJMEDIA_SDP_EFORMATNOTEQUAL; + } + + /* Match octet-align setting */ + if (a_octet_align != o_octet_align) { + /* Check if answer can be modified to match to the offer */ + if (option & PJMEDIA_SDP_NEG_FMT_MATCH_ALLOW_MODIFY_ANSWER) { + status = amr_toggle_octet_align(pool, answer, a_fmt_idx); + return status; + } else { + return PJMEDIA_SDP_EFORMATNOTEQUAL; + } + } + + return PJ_SUCCESS; +} diff --git a/pjmedia/src/pjmedia-codec/audio_codecs.c b/pjmedia/src/pjmedia-codec/audio_codecs.c new file mode 100644 index 0000000..caf63e5 --- /dev/null +++ b/pjmedia/src/pjmedia-codec/audio_codecs.c @@ -0,0 +1,119 @@ +/* $Id: audio_codecs.c 3910 2011-12-15 06:34:25Z nanang $ */ +/* + * 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->passthrough.setting); + 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 */ + +#if PJMEDIA_HAS_OPENCORE_AMRNB_CODEC + /* Register OpenCORE AMR-NB */ + status = pjmedia_codec_opencore_amrnb_init(endpt); + if (status != PJ_SUCCESS) + return status; +#endif + + return PJ_SUCCESS; +} + diff --git a/pjmedia/src/pjmedia-codec/ffmpeg_vid_codecs.c b/pjmedia/src/pjmedia-codec/ffmpeg_vid_codecs.c new file mode 100644 index 0000000..d0e87ef --- /dev/null +++ b/pjmedia/src/pjmedia-codec/ffmpeg_vid_codecs.c @@ -0,0 +1,1818 @@ +/* $Id: ffmpeg_vid_codecs.c 4089 2012-04-26 07:27:06Z nanang $ */ +/* + * 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) +/* 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) +# 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 = 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 = ff->enc_vafp.framebytes; + ff->enc_buf = pj_pool_alloc(ff->pool, ff->enc_buf_size); + + ff->dec_buf_size = 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)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, + 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 = 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 = 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/pjmedia/src/pjmedia-codec/g722.c b/pjmedia/src/pjmedia-codec/g722.c new file mode 100644 index 0000000..7e0e4e4 --- /dev/null +++ b/pjmedia/src/pjmedia-codec/g722.c @@ -0,0 +1,714 @@ +/* $Id: g722.c 3664 2011-07-19 03:42:28Z nanang $ */ +/* + * 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-codec/g722.h> +#include <pjmedia/codec.h> +#include <pjmedia/errno.h> +#include <pjmedia/endpoint.h> +#include <pjmedia/plc.h> +#include <pjmedia/port.h> +#include <pjmedia/silencedet.h> +#include <pj/assert.h> +#include <pj/log.h> +#include <pj/pool.h> +#include <pj/string.h> +#include <pj/os.h> + +#if defined(PJMEDIA_HAS_G722_CODEC) && (PJMEDIA_HAS_G722_CODEC != 0) + +#include "g722/g722_enc.h" +#include "g722/g722_dec.h" + +#define THIS_FILE "g722.c" + +/* Defines */ +#define PTIME (10) +#define SAMPLES_PER_FRAME (16000 * PTIME /1000) +#define FRAME_LEN (80) +#define PLC_DISABLED 0 + +/* Tracing */ +#ifndef PJ_TRACE +# define PJ_TRACE 0 +#endif + +#if PJ_TRACE +# define TRACE_(expr) PJ_LOG(4,expr) +#else +# define TRACE_(expr) +#endif + + +/* Prototypes for G722 factory */ +static pj_status_t g722_test_alloc(pjmedia_codec_factory *factory, + const pjmedia_codec_info *id ); +static pj_status_t g722_default_attr(pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec_param *attr ); +static pj_status_t g722_enum_codecs(pjmedia_codec_factory *factory, + unsigned *count, + pjmedia_codec_info codecs[]); +static pj_status_t g722_alloc_codec(pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec **p_codec); +static pj_status_t g722_dealloc_codec(pjmedia_codec_factory *factory, + pjmedia_codec *codec ); + +/* Prototypes for G722 implementation. */ +static pj_status_t g722_codec_init(pjmedia_codec *codec, + pj_pool_t *pool ); +static pj_status_t g722_codec_open(pjmedia_codec *codec, + pjmedia_codec_param *attr ); +static pj_status_t g722_codec_close(pjmedia_codec *codec ); +static pj_status_t g722_codec_modify(pjmedia_codec *codec, + const pjmedia_codec_param *attr ); +static pj_status_t g722_codec_parse(pjmedia_codec *codec, + void *pkt, + pj_size_t pkt_size, + const pj_timestamp *ts, + unsigned *frame_cnt, + pjmedia_frame frames[]); +static pj_status_t g722_codec_encode(pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output); +static pj_status_t g722_codec_decode(pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output); +#if !PLC_DISABLED +static pj_status_t g722_codec_recover(pjmedia_codec *codec, + unsigned output_buf_len, + struct pjmedia_frame *output); +#endif + +/* Definition for G722 codec operations. */ +static pjmedia_codec_op g722_op = +{ + &g722_codec_init, + &g722_codec_open, + &g722_codec_close, + &g722_codec_modify, + &g722_codec_parse, + &g722_codec_encode, + &g722_codec_decode, +#if !PLC_DISABLED + &g722_codec_recover +#else + NULL +#endif +}; + +/* Definition for G722 codec factory operations. */ +static pjmedia_codec_factory_op g722_factory_op = +{ + &g722_test_alloc, + &g722_default_attr, + &g722_enum_codecs, + &g722_alloc_codec, + &g722_dealloc_codec, + &pjmedia_codec_g722_deinit +}; + +/* G722 factory */ +static struct g722_codec_factory +{ + pjmedia_codec_factory base; + pjmedia_endpt *endpt; + pj_pool_t *pool; + pj_mutex_t *mutex; + pjmedia_codec codec_list; + unsigned pcm_shift; +} g722_codec_factory; + + +/* G722 codec private data. */ +struct g722_data +{ + g722_enc_t encoder; + g722_dec_t decoder; + unsigned pcm_shift; + pj_int16_t pcm_clip_mask; + pj_bool_t plc_enabled; + pj_bool_t vad_enabled; + pjmedia_silence_det *vad; + pj_timestamp last_tx; +#if !PLC_DISABLED + pjmedia_plc *plc; +#endif +}; + + + +/* + * Initialize and register G722 codec factory to pjmedia endpoint. + */ +PJ_DEF(pj_status_t) pjmedia_codec_g722_init( pjmedia_endpt *endpt ) +{ + pjmedia_codec_mgr *codec_mgr; + pj_status_t status; + + if (g722_codec_factory.pool != NULL) + return PJ_SUCCESS; + + /* Create G722 codec factory. */ + g722_codec_factory.base.op = &g722_factory_op; + g722_codec_factory.base.factory_data = NULL; + g722_codec_factory.endpt = endpt; + g722_codec_factory.pcm_shift = PJMEDIA_G722_DEFAULT_PCM_SHIFT; + + g722_codec_factory.pool = pjmedia_endpt_create_pool(endpt, "g722", 1000, + 1000); + if (!g722_codec_factory.pool) + return PJ_ENOMEM; + + pj_list_init(&g722_codec_factory.codec_list); + + /* Create mutex. */ + status = pj_mutex_create_simple(g722_codec_factory.pool, "g722", + &g722_codec_factory.mutex); + if (status != PJ_SUCCESS) + goto on_error; + + /* Get the codec manager. */ + codec_mgr = pjmedia_endpt_get_codec_mgr(endpt); + if (!codec_mgr) { + status = PJ_EINVALIDOP; + goto on_error; + } + + /* Register codec factory to endpoint. */ + status = pjmedia_codec_mgr_register_factory(codec_mgr, + &g722_codec_factory.base); + if (status != PJ_SUCCESS) + goto on_error; + + TRACE_((THIS_FILE, "G722 codec factory initialized")); + + /* Done. */ + return PJ_SUCCESS; + +on_error: + pj_pool_release(g722_codec_factory.pool); + g722_codec_factory.pool = NULL; + return status; +} + +/* + * Unregister G722 codec factory from pjmedia endpoint and deinitialize + * the G722 codec library. + */ +PJ_DEF(pj_status_t) pjmedia_codec_g722_deinit(void) +{ + pjmedia_codec_mgr *codec_mgr; + pj_status_t status; + + if (g722_codec_factory.pool == NULL) + return PJ_SUCCESS; + + /* Get the codec manager. */ + codec_mgr = pjmedia_endpt_get_codec_mgr(g722_codec_factory.endpt); + if (!codec_mgr) { + pj_pool_release(g722_codec_factory.pool); + g722_codec_factory.pool = NULL; + return PJ_EINVALIDOP; + } + + /* Unregister G722 codec factory. */ + status = pjmedia_codec_mgr_unregister_factory(codec_mgr, + &g722_codec_factory.base); + + /* Destroy mutex. */ + pj_mutex_destroy(g722_codec_factory.mutex); + + /* Destroy pool. */ + pj_pool_release(g722_codec_factory.pool); + g722_codec_factory.pool = NULL; + + TRACE_((THIS_FILE, "G722 codec factory shutdown")); + return status; +} + + +/* + * Set level adjustment. + */ +PJ_DEF(pj_status_t) pjmedia_codec_g722_set_pcm_shift(unsigned val) +{ + g722_codec_factory.pcm_shift = val; + return PJ_SUCCESS; +} + + +/* + * Check if factory can allocate the specified codec. + */ +static pj_status_t g722_test_alloc(pjmedia_codec_factory *factory, + const pjmedia_codec_info *info ) +{ + PJ_UNUSED_ARG(factory); + + /* Check payload type. */ + if (info->pt != PJMEDIA_RTP_PT_G722) + return PJMEDIA_CODEC_EUNSUP; + + /* Ignore the rest, since it's static payload type. */ + + return PJ_SUCCESS; +} + +/* + * Generate default attribute. + */ +static pj_status_t g722_default_attr( pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec_param *attr ) +{ + PJ_UNUSED_ARG(factory); + PJ_UNUSED_ARG(id); + + pj_bzero(attr, sizeof(pjmedia_codec_param)); + attr->info.clock_rate = 16000; + attr->info.channel_cnt = 1; + attr->info.avg_bps = 64000; + attr->info.max_bps = 64000; + attr->info.pcm_bits_per_sample = 16; + attr->info.frm_ptime = PTIME; + attr->info.pt = PJMEDIA_RTP_PT_G722; + + attr->setting.frm_per_pkt = 2; + attr->setting.vad = 1; + attr->setting.plc = 1; + + /* Default all other flag bits disabled. */ + + return PJ_SUCCESS; +} + +/* + * Enum codecs supported by this factory (i.e. only G722!). + */ +static pj_status_t g722_enum_codecs(pjmedia_codec_factory *factory, + unsigned *count, + pjmedia_codec_info codecs[]) +{ + PJ_UNUSED_ARG(factory); + PJ_ASSERT_RETURN(codecs && *count > 0, PJ_EINVAL); + + pj_bzero(&codecs[0], sizeof(pjmedia_codec_info)); + codecs[0].encoding_name = pj_str("G722"); + codecs[0].pt = PJMEDIA_RTP_PT_G722; + codecs[0].type = PJMEDIA_TYPE_AUDIO; + codecs[0].clock_rate = 16000; + codecs[0].channel_cnt = 1; + + *count = 1; + + return PJ_SUCCESS; +} + +/* + * Allocate a new G722 codec instance. + */ +static pj_status_t g722_alloc_codec(pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec **p_codec) +{ + pjmedia_codec *codec; + struct g722_data *g722_data; + + PJ_ASSERT_RETURN(factory && id && p_codec, PJ_EINVAL); + PJ_ASSERT_RETURN(factory == &g722_codec_factory.base, PJ_EINVAL); + + pj_mutex_lock(g722_codec_factory.mutex); + + /* Get free nodes, if any. */ + if (!pj_list_empty(&g722_codec_factory.codec_list)) { + codec = g722_codec_factory.codec_list.next; + pj_list_erase(codec); + } else { + pj_status_t status; + + codec = PJ_POOL_ZALLOC_T(g722_codec_factory.pool, pjmedia_codec); + PJ_ASSERT_RETURN(codec != NULL, PJ_ENOMEM); + codec->op = &g722_op; + codec->factory = factory; + + g722_data = PJ_POOL_ZALLOC_T(g722_codec_factory.pool, struct g722_data); + codec->codec_data = g722_data; + +#if !PLC_DISABLED + /* Create PLC */ + status = pjmedia_plc_create(g722_codec_factory.pool, 16000, + SAMPLES_PER_FRAME, 0, &g722_data->plc); + if (status != PJ_SUCCESS) { + pj_mutex_unlock(g722_codec_factory.mutex); + return status; + } +#endif + + /* Create silence detector */ + status = pjmedia_silence_det_create(g722_codec_factory.pool, + 16000, SAMPLES_PER_FRAME, + &g722_data->vad); + if (status != PJ_SUCCESS) { + pj_mutex_unlock(g722_codec_factory.mutex); + TRACE_((THIS_FILE, "Create silence detector failed (status = %d)", + status)); + return status; + } + } + + + pj_mutex_unlock(g722_codec_factory.mutex); + + *p_codec = codec; + return PJ_SUCCESS; +} + +/* + * Free codec. + */ +static pj_status_t g722_dealloc_codec(pjmedia_codec_factory *factory, + pjmedia_codec *codec ) +{ + struct g722_data *g722_data; + int i; + + PJ_ASSERT_RETURN(factory && codec, PJ_EINVAL); + PJ_ASSERT_RETURN(factory == &g722_codec_factory.base, PJ_EINVAL); + + g722_data = (struct g722_data*) codec->codec_data; + + /* Close codec, if it's not closed. */ + g722_codec_close(codec); + +#if !PLC_DISABLED + /* Clear left samples in the PLC, since codec+plc will be reused + * next time. + */ + for (i=0; i<2; ++i) { + pj_int16_t frame[SAMPLES_PER_FRAME]; + pjmedia_zero_samples(frame, PJ_ARRAY_SIZE(frame)); + pjmedia_plc_save(g722_data->plc, frame); + } +#else + PJ_UNUSED_ARG(i); +#endif + + /* Re-init silence_period */ + pj_set_timestamp32(&g722_data->last_tx, 0, 0); + + /* Put in the free list. */ + pj_mutex_lock(g722_codec_factory.mutex); + pj_list_push_front(&g722_codec_factory.codec_list, codec); + pj_mutex_unlock(g722_codec_factory.mutex); + + return PJ_SUCCESS; +} + +/* + * Init codec. + */ +static pj_status_t g722_codec_init(pjmedia_codec *codec, + pj_pool_t *pool ) +{ + PJ_UNUSED_ARG(codec); + PJ_UNUSED_ARG(pool); + return PJ_SUCCESS; +} + +/* + * Open codec. + */ +static pj_status_t g722_codec_open(pjmedia_codec *codec, + pjmedia_codec_param *attr ) +{ + struct g722_data *g722_data = (struct g722_data*) codec->codec_data; + pj_status_t status; + + PJ_ASSERT_RETURN(codec && attr, PJ_EINVAL); + PJ_ASSERT_RETURN(g722_data != NULL, PJ_EINVALIDOP); + + status = g722_enc_init(&g722_data->encoder); + if (status != PJ_SUCCESS) { + TRACE_((THIS_FILE, "g722_enc_init() failed, status=%d", status)); + pj_mutex_unlock(g722_codec_factory.mutex); + return PJMEDIA_CODEC_EFAILED; + } + + status = g722_dec_init(&g722_data->decoder); + if (status != PJ_SUCCESS) { + TRACE_((THIS_FILE, "g722_dec_init() failed, status=%d", status)); + pj_mutex_unlock(g722_codec_factory.mutex); + return PJMEDIA_CODEC_EFAILED; + } + + g722_data->vad_enabled = (attr->setting.vad != 0); + g722_data->plc_enabled = (attr->setting.plc != 0); + g722_data->pcm_shift = g722_codec_factory.pcm_shift; + g722_data->pcm_clip_mask = (pj_int16_t)(1<<g722_codec_factory.pcm_shift)-1; + g722_data->pcm_clip_mask <<= (16-g722_codec_factory.pcm_shift); + + TRACE_((THIS_FILE, "G722 codec opened: vad=%d, plc=%d", + g722_data->vad_enabled, g722_data->plc_enabled)); + return PJ_SUCCESS; +} + +/* + * Close codec. + */ +static pj_status_t g722_codec_close( pjmedia_codec *codec ) +{ + /* The codec, encoder, and decoder will be reused, so there's + * nothing to do here + */ + + PJ_UNUSED_ARG(codec); + + TRACE_((THIS_FILE, "G722 codec closed")); + return PJ_SUCCESS; +} + + +/* + * Modify codec settings. + */ +static pj_status_t g722_codec_modify(pjmedia_codec *codec, + const pjmedia_codec_param *attr ) +{ + struct g722_data *g722_data = (struct g722_data*) codec->codec_data; + + pj_assert(g722_data != NULL); + + g722_data->vad_enabled = (attr->setting.vad != 0); + g722_data->plc_enabled = (attr->setting.plc != 0); + + TRACE_((THIS_FILE, "G722 codec modified: vad=%d, plc=%d", + g722_data->vad_enabled, g722_data->plc_enabled)); + return PJ_SUCCESS; +} + + +/* + * Get frames in the packet. + */ +static pj_status_t g722_codec_parse(pjmedia_codec *codec, + void *pkt, + pj_size_t pkt_size, + const pj_timestamp *ts, + unsigned *frame_cnt, + pjmedia_frame frames[]) +{ + unsigned count = 0; + + PJ_UNUSED_ARG(codec); + + PJ_ASSERT_RETURN(frame_cnt, PJ_EINVAL); + + TRACE_((THIS_FILE, "G722 parse(): input len=%d", pkt_size)); + + while (pkt_size >= FRAME_LEN && count < *frame_cnt) { + frames[count].type = PJMEDIA_FRAME_TYPE_AUDIO; + frames[count].buf = pkt; + frames[count].size = FRAME_LEN; + frames[count].timestamp.u64 = ts->u64 + count * SAMPLES_PER_FRAME; + + pkt = ((char*)pkt) + FRAME_LEN; + pkt_size -= FRAME_LEN; + + ++count; + } + + TRACE_((THIS_FILE, "G722 parse(): got %d frames", count)); + + *frame_cnt = count; + return PJ_SUCCESS; +} + +/* + * Encode frame. + */ +static pj_status_t g722_codec_encode(pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output) +{ + struct g722_data *g722_data = (struct g722_data*) codec->codec_data; + pj_status_t status; + + pj_assert(g722_data && input && output); + + PJ_ASSERT_RETURN((input->size >> 2) <= output_buf_len, + PJMEDIA_CODEC_EFRMTOOSHORT); + + /* Detect silence */ + if (g722_data->vad_enabled) { + pj_bool_t is_silence; + pj_int32_t silence_duration; + + silence_duration = pj_timestamp_diff32(&g722_data->last_tx, + &input->timestamp); + + is_silence = pjmedia_silence_det_detect(g722_data->vad, + (const pj_int16_t*) input->buf, + (input->size >> 1), + NULL); + if (is_silence && + (PJMEDIA_CODEC_MAX_SILENCE_PERIOD == -1 || + silence_duration < PJMEDIA_CODEC_MAX_SILENCE_PERIOD*16000/1000)) + { + output->type = PJMEDIA_FRAME_TYPE_NONE; + output->buf = NULL; + output->size = 0; + output->timestamp = input->timestamp; + return PJ_SUCCESS; + } else { + g722_data->last_tx = input->timestamp; + } + } + + /* Adjust input signal level from 16-bit to 14-bit */ + if (g722_data->pcm_shift) { + pj_int16_t *p, *end; + + p = (pj_int16_t*)input->buf; + end = p + input->size/2; + while (p < end) { + *p++ >>= g722_data->pcm_shift; + } + } + + /* Encode to temporary buffer */ + output->size = output_buf_len; + status = g722_enc_encode(&g722_data->encoder, (pj_int16_t*)input->buf, + (input->size >> 1), output->buf, &output->size); + if (status != PJ_SUCCESS) { + output->size = 0; + output->buf = NULL; + output->type = PJMEDIA_FRAME_TYPE_NONE; + TRACE_((THIS_FILE, "G722 encode() status: %d", status)); + return PJMEDIA_CODEC_EFAILED; + } + + output->type = PJMEDIA_FRAME_TYPE_AUDIO; + output->timestamp = input->timestamp; + + TRACE_((THIS_FILE, "G722 encode(): size=%d", output->size)); + return PJ_SUCCESS; +} + +/* + * Decode frame. + */ +static pj_status_t g722_codec_decode(pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output) +{ + struct g722_data *g722_data = (struct g722_data*) codec->codec_data; + pj_status_t status; + + pj_assert(g722_data != NULL); + PJ_ASSERT_RETURN(input && output, PJ_EINVAL); + + TRACE_((THIS_FILE, "G722 decode(): inbuf=%p, insize=%d, outbuf=%p," + "outsize=%d", + input->buf, input->size, output->buf, output_buf_len)); + + if (output_buf_len < SAMPLES_PER_FRAME * 2) { + TRACE_((THIS_FILE, "G722 decode() ERROR: PJMEDIA_CODEC_EPCMTOOSHORT")); + return PJMEDIA_CODEC_EPCMTOOSHORT; + } + + if (input->size != FRAME_LEN) { + TRACE_((THIS_FILE, "G722 decode() ERROR: PJMEDIA_CODEC_EFRMTOOSHORT")); + return PJMEDIA_CODEC_EFRMTOOSHORT; + } + + + /* Decode */ + output->size = SAMPLES_PER_FRAME; + status = g722_dec_decode(&g722_data->decoder, input->buf, input->size, + (pj_int16_t*)output->buf, &output->size); + if (status != PJ_SUCCESS) { + TRACE_((THIS_FILE, "G722 decode() status: %d", status)); + return PJMEDIA_CODEC_EFAILED; + } + + pj_assert(output->size == SAMPLES_PER_FRAME); + + /* Adjust input signal level from 14-bit to 16-bit */ + if (g722_data->pcm_shift) { + pj_int16_t *p, *end; + + p = (pj_int16_t*)output->buf; + end = p + output->size; + while (p < end) { +#if PJMEDIA_G722_STOP_PCM_SHIFT_ON_CLIPPING + /* If there is clipping, stop the PCM shifting */ + if (*p & g722_data->pcm_clip_mask) { + g722_data->pcm_shift = 0; + break; + } +#endif + *p++ <<= g722_data->pcm_shift; + } + } + + output->size = SAMPLES_PER_FRAME * 2; + output->type = PJMEDIA_FRAME_TYPE_AUDIO; + output->timestamp = input->timestamp; + +#if !PLC_DISABLED + if (g722_data->plc_enabled) + pjmedia_plc_save(g722_data->plc, (pj_int16_t*)output->buf); +#endif + + TRACE_((THIS_FILE, "G722 decode done")); + return PJ_SUCCESS; +} + + +#if !PLC_DISABLED +/* + * Recover lost frame. + */ +static pj_status_t g722_codec_recover(pjmedia_codec *codec, + unsigned output_buf_len, + struct pjmedia_frame *output) +{ + struct g722_data *g722_data = (struct g722_data*)codec->codec_data; + + PJ_ASSERT_RETURN(g722_data->plc_enabled, PJ_EINVALIDOP); + + PJ_ASSERT_RETURN(output_buf_len >= SAMPLES_PER_FRAME * 2, + PJMEDIA_CODEC_EPCMTOOSHORT); + + pjmedia_plc_generate(g722_data->plc, (pj_int16_t*)output->buf); + + output->size = SAMPLES_PER_FRAME * 2; + output->type = PJMEDIA_FRAME_TYPE_AUDIO; + + return PJ_SUCCESS; +} +#endif + +#endif // PJMEDIA_HAS_G722_CODEC + diff --git a/pjmedia/src/pjmedia-codec/g722/g722_dec.c b/pjmedia/src/pjmedia-codec/g722/g722_dec.c new file mode 100644 index 0000000..c6edc36 --- /dev/null +++ b/pjmedia/src/pjmedia-codec/g722/g722_dec.c @@ -0,0 +1,549 @@ +/* $Id: g722_dec.c 3553 2011-05-05 06:14:19Z nanang $ */ +/* + * 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 + */ +/* + * Based on implementation found in Carnegie Mellon Speech Group Software + * depository (ftp://ftp.cs.cmu.edu/project/fgdata/index.html). No copyright + * was claimed in the original source codes. + */ +#include <pjmedia/errno.h> +#include <pj/assert.h> +#include <pj/pool.h> + +#include "g722_dec.h" + +#if defined(PJMEDIA_HAS_G722_CODEC) && (PJMEDIA_HAS_G722_CODEC != 0) + +#define MODE 1 + +#define SATURATE(v, max, min) \ + if (v>max) v = max; \ + else if (v<min) v = min + +extern const int g722_qmf_coeff[24]; + +static const int qm4[16] = +{ + 0, -20456, -12896, -8968, + -6288, -4240, -2584, -1200, + 20456, 12896, 8968, 6288, + 4240, 2584, 1200, 0 +}; +static const int ilb[32] = { + 2048, 2093, 2139, 2186, 2233, 2282, 2332, + 2383, 2435, 2489, 2543, 2599, 2656, 2714, + 2774, 2834, 2896, 2960, 3025, 3091, 3158, + 3228, 3298, 3371, 3444, 3520, 3597, 3676, + 3756, 3838, 3922, 4008 +}; + + +static int block2l (int il, int detl) +{ + int dlt ; + int ril, wd2 ; + + /* INVQAL */ + ril = il >> 2 ; + wd2 = qm4[ril] ; + dlt = (detl * wd2) >> 15 ; + + return (dlt) ; +} + + +static int block3l (g722_dec_t *dec, int il) +{ + int detl ; + int ril, il4, wd, wd1, wd2, wd3, nbpl, depl ; + static const int wl[8] = { + -60, -30, 58, 172, 334, 538, 1198, 3042 + }; + static const int rl42[16] = { + 0, 7, 6, 5, 4, 3, 2, 1, 7, 6, 5, 4, 3, 2, 1, 0 + }; + + /* LOGSCL */ + ril = il >> 2 ; + il4 = rl42[ril] ; + wd = (dec->nbl * 32512) >> 15 ; + nbpl = wd + wl[il4] ; + + if (nbpl < 0) nbpl = 0 ; + if (nbpl > 18432) nbpl = 18432 ; + + /* SCALEL */ + wd1 = (nbpl >> 6) & 31 ; + wd2 = nbpl >> 11 ; + if ((8 - wd2) < 0) wd3 = ilb[wd1] << (wd2 - 8) ; + else wd3 = ilb[wd1] >> (8 - wd2) ; + depl = wd3 << 2 ; + + /* DELAYA */ + dec->nbl = nbpl ; + /* DELAYL */ + detl = depl ; + + return (detl) ; +} + + +static int block4l (g722_dec_t *dec, int dl) +{ + int sl = dec->slow ; + int i ; + int wd, wd1, wd2, wd3, wd4, wd5/*, wd6 */; + + dec->dlt[0] = dl; + + /* RECONS */ + dec->rlt[0] = sl + dec->dlt[0] ; + SATURATE(dec->rlt[0], 32767, -32768); + + /* PARREC */ + dec->plt[0] = dec->dlt[0] + dec->szl ; + SATURATE(dec->plt[0], 32767, -32768); + + /* UPPOL2 */ + dec->sgl[0] = dec->plt[0] >> 15 ; + dec->sgl[1] = dec->plt[1] >> 15 ; + dec->sgl[2] = dec->plt[2] >> 15 ; + + wd1 = dec->al[1] << 2; + SATURATE(wd1, 32767, -32768); + + if ( dec->sgl[0] == dec->sgl[1] ) wd2 = - wd1 ; + else wd2 = wd1 ; + if (wd2 > 32767) wd2 = 32767; + wd2 = wd2 >> 7 ; + + if ( dec->sgl[0] == dec->sgl[2] ) wd3 = 128 ; + else wd3 = - 128 ; + + wd4 = wd2 + wd3 ; + wd5 = (dec->al[2] * 32512) >> 15 ; + + dec->apl[2] = wd4 + wd5 ; + SATURATE(dec->apl[2], 12288, -12288); + + /* UPPOL1 */ + dec->sgl[0] = dec->plt[0] >> 15 ; + dec->sgl[1] = dec->plt[1] >> 15 ; + + if ( dec->sgl[0] == dec->sgl[1] ) wd1 = 192 ; + else wd1 = - 192 ; + + wd2 = (dec->al[1] * 32640) >> 15 ; + + dec->apl[1] = wd1 + wd2 ; + SATURATE(dec->apl[1], 32767, -32768); + + wd3 = (15360 - dec->apl[2]) ; + SATURATE(wd3, 32767, -32768); + if ( dec->apl[1] > wd3) dec->apl[1] = wd3 ; + if ( dec->apl[1] < -wd3) dec->apl[1] = -wd3 ; + + /* UPZERO */ + if ( dec->dlt[0] == 0 ) wd1 = 0 ; + else wd1 = 128 ; + + dec->sgl[0] = dec->dlt[0] >> 15 ; + + for ( i = 1; i < 7; i++ ) { + dec->sgl[i] = dec->dlt[i] >> 15 ; + if ( dec->sgl[i] == dec->sgl[0] ) wd2 = wd1 ; + else wd2 = - wd1 ; + wd3 = (dec->bl[i] * 32640) >> 15 ; + dec->bpl[i] = wd2 + wd3 ; + SATURATE(dec->bpl[i], 32767, -32768); + } + + /* DELAYA */ + for ( i = 6; i > 0; i-- ) { + dec->dlt[i] = dec->dlt[i-1] ; + dec->bl[i] = dec->bpl[i] ; + } + + for ( i = 2; i > 0; i-- ) { + dec->rlt[i] = dec->rlt[i-1] ; + dec->plt[i] = dec->plt[i-1] ; + dec->al[i] = dec->apl[i] ; + } + + /* FILTEP */ + wd1 = dec->rlt[1] << 1; + SATURATE(wd1, 32767, -32768); + wd1 = ( dec->al[1] * wd1 ) >> 15 ; + + wd2 = dec->rlt[2] << 1; + SATURATE(wd2, 32767, -32768); + wd2 = ( dec->al[2] * wd2 ) >> 15 ; + + dec->spl = wd1 + wd2 ; + SATURATE(dec->spl, 32767, -32768); + + /* FILTEZ */ + dec->szl = 0 ; + for (i=6; i>0; i--) { + wd = dec->dlt[i] << 1; + SATURATE(wd, 32767, -32768); + dec->szl += (dec->bl[i] * wd) >> 15 ; + SATURATE(dec->szl, 32767, -32768); + } + + /* PREDIC */ + sl = dec->spl + dec->szl ; + SATURATE(sl, 32767, -32768); + + return (sl) ; +} + +static int block5l (int ilr, int sl, int detl, int mode) +{ + int yl ; + int ril, dl, wd2 = 0; + static const int qm5[32] = { + -280, -280, -23352, -17560, + -14120, -11664, -9752, -8184, + -6864, -5712, -4696, -3784, + -2960, -2208, -1520, -880, + 23352, 17560, 14120, 11664, + 9752, 8184, 6864, 5712, + 4696, 3784, 2960, 2208, + 1520, 880, 280, -280 + }; + static const int qm6[64] = { + -136, -136, -136, -136, + -24808, -21904, -19008, -16704, + -14984, -13512, -12280, -11192, + -10232, -9360, -8576, -7856, + -7192, -6576, -6000, -5456, + -4944, -4464, -4008, -3576, + -3168, -2776, -2400, -2032, + -1688, -1360, -1040, -728, + 24808, 21904, 19008, 16704, + 14984, 13512, 12280, 11192, + 10232, 9360, 8576, 7856, + 7192, 6576, 6000, 5456, + 4944, 4464, 4008, 3576, + 3168, 2776, 2400, 2032, + 1688, 1360, 1040, 728, + 432, 136, -432, -136 + }; + + /* INVQBL */ + if (mode == 1) { + ril = ilr ; + wd2 = qm6[ril] ; + } + + if (mode == 2) { + ril = ilr >> 1 ; + wd2 = qm5[ril] ; + } + + if (mode == 3) { + ril = ilr >> 2 ; + wd2 = qm4[ril] ; + } + + dl = (detl * wd2 ) >> 15 ; + + /* RECONS */ + yl = sl + dl ; + SATURATE(yl, 32767, -32768); + + return (yl) ; +} + +static int block6l (int yl) +{ + int rl ; + + rl = yl ; + SATURATE(rl, 16383, -16384); + + return (rl) ; +} + +static int block2h (int ih, int deth) +{ + int dh ; + int wd2 ; + static const int qm2[4] = {-7408, -1616, 7408, 1616} ; + + /* INVQAH */ + wd2 = qm2[ih] ; + dh = (deth * wd2) >> 15 ; + + return (dh) ; +} + +static int block3h (g722_dec_t *dec, int ih) +{ + int deth ; + int ih2, wd, wd1, wd2, wd3, nbph, deph ; + static const int wh[3] = {0, -214, 798} ; + static const int rh2[4] = {2, 1, 2, 1} ; + + /* LOGSCH */ + ih2 = rh2[ih] ; + wd = (dec->nbh * 32512) >> 15 ; + nbph = wd + wh[ih2] ; + + if (nbph < 0) nbph = 0 ; + if (nbph > 22528) nbph = 22528 ; + + + /* SCALEH */ + wd1 = (nbph >> 6) & 31 ; + wd2 = nbph >> 11 ; + if ((10 - wd2) < 0) wd3 = ilb[wd1] << (wd2 - 10) ; + else wd3 = ilb[wd1] >> (10 - wd2) ; + deph = wd3 << 2 ; + + /* DELAYA */ + dec->nbh = nbph ; + + /* DELAYH */ + deth = deph ; + + return (deth) ; +} + +static int block4h (g722_dec_t *dec, int d) +{ + int sh = dec->shigh; + int i ; + int wd, wd1, wd2, wd3, wd4, wd5/*, wd6 */; + + dec->dh[0] = d; + + /* RECONS */ + dec->rh[0] = sh + dec->dh[0] ; + SATURATE(dec->rh[0], 32767, -32768); + + /* PARREC */ + dec->ph[0] = dec->dh[0] + dec->szh ; + SATURATE(dec->ph[0], 32767, -32768); + + /* UPPOL2 */ + dec->sgh[0] = dec->ph[0] >> 15 ; + dec->sgh[1] = dec->ph[1] >> 15 ; + dec->sgh[2] = dec->ph[2] >> 15 ; + + wd1 = dec->ah[1] << 2; + SATURATE(wd1, 32767, -32768); + + if ( dec->sgh[0] == dec->sgh[1] ) wd2 = - wd1 ; + else wd2 = wd1 ; + if (wd2 > 32767) wd2 = 32767; + + wd2 = wd2 >> 7 ; + + if ( dec->sgh[0] == dec->sgh[2] ) wd3 = 128 ; + else wd3 = - 128 ; + + wd4 = wd2 + wd3 ; + wd5 = (dec->ah[2] * 32512) >> 15 ; + + dec->aph[2] = wd4 + wd5 ; + SATURATE(dec->aph[2], 12288, -12288); + + /* UPPOL1 */ + dec->sgh[0] = dec->ph[0] >> 15 ; + dec->sgh[1] = dec->ph[1] >> 15 ; + + if ( dec->sgh[0] == dec->sgh[1] ) wd1 = 192 ; + else wd1 = - 192 ; + + wd2 = (dec->ah[1] * 32640) >> 15 ; + + dec->aph[1] = wd1 + wd2 ; + SATURATE(dec->aph[1], 32767, -32768); + //dec->aph[2]? + //if (aph[2] > 32767) aph[2] = 32767; + //if (aph[2] < -32768) aph[2] = -32768; + + wd3 = (15360 - dec->aph[2]) ; + SATURATE(wd3, 32767, -32768); + if ( dec->aph[1] > wd3) dec->aph[1] = wd3 ; + if ( dec->aph[1] < -wd3) dec->aph[1] = -wd3 ; + + /* UPZERO */ + if ( dec->dh[0] == 0 ) wd1 = 0 ; + if ( dec->dh[0] != 0 ) wd1 = 128 ; + + dec->sgh[0] = dec->dh[0] >> 15 ; + + for ( i = 1; i < 7; i++ ) { + dec->sgh[i] = dec->dh[i] >> 15 ; + if ( dec->sgh[i] == dec->sgh[0] ) wd2 = wd1 ; + else wd2 = - wd1 ; + wd3 = (dec->bh[i] * 32640) >> 15 ; + dec->bph[i] = wd2 + wd3 ; + } + + /* DELAYA */ + for ( i = 6; i > 0; i-- ) { + dec->dh[i] = dec->dh[i-1] ; + dec->bh[i] = dec->bph[i] ; + } + + for ( i = 2; i > 0; i-- ) { + dec->rh[i] = dec->rh[i-1] ; + dec->ph[i] = dec->ph[i-1] ; + dec->ah[i] = dec->aph[i] ; + } + + /* FILTEP */ + wd1 = dec->rh[1] << 1 ; + SATURATE(wd1, 32767, -32768); + wd1 = ( dec->ah[1] * wd1 ) >> 15 ; + + wd2 = dec->rh[2] << 1; + SATURATE(wd2, 32767, -32768); + wd2 = ( dec->ah[2] * wd2 ) >> 15 ; + + dec->sph = wd1 + wd2 ; + SATURATE(dec->sph, 32767, -32768); + + /* FILTEZ */ + dec->szh = 0 ; + for (i=6; i>0; i--) { + wd = dec->dh[i] << 1; + SATURATE(wd, 32767, -32768); + dec->szh += (dec->bh[i] * wd) >> 15 ; + SATURATE(dec->szh, 32767, -32768); + } + + /* PREDIC */ + sh = dec->sph + dec->szh ; + SATURATE(sh, 32767, -32768); + + return (sh) ; +} + +static int block5h (int dh, int sh) +{ + int rh ; + + rh = dh + sh; + SATURATE(rh, 16383, -16384); + + return (rh) ; +} + +void rx_qmf(g722_dec_t *dec, int rl, int rh, int *xout1, int *xout2) +{ + int i; + + pj_memmove(&dec->xd[1], dec->xd, 11*sizeof(dec->xd[0])); + pj_memmove(&dec->xs[1], dec->xs, 11*sizeof(dec->xs[0])); + + /* RECA */ + dec->xd[0] = rl - rh ; + if (dec->xd[0] > 16383) dec->xd[0] = 16383; + else if (dec->xd[0] < -16384) dec->xd[0] = -16384; + + /* RECB */ + dec->xs[0] = rl + rh ; + if (dec->xs[0] > 16383) dec->xs[0] = 16383; + else if (dec->xs[0] < -16384) dec->xs[0] = -16384; + + /* ACCUMC */ + *xout1 = 0; + for (i=0; i<12; ++i) *xout1 += dec->xd[i] * g722_qmf_coeff[2*i]; + *xout1 = *xout1 >> 12 ; + if (*xout1 > 16383) *xout1 = 16383 ; + else if (*xout1 < -16384) *xout1 = -16384 ; + + /* ACCUMD */ + *xout2 = 0; + for (i=0; i<12; ++i) *xout2 += dec->xs[i] * g722_qmf_coeff[2*i+1]; + *xout2 = *xout2 >> 12 ; + if (*xout2 > 16383) *xout2 = 16383 ; + else if (*xout2 < -16384) *xout2 = -16384 ; +} + + +PJ_DEF(pj_status_t) g722_dec_init(g722_dec_t *dec) +{ + PJ_ASSERT_RETURN(dec, PJ_EINVAL); + + pj_bzero(dec, sizeof(g722_dec_t)); + + dec->detlow = 32; + dec->dethigh = 8; + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) g722_dec_decode( g722_dec_t *dec, + void *in, + pj_size_t in_size, + pj_int16_t out[], + pj_size_t *nsamples) +{ + unsigned i; + int ilowr, ylow, rlow, dlowt; + int ihigh, rhigh, dhigh; + int pcm1, pcm2; + pj_uint8_t *in_ = (pj_uint8_t*) in; + + PJ_ASSERT_RETURN(dec && in && in_size && out && nsamples, PJ_EINVAL); + PJ_ASSERT_RETURN(*nsamples >= (in_size << 1), PJ_ETOOSMALL); + + for(i = 0; i < in_size; ++i) { + ilowr = in_[i] & 63; + ihigh = (in_[i] >> 6) & 3; + + /* low band decoder */ + ylow = block5l (ilowr, dec->slow, dec->detlow, MODE) ; + rlow = block6l (ylow) ; + dlowt = block2l (ilowr, dec->detlow) ; + dec->detlow = block3l (dec, ilowr) ; + dec->slow = block4l (dec, dlowt) ; + /* rlow <= output low band pcm */ + + /* high band decoder */ + dhigh = block2h (ihigh, dec->dethigh) ; + rhigh = block5h (dhigh, dec->shigh) ; + dec->dethigh = block3h (dec, ihigh) ; + dec->shigh = block4h (dec, dhigh) ; + /* rhigh <= output high band pcm */ + + rx_qmf(dec, rlow, rhigh, &pcm1, &pcm2); + out[i*2] = (pj_int16_t)pcm1; + out[i*2+1] = (pj_int16_t)pcm2; + } + + *nsamples = in_size << 1; + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) g722_dec_deinit(g722_dec_t *dec) +{ + pj_bzero(dec, sizeof(g722_dec_t)); + + return PJ_SUCCESS; +} + +#endif diff --git a/pjmedia/src/pjmedia-codec/g722/g722_dec.h b/pjmedia/src/pjmedia-codec/g722/g722_dec.h new file mode 100644 index 0000000..3cf841e --- /dev/null +++ b/pjmedia/src/pjmedia-codec/g722/g722_dec.h @@ -0,0 +1,79 @@ +/* $Id: g722_dec.h 3553 2011-05-05 06:14:19Z nanang $ */ +/* + * 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 + */ +/* + * Based on implementation found in Carnegie Mellon Speech Group Software + * depository (ftp://ftp.cs.cmu.edu/project/fgdata/index.html). No copyright + * was claimed in the original source codes. + */ +#ifndef __PJMEDIA_CODEC_G722_DEC_H__ +#define __PJMEDIA_CODEC_G722_DEC_H__ + +#include <pjmedia-codec/types.h> + +/* Decoder state */ +typedef struct g722_dec_t { + /* PCM low band */ + int slow; + int detlow; + int spl; + int szl; + int rlt [3]; + int al [3]; + int apl [3]; + int plt [3]; + int dlt [7]; + int bl [7]; + int bpl [7]; + int sgl [7]; + int nbl; + + /* PCM high band*/ + int shigh; + int dethigh; + int sph; + int szh; + int rh [3]; + int ah [3]; + int aph [3]; + int ph [3]; + int dh [7]; + int bh [7]; + int bph [7]; + int sgh [7]; + int nbh; + + /* QMF signal history */ + int xd[12]; + int xs[12]; +} g722_dec_t; + + +PJ_DECL(pj_status_t) g722_dec_init(g722_dec_t *dec); + +PJ_DECL(pj_status_t) g722_dec_decode(g722_dec_t *dec, + void *in, + pj_size_t in_size, + pj_int16_t out[], + pj_size_t *nsamples); + +PJ_DECL(pj_status_t) g722_dec_deinit(g722_dec_t *dec); + +#endif /* __PJMEDIA_CODEC_G722_DEC_H__ */ + diff --git a/pjmedia/src/pjmedia-codec/g722/g722_enc.c b/pjmedia/src/pjmedia-codec/g722/g722_enc.c new file mode 100644 index 0000000..d0b2011 --- /dev/null +++ b/pjmedia/src/pjmedia-codec/g722/g722_enc.c @@ -0,0 +1,576 @@ +/* $Id: g722_enc.c 3553 2011-05-05 06:14:19Z nanang $ */ +/* + * 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 + */ +/* + * Based on implementation found in Carnegie Mellon Speech Group Software + * depository (ftp://ftp.cs.cmu.edu/project/fgdata/index.html). No copyright + * was claimed in the original source codes. + */ +#include <pjmedia/errno.h> +#include <pj/assert.h> +#include <pj/pool.h> + +#include "g722_enc.h" + +#if defined(PJMEDIA_HAS_G722_CODEC) && (PJMEDIA_HAS_G722_CODEC != 0) + +#define SATURATE(v, max, min) \ + if (v>max) v = max; \ + else if (v<min) v = min + +/* QMF tap coefficients */ +const int g722_qmf_coeff[24] = { + 3, -11, -11, 53, 12, -156, + 32, 362, -210, -805, 951, 3876, + 3876, 951, -805, -210, 362, 32, + -156, 12, 53, -11, -11, 3 +}; + + +static int block1l (int xl, int sl, int detl) +{ + int il ; + + int i, el, sil, mil, wd, wd1, hdu ; + + static const int q6[32] = { + 0, 35, 72, 110, 150, 190, 233, 276, 323, + 370, 422, 473, 530, 587, 650, 714, 786, + 858, 940, 1023, 1121, 1219, 1339, 1458, + 1612, 1765, 1980, 2195, 2557, 2919, 0, 0 + }; + + static const int iln[32] = { + 0, 63, 62, 31, 30, 29, 28, 27, 26, 25, + 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, + 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 0 + }; + + static const int ilp[32] = { + 0, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, + 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, + 40, 39, 38, 37, 36, 35, 34, 33, 32, 0 + }; + + /* SUBTRA */ + + el = xl - sl ; + SATURATE(el, 32767, -32768); + + /* QUANTL */ + + sil = el >> 15 ; + if (sil == 0 ) wd = el ; + else wd = (32767 - el) & 32767 ; + + mil = 1 ; + + for (i = 1; i < 30; i++) { + hdu = (q6[i] << 3) * detl; + wd1 = (hdu >> 15) ; + if (wd >= wd1) mil = (i + 1) ; + else break ; + } + + if (sil == -1 ) il = iln[mil] ; + else il = ilp[mil] ; + + return (il) ; +} + +static int block2l (int il, int detl) +{ + int dlt; + int ril, wd2 ; + static const int qm4[16] = { + 0, -20456, -12896, -8968, + -6288, -4240, -2584, -1200, + 20456, 12896, 8968, 6288, + 4240, 2584, 1200, 0 + }; + + /* INVQAL */ + ril = il >> 2 ; + wd2 = qm4[ril] ; + dlt = (detl * wd2) >> 15 ; + + return (dlt) ; +} + +static int block3l (g722_enc_t *enc, int il) +{ + int detl; + int ril, il4, wd, wd1, wd2, wd3, nbpl, depl ; + static int const wl[8] = { + -60, -30, 58, 172, 334, 538, 1198, 3042 + } ; + static int const rl42[16] = { + 0, 7, 6, 5, 4, 3, 2, 1, 7, 6, 5, 4, 3, 2, 1, 0 + }; + static const int ilb[32] = { + 2048, 2093, 2139, 2186, 2233, 2282, 2332, + 2383, 2435, 2489, 2543, 2599, 2656, 2714, + 2774, 2834, 2896, 2960, 3025, 3091, 3158, + 3228, 3298, 3371, 3444, 3520, 3597, 3676, + 3756, 3838, 3922, 4008 + }; + + /* LOGSCL */ + + ril = il >> 2 ; + il4 = rl42[ril] ; + + wd = (enc->nbl * 32512) >> 15 ; + nbpl = wd + wl[il4] ; + + if (nbpl < 0) nbpl = 0 ; + if (nbpl > 18432) nbpl = 18432 ; + + /* SCALEL */ + + wd1 = (nbpl >> 6) & 31 ; + wd2 = nbpl >> 11 ; + if ((8 - wd2) < 0) wd3 = ilb[wd1] << (wd2 - 8) ; + else wd3 = ilb[wd1] >> (8 - wd2) ; + depl = wd3 << 2 ; + + /* DELAYA */ + enc->nbl = nbpl ; + + /* DELAYL */ + detl = depl ; + +#ifdef DEBUG_VERBOSE + printf ("BLOCK3L il=%4d, ril=%4d, il4=%4d, nbl=%4d, wd=%4d, nbpl=%4d\n", + il, ril, il4, enc->nbl, wd, nbpl) ; + printf ("wd1=%4d, wd2=%4d, wd3=%4d, depl=%4d, detl=%4d\n", + wd1, wd2, wd3, depl, detl) ; +#endif + + return (detl) ; +} + +static int block4l (g722_enc_t *enc, int dl) +{ + int sl = enc->slow; + int i ; + int wd, wd1, wd2, wd3, wd4, wd5 /*, wd6 */; + + enc->dlt[0] = dl; + + /* RECONS */ + + enc->rlt[0] = sl + enc->dlt[0] ; + SATURATE(enc->rlt[0], 32767, -32768); + + /* PARREC */ + + enc->plt[0] = enc->dlt[0] + enc->szl ; + SATURATE(enc->plt[0], 32767, -32768); + + /* UPPOL2 */ + + enc->sgl[0] = enc->plt[0] >> 15 ; + enc->sgl[1] = enc->plt[1] >> 15 ; + enc->sgl[2] = enc->plt[2] >> 15 ; + + wd1 = enc->al[1] << 2; + SATURATE(wd1, 32767, -32768); + + if ( enc->sgl[0] == enc->sgl[1] ) wd2 = - wd1 ; + else wd2 = wd1 ; + if ( wd2 > 32767 ) wd2 = 32767; + + wd2 = wd2 >> 7 ; + + if ( enc->sgl[0] == enc->sgl[2] ) wd3 = 128 ; + else wd3 = - 128 ; + + wd4 = wd2 + wd3 ; + wd5 = (enc->al[2] * 32512) >> 15 ; + + enc->apl[2] = wd4 + wd5 ; + SATURATE(enc->apl[2], 12288, -12288); + + /* UPPOL1 */ + + enc->sgl[0] = enc->plt[0] >> 15 ; + enc->sgl[1] = enc->plt[1] >> 15 ; + + if ( enc->sgl[0] == enc->sgl[1] ) wd1 = 192 ; + else wd1 = - 192 ; + + wd2 = (enc->al[1] * 32640) >> 15 ; + + enc->apl[1] = wd1 + wd2 ; + SATURATE(enc->apl[1], 32767, -32768); + + wd3 = (15360 - enc->apl[2]) ; + SATURATE(wd3, 32767, -32768); + + if ( enc->apl[1] > wd3) enc->apl[1] = wd3 ; + if ( enc->apl[1] < -wd3) enc->apl[1] = -wd3 ; + + /* UPZERO */ + + if ( enc->dlt[0] == 0 ) wd1 = 0 ; + else wd1 = 128 ; + + enc->sgl[0] = enc->dlt[0] >> 15 ; + + for ( i = 1; i < 7; i++ ) { + enc->sgl[i] = enc->dlt[i] >> 15 ; + if ( enc->sgl[i] == enc->sgl[0] ) wd2 = wd1 ; + else wd2 = - wd1 ; + wd3 = (enc->bl[i] * 32640) >> 15 ; + enc->bpl[i] = wd2 + wd3 ; + SATURATE(enc->bpl[i], 32767, -32768); + } + + /* DELAYA */ + + for ( i = 6; i > 0; i-- ) { + enc->dlt[i] = enc->dlt[i-1] ; + enc->bl[i] = enc->bpl[i] ; + } + + for ( i = 2; i > 0; i-- ) { + enc->rlt[i] = enc->rlt[i-1] ; + enc->plt[i] = enc->plt[i-1] ; + enc->al[i] = enc->apl[i] ; + } + + /* FILTEP */ + + wd1 = enc->rlt[1] + enc->rlt[1]; + SATURATE(wd1, 32767, -32768); + wd1 = ( enc->al[1] * wd1 ) >> 15 ; + + wd2 = enc->rlt[2] + enc->rlt[2]; + SATURATE(wd2, 32767, -32768); + wd2 = ( enc->al[2] * wd2 ) >> 15 ; + + enc->spl = wd1 + wd2 ; + SATURATE(enc->spl, 32767, -32768); + + /* FILTEZ */ + + enc->szl = 0 ; + for (i=6; i>0; i--) { + wd = enc->dlt[i] + enc->dlt[i]; + SATURATE(wd, 32767, -32768); + enc->szl += (enc->bl[i] * wd) >> 15 ; + SATURATE(enc->szl, 32767, -32768); + } + + /* PREDIC */ + + sl = enc->spl + enc->szl ; + SATURATE(sl, 32767, -32768); + + return (sl) ; +} + +static int block1h (int xh, int sh, int deth) +{ + int ih ; + + int eh, sih, mih, wd, wd1, hdu ; + + static const int ihn[3] = { 0, 1, 0 } ; + static const int ihp[3] = { 0, 3, 2 } ; + + /* SUBTRA */ + + eh = xh - sh ; + SATURATE(eh, 32767, -32768); + + /* QUANTH */ + + sih = eh >> 15 ; + if (sih == 0 ) wd = eh ; + else wd = (32767 - eh) & 32767 ; + + hdu = (564 << 3) * deth; + wd1 = (hdu >> 15) ; + if (wd >= wd1) mih = 2 ; + else mih = 1 ; + + if (sih == -1 ) ih = ihn[mih] ; + else ih = ihp[mih] ; + + return (ih) ; +} + +static int block2h (int ih, int deth) +{ + int dh ; + int wd2 ; + static const int qm2[4] = {-7408, -1616, 7408, 1616}; + + /* INVQAH */ + + wd2 = qm2[ih] ; + dh = (deth * wd2) >> 15 ; + + return (dh) ; +} + +static int block3h (g722_enc_t *enc, int ih) +{ + int deth ; + int ih2, wd, wd1, wd2, wd3, nbph, deph ; + static const int wh[3] = {0, -214, 798} ; + static const int rh2[4] = {2, 1, 2, 1} ; + static const int ilb[32] = { + 2048, 2093, 2139, 2186, 2233, 2282, 2332, + 2383, 2435, 2489, 2543, 2599, 2656, 2714, + 2774, 2834, 2896, 2960, 3025, 3091, 3158, + 3228, 3298, 3371, 3444, 3520, 3597, 3676, + 3756, 3838, 3922, 4008 + }; + + /* LOGSCH */ + + ih2 = rh2[ih] ; + wd = (enc->nbh * 32512) >> 15 ; + nbph = wd + wh[ih2] ; + + if (nbph < 0) nbph = 0 ; + if (nbph > 22528) nbph = 22528 ; + + /* SCALEH */ + + wd1 = (nbph >> 6) & 31 ; + wd2 = nbph >> 11 ; + if ((10-wd2) < 0) wd3 = ilb[wd1] << (wd2-10) ; + else wd3 = ilb[wd1] >> (10-wd2) ; + deph = wd3 << 2 ; + + /* DELAYA */ + enc->nbh = nbph ; + /* DELAYH */ + deth = deph ; + + return (deth) ; +} + +static int block4h (g722_enc_t *enc, int d) +{ + int sh = enc->shigh; + int i ; + int wd, wd1, wd2, wd3, wd4, wd5 /*, wd6 */; + + enc->dh[0] = d; + + /* RECONS */ + + enc->rh[0] = sh + enc->dh[0] ; + SATURATE(enc->rh[0], 32767, -32768); + + /* PARREC */ + + enc->ph[0] = enc->dh[0] + enc->szh ; + SATURATE(enc->ph[0], 32767, -32768); + + /* UPPOL2 */ + + enc->sgh[0] = enc->ph[0] >> 15 ; + enc->sgh[1] = enc->ph[1] >> 15 ; + enc->sgh[2] = enc->ph[2] >> 15 ; + + wd1 = enc->ah[1] << 2; + SATURATE(wd1, 32767, -32768); + + if ( enc->sgh[0] == enc->sgh[1] ) wd2 = - wd1 ; + else wd2 = wd1 ; + if ( wd2 > 32767 ) wd2 = 32767; + + wd2 = wd2 >> 7 ; + + if ( enc->sgh[0] == enc->sgh[2] ) wd3 = 128 ; + else wd3 = - 128 ; + + wd4 = wd2 + wd3 ; + wd5 = (enc->ah[2] * 32512) >> 15 ; + + enc->aph[2] = wd4 + wd5 ; + SATURATE(enc->aph[2], 12288, -12288); + + /* UPPOL1 */ + + enc->sgh[0] = enc->ph[0] >> 15 ; + enc->sgh[1] = enc->ph[1] >> 15 ; + + if ( enc->sgh[0] == enc->sgh[1] ) wd1 = 192 ; + else wd1 = - 192 ; + + wd2 = (enc->ah[1] * 32640) >> 15 ; + + enc->aph[1] = wd1 + wd2 ; + SATURATE(enc->aph[1], 32767, -32768); + + wd3 = (15360 - enc->aph[2]) ; + SATURATE(wd3, 32767, -32768); + + if ( enc->aph[1] > wd3) enc->aph[1] = wd3 ; + else if ( enc->aph[1] < -wd3) enc->aph[1] = -wd3 ; + + /* UPZERO */ + + if ( enc->dh[0] == 0 ) wd1 = 0 ; + else wd1 = 128 ; + + enc->sgh[0] = enc->dh[0] >> 15 ; + + for ( i = 1; i < 7; i++ ) { + enc->sgh[i] = enc->dh[i] >> 15 ; + if ( enc->sgh[i] == enc->sgh[0] ) wd2 = wd1 ; + else wd2 = - wd1 ; + wd3 = (enc->bh[i] * 32640) >> 15 ; + enc->bph[i] = wd2 + wd3 ; + SATURATE(enc->bph[i], 32767, -32768); + } + + /* DELAYA */ + for ( i = 6; i > 0; i-- ) { + enc->dh[i] = enc->dh[i-1] ; + enc->bh[i] = enc->bph[i] ; + } + + for ( i = 2; i > 0; i-- ) { + enc->rh[i] = enc->rh[i-1] ; + enc->ph[i] = enc->ph[i-1] ; + enc->ah[i] = enc->aph[i] ; + } + + /* FILTEP */ + + wd1 = enc->rh[1] + enc->rh[1]; + SATURATE(wd1, 32767, -32768); + wd1 = ( enc->ah[1] * wd1 ) >> 15 ; + + wd2 = enc->rh[2] + enc->rh[2]; + SATURATE(wd2, 32767, -32768); + wd2 = ( enc->ah[2] * wd2 ) >> 15 ; + + enc->sph = wd1 + wd2 ; + SATURATE(enc->sph, 32767, -32768); + + /* FILTEZ */ + + enc->szh = 0 ; + for (i=6; i>0; i--) { + wd = enc->dh[i] + enc->dh[i]; + SATURATE(wd, 32767, -32768); + enc->szh += (enc->bh[i] * wd) >> 15 ; + SATURATE(enc->szh, 32767, -32768); + } + + /* PREDIC */ + + sh = enc->sph + enc->szh ; + SATURATE(sh, 32767, -32768); + + return (sh) ; +} + +/* PROCESS PCM THROUGH THE QMF FILTER */ +static void tx_qmf(g722_enc_t *enc, int pcm1, int pcm2, int *lo, int *hi) +{ + int sumodd, sumeven; + int i; + + pj_memmove(&enc->x[2], enc->x, 22 * sizeof(enc->x[0])); + enc->x[1] = pcm1; + enc->x[0] = pcm2; + + sumodd = 0; + for (i=1; i<24; i+=2) sumodd += enc->x[i] * g722_qmf_coeff[i]; + + sumeven = 0; + for (i=0; i<24; i+=2) sumeven += enc->x[i] * g722_qmf_coeff[i]; + + *lo = (sumeven + sumodd) >> 13 ; + *hi = (sumeven - sumodd) >> 13 ; + + SATURATE(*lo, 16383, -16384); + SATURATE(*hi, 16383, -16383); +} + + +PJ_DEF(pj_status_t) g722_enc_init(g722_enc_t *enc) +{ + PJ_ASSERT_RETURN(enc, PJ_EINVAL); + + pj_bzero(enc, sizeof(g722_enc_t)); + + enc->detlow = 32; + enc->dethigh = 8; + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) g722_enc_encode( g722_enc_t *enc, + pj_int16_t in[], + pj_size_t nsamples, + void *out, + pj_size_t *out_size) +{ + unsigned i; + int xlow, ilow, dlowt; + int xhigh, ihigh, dhigh; + pj_uint8_t *out_ = (pj_uint8_t*) out; + + PJ_ASSERT_RETURN(enc && in && nsamples && out && out_size, PJ_EINVAL); + PJ_ASSERT_RETURN(nsamples % 2 == 0, PJ_EINVAL); + PJ_ASSERT_RETURN(*out_size >= (nsamples >> 1), PJ_ETOOSMALL); + + for(i = 0; i < nsamples; i += 2) { + tx_qmf(enc, in[i], in[i+1], &xlow, &xhigh); + + /* low band encoder */ + ilow = block1l (xlow, enc->slow, enc->detlow) ; + dlowt = block2l (ilow, enc->detlow) ; + enc->detlow = block3l (enc, ilow) ; + enc->slow = block4l (enc, dlowt) ; + + /* high band encoder */ + ihigh = block1h (xhigh, enc->shigh, enc->dethigh) ; + dhigh = block2h (ihigh, enc->dethigh) ; + enc->dethigh = block3h (enc, ihigh) ; + enc->shigh = block4h (enc, dhigh) ; + + /* bits mix low & high adpcm */ + out_[i/2] = (pj_uint8_t)((ihigh << 6) | ilow); + } + + *out_size = nsamples >> 1; + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) g722_enc_deinit(g722_enc_t *enc) +{ + pj_bzero(enc, sizeof(g722_enc_t)); + + return PJ_SUCCESS; +} + +#endif diff --git a/pjmedia/src/pjmedia-codec/g722/g722_enc.h b/pjmedia/src/pjmedia-codec/g722/g722_enc.h new file mode 100644 index 0000000..96f9af6 --- /dev/null +++ b/pjmedia/src/pjmedia-codec/g722/g722_enc.h @@ -0,0 +1,78 @@ +/* $Id: g722_enc.h 3553 2011-05-05 06:14:19Z nanang $ */ +/* + * 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 + */ +/* + * Based on implementation found in Carnegie Mellon Speech Group Software + * depository (ftp://ftp.cs.cmu.edu/project/fgdata/index.html). No copyright + * was claimed in the original source codes. + */ +#ifndef __PJMEDIA_CODEC_G722_ENC_H__ +#define __PJMEDIA_CODEC_G722_ENC_H__ + +#include <pjmedia-codec/types.h> + +/* Encoder state */ +typedef struct g722_enc_t { + /* PCM low band */ + int slow; + int detlow; + int spl; + int szl; + int rlt [3]; + int al [3]; + int apl [3]; + int plt [3]; + int dlt [7]; + int bl [7]; + int bpl [7]; + int sgl [7]; + int nbl; + + /* PCM high band*/ + int shigh; + int dethigh; + int sph; + int szh; + int rh [3]; + int ah [3]; + int aph [3]; + int ph [3]; + int dh [7]; + int bh [7]; + int bph [7]; + int sgh [7]; + int nbh; + + /* QMF signal history */ + int x[24]; +} g722_enc_t; + + +PJ_DECL(pj_status_t) g722_enc_init(g722_enc_t *enc); + +PJ_DECL(pj_status_t) g722_enc_encode(g722_enc_t *enc, + pj_int16_t in[], + pj_size_t nsamples, + void *out, + pj_size_t *out_size); + +PJ_DECL(pj_status_t) g722_enc_deinit(g722_enc_t *enc); + +#endif /* __PJMEDIA_CODEC_G722_ENC_H__ */ + diff --git a/pjmedia/src/pjmedia-codec/g7221.c b/pjmedia/src/pjmedia-codec/g7221.c new file mode 100644 index 0000000..2d38483 --- /dev/null +++ b/pjmedia/src/pjmedia-codec/g7221.c @@ -0,0 +1,950 @@ +/* $Id: g7221.c 4001 2012-03-30 07:53:36Z bennylp $ */ +/* + * 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-codec/g7221.h> +#include <pjmedia-codec/g7221_sdp_match.h> +#include <pjmedia/codec.h> +#include <pjmedia/errno.h> +#include <pjmedia/endpoint.h> +#include <pjmedia/port.h> +#include <pjmedia/silencedet.h> +#include <pj/assert.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_G7221_CODEC != 0 + */ +#if defined(PJMEDIA_HAS_G7221_CODEC) && PJMEDIA_HAS_G7221_CODEC!=0 + +#include "../../../third_party/g7221/common/defs.h" + +#define THIS_FILE "g7221.c" + +/* Codec tag, it is the SDP encoding name and also MIME subtype name */ +#define CODEC_TAG "G7221" + +/* Sampling rates definition */ +#define WB_SAMPLE_RATE 16000 +#define UWB_SAMPLE_RATE 32000 + +/* Maximum number of samples per frame. */ +#define MAX_SAMPLES_PER_FRAME (UWB_SAMPLE_RATE * 20 / 1000) + +/* Maximum number of codec params. */ +#define MAX_CODEC_MODES 8 +#define START_RSV_MODES_IDX 6 + + +/* Prototypes for G722.1 codec factory */ +static pj_status_t test_alloc( pjmedia_codec_factory *factory, + const pjmedia_codec_info *id ); +static pj_status_t default_attr( pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec_param *attr ); +static pj_status_t enum_codecs( pjmedia_codec_factory *factory, + unsigned *count, + pjmedia_codec_info codecs[]); +static pj_status_t alloc_codec( pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec **p_codec); +static pj_status_t dealloc_codec( pjmedia_codec_factory *factory, + pjmedia_codec *codec ); + +/* Prototypes for G722.1 codec implementation. */ +static pj_status_t codec_init( pjmedia_codec *codec, + pj_pool_t *pool ); +static pj_status_t codec_open( pjmedia_codec *codec, + pjmedia_codec_param *attr ); +static pj_status_t codec_close( pjmedia_codec *codec ); +static pj_status_t codec_modify(pjmedia_codec *codec, + const pjmedia_codec_param *attr ); +static pj_status_t codec_parse( pjmedia_codec *codec, + void *pkt, + pj_size_t pkt_size, + const pj_timestamp *ts, + unsigned *frame_cnt, + pjmedia_frame frames[]); +static pj_status_t codec_encode( pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output); +static pj_status_t codec_decode( pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output); +static pj_status_t codec_recover( pjmedia_codec *codec, + unsigned output_buf_len, + struct pjmedia_frame *output); + +/* Definition for G722.1 codec operations. */ +static pjmedia_codec_op codec_op = +{ + &codec_init, + &codec_open, + &codec_close, + &codec_modify, + &codec_parse, + &codec_encode, + &codec_decode, + &codec_recover +}; + +/* Definition for G722.1 codec factory operations. */ +static pjmedia_codec_factory_op codec_factory_op = +{ + &test_alloc, + &default_attr, + &enum_codecs, + &alloc_codec, + &dealloc_codec, + &pjmedia_codec_g7221_deinit +}; + + +/* Structure of G722.1 mode */ +typedef struct codec_mode +{ + pj_bool_t enabled; /* Is this mode enabled? */ + pj_uint8_t pt; /* Payload type. */ + unsigned sample_rate; /* Default sampling rate to be used.*/ + unsigned bitrate; /* Bitrate. */ + char bitrate_str[8]; /* Bitrate in string. */ +} codec_mode; + + +/* G722.1 codec factory */ +static struct codec_factory { + pjmedia_codec_factory base; /**< Base class. */ + pjmedia_endpt *endpt; /**< PJMEDIA endpoint instance. */ + pj_pool_t *pool; /**< Codec factory pool. */ + pj_mutex_t *mutex; /**< Codec factory mutex. */ + + int pcm_shift; /**< Level adjustment */ + unsigned mode_count; /**< Number of G722.1 modes. */ + codec_mode modes[MAX_CODEC_MODES]; /**< The G722.1 modes. */ + unsigned mode_rsv_start;/**< Start index of G722.1 non- + standard modes, currently + there can only be up to two + non-standard modes enabled + at the same time. */ +} codec_factory; + +/* G722.1 codec private data. */ +typedef struct codec_private { + pj_pool_t *pool; /**< Pool for each instance. */ + pj_bool_t plc_enabled; /**< PLC enabled? */ + pj_bool_t vad_enabled; /**< VAD enabled? */ + pjmedia_silence_det *vad; /**< PJMEDIA VAD instance. */ + pj_timestamp last_tx; /**< Timestamp of last transmit.*/ + + /* ITU ref implementation seems to leave the codec engine states to be + * managed by the application, so here we go. + */ + + /* Common engine state */ + pj_uint16_t samples_per_frame; /**< Samples per frame. */ + pj_uint16_t bitrate; /**< Coded stream bitrate. */ + pj_uint16_t frame_size; /**< Coded frame size. */ + pj_uint16_t frame_size_bits; /**< Coded frame size in bits. */ + pj_uint16_t number_of_regions; /**< Number of regions. */ + int pcm_shift; /**< Adjustment for PCM in/out */ + + /* Encoder specific state */ + Word16 *enc_frame; /**< 16bit to 14bit buffer */ + Word16 *enc_old_frame; + + /* Decoder specific state */ + Word16 *dec_old_frame; + Rand_Obj dec_randobj; + Word16 dec_old_mag_shift; + Word16 *dec_old_mlt_coefs; +} codec_private_t; + +/* + * Helper function for looking up mode based on payload type. + */ +static codec_mode* lookup_mode(unsigned pt) +{ + codec_mode* mode = NULL; + unsigned i; + + for (i = 0; i < codec_factory.mode_count; ++i) { + mode = &codec_factory.modes[i]; + if (mode->pt == pt) + break; + } + + return mode; +} + +/* + * Helper function to validate G722.1 mode. Valid modes are defined as: + * 1. sample rate must be 16kHz or 32kHz, + * 2. bitrate: + * - for sampling rate 16kHz: 16000 to 32000 bps, it must be a multiple + * of 400 (to keep RTP payload octed-aligned) + * - for sampling rate 32kHz: 24000 to 48000 bps, it must be a multiple + * of 400 (to keep RTP payload octed-aligned) + */ +static pj_bool_t validate_mode(unsigned sample_rate, unsigned bitrate) +{ + if (sample_rate == WB_SAMPLE_RATE) { + if (bitrate < 16000 || bitrate > 32000 || + ((bitrate-16000) % 400 != 0)) + { + return PJ_FALSE; + } + } else if (sample_rate == UWB_SAMPLE_RATE) { + if (bitrate < 24000 || bitrate > 48000 || + ((bitrate-24000) % 400 != 0)) + { + return PJ_FALSE; + } + } else { + return PJ_FALSE; + } + + return PJ_TRUE; +} + +#if defined(PJ_IS_LITTLE_ENDIAN) && PJ_IS_LITTLE_ENDIAN!=0 +PJ_INLINE(void) swap_bytes(pj_uint16_t *buf, unsigned count) +{ + pj_uint16_t *end = buf + count; + while (buf != end) { + *buf = (pj_uint16_t)((*buf << 8) | (*buf >> 8)); + ++buf; + } +} +#else +#define swap_bytes(buf, count) +#endif + +/* + * Initialize and register G722.1 codec factory to pjmedia endpoint. + */ +PJ_DEF(pj_status_t) pjmedia_codec_g7221_init( pjmedia_endpt *endpt ) +{ + pjmedia_codec_mgr *codec_mgr; + codec_mode *mode; + pj_str_t codec_name; + pj_status_t status; + + if (codec_factory.pool != NULL) { + /* Already initialized. */ + return PJ_SUCCESS; + } + + /* Initialize codec modes, by default all standard bitrates are enabled */ + codec_factory.mode_count = 0; + codec_factory.pcm_shift = PJMEDIA_G7221_DEFAULT_PCM_SHIFT; + + mode = &codec_factory.modes[codec_factory.mode_count++]; + mode->enabled = PJ_TRUE; + mode->pt = PJMEDIA_RTP_PT_G722_1_24; + mode->sample_rate = WB_SAMPLE_RATE; + mode->bitrate = 24000; + pj_utoa(mode->bitrate, mode->bitrate_str); + + mode = &codec_factory.modes[codec_factory.mode_count++]; + mode->enabled = PJ_TRUE; + mode->pt = PJMEDIA_RTP_PT_G722_1_32; + mode->sample_rate = WB_SAMPLE_RATE; + mode->bitrate = 32000; + pj_utoa(mode->bitrate, mode->bitrate_str); + + mode = &codec_factory.modes[codec_factory.mode_count++]; + mode->enabled = PJ_TRUE; + mode->pt = PJMEDIA_RTP_PT_G7221C_24; + mode->sample_rate = UWB_SAMPLE_RATE; + mode->bitrate = 24000; + pj_utoa(mode->bitrate, mode->bitrate_str); + + mode = &codec_factory.modes[codec_factory.mode_count++]; + mode->enabled = PJ_TRUE; + mode->pt = PJMEDIA_RTP_PT_G7221C_32; + mode->sample_rate = UWB_SAMPLE_RATE; + mode->bitrate = 32000; + pj_utoa(mode->bitrate, mode->bitrate_str); + + mode = &codec_factory.modes[codec_factory.mode_count++]; + mode->enabled = PJ_TRUE; + mode->pt = PJMEDIA_RTP_PT_G7221C_48; + mode->sample_rate = UWB_SAMPLE_RATE; + mode->bitrate = 48000; + pj_utoa(mode->bitrate, mode->bitrate_str); + + /* Non-standard bitrates */ + + /* Bitrate 16kbps is non-standard but rather commonly used. */ + mode = &codec_factory.modes[codec_factory.mode_count++]; + mode->enabled = PJ_FALSE; + mode->pt = PJMEDIA_RTP_PT_G722_1_16; + mode->sample_rate = WB_SAMPLE_RATE; + mode->bitrate = 16000; + pj_utoa(mode->bitrate, mode->bitrate_str); + + /* Reserved two modes for non-standard bitrates */ + codec_factory.mode_rsv_start = codec_factory.mode_count; + mode = &codec_factory.modes[codec_factory.mode_count++]; + mode->enabled = PJ_FALSE; + mode->pt = PJMEDIA_RTP_PT_G7221_RSV1; + + mode = &codec_factory.modes[codec_factory.mode_count++]; + mode->enabled = PJ_FALSE; + mode->pt = PJMEDIA_RTP_PT_G7221_RSV2; + + pj_assert(codec_factory.mode_count <= MAX_CODEC_MODES); + + /* Create G722.1 codec factory. */ + codec_factory.base.op = &codec_factory_op; + codec_factory.base.factory_data = NULL; + codec_factory.endpt = endpt; + + codec_factory.pool = pjmedia_endpt_create_pool(endpt, "G722.1 codec", + 4000, 4000); + if (!codec_factory.pool) + return PJ_ENOMEM; + + /* Create mutex. */ + status = pj_mutex_create_simple(codec_factory.pool, "G722.1 codec", + &codec_factory.mutex); + if (status != PJ_SUCCESS) + goto on_error; + + /* Get the codec manager. */ + codec_mgr = pjmedia_endpt_get_codec_mgr(endpt); + if (!codec_mgr) { + status = PJ_EINVALIDOP; + goto on_error; + } + + /* Register format match callback. */ + pj_cstr(&codec_name, CODEC_TAG); + status = pjmedia_sdp_neg_register_fmt_match_cb( + &codec_name, + &pjmedia_codec_g7221_match_sdp); + if (status != PJ_SUCCESS) + goto on_error; + + /* Register codec factory to endpoint. */ + status = pjmedia_codec_mgr_register_factory(codec_mgr, + &codec_factory.base); + if (status != PJ_SUCCESS) + goto on_error; + + /* Done. */ + return PJ_SUCCESS; + +on_error: + if (codec_factory.mutex) { + pj_mutex_destroy(codec_factory.mutex); + codec_factory.mutex = NULL; + } + + pj_pool_release(codec_factory.pool); + codec_factory.pool = NULL; + return status; +} + + +/** + * Enable and disable G722.1 modes, including non-standard modes. + */ +PJ_DEF(pj_status_t) pjmedia_codec_g7221_set_mode(unsigned sample_rate, + unsigned bitrate, + pj_bool_t enabled) +{ + unsigned i; + + /* Validate mode */ + if (!validate_mode(sample_rate, bitrate)) + return PJMEDIA_CODEC_EINMODE; + + /* Look up in factory modes table */ + for (i = 0; i < codec_factory.mode_count; ++i) { + if (codec_factory.modes[i].sample_rate == sample_rate && + codec_factory.modes[i].bitrate == bitrate) + { + codec_factory.modes[i].enabled = enabled; + return PJ_SUCCESS; + } + } + + /* Mode not found in modes table, this may be a request to enable + * a non-standard G722.1 mode. + */ + + /* Non-standard mode need to be initialized first before user + * can disable it. + */ + if (!enabled) + return PJ_ENOTFOUND; + + /* Initialize a non-standard mode, look for available space. */ + for (i = codec_factory.mode_rsv_start; + i < codec_factory.mode_count; ++i) + { + if (!codec_factory.modes[i].enabled) + { + codec_mode *mode = &codec_factory.modes[i]; + mode->enabled = PJ_TRUE; + mode->sample_rate = sample_rate; + mode->bitrate = bitrate; + pj_utoa(mode->bitrate, mode->bitrate_str); + + return PJ_SUCCESS; + } + } + + /* No space for non-standard mode. */ + return PJ_ETOOMANY; +} + + +/* + * Set level adjustment. + */ +PJ_DEF(pj_status_t) pjmedia_codec_g7221_set_pcm_shift(int val) +{ + codec_factory.pcm_shift = val; + return PJ_SUCCESS; +} + +/* + * Unregister G722.1 codec factory from pjmedia endpoint. + */ +PJ_DEF(pj_status_t) pjmedia_codec_g7221_deinit(void) +{ + pjmedia_codec_mgr *codec_mgr; + pj_status_t status; + + if (codec_factory.pool == NULL) { + /* Already deinitialized */ + return PJ_SUCCESS; + } + + pj_mutex_lock(codec_factory.mutex); + + /* Get the codec manager. */ + codec_mgr = pjmedia_endpt_get_codec_mgr(codec_factory.endpt); + if (!codec_mgr) { + pj_pool_release(codec_factory.pool); + codec_factory.pool = NULL; + return PJ_EINVALIDOP; + } + + /* Unregister G722.1 codec factory. */ + status = pjmedia_codec_mgr_unregister_factory(codec_mgr, + &codec_factory.base); + + /* Destroy mutex. */ + pj_mutex_destroy(codec_factory.mutex); + + /* Destroy pool. */ + pj_pool_release(codec_factory.pool); + codec_factory.pool = NULL; + + return status; +} + +/* + * Check if factory can allocate the specified codec. + */ +static pj_status_t test_alloc( pjmedia_codec_factory *factory, + const pjmedia_codec_info *info ) +{ + PJ_UNUSED_ARG(factory); + + /* Type MUST be audio. */ + if (info->type != PJMEDIA_TYPE_AUDIO) + return PJMEDIA_CODEC_EUNSUP; + + /* Check encoding name. */ + if (pj_stricmp2(&info->encoding_name, CODEC_TAG) != 0) + return PJMEDIA_CODEC_EUNSUP; + + /* Check clock-rate */ + if (info->clock_rate != WB_SAMPLE_RATE && + info->clock_rate != UWB_SAMPLE_RATE) + { + return PJMEDIA_CODEC_EUNSUP; + } + + return PJ_SUCCESS; +} + +/* + * Generate default attribute. + */ +static pj_status_t default_attr ( pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec_param *attr ) +{ + codec_mode *mode; + + PJ_ASSERT_RETURN(factory==&codec_factory.base, PJ_EINVAL); + + pj_bzero(attr, sizeof(pjmedia_codec_param)); + + mode = lookup_mode(id->pt); + if (mode == NULL || !mode->enabled) + return PJMEDIA_CODEC_EUNSUP; + + attr->info.pt = (pj_uint8_t)id->pt; + attr->info.channel_cnt = 1; + attr->info.clock_rate = mode->sample_rate; + attr->info.max_bps = mode->bitrate; + attr->info.avg_bps = mode->bitrate; + attr->info.pcm_bits_per_sample = 16; + attr->info.frm_ptime = 20; + + /* Default flags. */ + attr->setting.plc = 1; + attr->setting.vad = 0; + attr->setting.frm_per_pkt = 1; + + /* Default FMTP setting */ + attr->setting.dec_fmtp.cnt = 1; + attr->setting.dec_fmtp.param[0].name = pj_str("bitrate"); + attr->setting.dec_fmtp.param[0].val = pj_str(mode->bitrate_str); + + return PJ_SUCCESS; +} + +/* + * Enum codecs supported by this factory. + */ +static pj_status_t enum_codecs( pjmedia_codec_factory *factory, + unsigned *count, + pjmedia_codec_info codecs[]) +{ + unsigned i, max_cnt; + + PJ_ASSERT_RETURN(factory==&codec_factory.base, PJ_EINVAL); + PJ_ASSERT_RETURN(codecs && *count > 0, PJ_EINVAL); + + max_cnt = *count; + *count = 0; + + for (i=0; (i < codec_factory.mode_count) && (*count < max_cnt); ++i) + { + if (!codec_factory.modes[i].enabled) + continue; + + pj_bzero(&codecs[*count], sizeof(pjmedia_codec_info)); + codecs[*count].encoding_name = pj_str((char*)CODEC_TAG); + codecs[*count].pt = codec_factory.modes[i].pt; + codecs[*count].type = PJMEDIA_TYPE_AUDIO; + codecs[*count].clock_rate = codec_factory.modes[i].sample_rate; + codecs[*count].channel_cnt = 1; + + ++ *count; + } + + return PJ_SUCCESS; +} + +/* + * Allocate a new codec instance. + */ +static pj_status_t alloc_codec( pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec **p_codec) +{ + codec_private_t *codec_data; + pjmedia_codec *codec; + pj_pool_t *pool; + pj_status_t status; + + PJ_ASSERT_RETURN(factory && id && p_codec, PJ_EINVAL); + PJ_ASSERT_RETURN(factory == &codec_factory.base, PJ_EINVAL); + + pj_mutex_lock(codec_factory.mutex); + + /* Create pool for codec instance */ + pool = pjmedia_endpt_create_pool(codec_factory.endpt, "G7221", 512, 512); + codec = PJ_POOL_ZALLOC_T(pool, pjmedia_codec); + codec->op = &codec_op; + codec->factory = factory; + codec->codec_data = PJ_POOL_ZALLOC_T(pool, codec_private_t); + codec_data = (codec_private_t*) codec->codec_data; + codec_data->pool = pool; + + /* Create silence detector */ + status = pjmedia_silence_det_create(pool, id->clock_rate, + id->clock_rate * 20 / 1000, + &codec_data->vad); + if (status != PJ_SUCCESS) { + pj_mutex_unlock(codec_factory.mutex); + return status; + } + + pj_mutex_unlock(codec_factory.mutex); + + *p_codec = codec; + return PJ_SUCCESS; +} + +/* + * Free codec. + */ +static pj_status_t dealloc_codec( pjmedia_codec_factory *factory, + pjmedia_codec *codec ) +{ + codec_private_t *codec_data; + pj_pool_t *pool; + + PJ_ASSERT_RETURN(factory && codec, PJ_EINVAL); + PJ_ASSERT_RETURN(factory == &codec_factory.base, PJ_EINVAL); + + /* Close codec, if it's not closed. */ + codec_data = (codec_private_t*) codec->codec_data; + pool = codec_data->pool; + codec_close(codec); + + /* Release codec pool */ + pj_pool_release(pool); + + return PJ_SUCCESS; +} + +/* + * Init codec. + */ +static pj_status_t codec_init( pjmedia_codec *codec, + pj_pool_t *pool ) +{ + PJ_UNUSED_ARG(codec); + PJ_UNUSED_ARG(pool); + return PJ_SUCCESS; +} + +/* + * Open codec. + */ +static pj_status_t codec_open( pjmedia_codec *codec, + pjmedia_codec_param *attr ) +{ + codec_private_t *codec_data = (codec_private_t*) codec->codec_data; + pj_pool_t *pool; + unsigned tmp; + + /* Validation mode first! */ + if (!validate_mode(attr->info.clock_rate, attr->info.avg_bps)) + return PJMEDIA_CODEC_EINMODE; + + pool = codec_data->pool; + + /* Initialize common state */ + codec_data->vad_enabled = (attr->setting.vad != 0); + codec_data->plc_enabled = (attr->setting.plc != 0); + + codec_data->bitrate = (pj_uint16_t)attr->info.avg_bps; + codec_data->frame_size_bits = (pj_uint16_t)(attr->info.avg_bps*20/1000); + codec_data->frame_size = (pj_uint16_t)(codec_data->frame_size_bits>>3); + codec_data->samples_per_frame = (pj_uint16_t) + (attr->info.clock_rate*20/1000); + codec_data->number_of_regions = (pj_uint16_t) + (attr->info.clock_rate <= WB_SAMPLE_RATE? + NUMBER_OF_REGIONS:MAX_NUMBER_OF_REGIONS); + codec_data->pcm_shift = codec_factory.pcm_shift; + + /* Initialize encoder state */ + tmp = codec_data->samples_per_frame << 1; + codec_data->enc_old_frame = (Word16*)pj_pool_zalloc(pool, tmp); + codec_data->enc_frame = (Word16*)pj_pool_alloc(pool, tmp); + + /* Initialize decoder state */ + tmp = codec_data->samples_per_frame; + codec_data->dec_old_frame = (Word16*)pj_pool_zalloc(pool, tmp); + + tmp = codec_data->samples_per_frame << 1; + codec_data->dec_old_mlt_coefs = (Word16*)pj_pool_zalloc(pool, tmp); + + codec_data->dec_randobj.seed0 = 1; + codec_data->dec_randobj.seed1 = 1; + codec_data->dec_randobj.seed2 = 1; + codec_data->dec_randobj.seed3 = 1; + + return PJ_SUCCESS; +} + +/* + * Close codec. + */ +static pj_status_t codec_close( pjmedia_codec *codec ) +{ + PJ_UNUSED_ARG(codec); + + return PJ_SUCCESS; +} + + +/* + * Modify codec settings. + */ +static pj_status_t codec_modify( pjmedia_codec *codec, + const pjmedia_codec_param *attr ) +{ + codec_private_t *codec_data = (codec_private_t*) codec->codec_data; + + codec_data->vad_enabled = (attr->setting.vad != 0); + codec_data->plc_enabled = (attr->setting.plc != 0); + + return PJ_SUCCESS; +} + +/* + * Get frames in the packet. + */ +static pj_status_t codec_parse( pjmedia_codec *codec, + void *pkt, + pj_size_t pkt_size, + const pj_timestamp *ts, + unsigned *frame_cnt, + pjmedia_frame frames[]) +{ + codec_private_t *codec_data = (codec_private_t*) codec->codec_data; + unsigned count = 0; + + PJ_ASSERT_RETURN(frame_cnt, PJ_EINVAL); + + /* Parse based on fixed frame size. */ + while (pkt_size >= codec_data->frame_size && count < *frame_cnt) { + frames[count].type = PJMEDIA_FRAME_TYPE_AUDIO; + frames[count].buf = pkt; + frames[count].size = codec_data->frame_size; + frames[count].timestamp.u64 = ts->u64 + + count * codec_data->samples_per_frame; + + pkt = (pj_uint8_t*)pkt + codec_data->frame_size; + pkt_size -= codec_data->frame_size; + + ++count; + } + + pj_assert(pkt_size == 0); + *frame_cnt = count; + + return PJ_SUCCESS; +} + +/* + * Encode frames. + */ +static pj_status_t codec_encode( pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output) +{ + codec_private_t *codec_data = (codec_private_t*) codec->codec_data; + unsigned nsamples, processed; + + /* Check frame in & out size */ + nsamples = input->size >> 1; + PJ_ASSERT_RETURN(nsamples % codec_data->samples_per_frame == 0, + PJMEDIA_CODEC_EPCMFRMINLEN); + PJ_ASSERT_RETURN(output_buf_len >= codec_data->frame_size * nsamples / + codec_data->samples_per_frame, + PJMEDIA_CODEC_EFRMTOOSHORT); + + /* Apply silence detection if VAD is enabled */ + if (codec_data->vad_enabled) { + pj_bool_t is_silence; + pj_int32_t silence_duration; + + pj_assert(codec_data->vad); + + silence_duration = pj_timestamp_diff32(&codec_data->last_tx, + &input->timestamp); + + is_silence = pjmedia_silence_det_detect(codec_data->vad, + (const pj_int16_t*) input->buf, + (input->size >> 1), + NULL); + if (is_silence && + (PJMEDIA_CODEC_MAX_SILENCE_PERIOD == -1 || + silence_duration < (PJMEDIA_CODEC_MAX_SILENCE_PERIOD * + (int)codec_data->samples_per_frame / 20))) + { + output->type = PJMEDIA_FRAME_TYPE_NONE; + output->buf = NULL; + output->size = 0; + output->timestamp = input->timestamp; + return PJ_SUCCESS; + } else { + codec_data->last_tx = input->timestamp; + } + } + + processed = 0; + output->size = 0; + while (processed < nsamples) { + Word16 mlt_coefs[MAX_SAMPLES_PER_FRAME]; + Word16 mag_shift; + const Word16 *pcm_input; + pj_int8_t *out_bits; + + pcm_input = (const Word16*)input->buf + processed; + out_bits = (pj_int8_t*)output->buf + output->size; + + /* Encoder adjust the input signal level */ + if (codec_data->pcm_shift) { + unsigned i; + for (i=0; i<codec_data->samples_per_frame; ++i) { + codec_data->enc_frame[i] = + (Word16)(pcm_input[i] >> codec_data->pcm_shift); + } + pcm_input = codec_data->enc_frame; + } + + /* Convert input samples to rmlt coefs */ + mag_shift = samples_to_rmlt_coefs(pcm_input, + codec_data->enc_old_frame, + mlt_coefs, + codec_data->samples_per_frame); + + /* Encode the mlt coefs. Note that encoder output stream is + * 16 bit array, so we need to take care about endianness. + */ + encoder(codec_data->frame_size_bits, + codec_data->number_of_regions, + mlt_coefs, + mag_shift, + (Word16*)out_bits); + + /* Encoder output are in native host byte order, while ITU says + * it must be in network byte order (MSB first). + */ + swap_bytes((pj_uint16_t*)out_bits, codec_data->frame_size/2); + + processed += codec_data->samples_per_frame; + output->size += codec_data->frame_size; + } + + output->type = PJMEDIA_FRAME_TYPE_AUDIO; + output->timestamp = input->timestamp; + + return PJ_SUCCESS; +} + +/* + * Decode frame. + */ +static pj_status_t codec_decode( pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output) +{ + codec_private_t *codec_data = (codec_private_t*) codec->codec_data; + Word16 mlt_coefs[MAX_SAMPLES_PER_FRAME]; + Word16 mag_shift; + Bit_Obj bitobj; + Word16 frame_error_flag = 0; + + /* Check frame out length size */ + PJ_ASSERT_RETURN(output_buf_len >= + (unsigned)(codec_data->samples_per_frame<<1), + PJMEDIA_CODEC_EPCMTOOSHORT); + + /* If input is NULL, perform PLC by settting frame_error_flag to 1 */ + if (input) { + /* Check frame in length size */ + PJ_ASSERT_RETURN((pj_uint16_t)input->size == codec_data->frame_size, + PJMEDIA_CODEC_EFRMINLEN); + + /* Decoder requires input of 16-bits array in native host byte + * order, while the frame received from the network are in + * network byte order (MSB first). + */ + swap_bytes((pj_uint16_t*)input->buf, codec_data->frame_size/2); + + bitobj.code_word_ptr = (Word16*)input->buf; + bitobj.current_word = *bitobj.code_word_ptr; + bitobj.code_bit_count = 0; + bitobj.number_of_bits_left = codec_data->frame_size_bits; + + output->timestamp = input->timestamp; + } else { + pj_bzero(&bitobj, sizeof(bitobj)); + frame_error_flag = 1; + } + + /* Process the input frame to get mlt coefs */ + decoder(&bitobj, + &codec_data->dec_randobj, + codec_data->number_of_regions, + mlt_coefs, + &mag_shift, + &codec_data->dec_old_mag_shift, + codec_data->dec_old_mlt_coefs, + frame_error_flag); + + /* Convert the mlt_coefs to PCM samples */ + rmlt_coefs_to_samples(mlt_coefs, + codec_data->dec_old_frame, + (Word16*)output->buf, + codec_data->samples_per_frame, + mag_shift); + + /* Decoder adjust PCM signal */ + if (codec_data->pcm_shift) { + unsigned i; + pj_int16_t *buf = (Word16*)output->buf; + + for (i=0; i<codec_data->samples_per_frame; ++i) { + buf[i] <<= codec_data->pcm_shift; + } + } + + output->type = PJMEDIA_FRAME_TYPE_AUDIO; + output->size = codec_data->samples_per_frame << 1; + + return PJ_SUCCESS; +} + +/* + * Recover lost frame. + */ +static pj_status_t codec_recover( pjmedia_codec *codec, + unsigned output_buf_len, + struct pjmedia_frame *output) +{ + codec_private_t *codec_data = (codec_private_t*) codec->codec_data; + + /* Use native PLC when PLC is enabled. */ + if (codec_data->plc_enabled) + return codec_decode(codec, NULL, output_buf_len, output); + + /* Otherwise just return zero-fill frame. */ + output->type = PJMEDIA_FRAME_TYPE_AUDIO; + output->size = codec_data->samples_per_frame << 1; + + pjmedia_zero_samples((pj_int16_t*)output->buf, + codec_data->samples_per_frame); + + return PJ_SUCCESS; +} + +#endif /* PJMEDIA_HAS_G7221_CODEC */ diff --git a/pjmedia/src/pjmedia-codec/g7221_sdp_match.c b/pjmedia/src/pjmedia-codec/g7221_sdp_match.c new file mode 100644 index 0000000..21e5b2c --- /dev/null +++ b/pjmedia/src/pjmedia-codec/g7221_sdp_match.c @@ -0,0 +1,91 @@ +/* $Id: g7221_sdp_match.c 3911 2011-12-15 06:45:23Z nanang $ */ +/* + * 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-codec/g7221_sdp_match.h> +#include <pjmedia/errno.h> +#include <pj/pool.h> +#include <pj/string.h> + + +#define GET_FMTP_IVAL_BASE(ival, base, fmtp, param, default_val) \ + do { \ + pj_str_t s; \ + char *p; \ + p = pj_stristr(&fmtp.fmt_param, ¶m); \ + if (!p) { \ + ival = default_val; \ + break; \ + } \ + pj_strset(&s, p + param.slen, fmtp.fmt_param.slen - \ + (p - fmtp.fmt_param.ptr) - param.slen); \ + ival = pj_strtoul2(&s, NULL, base); \ + } while (0) + +#define GET_FMTP_IVAL(ival, fmtp, param, default_val) \ + GET_FMTP_IVAL_BASE(ival, 10, fmtp, param, default_val) + + + +PJ_DEF(pj_status_t) pjmedia_codec_g7221_match_sdp(pj_pool_t *pool, + pjmedia_sdp_media *offer, + unsigned o_fmt_idx, + pjmedia_sdp_media *answer, + unsigned a_fmt_idx, + unsigned option) +{ + const pjmedia_sdp_attr *attr_ans; + const pjmedia_sdp_attr *attr_ofr; + pjmedia_sdp_fmtp fmtp; + unsigned a_bitrate, o_bitrate; + const pj_str_t bitrate = {"bitrate=", 8}; + pj_status_t status; + + PJ_UNUSED_ARG(pool); + PJ_UNUSED_ARG(option); + + /* Parse offer */ + attr_ofr = pjmedia_sdp_media_find_attr2(offer, "fmtp", + &offer->desc.fmt[o_fmt_idx]); + if (!attr_ofr) + return PJMEDIA_SDP_EINFMTP; + + status = pjmedia_sdp_attr_get_fmtp(attr_ofr, &fmtp); + if (status != PJ_SUCCESS) + return status; + + GET_FMTP_IVAL(o_bitrate, fmtp, bitrate, 0); + + /* Parse answer */ + attr_ans = pjmedia_sdp_media_find_attr2(answer, "fmtp", + &answer->desc.fmt[a_fmt_idx]); + if (!attr_ans) + return PJMEDIA_SDP_EINFMTP; + + status = pjmedia_sdp_attr_get_fmtp(attr_ans, &fmtp); + if (status != PJ_SUCCESS) + return status; + + GET_FMTP_IVAL(a_bitrate, fmtp, bitrate, 0); + + /* Compare bitrate in answer and offer. */ + if (a_bitrate != o_bitrate) + return PJMEDIA_SDP_EFORMATNOTEQUAL; + + return PJ_SUCCESS; +} diff --git a/pjmedia/src/pjmedia-codec/gsm.c b/pjmedia/src/pjmedia-codec/gsm.c new file mode 100644 index 0000000..c84f241 --- /dev/null +++ b/pjmedia/src/pjmedia-codec/gsm.c @@ -0,0 +1,645 @@ +/* $Id: gsm.c 3664 2011-07-19 03:42:28Z nanang $ */ +/* + * 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-codec/gsm.h> +#include <pjmedia/codec.h> +#include <pjmedia/errno.h> +#include <pjmedia/endpoint.h> +#include <pjmedia/plc.h> +#include <pjmedia/port.h> +#include <pjmedia/silencedet.h> +#include <pj/assert.h> +#include <pj/pool.h> +#include <pj/string.h> +#include <pj/os.h> + +/* + * Only build this file if PJMEDIA_HAS_GSM_CODEC != 0 + */ +#if defined(PJMEDIA_HAS_GSM_CODEC) && PJMEDIA_HAS_GSM_CODEC != 0 + +#if defined(PJMEDIA_EXTERNAL_GSM_CODEC) && PJMEDIA_EXTERNAL_GSM_CODEC +# if PJMEDIA_EXTERNAL_GSM_GSM_H +# include <gsm/gsm.h> +# elif PJMEDIA_EXTERNAL_GSM_H +# include <gsm.h> +# else +# error Please set the location of gsm.h +# endif +#else +# include "../../third_party/gsm/inc/gsm.h" +#endif + +/* We removed PLC in 0.6 (and re-enabled it again in 0.9!) */ +#define PLC_DISABLED 0 + + +/* Prototypes for GSM factory */ +static pj_status_t gsm_test_alloc( pjmedia_codec_factory *factory, + const pjmedia_codec_info *id ); +static pj_status_t gsm_default_attr( pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec_param *attr ); +static pj_status_t gsm_enum_codecs( pjmedia_codec_factory *factory, + unsigned *count, + pjmedia_codec_info codecs[]); +static pj_status_t gsm_alloc_codec( pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec **p_codec); +static pj_status_t gsm_dealloc_codec( pjmedia_codec_factory *factory, + pjmedia_codec *codec ); + +/* Prototypes for GSM implementation. */ +static pj_status_t gsm_codec_init( pjmedia_codec *codec, + pj_pool_t *pool ); +static pj_status_t gsm_codec_open( pjmedia_codec *codec, + pjmedia_codec_param *attr ); +static pj_status_t gsm_codec_close( pjmedia_codec *codec ); +static pj_status_t gsm_codec_modify(pjmedia_codec *codec, + const pjmedia_codec_param *attr ); +static pj_status_t gsm_codec_parse( pjmedia_codec *codec, + void *pkt, + pj_size_t pkt_size, + const pj_timestamp *ts, + unsigned *frame_cnt, + pjmedia_frame frames[]); +static pj_status_t gsm_codec_encode( pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output); +static pj_status_t gsm_codec_decode( pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output); +#if !PLC_DISABLED +static pj_status_t gsm_codec_recover(pjmedia_codec *codec, + unsigned output_buf_len, + struct pjmedia_frame *output); +#endif + +/* Definition for GSM codec operations. */ +static pjmedia_codec_op gsm_op = +{ + &gsm_codec_init, + &gsm_codec_open, + &gsm_codec_close, + &gsm_codec_modify, + &gsm_codec_parse, + &gsm_codec_encode, + &gsm_codec_decode, +#if !PLC_DISABLED + &gsm_codec_recover +#else + NULL +#endif +}; + +/* Definition for GSM codec factory operations. */ +static pjmedia_codec_factory_op gsm_factory_op = +{ + &gsm_test_alloc, + &gsm_default_attr, + &gsm_enum_codecs, + &gsm_alloc_codec, + &gsm_dealloc_codec, + &pjmedia_codec_gsm_deinit +}; + +/* GSM factory */ +static struct gsm_codec_factory +{ + pjmedia_codec_factory base; + pjmedia_endpt *endpt; + pj_pool_t *pool; + pj_mutex_t *mutex; + pjmedia_codec codec_list; +} gsm_codec_factory; + + +/* GSM codec private data. */ +struct gsm_data +{ + struct gsm_state *encoder; + struct gsm_state *decoder; + pj_bool_t plc_enabled; +#if !PLC_DISABLED + pjmedia_plc *plc; +#endif + pj_bool_t vad_enabled; + pjmedia_silence_det *vad; + pj_timestamp last_tx; +}; + + + +/* + * Initialize and register GSM codec factory to pjmedia endpoint. + */ +PJ_DEF(pj_status_t) pjmedia_codec_gsm_init( pjmedia_endpt *endpt ) +{ + pjmedia_codec_mgr *codec_mgr; + pj_status_t status; + + if (gsm_codec_factory.pool != NULL) + return PJ_SUCCESS; + + /* Create GSM codec factory. */ + gsm_codec_factory.base.op = &gsm_factory_op; + gsm_codec_factory.base.factory_data = NULL; + gsm_codec_factory.endpt = endpt; + + gsm_codec_factory.pool = pjmedia_endpt_create_pool(endpt, "gsm", 4000, + 4000); + if (!gsm_codec_factory.pool) + return PJ_ENOMEM; + + pj_list_init(&gsm_codec_factory.codec_list); + + /* Create mutex. */ + status = pj_mutex_create_simple(gsm_codec_factory.pool, "gsm", + &gsm_codec_factory.mutex); + if (status != PJ_SUCCESS) + goto on_error; + + /* Get the codec manager. */ + codec_mgr = pjmedia_endpt_get_codec_mgr(endpt); + if (!codec_mgr) { + status = PJ_EINVALIDOP; + goto on_error; + } + + /* Register codec factory to endpoint. */ + status = pjmedia_codec_mgr_register_factory(codec_mgr, + &gsm_codec_factory.base); + if (status != PJ_SUCCESS) + goto on_error; + + /* Done. */ + return PJ_SUCCESS; + +on_error: + pj_pool_release(gsm_codec_factory.pool); + gsm_codec_factory.pool = NULL; + return status; +} + + + +/* + * Unregister GSM codec factory from pjmedia endpoint and deinitialize + * the GSM codec library. + */ +PJ_DEF(pj_status_t) pjmedia_codec_gsm_deinit(void) +{ + pjmedia_codec_mgr *codec_mgr; + pj_status_t status; + + if (gsm_codec_factory.pool == NULL) + return PJ_SUCCESS; + + /* We don't want to deinit if there's outstanding codec. */ + /* This is silly, as we'll always have codec in the list if + we ever allocate a codec! A better behavior maybe is to + deallocate all codecs in the list. + pj_mutex_lock(gsm_codec_factory.mutex); + if (!pj_list_empty(&gsm_codec_factory.codec_list)) { + pj_mutex_unlock(gsm_codec_factory.mutex); + return PJ_EBUSY; + } + */ + + /* Get the codec manager. */ + codec_mgr = pjmedia_endpt_get_codec_mgr(gsm_codec_factory.endpt); + if (!codec_mgr) { + pj_pool_release(gsm_codec_factory.pool); + gsm_codec_factory.pool = NULL; + return PJ_EINVALIDOP; + } + + /* Unregister GSM codec factory. */ + status = pjmedia_codec_mgr_unregister_factory(codec_mgr, + &gsm_codec_factory.base); + + /* Destroy mutex. */ + pj_mutex_destroy(gsm_codec_factory.mutex); + + /* Destroy pool. */ + pj_pool_release(gsm_codec_factory.pool); + gsm_codec_factory.pool = NULL; + + return status; +} + +/* + * Check if factory can allocate the specified codec. + */ +static pj_status_t gsm_test_alloc( pjmedia_codec_factory *factory, + const pjmedia_codec_info *info ) +{ + PJ_UNUSED_ARG(factory); + + /* Check payload type. */ + if (info->pt != PJMEDIA_RTP_PT_GSM) + return PJMEDIA_CODEC_EUNSUP; + + /* Ignore the rest, since it's static payload type. */ + + return PJ_SUCCESS; +} + +/* + * Generate default attribute. + */ +static pj_status_t gsm_default_attr (pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec_param *attr ) +{ + PJ_UNUSED_ARG(factory); + PJ_UNUSED_ARG(id); + + pj_bzero(attr, sizeof(pjmedia_codec_param)); + attr->info.clock_rate = 8000; + attr->info.channel_cnt = 1; + attr->info.avg_bps = 13200; + attr->info.max_bps = 13200; + attr->info.pcm_bits_per_sample = 16; + attr->info.frm_ptime = 20; + attr->info.pt = PJMEDIA_RTP_PT_GSM; + + attr->setting.frm_per_pkt = 1; + attr->setting.vad = 1; +#if !PLC_DISABLED + attr->setting.plc = 1; +#endif + + /* Default all other flag bits disabled. */ + + return PJ_SUCCESS; +} + +/* + * Enum codecs supported by this factory (i.e. only GSM!). + */ +static pj_status_t gsm_enum_codecs(pjmedia_codec_factory *factory, + unsigned *count, + pjmedia_codec_info codecs[]) +{ + PJ_UNUSED_ARG(factory); + PJ_ASSERT_RETURN(codecs && *count > 0, PJ_EINVAL); + + pj_bzero(&codecs[0], sizeof(pjmedia_codec_info)); + codecs[0].encoding_name = pj_str("GSM"); + codecs[0].pt = PJMEDIA_RTP_PT_GSM; + codecs[0].type = PJMEDIA_TYPE_AUDIO; + codecs[0].clock_rate = 8000; + codecs[0].channel_cnt = 1; + + *count = 1; + + return PJ_SUCCESS; +} + +/* + * Allocate a new GSM codec instance. + */ +static pj_status_t gsm_alloc_codec( pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec **p_codec) +{ + pjmedia_codec *codec; + struct gsm_data *gsm_data; + pj_status_t status; + + PJ_ASSERT_RETURN(factory && id && p_codec, PJ_EINVAL); + PJ_ASSERT_RETURN(factory == &gsm_codec_factory.base, PJ_EINVAL); + + + pj_mutex_lock(gsm_codec_factory.mutex); + + /* Get free nodes, if any. */ + if (!pj_list_empty(&gsm_codec_factory.codec_list)) { + codec = gsm_codec_factory.codec_list.next; + pj_list_erase(codec); + } else { + codec = PJ_POOL_ZALLOC_T(gsm_codec_factory.pool, pjmedia_codec); + PJ_ASSERT_RETURN(codec != NULL, PJ_ENOMEM); + codec->op = &gsm_op; + codec->factory = factory; + + gsm_data = PJ_POOL_ZALLOC_T(gsm_codec_factory.pool, struct gsm_data); + codec->codec_data = gsm_data; + +#if !PLC_DISABLED + /* Create PLC */ + status = pjmedia_plc_create(gsm_codec_factory.pool, 8000, + 160, 0, &gsm_data->plc); + if (status != PJ_SUCCESS) { + pj_mutex_unlock(gsm_codec_factory.mutex); + return status; + } +#endif + + /* Create silence detector */ + status = pjmedia_silence_det_create(gsm_codec_factory.pool, + 8000, 160, + &gsm_data->vad); + if (status != PJ_SUCCESS) { + pj_mutex_unlock(gsm_codec_factory.mutex); + return status; + } + } + + pj_mutex_unlock(gsm_codec_factory.mutex); + + *p_codec = codec; + return PJ_SUCCESS; +} + +/* + * Free codec. + */ +static pj_status_t gsm_dealloc_codec( pjmedia_codec_factory *factory, + pjmedia_codec *codec ) +{ + struct gsm_data *gsm_data; + int i; + + PJ_ASSERT_RETURN(factory && codec, PJ_EINVAL); + PJ_ASSERT_RETURN(factory == &gsm_codec_factory.base, PJ_EINVAL); + + gsm_data = (struct gsm_data*) codec->codec_data; + + /* Close codec, if it's not closed. */ + gsm_codec_close(codec); + +#if !PLC_DISABLED + /* Clear left samples in the PLC, since codec+plc will be reused + * next time. + */ + for (i=0; i<2; ++i) { + pj_int16_t frame[160]; + pjmedia_zero_samples(frame, PJ_ARRAY_SIZE(frame)); + pjmedia_plc_save(gsm_data->plc, frame); + } +#else + PJ_UNUSED_ARG(i); +#endif + + /* Re-init silence_period */ + pj_set_timestamp32(&gsm_data->last_tx, 0, 0); + + /* Put in the free list. */ + pj_mutex_lock(gsm_codec_factory.mutex); + pj_list_push_front(&gsm_codec_factory.codec_list, codec); + pj_mutex_unlock(gsm_codec_factory.mutex); + + return PJ_SUCCESS; +} + +/* + * Init codec. + */ +static pj_status_t gsm_codec_init( pjmedia_codec *codec, + pj_pool_t *pool ) +{ + PJ_UNUSED_ARG(codec); + PJ_UNUSED_ARG(pool); + return PJ_SUCCESS; +} + +/* + * Open codec. + */ +static pj_status_t gsm_codec_open( pjmedia_codec *codec, + pjmedia_codec_param *attr ) +{ + struct gsm_data *gsm_data = (struct gsm_data*) codec->codec_data; + + pj_assert(gsm_data != NULL); + pj_assert(gsm_data->encoder == NULL && gsm_data->decoder == NULL); + + gsm_data->encoder = gsm_create(); + if (!gsm_data->encoder) + return PJMEDIA_CODEC_EFAILED; + + gsm_data->decoder = gsm_create(); + if (!gsm_data->decoder) + return PJMEDIA_CODEC_EFAILED; + + gsm_data->vad_enabled = (attr->setting.vad != 0); + gsm_data->plc_enabled = (attr->setting.plc != 0); + + return PJ_SUCCESS; +} + +/* + * Close codec. + */ +static pj_status_t gsm_codec_close( pjmedia_codec *codec ) +{ + struct gsm_data *gsm_data = (struct gsm_data*) codec->codec_data; + + pj_assert(gsm_data != NULL); + + if (gsm_data->encoder) { + gsm_destroy(gsm_data->encoder); + gsm_data->encoder = NULL; + } + if (gsm_data->decoder) { + gsm_destroy(gsm_data->decoder); + gsm_data->decoder = NULL; + } + + return PJ_SUCCESS; +} + + +/* + * Modify codec settings. + */ +static pj_status_t gsm_codec_modify(pjmedia_codec *codec, + const pjmedia_codec_param *attr ) +{ + struct gsm_data *gsm_data = (struct gsm_data*) codec->codec_data; + + pj_assert(gsm_data != NULL); + pj_assert(gsm_data->encoder != NULL && gsm_data->decoder != NULL); + + gsm_data->vad_enabled = (attr->setting.vad != 0); + gsm_data->plc_enabled = (attr->setting.plc != 0); + + return PJ_SUCCESS; +} + + +/* + * Get frames in the packet. + */ +static pj_status_t gsm_codec_parse( pjmedia_codec *codec, + void *pkt, + pj_size_t pkt_size, + const pj_timestamp *ts, + unsigned *frame_cnt, + pjmedia_frame frames[]) +{ + unsigned count = 0; + + PJ_UNUSED_ARG(codec); + + PJ_ASSERT_RETURN(frame_cnt, PJ_EINVAL); + + while (pkt_size >= 33 && count < *frame_cnt) { + frames[count].type = PJMEDIA_FRAME_TYPE_AUDIO; + frames[count].buf = pkt; + frames[count].size = 33; + frames[count].timestamp.u64 = ts->u64 + count * 160; + + pkt = ((char*)pkt) + 33; + pkt_size -= 33; + + ++count; + } + + *frame_cnt = count; + return PJ_SUCCESS; +} + +/* + * Encode frame. + */ +static pj_status_t gsm_codec_encode( pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output) +{ + struct gsm_data *gsm_data = (struct gsm_data*) codec->codec_data; + pj_int16_t *pcm_in; + unsigned in_size; + + pj_assert(gsm_data && input && output); + + pcm_in = (pj_int16_t*)input->buf; + in_size = input->size; + + PJ_ASSERT_RETURN(in_size % 320 == 0, PJMEDIA_CODEC_EPCMFRMINLEN); + PJ_ASSERT_RETURN(output_buf_len >= 33 * in_size/320, + PJMEDIA_CODEC_EFRMTOOSHORT); + + /* Detect silence */ + if (gsm_data->vad_enabled) { + pj_bool_t is_silence; + pj_int32_t silence_duration; + + silence_duration = pj_timestamp_diff32(&gsm_data->last_tx, + &input->timestamp); + + is_silence = pjmedia_silence_det_detect(gsm_data->vad, + (const pj_int16_t*) input->buf, + (input->size >> 1), + NULL); + if (is_silence && + (PJMEDIA_CODEC_MAX_SILENCE_PERIOD == -1 || + silence_duration < PJMEDIA_CODEC_MAX_SILENCE_PERIOD*8000/1000)) + { + output->type = PJMEDIA_FRAME_TYPE_NONE; + output->buf = NULL; + output->size = 0; + output->timestamp = input->timestamp; + return PJ_SUCCESS; + } else { + gsm_data->last_tx = input->timestamp; + } + } + + /* Encode */ + output->size = 0; + while (in_size >= 320) { + gsm_encode(gsm_data->encoder, pcm_in, + (unsigned char*)output->buf + output->size); + pcm_in += 160; + output->size += 33; + in_size -= 320; + } + + output->type = PJMEDIA_FRAME_TYPE_AUDIO; + output->timestamp = input->timestamp; + + return PJ_SUCCESS; +} + +/* + * Decode frame. + */ +static pj_status_t gsm_codec_decode( pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output) +{ + struct gsm_data *gsm_data = (struct gsm_data*) codec->codec_data; + + pj_assert(gsm_data != NULL); + PJ_ASSERT_RETURN(input && output, PJ_EINVAL); + + if (output_buf_len < 320) + return PJMEDIA_CODEC_EPCMTOOSHORT; + + if (input->size < 33) + return PJMEDIA_CODEC_EFRMTOOSHORT; + + gsm_decode(gsm_data->decoder, + (unsigned char*)input->buf, + (short*)output->buf); + + output->size = 320; + output->type = PJMEDIA_FRAME_TYPE_AUDIO; + output->timestamp = input->timestamp; + +#if !PLC_DISABLED + if (gsm_data->plc_enabled) + pjmedia_plc_save( gsm_data->plc, (pj_int16_t*)output->buf); +#endif + + return PJ_SUCCESS; +} + + +#if !PLC_DISABLED +/* + * Recover lost frame. + */ +static pj_status_t gsm_codec_recover(pjmedia_codec *codec, + unsigned output_buf_len, + struct pjmedia_frame *output) +{ + struct gsm_data *gsm_data = (struct gsm_data*) codec->codec_data; + + PJ_ASSERT_RETURN(gsm_data->plc_enabled, PJ_EINVALIDOP); + + PJ_ASSERT_RETURN(output_buf_len >= 320, PJMEDIA_CODEC_EPCMTOOSHORT); + + pjmedia_plc_generate(gsm_data->plc, (pj_int16_t*)output->buf); + output->size = 320; + + return PJ_SUCCESS; +} +#endif + + +#endif /* PJMEDIA_HAS_GSM_CODEC */ + diff --git a/pjmedia/src/pjmedia-codec/h263_packetizer.c b/pjmedia/src/pjmedia-codec/h263_packetizer.c new file mode 100644 index 0000000..a953242 --- /dev/null +++ b/pjmedia/src/pjmedia-codec/h263_packetizer.c @@ -0,0 +1,294 @@ +/* $Id: h263_packetizer.c 4006 2012-04-02 08:40:54Z nanang $ */ +/* + * 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 <pjmedia-codec/h263_packetizer.h> +#include <pjmedia/types.h> +#include <pj/assert.h> +#include <pj/errno.h> +#include <pj/string.h> + + +#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0) + + +#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_VID_PAYLOAD_SIZE; + } + + *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; +} + + +#endif /* PJMEDIA_HAS_VIDEO */ diff --git a/pjmedia/src/pjmedia-codec/h264_packetizer.c b/pjmedia/src/pjmedia-codec/h264_packetizer.c new file mode 100644 index 0000000..59ca418 --- /dev/null +++ b/pjmedia/src/pjmedia-codec/h264_packetizer.c @@ -0,0 +1,535 @@ +/* $Id: h264_packetizer.c 4006 2012-04-02 08:40:54Z nanang $ */ +/* + * 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> + + +#if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0) + + +#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_VID_PAYLOAD_SIZE; + } + + *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 */ + *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; +} + + +#endif /* PJMEDIA_HAS_VIDEO */ diff --git a/pjmedia/src/pjmedia-codec/ilbc.c b/pjmedia/src/pjmedia-codec/ilbc.c new file mode 100644 index 0000000..bbebdfe --- /dev/null +++ b/pjmedia/src/pjmedia-codec/ilbc.c @@ -0,0 +1,883 @@ +/* $Id: ilbc.c 3664 2011-07-19 03:42:28Z nanang $ */ +/* + * 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-codec/ilbc.h> +#include <pjmedia-codec/types.h> +#include <pjmedia/codec.h> +#include <pjmedia/errno.h> +#include <pjmedia/endpoint.h> +#include <pjmedia/plc.h> +#include <pjmedia/port.h> +#include <pjmedia/silencedet.h> +#include <pj/assert.h> +#include <pj/log.h> +#include <pj/pool.h> +#include <pj/string.h> +#include <pj/os.h> + +#if defined(PJMEDIA_ILBC_CODEC_USE_COREAUDIO)&& PJMEDIA_ILBC_CODEC_USE_COREAUDIO + #include <AudioToolbox/AudioToolbox.h> + #define iLBC_Enc_Inst_t AudioConverterRef + #define iLBC_Dec_Inst_t AudioConverterRef + #define BLOCKL_MAX 1 +#else + #include "../../third_party/ilbc/iLBC_encode.h" + #include "../../third_party/ilbc/iLBC_decode.h" +#endif + +/* + * Only build this file if PJMEDIA_HAS_ILBC_CODEC != 0 + */ +#if defined(PJMEDIA_HAS_ILBC_CODEC) && PJMEDIA_HAS_ILBC_CODEC != 0 + + +#define THIS_FILE "ilbc.c" +#define CLOCK_RATE 8000 +#define DEFAULT_MODE 30 + + +/* Prototypes for iLBC factory */ +static pj_status_t ilbc_test_alloc(pjmedia_codec_factory *factory, + const pjmedia_codec_info *id ); +static pj_status_t ilbc_default_attr(pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec_param *attr ); +static pj_status_t ilbc_enum_codecs(pjmedia_codec_factory *factory, + unsigned *count, + pjmedia_codec_info codecs[]); +static pj_status_t ilbc_alloc_codec(pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec **p_codec); +static pj_status_t ilbc_dealloc_codec(pjmedia_codec_factory *factory, + pjmedia_codec *codec ); + +/* Prototypes for iLBC implementation. */ +static pj_status_t ilbc_codec_init(pjmedia_codec *codec, + pj_pool_t *pool ); +static pj_status_t ilbc_codec_open(pjmedia_codec *codec, + pjmedia_codec_param *attr ); +static pj_status_t ilbc_codec_close(pjmedia_codec *codec ); +static pj_status_t ilbc_codec_modify(pjmedia_codec *codec, + const pjmedia_codec_param *attr ); +static pj_status_t ilbc_codec_parse(pjmedia_codec *codec, + void *pkt, + pj_size_t pkt_size, + const pj_timestamp *ts, + unsigned *frame_cnt, + pjmedia_frame frames[]); +static pj_status_t ilbc_codec_encode(pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output); +static pj_status_t ilbc_codec_decode(pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output); +static pj_status_t ilbc_codec_recover(pjmedia_codec *codec, + unsigned output_buf_len, + struct pjmedia_frame *output); + +/* Definition for iLBC codec operations. */ +static pjmedia_codec_op ilbc_op = +{ + &ilbc_codec_init, + &ilbc_codec_open, + &ilbc_codec_close, + &ilbc_codec_modify, + &ilbc_codec_parse, + &ilbc_codec_encode, + &ilbc_codec_decode, + &ilbc_codec_recover +}; + +/* Definition for iLBC codec factory operations. */ +static pjmedia_codec_factory_op ilbc_factory_op = +{ + &ilbc_test_alloc, + &ilbc_default_attr, + &ilbc_enum_codecs, + &ilbc_alloc_codec, + &ilbc_dealloc_codec, + &pjmedia_codec_ilbc_deinit +}; + +/* iLBC factory */ +static struct ilbc_factory +{ + pjmedia_codec_factory base; + pjmedia_endpt *endpt; + + int mode; + int bps; +} ilbc_factory; + + +/* iLBC codec private data. */ +struct ilbc_codec +{ + pjmedia_codec base; + pj_pool_t *pool; + char obj_name[PJ_MAX_OBJ_NAME]; + pjmedia_silence_det *vad; + pj_bool_t vad_enabled; + pj_bool_t plc_enabled; + pj_timestamp last_tx; + + + pj_bool_t enc_ready; + iLBC_Enc_Inst_t enc; + unsigned enc_frame_size; + unsigned enc_samples_per_frame; + float enc_block[BLOCKL_MAX]; + + pj_bool_t dec_ready; + iLBC_Dec_Inst_t dec; + unsigned dec_frame_size; + unsigned dec_samples_per_frame; + float dec_block[BLOCKL_MAX]; + +#if defined(PJMEDIA_ILBC_CODEC_USE_COREAUDIO)&& PJMEDIA_ILBC_CODEC_USE_COREAUDIO + unsigned enc_total_packets; + char *enc_buffer; + unsigned enc_buffer_offset; + + unsigned dec_total_packets; + char *dec_buffer; + unsigned dec_buffer_offset; +#endif +}; + +static pj_str_t STR_MODE = {"mode", 4}; + +/* + * Initialize and register iLBC codec factory to pjmedia endpoint. + */ +PJ_DEF(pj_status_t) pjmedia_codec_ilbc_init( pjmedia_endpt *endpt, + int mode ) +{ + pjmedia_codec_mgr *codec_mgr; + pj_status_t status; + + PJ_ASSERT_RETURN(endpt != NULL, PJ_EINVAL); + PJ_ASSERT_RETURN(mode==0 || mode==20 || mode==30, PJ_EINVAL); + + /* Create iLBC codec factory. */ + ilbc_factory.base.op = &ilbc_factory_op; + ilbc_factory.base.factory_data = NULL; + ilbc_factory.endpt = endpt; + + if (mode == 0) + mode = DEFAULT_MODE; + + ilbc_factory.mode = mode; + + if (mode == 20) { + ilbc_factory.bps = 15200; + } else { + ilbc_factory.bps = 13333; + } + + /* Get the codec manager. */ + codec_mgr = pjmedia_endpt_get_codec_mgr(endpt); + if (!codec_mgr) + return PJ_EINVALIDOP; + + /* Register codec factory to endpoint. */ + status = pjmedia_codec_mgr_register_factory(codec_mgr, + &ilbc_factory.base); + if (status != PJ_SUCCESS) + return status; + + + /* Done. */ + return PJ_SUCCESS; +} + + + +/* + * Unregister iLBC codec factory from pjmedia endpoint and deinitialize + * the iLBC codec library. + */ +PJ_DEF(pj_status_t) pjmedia_codec_ilbc_deinit(void) +{ + pjmedia_codec_mgr *codec_mgr; + pj_status_t status; + + + /* Get the codec manager. */ + codec_mgr = pjmedia_endpt_get_codec_mgr(ilbc_factory.endpt); + if (!codec_mgr) + return PJ_EINVALIDOP; + + /* Unregister iLBC codec factory. */ + status = pjmedia_codec_mgr_unregister_factory(codec_mgr, + &ilbc_factory.base); + + return status; +} + +/* + * Check if factory can allocate the specified codec. + */ +static pj_status_t ilbc_test_alloc( pjmedia_codec_factory *factory, + const pjmedia_codec_info *info ) +{ + const pj_str_t ilbc_tag = { "iLBC", 4}; + + PJ_UNUSED_ARG(factory); + PJ_ASSERT_RETURN(factory==&ilbc_factory.base, PJ_EINVAL); + + + /* Type MUST be audio. */ + if (info->type != PJMEDIA_TYPE_AUDIO) + return PJMEDIA_CODEC_EUNSUP; + + /* Check encoding name. */ + if (pj_stricmp(&info->encoding_name, &ilbc_tag) != 0) + return PJMEDIA_CODEC_EUNSUP; + + /* Check clock-rate */ + if (info->clock_rate != CLOCK_RATE) + return PJMEDIA_CODEC_EUNSUP; + + /* Channel count must be one */ + if (info->channel_cnt != 1) + return PJMEDIA_CODEC_EUNSUP; + + /* Yes, this should be iLBC! */ + return PJ_SUCCESS; +} + + +/* + * Generate default attribute. + */ +static pj_status_t ilbc_default_attr (pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec_param *attr ) +{ + PJ_UNUSED_ARG(factory); + PJ_ASSERT_RETURN(factory==&ilbc_factory.base, PJ_EINVAL); + + PJ_UNUSED_ARG(id); + PJ_ASSERT_RETURN(pj_stricmp2(&id->encoding_name, "iLBC")==0, PJ_EINVAL); + + pj_bzero(attr, sizeof(pjmedia_codec_param)); + + attr->info.clock_rate = CLOCK_RATE; + attr->info.channel_cnt = 1; + attr->info.avg_bps = ilbc_factory.bps; + attr->info.max_bps = 15200; + attr->info.pcm_bits_per_sample = 16; + attr->info.frm_ptime = (short)ilbc_factory.mode; + attr->info.pt = PJMEDIA_RTP_PT_ILBC; + + attr->setting.frm_per_pkt = 1; + attr->setting.vad = 1; + attr->setting.plc = 1; + attr->setting.penh = 1; + attr->setting.dec_fmtp.cnt = 1; + attr->setting.dec_fmtp.param[0].name = STR_MODE; + if (ilbc_factory.mode == 30) + attr->setting.dec_fmtp.param[0].val = pj_str("30"); + else + attr->setting.dec_fmtp.param[0].val = pj_str("20"); + + return PJ_SUCCESS; +} + +/* + * Enum codecs supported by this factory (i.e. only iLBC!). + */ +static pj_status_t ilbc_enum_codecs(pjmedia_codec_factory *factory, + unsigned *count, + pjmedia_codec_info codecs[]) +{ + PJ_UNUSED_ARG(factory); + PJ_ASSERT_RETURN(factory==&ilbc_factory.base, PJ_EINVAL); + + PJ_ASSERT_RETURN(codecs && *count > 0, PJ_EINVAL); + + pj_bzero(&codecs[0], sizeof(pjmedia_codec_info)); + + codecs[0].encoding_name = pj_str("iLBC"); + codecs[0].pt = PJMEDIA_RTP_PT_ILBC; + codecs[0].type = PJMEDIA_TYPE_AUDIO; + codecs[0].clock_rate = 8000; + codecs[0].channel_cnt = 1; + + *count = 1; + + return PJ_SUCCESS; +} + +/* + * Allocate a new iLBC codec instance. + */ +static pj_status_t ilbc_alloc_codec(pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec **p_codec) +{ + pj_pool_t *pool; + struct ilbc_codec *codec; + + PJ_ASSERT_RETURN(factory && id && p_codec, PJ_EINVAL); + PJ_ASSERT_RETURN(factory == &ilbc_factory.base, PJ_EINVAL); + + pool = pjmedia_endpt_create_pool(ilbc_factory.endpt, "iLBC%p", + 2000, 2000); + PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); + + codec = PJ_POOL_ZALLOC_T(pool, struct ilbc_codec); + codec->base.op = &ilbc_op; + codec->base.factory = factory; + codec->pool = pool; + + pj_ansi_snprintf(codec->obj_name, sizeof(codec->obj_name), + "ilbc%p", codec); + + *p_codec = &codec->base; + return PJ_SUCCESS; +} + + +/* + * Free codec. + */ +static pj_status_t ilbc_dealloc_codec( pjmedia_codec_factory *factory, + pjmedia_codec *codec ) +{ + struct ilbc_codec *ilbc_codec; + + PJ_ASSERT_RETURN(factory && codec, PJ_EINVAL); + PJ_UNUSED_ARG(factory); + PJ_ASSERT_RETURN(factory == &ilbc_factory.base, PJ_EINVAL); + + ilbc_codec = (struct ilbc_codec*) codec; + +#if defined(PJMEDIA_ILBC_CODEC_USE_COREAUDIO)&& PJMEDIA_ILBC_CODEC_USE_COREAUDIO + if (ilbc_codec->enc) { + AudioConverterDispose(ilbc_codec->enc); + ilbc_codec->enc = NULL; + } + if (ilbc_codec->dec) { + AudioConverterDispose(ilbc_codec->dec); + ilbc_codec->dec = NULL; + } +#endif + + pj_pool_release(ilbc_codec->pool); + + return PJ_SUCCESS; +} + +/* + * Init codec. + */ +static pj_status_t ilbc_codec_init(pjmedia_codec *codec, + pj_pool_t *pool ) +{ + PJ_UNUSED_ARG(codec); + PJ_UNUSED_ARG(pool); + return PJ_SUCCESS; +} + +/* + * Open codec. + */ +static pj_status_t ilbc_codec_open(pjmedia_codec *codec, + pjmedia_codec_param *attr ) +{ + struct ilbc_codec *ilbc_codec = (struct ilbc_codec*)codec; + pj_status_t status; + unsigned i; + pj_uint16_t dec_fmtp_mode = DEFAULT_MODE, + enc_fmtp_mode = DEFAULT_MODE; + +#if defined(PJMEDIA_ILBC_CODEC_USE_COREAUDIO)&& PJMEDIA_ILBC_CODEC_USE_COREAUDIO + AudioStreamBasicDescription srcFormat, dstFormat; + UInt32 size; + + srcFormat.mSampleRate = attr->info.clock_rate; + srcFormat.mFormatID = kAudioFormatLinearPCM; + srcFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger + | kLinearPCMFormatFlagIsPacked; + srcFormat.mBitsPerChannel = attr->info.pcm_bits_per_sample; + srcFormat.mChannelsPerFrame = attr->info.channel_cnt; + srcFormat.mBytesPerFrame = srcFormat.mChannelsPerFrame + * srcFormat.mBitsPerChannel >> 3; + srcFormat.mFramesPerPacket = 1; + srcFormat.mBytesPerPacket = srcFormat.mBytesPerFrame * + srcFormat.mFramesPerPacket; + + memset(&dstFormat, 0, sizeof(dstFormat)); + dstFormat.mSampleRate = attr->info.clock_rate; + dstFormat.mFormatID = kAudioFormatiLBC; + dstFormat.mChannelsPerFrame = attr->info.channel_cnt; +#endif + + pj_assert(ilbc_codec != NULL); + pj_assert(ilbc_codec->enc_ready == PJ_FALSE && + ilbc_codec->dec_ready == PJ_FALSE); + + /* Get decoder mode */ + for (i = 0; i < attr->setting.dec_fmtp.cnt; ++i) { + if (pj_stricmp(&attr->setting.dec_fmtp.param[i].name, &STR_MODE) == 0) + { + dec_fmtp_mode = (pj_uint16_t) + pj_strtoul(&attr->setting.dec_fmtp.param[i].val); + break; + } + } + + /* Decoder mode must be set */ + PJ_ASSERT_RETURN(dec_fmtp_mode == 20 || dec_fmtp_mode == 30, + PJMEDIA_CODEC_EINMODE); + + /* Get encoder mode */ + for (i = 0; i < attr->setting.enc_fmtp.cnt; ++i) { + if (pj_stricmp(&attr->setting.enc_fmtp.param[i].name, &STR_MODE) == 0) + { + enc_fmtp_mode = (pj_uint16_t) + pj_strtoul(&attr->setting.enc_fmtp.param[i].val); + break; + } + } + + PJ_ASSERT_RETURN(enc_fmtp_mode==20 || enc_fmtp_mode==30, + PJMEDIA_CODEC_EINMODE); + + /* Both sides of a bi-directional session MUST use the same "mode" value. + * In this point, possible values are only 20 or 30, so when encoder and + * decoder modes are not same, just use the default mode, it is 30. + */ + if (enc_fmtp_mode != dec_fmtp_mode) { + enc_fmtp_mode = dec_fmtp_mode = DEFAULT_MODE; + PJ_LOG(4,(ilbc_codec->obj_name, + "Normalized iLBC encoder and decoder modes to %d", + DEFAULT_MODE)); + } + + /* Update some attributes based on negotiated mode. */ + attr->info.avg_bps = (dec_fmtp_mode == 30? 13333 : 15200); + attr->info.frm_ptime = dec_fmtp_mode; + + /* Create encoder */ +#if defined(PJMEDIA_ILBC_CODEC_USE_COREAUDIO)&& PJMEDIA_ILBC_CODEC_USE_COREAUDIO + dstFormat.mFramesPerPacket = CLOCK_RATE * enc_fmtp_mode / 1000; + dstFormat.mBytesPerPacket = (enc_fmtp_mode == 20? 38 : 50); + + /* Use AudioFormat API to fill out the rest of the description */ + size = sizeof(dstFormat); + AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, + 0, NULL, &size, &dstFormat); + + if (AudioConverterNew(&srcFormat, &dstFormat, &ilbc_codec->enc) != noErr) + return PJMEDIA_CODEC_EFAILED; + ilbc_codec->enc_frame_size = (enc_fmtp_mode == 20? 38 : 50); +#else + ilbc_codec->enc_frame_size = initEncode(&ilbc_codec->enc, enc_fmtp_mode); +#endif + ilbc_codec->enc_samples_per_frame = CLOCK_RATE * enc_fmtp_mode / 1000; + ilbc_codec->enc_ready = PJ_TRUE; + + /* Create decoder */ +#if defined(PJMEDIA_ILBC_CODEC_USE_COREAUDIO)&& PJMEDIA_ILBC_CODEC_USE_COREAUDIO + if (AudioConverterNew(&dstFormat, &srcFormat, &ilbc_codec->dec) != noErr) + return PJMEDIA_CODEC_EFAILED; + ilbc_codec->dec_samples_per_frame = CLOCK_RATE * dec_fmtp_mode / 1000; +#else + ilbc_codec->dec_samples_per_frame = initDecode(&ilbc_codec->dec, + dec_fmtp_mode, + attr->setting.penh); +#endif + ilbc_codec->dec_frame_size = (dec_fmtp_mode == 20? 38 : 50); + ilbc_codec->dec_ready = PJ_TRUE; + + /* Save plc flags */ + ilbc_codec->plc_enabled = (attr->setting.plc != 0); + + /* Create silence detector. */ + ilbc_codec->vad_enabled = (attr->setting.vad != 0); + status = pjmedia_silence_det_create(ilbc_codec->pool, CLOCK_RATE, + ilbc_codec->enc_samples_per_frame, + &ilbc_codec->vad); + if (status != PJ_SUCCESS) + return status; + + /* Init last_tx (not necessary because of zalloc, but better + * be safe in case someone remove zalloc later. + */ + pj_set_timestamp32(&ilbc_codec->last_tx, 0, 0); + + PJ_LOG(5,(ilbc_codec->obj_name, + "iLBC codec opened, mode=%d", dec_fmtp_mode)); + + return PJ_SUCCESS; +} + + +/* + * Close codec. + */ +static pj_status_t ilbc_codec_close( pjmedia_codec *codec ) +{ + struct ilbc_codec *ilbc_codec = (struct ilbc_codec*)codec; + + PJ_UNUSED_ARG(codec); + + PJ_LOG(5,(ilbc_codec->obj_name, "iLBC codec closed")); + + return PJ_SUCCESS; +} + +/* + * Modify codec settings. + */ +static pj_status_t ilbc_codec_modify(pjmedia_codec *codec, + const pjmedia_codec_param *attr ) +{ + struct ilbc_codec *ilbc_codec = (struct ilbc_codec*)codec; + + ilbc_codec->plc_enabled = (attr->setting.plc != 0); + ilbc_codec->vad_enabled = (attr->setting.vad != 0); + + return PJ_SUCCESS; +} + +/* + * Get frames in the packet. + */ +static pj_status_t ilbc_codec_parse( pjmedia_codec *codec, + void *pkt, + pj_size_t pkt_size, + const pj_timestamp *ts, + unsigned *frame_cnt, + pjmedia_frame frames[]) +{ + struct ilbc_codec *ilbc_codec = (struct ilbc_codec*)codec; + unsigned count; + + PJ_ASSERT_RETURN(frame_cnt, PJ_EINVAL); + + count = 0; + while (pkt_size >= ilbc_codec->dec_frame_size && count < *frame_cnt) { + frames[count].type = PJMEDIA_FRAME_TYPE_AUDIO; + frames[count].buf = pkt; + frames[count].size = ilbc_codec->dec_frame_size; + frames[count].timestamp.u64 = ts->u64 + count * + ilbc_codec->dec_samples_per_frame; + + pkt = ((char*)pkt) + ilbc_codec->dec_frame_size; + pkt_size -= ilbc_codec->dec_frame_size; + + ++count; + } + + *frame_cnt = count; + return PJ_SUCCESS; +} + +#if defined(PJMEDIA_ILBC_CODEC_USE_COREAUDIO)&& PJMEDIA_ILBC_CODEC_USE_COREAUDIO +static OSStatus encodeDataProc ( + AudioConverterRef inAudioConverter, + UInt32 *ioNumberDataPackets, + AudioBufferList *ioData, + AudioStreamPacketDescription **outDataPacketDescription, + void *inUserData +) +{ + struct ilbc_codec *ilbc_codec = (struct ilbc_codec*)inUserData; + + /* Initialize in case of failure */ + ioData->mBuffers[0].mData = NULL; + ioData->mBuffers[0].mDataByteSize = 0; + + if (ilbc_codec->enc_total_packets < *ioNumberDataPackets) { + *ioNumberDataPackets = ilbc_codec->enc_total_packets; + } + + if (*ioNumberDataPackets) { + ioData->mBuffers[0].mData = ilbc_codec->enc_buffer + + ilbc_codec->enc_buffer_offset; + ioData->mBuffers[0].mDataByteSize = *ioNumberDataPackets * + ilbc_codec->enc_samples_per_frame + << 1; + ilbc_codec->enc_buffer_offset += ioData->mBuffers[0].mDataByteSize; + } + + ilbc_codec->enc_total_packets -= *ioNumberDataPackets; + return noErr; +} + +static OSStatus decodeDataProc ( + AudioConverterRef inAudioConverter, + UInt32 *ioNumberDataPackets, + AudioBufferList *ioData, + AudioStreamPacketDescription **outDataPacketDescription, + void *inUserData +) +{ + struct ilbc_codec *ilbc_codec = (struct ilbc_codec*)inUserData; + + /* Initialize in case of failure */ + ioData->mBuffers[0].mData = NULL; + ioData->mBuffers[0].mDataByteSize = 0; + + if (ilbc_codec->dec_total_packets < *ioNumberDataPackets) { + *ioNumberDataPackets = ilbc_codec->dec_total_packets; + } + + if (*ioNumberDataPackets) { + ioData->mBuffers[0].mData = ilbc_codec->dec_buffer + + ilbc_codec->dec_buffer_offset; + ioData->mBuffers[0].mDataByteSize = *ioNumberDataPackets * + ilbc_codec->dec_frame_size; + ilbc_codec->dec_buffer_offset += ioData->mBuffers[0].mDataByteSize; + } + + ilbc_codec->dec_total_packets -= *ioNumberDataPackets; + return noErr; +} +#endif + +/* + * Encode frame. + */ +static pj_status_t ilbc_codec_encode(pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output) +{ + struct ilbc_codec *ilbc_codec = (struct ilbc_codec*)codec; + pj_int16_t *pcm_in; + unsigned nsamples; +#if defined(PJMEDIA_ILBC_CODEC_USE_COREAUDIO)&& PJMEDIA_ILBC_CODEC_USE_COREAUDIO + UInt32 npackets; + OSStatus err; + AudioBufferList theABL; +#endif + + pj_assert(ilbc_codec && input && output); + + pcm_in = (pj_int16_t*)input->buf; + nsamples = input->size >> 1; + + PJ_ASSERT_RETURN(nsamples % ilbc_codec->enc_samples_per_frame == 0, + PJMEDIA_CODEC_EPCMFRMINLEN); + PJ_ASSERT_RETURN(output_buf_len >= ilbc_codec->enc_frame_size * nsamples / + ilbc_codec->enc_samples_per_frame, + PJMEDIA_CODEC_EFRMTOOSHORT); + + /* Detect silence */ + if (ilbc_codec->vad_enabled) { + pj_bool_t is_silence; + pj_int32_t silence_period; + + silence_period = pj_timestamp_diff32(&ilbc_codec->last_tx, + &input->timestamp); + + is_silence = pjmedia_silence_det_detect(ilbc_codec->vad, + (const pj_int16_t*)input->buf, + (input->size >> 1), + NULL); + if (is_silence && + (PJMEDIA_CODEC_MAX_SILENCE_PERIOD == -1 || + silence_period < PJMEDIA_CODEC_MAX_SILENCE_PERIOD*8000/1000)) + { + output->type = PJMEDIA_FRAME_TYPE_NONE; + output->buf = NULL; + output->size = 0; + output->timestamp = input->timestamp; + return PJ_SUCCESS; + } else { + ilbc_codec->last_tx = input->timestamp; + } + } + + /* Encode */ + output->size = 0; +#if defined(PJMEDIA_ILBC_CODEC_USE_COREAUDIO)&& PJMEDIA_ILBC_CODEC_USE_COREAUDIO + npackets = nsamples / ilbc_codec->enc_samples_per_frame; + + theABL.mNumberBuffers = 1; + theABL.mBuffers[0].mNumberChannels = 1; + theABL.mBuffers[0].mDataByteSize = output_buf_len; + theABL.mBuffers[0].mData = output->buf; + + ilbc_codec->enc_total_packets = npackets; + ilbc_codec->enc_buffer = (char *)input->buf; + ilbc_codec->enc_buffer_offset = 0; + + err = AudioConverterFillComplexBuffer(ilbc_codec->enc, encodeDataProc, + ilbc_codec, &npackets, + &theABL, NULL); + if (err == noErr) { + output->size = npackets * ilbc_codec->enc_frame_size; + } +#else + while (nsamples >= ilbc_codec->enc_samples_per_frame) { + unsigned i; + + /* Convert to float */ + for (i=0; i<ilbc_codec->enc_samples_per_frame; ++i) { + ilbc_codec->enc_block[i] = (float) (*pcm_in++); + } + + iLBC_encode((unsigned char *)output->buf + output->size, + ilbc_codec->enc_block, + &ilbc_codec->enc); + + output->size += ilbc_codec->enc.no_of_bytes; + nsamples -= ilbc_codec->enc_samples_per_frame; + } +#endif + + output->type = PJMEDIA_FRAME_TYPE_AUDIO; + output->timestamp = input->timestamp; + + return PJ_SUCCESS; +} + +/* + * Decode frame. + */ +static pj_status_t ilbc_codec_decode(pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output) +{ + struct ilbc_codec *ilbc_codec = (struct ilbc_codec*)codec; +#if defined(PJMEDIA_ILBC_CODEC_USE_COREAUDIO)&& PJMEDIA_ILBC_CODEC_USE_COREAUDIO + UInt32 npackets; + OSStatus err; + AudioBufferList theABL; +#else + unsigned i; +#endif + + pj_assert(ilbc_codec != NULL); + PJ_ASSERT_RETURN(input && output, PJ_EINVAL); + + if (output_buf_len < (ilbc_codec->dec_samples_per_frame << 1)) + return PJMEDIA_CODEC_EPCMTOOSHORT; + + if (input->size != ilbc_codec->dec_frame_size) + return PJMEDIA_CODEC_EFRMINLEN; + + /* Decode to temporary buffer */ +#if defined(PJMEDIA_ILBC_CODEC_USE_COREAUDIO)&& PJMEDIA_ILBC_CODEC_USE_COREAUDIO + npackets = input->size / ilbc_codec->dec_frame_size * + ilbc_codec->dec_samples_per_frame; + + theABL.mNumberBuffers = 1; + theABL.mBuffers[0].mNumberChannels = 1; + theABL.mBuffers[0].mDataByteSize = output_buf_len; + theABL.mBuffers[0].mData = output->buf; + + ilbc_codec->dec_total_packets = npackets; + ilbc_codec->dec_buffer = (char *)input->buf; + ilbc_codec->dec_buffer_offset = 0; + + err = AudioConverterFillComplexBuffer(ilbc_codec->dec, decodeDataProc, + ilbc_codec, &npackets, + &theABL, NULL); + if (err == noErr) { + output->size = npackets * (ilbc_codec->dec_samples_per_frame << 1); + } +#else + iLBC_decode(ilbc_codec->dec_block, (unsigned char*) input->buf, + &ilbc_codec->dec, 1); + + /* Convert decodec samples from float to short */ + for (i=0; i<ilbc_codec->dec_samples_per_frame; ++i) { + ((short*)output->buf)[i] = (short)ilbc_codec->dec_block[i]; + } + output->size = (ilbc_codec->dec_samples_per_frame << 1); +#endif + + output->type = PJMEDIA_FRAME_TYPE_AUDIO; + output->timestamp = input->timestamp; + + return PJ_SUCCESS; +} + + +/* + * Recover lost frame. + */ +static pj_status_t ilbc_codec_recover(pjmedia_codec *codec, + unsigned output_buf_len, + struct pjmedia_frame *output) +{ + struct ilbc_codec *ilbc_codec = (struct ilbc_codec*)codec; +#if defined(PJMEDIA_ILBC_CODEC_USE_COREAUDIO)&& PJMEDIA_ILBC_CODEC_USE_COREAUDIO + UInt32 npackets; + OSStatus err; + AudioBufferList theABL; +#else + unsigned i; +#endif + + pj_assert(ilbc_codec != NULL); + PJ_ASSERT_RETURN(output, PJ_EINVAL); + + if (output_buf_len < (ilbc_codec->dec_samples_per_frame << 1)) + return PJMEDIA_CODEC_EPCMTOOSHORT; + + /* Decode to temporary buffer */ +#if defined(PJMEDIA_ILBC_CODEC_USE_COREAUDIO)&& PJMEDIA_ILBC_CODEC_USE_COREAUDIO + npackets = 1; + + theABL.mNumberBuffers = 1; + theABL.mBuffers[0].mNumberChannels = 1; + theABL.mBuffers[0].mDataByteSize = output_buf_len; + theABL.mBuffers[0].mData = output->buf; + + ilbc_codec->dec_total_packets = npackets; + ilbc_codec->dec_buffer_offset = 0; + if (ilbc_codec->dec_buffer) { + err = AudioConverterFillComplexBuffer(ilbc_codec->dec, decodeDataProc, + ilbc_codec, &npackets, + &theABL, NULL); + if (err == noErr) { + output->size = npackets * + (ilbc_codec->dec_samples_per_frame << 1); + } + } else { + output->size = npackets * (ilbc_codec->dec_samples_per_frame << 1); + pj_bzero(output->buf, output->size); + } +#else + iLBC_decode(ilbc_codec->dec_block, NULL, &ilbc_codec->dec, 0); + + /* Convert decodec samples from float to short */ + for (i=0; i<ilbc_codec->dec_samples_per_frame; ++i) { + ((short*)output->buf)[i] = (short)ilbc_codec->dec_block[i]; + } + output->size = (ilbc_codec->dec_samples_per_frame << 1); +#endif + output->type = PJMEDIA_FRAME_TYPE_AUDIO; + + return PJ_SUCCESS; +} + + +#endif /* PJMEDIA_HAS_ILBC_CODEC */ diff --git a/pjmedia/src/pjmedia-codec/ipp_codecs.c b/pjmedia/src/pjmedia-codec/ipp_codecs.c new file mode 100644 index 0000000..c7a3a77 --- /dev/null +++ b/pjmedia/src/pjmedia-codec/ipp_codecs.c @@ -0,0 +1,1680 @@ +/* $Id: ipp_codecs.c 4002 2012-03-30 08:05:43Z bennylp $ */ +/* + * 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-codec/ipp_codecs.h> +#include <pjmedia-codec/amr_sdp_match.h> +#include <pjmedia-codec/g7221_sdp_match.h> +#include <pjmedia/codec.h> +#include <pjmedia/errno.h> +#include <pjmedia/endpoint.h> +#include <pjmedia/plc.h> +#include <pjmedia/port.h> +#include <pjmedia/silencedet.h> +#include <pj/assert.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_INTEL_IPP != 0 + */ +#if defined(PJMEDIA_HAS_INTEL_IPP) && PJMEDIA_HAS_INTEL_IPP != 0 + +#include <usc.h> +#include <ippversion.h> + +#define THIS_FILE "ipp_codecs.c" + + +/* Prototypes for IPP codecs factory */ +static pj_status_t ipp_test_alloc( pjmedia_codec_factory *factory, + const pjmedia_codec_info *id ); +static pj_status_t ipp_default_attr( pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec_param *attr ); +static pj_status_t ipp_enum_codecs( pjmedia_codec_factory *factory, + unsigned *count, + pjmedia_codec_info codecs[]); +static pj_status_t ipp_alloc_codec( pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec **p_codec); +static pj_status_t ipp_dealloc_codec( pjmedia_codec_factory *factory, + pjmedia_codec *codec ); + +/* Prototypes for IPP codecs implementation. */ +static pj_status_t ipp_codec_init( pjmedia_codec *codec, + pj_pool_t *pool ); +static pj_status_t ipp_codec_open( pjmedia_codec *codec, + pjmedia_codec_param *attr ); +static pj_status_t ipp_codec_close( pjmedia_codec *codec ); +static pj_status_t ipp_codec_modify(pjmedia_codec *codec, + const pjmedia_codec_param *attr ); +static pj_status_t ipp_codec_parse( pjmedia_codec *codec, + void *pkt, + pj_size_t pkt_size, + const pj_timestamp *ts, + unsigned *frame_cnt, + pjmedia_frame frames[]); +static pj_status_t ipp_codec_encode( pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output); +static pj_status_t ipp_codec_decode( pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output); +static pj_status_t ipp_codec_recover(pjmedia_codec *codec, + unsigned output_buf_len, + struct pjmedia_frame *output); + +/* Definition for IPP codecs operations. */ +static pjmedia_codec_op ipp_op = +{ + &ipp_codec_init, + &ipp_codec_open, + &ipp_codec_close, + &ipp_codec_modify, + &ipp_codec_parse, + &ipp_codec_encode, + &ipp_codec_decode, + &ipp_codec_recover +}; + +/* Definition for IPP codecs factory operations. */ +static pjmedia_codec_factory_op ipp_factory_op = +{ + &ipp_test_alloc, + &ipp_default_attr, + &ipp_enum_codecs, + &ipp_alloc_codec, + &ipp_dealloc_codec, + &pjmedia_codec_ipp_deinit +}; + +/* IPP codecs factory */ +static struct ipp_factory { + pjmedia_codec_factory base; + pjmedia_endpt *endpt; + pj_pool_t *pool; + pj_mutex_t *mutex; + unsigned g7221_pcm_shift; +} ipp_factory; + +/* IPP codecs private data. */ +typedef struct ipp_private { + int codec_idx; /**< Codec index. */ + void *codec_setting; /**< Specific codec setting. */ + pj_pool_t *pool; /**< Pool for each instance. */ + + USC_Handle enc; /**< Encoder state. */ + USC_Handle dec; /**< Decoder state. */ + USC_CodecInfo *info; /**< Native codec info. */ + pj_uint16_t frame_size; /**< Bitstream frame size. */ + + pj_bool_t plc_enabled; /**< PLC enabled flag. */ + pjmedia_plc *plc; /**< PJMEDIA PLC engine, NULL if + codec has internal PLC. */ + + pj_bool_t vad_enabled; /**< VAD enabled flag. */ + pjmedia_silence_det *vad; /**< PJMEDIA VAD engine, NULL if + codec has internal VAD. */ + pj_timestamp last_tx; /**< Timestamp of last transmit.*/ + + unsigned g7221_pcm_shift; /**< G722.1 PCM level adjustment*/ +} ipp_private_t; + + +/* USC codec implementations. */ +extern USC_Fxns USC_G729AFP_Fxns; +extern USC_Fxns USC_G729I_Fxns; +extern USC_Fxns USC_G723_Fxns; +extern USC_Fxns USC_G726_Fxns; +extern USC_Fxns USC_G728_Fxns; +extern USC_Fxns USC_G722_Fxns; +extern USC_Fxns USC_GSMAMR_Fxns; +extern USC_Fxns USC_AMRWB_Fxns; +extern USC_Fxns USC_AMRWBE_Fxns; + + +/* CUSTOM CALLBACKS */ + +/* This callback is useful for translating RTP frame into USC frame, e.g: + * reassigning frame attributes, reorder bitstream. Default behaviour of + * the translation is just setting the USC frame buffer & its size as + * specified in RTP frame, setting USC frame frametype to 0, setting bitrate + * of USC frame to bitrate info of codec_data. Implement this callback when + * the default behaviour is unapplicable. + */ +typedef void (*predecode_cb)(ipp_private_t *codec_data, + const pjmedia_frame *rtp_frame, + USC_Bitstream *usc_frame); + +/* Parse frames from a packet. Default behaviour of frame parsing is + * just separating frames based on calculating frame length derived + * from bitrate. Implement this callback when the default behaviour is + * unapplicable. + */ +typedef pj_status_t (*parse_cb)(ipp_private_t *codec_data, void *pkt, + pj_size_t pkt_size, const pj_timestamp *ts, + unsigned *frame_cnt, pjmedia_frame frames[]); + +/* Pack frames into a packet. Default behaviour of packing frames is + * just stacking the frames with octet aligned without adding any + * payload header. Implement this callback when the default behaviour is + * unapplicable. + */ +typedef pj_status_t (*pack_cb)(ipp_private_t *codec_data, void *pkt, + pj_size_t *pkt_size, pj_size_t max_pkt_size); + + + +/* Custom callback implementations. */ +static void predecode_g723( ipp_private_t *codec_data, + const pjmedia_frame *rtp_frame, + USC_Bitstream *usc_frame); +static pj_status_t parse_g723( ipp_private_t *codec_data, void *pkt, + pj_size_t pkt_size, const pj_timestamp *ts, + unsigned *frame_cnt, pjmedia_frame frames[]); + +static void predecode_g729( ipp_private_t *codec_data, + const pjmedia_frame *rtp_frame, + USC_Bitstream *usc_frame); + +static void predecode_amr( ipp_private_t *codec_data, + const pjmedia_frame *rtp_frame, + USC_Bitstream *usc_frame); +static pj_status_t parse_amr( ipp_private_t *codec_data, void *pkt, + pj_size_t pkt_size, const pj_timestamp *ts, + unsigned *frame_cnt, pjmedia_frame frames[]); +static pj_status_t pack_amr( ipp_private_t *codec_data, void *pkt, + pj_size_t *pkt_size, pj_size_t max_pkt_size); + +static void predecode_g7221( ipp_private_t *codec_data, + const pjmedia_frame *rtp_frame, + USC_Bitstream *usc_frame); +static pj_status_t pack_g7221( ipp_private_t *codec_data, void *pkt, + pj_size_t *pkt_size, pj_size_t max_pkt_size); + +/* IPP codec implementation descriptions. */ +static struct ipp_codec { + int enabled; /* Is this codec enabled? */ + const char *name; /* Codec name. */ + pj_uint8_t pt; /* Payload type. */ + USC_Fxns *fxns; /* USC callback functions. */ + unsigned clock_rate; /* Codec's clock rate. */ + unsigned channel_count; /* Codec's channel count. */ + unsigned samples_per_frame; /* Codec's samples count. */ + + unsigned def_bitrate; /* Default bitrate of this codec. */ + unsigned max_bitrate; /* Maximum bitrate of this codec. */ + pj_uint8_t frm_per_pkt; /* Default num of frames per packet.*/ + int has_native_vad; /* Codec has internal VAD? */ + int has_native_plc; /* Codec has internal PLC? */ + + predecode_cb predecode; /* Callback to translate RTP frame + into USC frame. */ + parse_cb parse; /* Callback to parse bitstream. */ + pack_cb pack; /* Callback to pack bitstream. */ + + pjmedia_codec_fmtp dec_fmtp; /* Decoder's fmtp params. */ +} + +ipp_codec[] = +{ +# if PJMEDIA_HAS_INTEL_IPP_CODEC_AMR + {1, "AMR", PJMEDIA_RTP_PT_AMR, &USC_GSMAMR_Fxns, 8000, 1, 160, + 7400, 12200, 2, 1, 1, + &predecode_amr, &parse_amr, &pack_amr, + {1, {{{"octet-align", 11}, {"1", 1}}} } + }, +# endif + +# if PJMEDIA_HAS_INTEL_IPP_CODEC_AMRWB + {1, "AMR-WB", PJMEDIA_RTP_PT_AMRWB, &USC_AMRWB_Fxns, 16000, 1, 320, + 15850, 23850, 2, 1, 1, + &predecode_amr, &parse_amr, &pack_amr, + {1, {{{"octet-align", 11}, {"1", 1}}} } + }, +# endif + +# if PJMEDIA_HAS_INTEL_IPP_CODEC_G729 +# if defined(PJ_HAS_FLOATING_POINT) && (PJ_HAS_FLOATING_POINT != 0) + {1, "G729", PJMEDIA_RTP_PT_G729, &USC_G729AFP_Fxns, 8000, 1, 80, + 8000, 11800, 2, 1, 1, + &predecode_g729, NULL, NULL + }, +# else + {1, "G729", PJMEDIA_RTP_PT_G729, &USC_G729I_Fxns, 8000, 1, 80, + 8000, 11800, 2, 1, 1, + &predecode_g729, NULL, NULL + }, +# endif +# endif + +# if PJMEDIA_HAS_INTEL_IPP_CODEC_G723_1 + /* This is actually G.723.1 */ + {1, "G723", PJMEDIA_RTP_PT_G723, &USC_G723_Fxns, 8000, 1, 240, + 6300, 6300, 1, 1, 1, + &predecode_g723, &parse_g723, NULL + }, +# endif + +# if PJMEDIA_HAS_INTEL_IPP_CODEC_G726 + {0, "G726-16", PJMEDIA_RTP_PT_G726_16, &USC_G726_Fxns, 8000, 1, 80, + 16000, 16000, 2, 0, 0, + NULL, NULL, NULL + }, + {0, "G726-24", PJMEDIA_RTP_PT_G726_24, &USC_G726_Fxns, 8000, 1, 80, + 24000, 24000, 2, 0, 0, + NULL, NULL, NULL + }, + {1, "G726-32", PJMEDIA_RTP_PT_G726_32, &USC_G726_Fxns, 8000, 1, 80, + 32000, 32000, 2, 0, 0, + NULL, NULL, NULL + }, + {0, "G726-40", PJMEDIA_RTP_PT_G726_40, &USC_G726_Fxns, 8000, 1, 80, + 40000, 40000, 2, 0, 0, + NULL, NULL, NULL + }, + /* Old definition of G726-32 */ + {1, "G721", PJMEDIA_RTP_PT_G721, &USC_G726_Fxns, 8000, 1, 80, + 32000, 32000, 2, 0, 0, + NULL, NULL, NULL + }, +# endif + +# if PJMEDIA_HAS_INTEL_IPP_CODEC_G728 + {1, "G728", PJMEDIA_RTP_PT_G728, &USC_G728_Fxns, 8000, 1, 80, + 16000, 16000, 2, 0, 1, + NULL, NULL, NULL + }, +# endif + +# if PJMEDIA_HAS_INTEL_IPP_CODEC_G722_1 + {0, "G7221", PJMEDIA_RTP_PT_G722_1_16, &USC_G722_Fxns, 16000, 1, 320, + 16000, 16000, 1, 0, 1, + predecode_g7221, NULL, pack_g7221, + {1, {{{"bitrate", 7}, {"16000", 5}}} } + }, + {1, "G7221", PJMEDIA_RTP_PT_G722_1_24, &USC_G722_Fxns, 16000, 1, 320, + 24000, 24000, 1, 0, 1, + predecode_g7221, NULL, pack_g7221, + {1, {{{"bitrate", 7}, {"24000", 5}}} } + }, + {1, "G7221", PJMEDIA_RTP_PT_G722_1_32, &USC_G722_Fxns, 16000, 1, 320, + 32000, 32000, 1, 0, 1, + predecode_g7221, NULL, pack_g7221, + {1, {{{"bitrate", 7}, {"32000", 5}}} } + }, +# endif +}; + + +#if PJMEDIA_HAS_INTEL_IPP_CODEC_G729 + +static void predecode_g729( ipp_private_t *codec_data, + const pjmedia_frame *rtp_frame, + USC_Bitstream *usc_frame) +{ + switch (rtp_frame->size) { + case 2: + /* SID */ + usc_frame->frametype = 1; + usc_frame->bitrate = codec_data->info->params.modes.bitrate; + break; + case 8: + /* G729D */ + usc_frame->frametype = 2; + usc_frame->bitrate = 6400; + break; + case 10: + /* G729 */ + usc_frame->frametype = 3; + usc_frame->bitrate = 8000; + break; + case 15: + /* G729E */ + usc_frame->frametype = 4; + usc_frame->bitrate = 11800; + break; + default: + usc_frame->frametype = 0; + usc_frame->bitrate = 0; + break; + } + + usc_frame->pBuffer = rtp_frame->buf; + usc_frame->nbytes = rtp_frame->size; +} + +#endif /* PJMEDIA_HAS_INTEL_IPP_CODEC_G729 */ + + +#if PJMEDIA_HAS_INTEL_IPP_CODEC_G723_1 + +static void predecode_g723( ipp_private_t *codec_data, + const pjmedia_frame *rtp_frame, + USC_Bitstream *usc_frame) +{ + int i, HDR = 0; + pj_uint8_t *f = (pj_uint8_t*)rtp_frame->buf; + + PJ_UNUSED_ARG(codec_data); + + for (i = 0; i < 2; ++i){ + int tmp; + tmp = (f[0] >> (i & 0x7)) & 1; + HDR += tmp << i ; + } + + usc_frame->pBuffer = rtp_frame->buf; + usc_frame->nbytes = rtp_frame->size; + usc_frame->bitrate = HDR == 0? 6300 : 5300; + usc_frame->frametype = 0; +} + +static pj_status_t parse_g723(ipp_private_t *codec_data, void *pkt, + pj_size_t pkt_size, const pj_timestamp *ts, + unsigned *frame_cnt, pjmedia_frame frames[]) +{ + unsigned count = 0; + pj_uint8_t *f = (pj_uint8_t*)pkt; + + while (pkt_size && count < *frame_cnt) { + int framesize, i, j; + int HDR = 0; + + for (i = 0; i < 2; ++i){ + j = (f[0] >> (i & 0x7)) & 1; + HDR += j << i ; + } + + if (HDR == 0) + framesize = 24; + else if (HDR == 1) + framesize = 20; + else if (HDR == 2) + framesize = 4; + else if (HDR == 3) + framesize = 1; + else { + pj_assert(!"Unknown G723.1 frametype, packet may be corrupted!"); + return PJMEDIA_CODEC_EINMODE; + } + + frames[count].type = PJMEDIA_FRAME_TYPE_AUDIO; + frames[count].buf = f; + frames[count].size = framesize; + frames[count].timestamp.u64 = ts->u64 + count * + ipp_codec[codec_data->codec_idx].samples_per_frame; + + f += framesize; + pkt_size -= framesize; + + ++count; + } + + *frame_cnt = count; + return PJ_SUCCESS; +} + +#endif /* PJMEDIA_HAS_INTEL_IPP_CODEC_G723_1 */ + + +#if PJMEDIA_HAS_INTEL_IPP_CODEC_AMR || PJMEDIA_HAS_INTEL_IPP_CODEC_AMRWB + +#include <pjmedia-codec/amr_helper.h> + +typedef struct amr_settings_t { + pjmedia_codec_amr_pack_setting enc_setting; + pjmedia_codec_amr_pack_setting dec_setting; + pj_int8_t enc_mode; +} amr_settings_t; + + +/* Rearrange AMR bitstream and convert RTP frame into USC frame: + * - make the start_bit to be 0 + * - if it is speech frame, reorder bitstream from sensitivity bits order + * to encoder bits order. + * - set the appropriate value of usc_frame. + */ +static void predecode_amr( ipp_private_t *codec_data, + const pjmedia_frame *rtp_frame, + USC_Bitstream *usc_frame) +{ + pjmedia_frame frame; + pjmedia_codec_amr_bit_info *info; + pjmedia_codec_amr_pack_setting *setting; + + setting = &((amr_settings_t*)codec_data->codec_setting)->dec_setting; + + frame = *rtp_frame; + pjmedia_codec_amr_predecode(rtp_frame, setting, &frame); + info = (pjmedia_codec_amr_bit_info*) &frame.bit_info; + + usc_frame->pBuffer = frame.buf; + usc_frame->nbytes = frame.size; + if (info->mode != -1) { + usc_frame->bitrate = setting->amr_nb? + pjmedia_codec_amrnb_bitrates[info->mode]: + pjmedia_codec_amrwb_bitrates[info->mode]; + } else { + usc_frame->bitrate = 0; + } + + if (frame.size > 5) { + /* Speech */ + if (info->good_quality) + usc_frame->frametype = 0; + else + usc_frame->frametype = setting->amr_nb ? 5 : 6; + } else if (frame.size == 5) { + /* SID */ + if (info->good_quality) { + usc_frame->frametype = info->STI? 2 : 1; + } else { + usc_frame->frametype = setting->amr_nb ? 6 : 7; + } + } else { + /* no data */ + usc_frame->frametype = 3; + } +} + +/* Pack AMR payload */ +static pj_status_t pack_amr(ipp_private_t *codec_data, void *pkt, + pj_size_t *pkt_size, pj_size_t max_pkt_size) +{ + enum {MAX_FRAMES_PER_PACKET = PJMEDIA_MAX_FRAME_DURATION_MS / 20}; + + pjmedia_frame frames[MAX_FRAMES_PER_PACKET]; + unsigned nframes = 0; + pjmedia_codec_amr_bit_info *info; + pj_uint8_t *r; /* Read cursor */ + pj_uint8_t SID_FT; + pjmedia_codec_amr_pack_setting *setting; + const pj_uint8_t *framelen_tbl; + + setting = &((amr_settings_t*)codec_data->codec_setting)->enc_setting; + framelen_tbl = setting->amr_nb? pjmedia_codec_amrnb_framelen: + pjmedia_codec_amrwb_framelen; + + SID_FT = (pj_uint8_t)(setting->amr_nb? 8 : 9); + + /* Align pkt buf right */ + r = (pj_uint8_t*)pkt + max_pkt_size - *pkt_size; + pj_memmove(r, pkt, *pkt_size); + + /* Get frames */ + for (;;) { + pj_bool_t eof; + pj_uint16_t info_; + + info_ = *((pj_uint16_t*)r); + eof = ((info_ & 0x40) != 0); + + info = (pjmedia_codec_amr_bit_info*) &frames[nframes].bit_info; + pj_bzero(info, sizeof(*info)); + info->frame_type = (pj_uint8_t)(info_ & 0x0F); + info->good_quality = (pj_uint8_t)((info_ & 0x80) == 0); + info->mode = (pj_int8_t) ((info_ >> 8) & 0x0F); + info->STI = (pj_uint8_t)((info_ >> 5) & 1); + + frames[nframes].buf = r + 2; + frames[nframes].size = info->frame_type <= SID_FT ? + framelen_tbl[info->frame_type] : 0; + + r += frames[nframes].size + 2; + + /* Last frame */ + if (++nframes >= MAX_FRAMES_PER_PACKET || eof) + break; + } + + /* Pack */ + *pkt_size = max_pkt_size; + return pjmedia_codec_amr_pack(frames, nframes, setting, pkt, pkt_size); +} + + +/* Parse AMR payload into frames. */ +static pj_status_t parse_amr(ipp_private_t *codec_data, void *pkt, + pj_size_t pkt_size, const pj_timestamp *ts, + unsigned *frame_cnt, pjmedia_frame frames[]) +{ + amr_settings_t* s = (amr_settings_t*)codec_data->codec_setting; + pjmedia_codec_amr_pack_setting *setting; + pj_status_t status; + pj_uint8_t cmr; + + setting = &s->dec_setting; + + status = pjmedia_codec_amr_parse(pkt, pkt_size, ts, setting, frames, + frame_cnt, &cmr); + if (status != PJ_SUCCESS) + return status; + + /* Check Change Mode Request. */ + if (((setting->amr_nb && cmr <= 7) || (!setting->amr_nb && cmr <= 8)) && + s->enc_mode != cmr) + { + struct ipp_codec *ippc = &ipp_codec[codec_data->codec_idx]; + + s->enc_mode = cmr; + codec_data->info->params.modes.bitrate = s->enc_setting.amr_nb? + pjmedia_codec_amrnb_bitrates[s->enc_mode] : + pjmedia_codec_amrwb_bitrates[s->enc_mode]; + ippc->fxns->std.Control(&codec_data->info->params.modes, + codec_data->enc); + + PJ_LOG(4,(THIS_FILE, "AMR%s switched encoding mode to: %d (%dbps)", + (s->enc_setting.amr_nb?"":"-WB"), + s->enc_mode, + codec_data->info->params.modes.bitrate)); + } + + return PJ_SUCCESS; +} + +#endif /* PJMEDIA_HAS_INTEL_IPP_CODEC_AMR */ + + +#if PJMEDIA_HAS_INTEL_IPP_CODEC_G722_1 + +static void predecode_g7221( ipp_private_t *codec_data, + const pjmedia_frame *rtp_frame, + USC_Bitstream *usc_frame) +{ + usc_frame->pBuffer = (char*)rtp_frame->buf; + usc_frame->nbytes = rtp_frame->size; + usc_frame->frametype = 0; + usc_frame->bitrate = codec_data->info->params.modes.bitrate; + +#if defined(PJ_IS_LITTLE_ENDIAN) && PJ_IS_LITTLE_ENDIAN!=0 + { + pj_uint16_t *p, *p_end; + + p = (pj_uint16_t*)rtp_frame->buf; + p_end = p + rtp_frame->size/2; + while (p < p_end) { + *p = pj_ntohs(*p); + ++p; + } + } +#endif +} + +static pj_status_t pack_g7221( ipp_private_t *codec_data, void *pkt, + pj_size_t *pkt_size, pj_size_t max_pkt_size) +{ + PJ_UNUSED_ARG(codec_data); + PJ_UNUSED_ARG(max_pkt_size); + +#if defined(PJ_IS_LITTLE_ENDIAN) && PJ_IS_LITTLE_ENDIAN!=0 + { + pj_uint16_t *p, *p_end; + + p = (pj_uint16_t*)pkt; + p_end = p + *pkt_size/2; + while (p < p_end) { + *p = pj_htons(*p); + ++p; + } + } +#else + PJ_UNUSED_ARG(pkt); + PJ_UNUSED_ARG(pkt_size); +#endif + + return PJ_SUCCESS; +} + + +#include <pjmedia-codec/g7221.h> + + +PJ_DEF(pj_status_t) pjmedia_codec_g7221_set_pcm_shift(int val) +{ + PJ_ASSERT_RETURN(val >= 0, PJ_EINVAL); + + ipp_factory.g7221_pcm_shift = val; + return PJ_SUCCESS; +} + + +#endif /* PJMEDIA_HAS_INTEL_IPP_CODEC_G722_1 */ + +/* + * Initialize and register IPP codec factory to pjmedia endpoint. + */ +PJ_DEF(pj_status_t) pjmedia_codec_ipp_init( pjmedia_endpt *endpt ) +{ + pjmedia_codec_mgr *codec_mgr; + pj_str_t codec_name; + pj_status_t status; + + if (ipp_factory.pool != NULL) { + /* Already initialized. */ + return PJ_SUCCESS; + } + + /* Create IPP codec factory. */ + ipp_factory.base.op = &ipp_factory_op; + ipp_factory.base.factory_data = NULL; + ipp_factory.endpt = endpt; + ipp_factory.g7221_pcm_shift = PJMEDIA_G7221_DEFAULT_PCM_SHIFT; + + ipp_factory.pool = pjmedia_endpt_create_pool(endpt, "IPP codecs", 4000, 4000); + if (!ipp_factory.pool) + return PJ_ENOMEM; + + /* Create mutex. */ + status = pj_mutex_create_simple(ipp_factory.pool, "IPP codecs", + &ipp_factory.mutex); + if (status != PJ_SUCCESS) + goto on_error; + + /* Get the codec manager. */ + codec_mgr = pjmedia_endpt_get_codec_mgr(endpt); + if (!codec_mgr) { + status = PJ_EINVALIDOP; + goto on_error; + } + + /* Register format match callback. */ +#if PJMEDIA_HAS_INTEL_IPP_CODEC_G722_1 + pj_cstr(&codec_name, "G7221"); + status = pjmedia_sdp_neg_register_fmt_match_cb( + &codec_name, + &pjmedia_codec_g7221_match_sdp); + if (status != PJ_SUCCESS) + goto on_error; +#endif + +#if PJMEDIA_HAS_INTEL_IPP_CODEC_AMR + pj_cstr(&codec_name, "AMR"); + status = pjmedia_sdp_neg_register_fmt_match_cb( + &codec_name, + &pjmedia_codec_amr_match_sdp); + if (status != PJ_SUCCESS) + goto on_error; +#endif + +#if PJMEDIA_HAS_INTEL_IPP_CODEC_AMRWB + pj_cstr(&codec_name, "AMR-WB"); + status = pjmedia_sdp_neg_register_fmt_match_cb( + &codec_name, + &pjmedia_codec_amr_match_sdp); + if (status != PJ_SUCCESS) + goto on_error; +#endif + + /* Suppress compile warning */ + PJ_UNUSED_ARG(codec_name); + + /* Register codec factory to endpoint. */ + status = pjmedia_codec_mgr_register_factory(codec_mgr, + &ipp_factory.base); + if (status != PJ_SUCCESS) + goto on_error; + + /* Done. */ + return PJ_SUCCESS; + +on_error: + pj_pool_release(ipp_factory.pool); + ipp_factory.pool = NULL; + return status; +} + +/* + * Unregister IPP codecs factory from pjmedia endpoint. + */ +PJ_DEF(pj_status_t) pjmedia_codec_ipp_deinit(void) +{ + pjmedia_codec_mgr *codec_mgr; + pj_status_t status; + + if (ipp_factory.pool == NULL) { + /* Already deinitialized */ + return PJ_SUCCESS; + } + + pj_mutex_lock(ipp_factory.mutex); + + /* Get the codec manager. */ + codec_mgr = pjmedia_endpt_get_codec_mgr(ipp_factory.endpt); + if (!codec_mgr) { + pj_pool_release(ipp_factory.pool); + ipp_factory.pool = NULL; + return PJ_EINVALIDOP; + } + + /* Unregister IPP codecs factory. */ + status = pjmedia_codec_mgr_unregister_factory(codec_mgr, + &ipp_factory.base); + + /* Destroy mutex. */ + pj_mutex_destroy(ipp_factory.mutex); + + /* Destroy pool. */ + pj_pool_release(ipp_factory.pool); + ipp_factory.pool = NULL; + + return status; +} + + +/* + * Check if factory can allocate the specified codec. + */ +static pj_status_t ipp_test_alloc( pjmedia_codec_factory *factory, + const pjmedia_codec_info *info ) +{ + unsigned i; + + PJ_UNUSED_ARG(factory); + + /* Type MUST be audio. */ + if (info->type != PJMEDIA_TYPE_AUDIO) + return PJMEDIA_CODEC_EUNSUP; + + for (i = 0; i < PJ_ARRAY_SIZE(ipp_codec); ++i) { + pj_str_t name = pj_str((char*)ipp_codec[i].name); + if ((pj_stricmp(&info->encoding_name, &name) == 0) && + (info->clock_rate == (unsigned)ipp_codec[i].clock_rate) && + (info->channel_cnt == (unsigned)ipp_codec[i].channel_count) && + (ipp_codec[i].enabled)) + { + return PJ_SUCCESS; + } + } + + /* Unsupported, or mode is disabled. */ + return PJMEDIA_CODEC_EUNSUP; +} + +/* + * Generate default attribute. + */ +static pj_status_t ipp_default_attr (pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec_param *attr ) +{ + unsigned i; + + PJ_ASSERT_RETURN(factory==&ipp_factory.base, PJ_EINVAL); + + pj_bzero(attr, sizeof(pjmedia_codec_param)); + + for (i = 0; i < PJ_ARRAY_SIZE(ipp_codec); ++i) { + pj_str_t name = pj_str((char*)ipp_codec[i].name); + if ((pj_stricmp(&id->encoding_name, &name) == 0) && + (id->clock_rate == (unsigned)ipp_codec[i].clock_rate) && + (id->channel_cnt == (unsigned)ipp_codec[i].channel_count) && + (id->pt == (unsigned)ipp_codec[i].pt)) + { + attr->info.pt = (pj_uint8_t)id->pt; + attr->info.channel_cnt = ipp_codec[i].channel_count; + attr->info.clock_rate = ipp_codec[i].clock_rate; + attr->info.avg_bps = ipp_codec[i].def_bitrate; + attr->info.max_bps = ipp_codec[i].max_bitrate; + attr->info.pcm_bits_per_sample = 16; + attr->info.frm_ptime = (pj_uint16_t) + (ipp_codec[i].samples_per_frame * 1000 / + ipp_codec[i].channel_count / + ipp_codec[i].clock_rate); + attr->setting.frm_per_pkt = ipp_codec[i].frm_per_pkt; + + /* Default flags. */ + attr->setting.plc = 1; + attr->setting.penh= 0; + attr->setting.vad = 1; + attr->setting.cng = attr->setting.vad; + attr->setting.dec_fmtp = ipp_codec[i].dec_fmtp; + + if (attr->setting.vad == 0) { +#if PJMEDIA_HAS_INTEL_IPP_CODEC_G729 + if (id->pt == PJMEDIA_RTP_PT_G729) { + /* Signal G729 Annex B is being disabled */ + attr->setting.dec_fmtp.cnt = 1; + pj_strset2(&attr->setting.dec_fmtp.param[0].name, "annexb"); + pj_strset2(&attr->setting.dec_fmtp.param[0].val, "no"); + } +#endif + } + + return PJ_SUCCESS; + } + } + + return PJMEDIA_CODEC_EUNSUP; +} + +/* + * Enum codecs supported by this factory. + */ +static pj_status_t ipp_enum_codecs(pjmedia_codec_factory *factory, + unsigned *count, + pjmedia_codec_info codecs[]) +{ + unsigned max; + unsigned i; + + PJ_UNUSED_ARG(factory); + PJ_ASSERT_RETURN(codecs && *count > 0, PJ_EINVAL); + + max = *count; + + for (i = 0, *count = 0; i < PJ_ARRAY_SIZE(ipp_codec) && *count < max; ++i) + { + if (!ipp_codec[i].enabled) + continue; + + pj_bzero(&codecs[*count], sizeof(pjmedia_codec_info)); + codecs[*count].encoding_name = pj_str((char*)ipp_codec[i].name); + codecs[*count].pt = ipp_codec[i].pt; + codecs[*count].type = PJMEDIA_TYPE_AUDIO; + codecs[*count].clock_rate = ipp_codec[i].clock_rate; + codecs[*count].channel_cnt = ipp_codec[i].channel_count; + + ++*count; + } + + return PJ_SUCCESS; +} + +/* + * Allocate a new codec instance. + */ +static pj_status_t ipp_alloc_codec( pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec **p_codec) +{ + ipp_private_t *codec_data; + pjmedia_codec *codec; + int idx; + pj_pool_t *pool; + unsigned i; + + PJ_ASSERT_RETURN(factory && id && p_codec, PJ_EINVAL); + PJ_ASSERT_RETURN(factory == &ipp_factory.base, PJ_EINVAL); + + pj_mutex_lock(ipp_factory.mutex); + + /* Find codec's index */ + idx = -1; + for (i = 0; i < PJ_ARRAY_SIZE(ipp_codec); ++i) { + pj_str_t name = pj_str((char*)ipp_codec[i].name); + if ((pj_stricmp(&id->encoding_name, &name) == 0) && + (id->clock_rate == (unsigned)ipp_codec[i].clock_rate) && + (id->channel_cnt == (unsigned)ipp_codec[i].channel_count) && + (ipp_codec[i].enabled)) + { + idx = i; + break; + } + } + if (idx == -1) { + *p_codec = NULL; + return PJMEDIA_CODEC_EFAILED; + } + + /* Create pool for codec instance */ + pool = pjmedia_endpt_create_pool(ipp_factory.endpt, "IPPcodec", 512, 512); + codec = PJ_POOL_ZALLOC_T(pool, pjmedia_codec); + PJ_ASSERT_RETURN(codec != NULL, PJ_ENOMEM); + codec->op = &ipp_op; + codec->factory = factory; + codec->codec_data = PJ_POOL_ZALLOC_T(pool, ipp_private_t); + codec_data = (ipp_private_t*) codec->codec_data; + + /* Create PLC if codec has no internal PLC */ + if (!ipp_codec[idx].has_native_plc) { + pj_status_t status; + status = pjmedia_plc_create(pool, ipp_codec[idx].clock_rate, + ipp_codec[idx].samples_per_frame, 0, + &codec_data->plc); + if (status != PJ_SUCCESS) { + pj_pool_release(pool); + pj_mutex_unlock(ipp_factory.mutex); + return status; + } + } + + /* Create silence detector if codec has no internal VAD */ + if (!ipp_codec[idx].has_native_vad) { + pj_status_t status; + status = pjmedia_silence_det_create(pool, + ipp_codec[idx].clock_rate, + ipp_codec[idx].samples_per_frame, + &codec_data->vad); + if (status != PJ_SUCCESS) { + pj_pool_release(pool); + pj_mutex_unlock(ipp_factory.mutex); + return status; + } + } + + codec_data->pool = pool; + codec_data->codec_idx = idx; + + pj_mutex_unlock(ipp_factory.mutex); + + *p_codec = codec; + return PJ_SUCCESS; +} + +/* + * Free codec. + */ +static pj_status_t ipp_dealloc_codec( pjmedia_codec_factory *factory, + pjmedia_codec *codec ) +{ + ipp_private_t *codec_data; + + PJ_ASSERT_RETURN(factory && codec, PJ_EINVAL); + PJ_ASSERT_RETURN(factory == &ipp_factory.base, PJ_EINVAL); + + /* Close codec, if it's not closed. */ + codec_data = (ipp_private_t*) codec->codec_data; + if (codec_data->enc != NULL || codec_data->dec != NULL) { + ipp_codec_close(codec); + } + + pj_pool_release(codec_data->pool); + + return PJ_SUCCESS; +} + +/* + * Init codec. + */ +static pj_status_t ipp_codec_init( pjmedia_codec *codec, + pj_pool_t *pool ) +{ + PJ_UNUSED_ARG(codec); + PJ_UNUSED_ARG(pool); + return PJ_SUCCESS; +} + +/* + * Open codec. + */ +static pj_status_t ipp_codec_open( pjmedia_codec *codec, + pjmedia_codec_param *attr ) +{ + ipp_private_t *codec_data = (ipp_private_t*) codec->codec_data; + struct ipp_codec *ippc = &ipp_codec[codec_data->codec_idx]; + int info_size; + pj_pool_t *pool; + int i, j; + USC_MemBank *membanks; + int nb_membanks; + + pool = codec_data->pool; + + /* Get the codec info size */ + if (USC_NoError != ippc->fxns->std.GetInfoSize(&info_size)) { + PJ_LOG(1,(THIS_FILE, "Error getting codec info size")); + goto on_error; + } + /* Get the codec info */ + codec_data->info = pj_pool_zalloc(pool, info_size); + if (USC_NoError != ippc->fxns->std.GetInfo((USC_Handle)NULL, + codec_data->info)) + { + PJ_LOG(1,(THIS_FILE, "Error getting codec info")); + goto on_error; + } + + /* PREPARING THE ENCODER */ + + /* Setting the encoder params */ + codec_data->info->params.direction = USC_ENCODE; + codec_data->info->params.modes.vad = attr->setting.vad && + ippc->has_native_vad; + codec_data->info->params.modes.bitrate = attr->info.avg_bps; + codec_data->info->params.law = 0; /* Linear PCM input */ + +#if PJMEDIA_HAS_INTEL_IPP_CODEC_G729 + if (ippc->pt == PJMEDIA_RTP_PT_G729) { + /* Check if G729 Annex B is signaled to be disabled */ + for (i = 0; i < attr->setting.enc_fmtp.cnt; ++i) { + if (pj_stricmp2(&attr->setting.enc_fmtp.param[i].name, "annexb")==0) + { + if (pj_stricmp2(&attr->setting.enc_fmtp.param[i].val, "no")==0) + { + attr->setting.vad = 0; + codec_data->info->params.modes.vad = 0; + } + break; + } + } + } +#endif + + /* Get number of memory blocks needed by the encoder */ + if (USC_NoError != ippc->fxns->std.NumAlloc(&codec_data->info->params, + &nb_membanks)) + { + PJ_LOG(1,(THIS_FILE, "Error getting no of memory blocks of encoder")); + goto on_error; + } + + /* Allocate memory blocks table */ + membanks = (USC_MemBank*) pj_pool_zalloc(pool, + sizeof(USC_MemBank) * nb_membanks); + /* Get size of each memory block */ + if (USC_NoError != ippc->fxns->std.MemAlloc(&codec_data->info->params, + membanks)) + { + PJ_LOG(1,(THIS_FILE, "Error getting memory blocks size of encoder")); + goto on_error; + } + + /* Allocate memory for each block */ + for (i = 0; i < nb_membanks; i++) { + membanks[i].pMem = (char*) pj_pool_zalloc(pool, membanks[i].nbytes); + } + + /* Create encoder instance */ + if (USC_NoError != ippc->fxns->std.Init(&codec_data->info->params, + membanks, + &codec_data->enc)) + { + PJ_LOG(1,(THIS_FILE, "Error initializing encoder")); + goto on_error; + } + + /* PREPARING THE DECODER */ + + /* Setting the decoder params */ + codec_data->info->params.direction = USC_DECODE; + + /* Not sure if VAD affects decoder, just try to be safe */ + //codec_data->info->params.modes.vad = ippc->has_native_vad; + + /* Get number of memory blocks needed by the decoder */ + if (USC_NoError != ippc->fxns->std.NumAlloc(&codec_data->info->params, + &nb_membanks)) + { + PJ_LOG(1,(THIS_FILE, "Error getting no of memory blocks of decoder")); + goto on_error; + } + + /* Allocate memory blocks table */ + membanks = (USC_MemBank*) pj_pool_zalloc(pool, + sizeof(USC_MemBank) * nb_membanks); + /* Get size of each memory block */ + if (USC_NoError != ippc->fxns->std.MemAlloc(&codec_data->info->params, + membanks)) + { + PJ_LOG(1,(THIS_FILE, "Error getting memory blocks size of decoder")); + goto on_error; + } + + /* Allocate memory for each block */ + for (i = 0; i < nb_membanks; i++) { + membanks[i].pMem = (char*) pj_pool_zalloc(pool, membanks[i].nbytes); + } + + /* Create decoder instance */ + if (USC_NoError != ippc->fxns->std.Init(&codec_data->info->params, + membanks, &codec_data->dec)) + { + PJ_LOG(1,(THIS_FILE, "Error initializing decoder")); + goto on_error; + } + + /* Update codec info */ + ippc->fxns->std.GetInfo((USC_Handle)codec_data->enc, codec_data->info); + + /* Get bitstream size */ + i = codec_data->info->params.modes.bitrate * ippc->samples_per_frame; + j = ippc->clock_rate << 3; + codec_data->frame_size = (pj_uint16_t)(i / j); + if (i % j) ++codec_data->frame_size; + + codec_data->vad_enabled = (attr->setting.vad != 0); + codec_data->plc_enabled = (attr->setting.plc != 0); + +#if PJMEDIA_HAS_INTEL_IPP_CODEC_AMR + /* Init AMR settings */ + if (ippc->pt == PJMEDIA_RTP_PT_AMR || ippc->pt == PJMEDIA_RTP_PT_AMRWB) { + amr_settings_t *s; + pj_uint8_t octet_align = 0; + pj_int8_t enc_mode; + + enc_mode = pjmedia_codec_amr_get_mode( + codec_data->info->params.modes.bitrate); + pj_assert(enc_mode >= 0 && enc_mode <= 8); + + /* Check AMR specific attributes */ + + for (i = 0; i < attr->setting.dec_fmtp.cnt; ++i) { + /* octet-align, one of the parameters that must have same value + * in offer & answer (RFC 4867 Section 8.3.1). Just check fmtp + * in the decoder side, since it's value is guaranteed to fulfil + * above requirement (by SDP negotiator). + */ + const pj_str_t STR_FMTP_OCTET_ALIGN = {"octet-align", 11}; + + if (pj_stricmp(&attr->setting.dec_fmtp.param[i].name, + &STR_FMTP_OCTET_ALIGN) == 0) + { + octet_align=(pj_uint8_t) + pj_strtoul(&attr->setting.dec_fmtp.param[i].val); + break; + } + } + + for (i = 0; i < attr->setting.enc_fmtp.cnt; ++i) { + /* mode-set, encoding mode is chosen based on local default mode + * setting: + * - if local default mode is included in the mode-set, use it + * - otherwise, find the closest mode to local default mode; + * if there are two closest modes, prefer to use the higher + * one, e.g: local default mode is 4, the mode-set param + * contains '2,3,5,6', then 5 will be chosen. + */ + const pj_str_t STR_FMTP_MODE_SET = {"mode-set", 8}; + + if (pj_stricmp(&attr->setting.enc_fmtp.param[i].name, + &STR_FMTP_MODE_SET) == 0) + { + const char *p; + pj_size_t l; + pj_int8_t diff = 99; + + p = pj_strbuf(&attr->setting.enc_fmtp.param[i].val); + l = pj_strlen(&attr->setting.enc_fmtp.param[i].val); + + while (l--) { + if ((ippc->pt==PJMEDIA_RTP_PT_AMR && *p>='0' && *p<='7') || + (ippc->pt==PJMEDIA_RTP_PT_AMRWB && *p>='0' && *p<='8')) + { + pj_int8_t tmp = (pj_int8_t)(*p - '0' - enc_mode); + + if (PJ_ABS(diff) > PJ_ABS(tmp) || + (PJ_ABS(diff) == PJ_ABS(tmp) && tmp > diff)) + { + diff = tmp; + if (diff == 0) break; + } + } + ++p; + } + + if (diff == 99) + goto on_error; + + enc_mode = (pj_int8_t)(enc_mode + diff); + + break; + } + } + + /* Initialize AMR specific settings */ + s = PJ_POOL_ZALLOC_T(pool, amr_settings_t); + codec_data->codec_setting = s; + + s->enc_setting.amr_nb = (pj_uint8_t)(ippc->pt == PJMEDIA_RTP_PT_AMR); + s->enc_setting.octet_aligned = octet_align; + s->enc_setting.reorder = PJ_TRUE; + s->enc_setting.cmr = 15; + + s->dec_setting.amr_nb = (pj_uint8_t)(ippc->pt == PJMEDIA_RTP_PT_AMR); + s->dec_setting.octet_aligned = octet_align; + s->dec_setting.reorder = PJ_TRUE; + + /* Apply encoder mode/bitrate */ + s->enc_mode = enc_mode; + codec_data->info->params.modes.bitrate = s->enc_setting.amr_nb? + pjmedia_codec_amrnb_bitrates[s->enc_mode]: + pjmedia_codec_amrwb_bitrates[s->enc_mode]; + ippc->fxns->std.Control(&codec_data->info->params.modes, + codec_data->enc); + + PJ_LOG(4,(THIS_FILE, "AMR%s encoding mode: %d (%dbps)", + (s->enc_setting.amr_nb?"":"-WB"), + s->enc_mode, + codec_data->info->params.modes.bitrate)); + + /* Return back bitrate info to application */ + attr->info.avg_bps = codec_data->info->params.modes.bitrate; + } +#endif + +#if PJMEDIA_HAS_INTEL_IPP_CODEC_G722_1 + if (ippc->pt >= PJMEDIA_RTP_PT_G722_1_16 && + ippc->pt <= PJMEDIA_RTP_PT_G7221_RSV2) + { + codec_data->g7221_pcm_shift = ipp_factory.g7221_pcm_shift; + } +#endif + + return PJ_SUCCESS; + +on_error: + return PJMEDIA_CODEC_EFAILED; +} + +/* + * Close codec. + */ +static pj_status_t ipp_codec_close( pjmedia_codec *codec ) +{ + PJ_UNUSED_ARG(codec); + + return PJ_SUCCESS; +} + + +/* + * Modify codec settings. + */ +static pj_status_t ipp_codec_modify(pjmedia_codec *codec, + const pjmedia_codec_param *attr ) +{ + ipp_private_t *codec_data = (ipp_private_t*) codec->codec_data; + struct ipp_codec *ippc = &ipp_codec[codec_data->codec_idx]; + + codec_data->vad_enabled = (attr->setting.vad != 0); + codec_data->plc_enabled = (attr->setting.plc != 0); + + if (ippc->has_native_vad) { + USC_Modes modes; + + modes = codec_data->info->params.modes; + modes.vad = codec_data->vad_enabled; + ippc->fxns->std.Control(&modes, codec_data->enc); + } + + return PJ_SUCCESS; +} + +/* + * Get frames in the packet. + */ +static pj_status_t ipp_codec_parse( pjmedia_codec *codec, + void *pkt, + pj_size_t pkt_size, + const pj_timestamp *ts, + unsigned *frame_cnt, + pjmedia_frame frames[]) +{ + ipp_private_t *codec_data = (ipp_private_t*) codec->codec_data; + struct ipp_codec *ippc = &ipp_codec[codec_data->codec_idx]; + unsigned count = 0; + + PJ_ASSERT_RETURN(frame_cnt, PJ_EINVAL); + + if (ippc->parse != NULL) { + return ippc->parse(codec_data, pkt, pkt_size, ts, frame_cnt, frames); + } + + while (pkt_size >= codec_data->frame_size && count < *frame_cnt) { + frames[count].type = PJMEDIA_FRAME_TYPE_AUDIO; + frames[count].buf = pkt; + frames[count].size = codec_data->frame_size; + frames[count].timestamp.u64 = ts->u64 + count*ippc->samples_per_frame; + + pkt = ((char*)pkt) + codec_data->frame_size; + pkt_size -= codec_data->frame_size; + + ++count; + } + + if (pkt_size && count < *frame_cnt) { + frames[count].type = PJMEDIA_FRAME_TYPE_AUDIO; + frames[count].buf = pkt; + frames[count].size = pkt_size; + frames[count].timestamp.u64 = ts->u64 + count*ippc->samples_per_frame; + ++count; + } + + *frame_cnt = count; + return PJ_SUCCESS; +} + +/* + * Encode frames. + */ +static pj_status_t ipp_codec_encode( pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output) +{ + ipp_private_t *codec_data = (ipp_private_t*) codec->codec_data; + struct ipp_codec *ippc = &ipp_codec[codec_data->codec_idx]; + unsigned samples_per_frame; + unsigned nsamples; + pj_size_t tx = 0; + pj_int16_t *pcm_in = (pj_int16_t*)input->buf; + pj_uint8_t *bits_out = (pj_uint8_t*) output->buf; + pj_uint8_t pt; + + /* Invoke external VAD if codec has no internal VAD */ + if (codec_data->vad && codec_data->vad_enabled) { + pj_bool_t is_silence; + pj_int32_t silence_duration; + + silence_duration = pj_timestamp_diff32(&codec_data->last_tx, + &input->timestamp); + + is_silence = pjmedia_silence_det_detect(codec_data->vad, + (const pj_int16_t*) input->buf, + (input->size >> 1), + NULL); + if (is_silence && + (PJMEDIA_CODEC_MAX_SILENCE_PERIOD == -1 || + silence_duration < (PJMEDIA_CODEC_MAX_SILENCE_PERIOD * + (int)ippc->clock_rate / 1000))) + { + output->type = PJMEDIA_FRAME_TYPE_NONE; + output->buf = NULL; + output->size = 0; + output->timestamp = input->timestamp; + return PJ_SUCCESS; + } else { + codec_data->last_tx = input->timestamp; + } + } + + nsamples = input->size >> 1; + samples_per_frame = ippc->samples_per_frame; + pt = ippc->pt; + + PJ_ASSERT_RETURN(nsamples % samples_per_frame == 0, + PJMEDIA_CODEC_EPCMFRMINLEN); + + /* Encode the frames */ + while (nsamples >= samples_per_frame) { + USC_PCMStream in; + USC_Bitstream out; + + in.bitrate = codec_data->info->params.modes.bitrate; + in.nbytes = samples_per_frame << 1; + in.pBuffer = (char*)pcm_in; + in.pcmType.bitPerSample = codec_data->info->params.pcmType.bitPerSample; + in.pcmType.nChannels = codec_data->info->params.pcmType.nChannels; + in.pcmType.sample_frequency = codec_data->info->params.pcmType.sample_frequency; + + out.pBuffer = (char*)bits_out; + +#if PJMEDIA_HAS_INTEL_IPP_CODEC_AMR + /* For AMR: reserve two octets for AMR frame info */ + if (pt == PJMEDIA_RTP_PT_AMR || pt == PJMEDIA_RTP_PT_AMRWB) { + out.pBuffer += 2; + } +#endif + +#if PJMEDIA_HAS_INTEL_IPP_CODEC_G722_1 + /* For G722.1: adjust the encoder input signal level */ + if (pt >= PJMEDIA_RTP_PT_G722_1_16 && + pt <= PJMEDIA_RTP_PT_G7221_RSV2 && + codec_data->g7221_pcm_shift) + { + unsigned i; + for (i = 0; i < samples_per_frame; ++i) + pcm_in[i] >>= codec_data->g7221_pcm_shift; + } +#endif + + if (USC_NoError != ippc->fxns->Encode(codec_data->enc, &in, &out)) { + break; + } + +#if PJMEDIA_HAS_INTEL_IPP_CODEC_AMR + /* For AMR: put info (frametype, degraded, last frame, mode) in the + * first two octets for payload packing. + */ + if (pt == PJMEDIA_RTP_PT_AMR || pt == PJMEDIA_RTP_PT_AMRWB) { + pj_uint16_t *info = (pj_uint16_t*)bits_out; + + /* Two octets for AMR frame info, 0=LSB: + * bit 0-3 : frame type + * bit 5 : STI flag + * bit 6 : last frame flag + * bit 7 : quality flag + * bit 8-11 : mode + */ + out.nbytes += 2; + if (out.frametype == 0 || out.frametype == 4 || + (pt == PJMEDIA_RTP_PT_AMR && out.frametype == 5) || + (pt == PJMEDIA_RTP_PT_AMRWB && out.frametype == 6)) + { + /* Speech frame type */ + *info = (char)pjmedia_codec_amr_get_mode(out.bitrate); + /* Quality */ + if (out.frametype == 5 || out.frametype == 6) + *info |= 0x80; + } else if (out.frametype == 1 || out.frametype == 2 || + (pt == PJMEDIA_RTP_PT_AMR && out.frametype == 6) || + (pt == PJMEDIA_RTP_PT_AMRWB && out.frametype == 7)) + { + /* SID frame type */ + *info = (pj_uint8_t)(pt == PJMEDIA_RTP_PT_AMRWB? 9 : 8); + /* Quality */ + if (out.frametype == 6 || out.frametype == 7) + *info |= 0x80; + /* STI */ + if (out.frametype != 1) + *info |= 0x20; + } else { + /* Untransmited */ + *info = 15; + out.nbytes = 2; + } + + /* Mode */ + *info |= (char)pjmedia_codec_amr_get_mode(out.bitrate) << 8; + + /* Last frame flag */ + if (nsamples == samples_per_frame) + *info |= 0x40; + } +#endif + + pcm_in += samples_per_frame; + nsamples -= samples_per_frame; + tx += out.nbytes; + bits_out += out.nbytes; + +#if PJMEDIA_HAS_INTEL_IPP_CODEC_G729 + if (pt == PJMEDIA_RTP_PT_G729) { + if (out.frametype == 1) { + /* SID */ + break; + } else if (out.frametype == 0) { + /* Untransmitted */ + tx -= out.nbytes; + break; + } + } +#endif + + } + + if (ippc->pack != NULL) { + ippc->pack(codec_data, output->buf, &tx, output_buf_len); + } + + /* Check if we don't need to transmit the frame (DTX) */ + if (tx == 0) { + output->buf = NULL; + output->size = 0; + output->timestamp.u64 = input->timestamp.u64; + output->type = PJMEDIA_FRAME_TYPE_NONE; + return PJ_SUCCESS; + } + + output->size = tx; + output->type = PJMEDIA_FRAME_TYPE_AUDIO; + output->timestamp = input->timestamp; + + return PJ_SUCCESS; +} + +/* + * Decode frame. + */ +static pj_status_t ipp_codec_decode( pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output) +{ + ipp_private_t *codec_data = (ipp_private_t*) codec->codec_data; + struct ipp_codec *ippc = &ipp_codec[codec_data->codec_idx]; + unsigned samples_per_frame; + USC_PCMStream out; + USC_Bitstream in; + pj_uint8_t pt; + + pt = ippc->pt; + samples_per_frame = ippc->samples_per_frame; + + PJ_ASSERT_RETURN(output_buf_len >= samples_per_frame << 1, + PJMEDIA_CODEC_EPCMTOOSHORT); + + if (input->type == PJMEDIA_FRAME_TYPE_AUDIO) { + if (ippc->predecode) { + ippc->predecode(codec_data, input, &in); + } else { + /* Most IPP codecs have frametype==0 for speech frame */ + in.pBuffer = (char*)input->buf; + in.nbytes = input->size; + in.frametype = 0; + in.bitrate = codec_data->info->params.modes.bitrate; + } + + out.pBuffer = output->buf; + } + + if (input->type != PJMEDIA_FRAME_TYPE_AUDIO || + USC_NoError != ippc->fxns->Decode(codec_data->dec, &in, &out)) + { + pjmedia_zero_samples((pj_int16_t*)output->buf, samples_per_frame); + output->size = samples_per_frame << 1; + output->timestamp.u64 = input->timestamp.u64; + output->type = PJMEDIA_FRAME_TYPE_AUDIO; + return PJ_SUCCESS; + } + +#if PJMEDIA_HAS_INTEL_IPP_CODEC_G726 + /* For G.726: amplify decoding result (USC G.726 encoder deamplified it) */ + if (pt == PJMEDIA_RTP_PT_G726_16 || pt == PJMEDIA_RTP_PT_G726_24 || + pt == PJMEDIA_RTP_PT_G726_32 || pt == PJMEDIA_RTP_PT_G726_40 || + pt == PJMEDIA_RTP_PT_G721) + { + unsigned i; + pj_int16_t *s = (pj_int16_t*)output->buf; + + for (i = 0; i < samples_per_frame; ++i) + s[i] <<= 2; + } +#endif + +#if PJMEDIA_HAS_INTEL_IPP_CODEC_G722_1 + /* For G722.1: adjust the decoder output signal level */ + if (pt >= PJMEDIA_RTP_PT_G722_1_16 && + pt <= PJMEDIA_RTP_PT_G7221_RSV2 && + codec_data->g7221_pcm_shift) + { + unsigned i; + pj_int16_t *s = (pj_int16_t*)output->buf; + + for (i = 0; i < samples_per_frame; ++i) + s[i] <<= codec_data->g7221_pcm_shift; + } +#endif + + output->type = PJMEDIA_FRAME_TYPE_AUDIO; + output->size = samples_per_frame << 1; + output->timestamp.u64 = input->timestamp.u64; + + /* Invoke external PLC if codec has no internal PLC */ + if (codec_data->plc && codec_data->plc_enabled) + pjmedia_plc_save(codec_data->plc, (pj_int16_t*)output->buf); + + return PJ_SUCCESS; +} + +/* + * Recover lost frame. + */ +static pj_status_t ipp_codec_recover(pjmedia_codec *codec, + unsigned output_buf_len, + struct pjmedia_frame *output) +{ + ipp_private_t *codec_data = (ipp_private_t*) codec->codec_data; + struct ipp_codec *ippc = &ipp_codec[codec_data->codec_idx]; + unsigned samples_per_frame; + + PJ_UNUSED_ARG(output_buf_len); + + samples_per_frame = ippc->samples_per_frame; + + output->type = PJMEDIA_FRAME_TYPE_AUDIO; + output->size = samples_per_frame << 1; + + if (codec_data->plc_enabled) { + if (codec_data->plc) { + pjmedia_plc_generate(codec_data->plc, (pj_int16_t*)output->buf); + } else { + USC_PCMStream out; + out.pBuffer = output->buf; + ippc->fxns->Decode(codec_data->dec, NULL, &out); + } + } else { + pjmedia_zero_samples((pj_int16_t*)output->buf, samples_per_frame); + } + + return PJ_SUCCESS; +} + + +#if defined(_MSC_VER) && PJMEDIA_AUTO_LINK_IPP_LIBS +# pragma comment( lib, "ippcore.lib") +# pragma comment( lib, "ipps.lib") +# pragma comment( lib, "ippsc.lib") +# if defined(IPP_VERSION_MAJOR) && IPP_VERSION_MAJOR<=6 +# pragma comment( lib, "ippsr.lib") +# endif +//# pragma comment( lib, "ippcorel.lib") +//# pragma comment( lib, "ippsemerged.lib") +//# pragma comment( lib, "ippsmerged.lib") +//# pragma comment( lib, "ippscemerged.lib") +//# pragma comment( lib, "ippscmerged.lib") +//# pragma comment( lib, "ippsremerged.lib") +//# pragma comment( lib, "ippsrmerged.lib") +# if defined(IPP_VERSION_MAJOR) && IPP_VERSION_MAJOR>=6 +# pragma comment( lib, "speech.lib") +# else +# pragma comment( lib, "usc.lib") +# endif +#endif + + +#endif /* PJMEDIA_HAS_INTEL_IPP */ + diff --git a/pjmedia/src/pjmedia-codec/l16.c b/pjmedia/src/pjmedia-codec/l16.c new file mode 100644 index 0000000..063abc9 --- /dev/null +++ b/pjmedia/src/pjmedia-codec/l16.c @@ -0,0 +1,729 @@ +/* $Id: l16.c 3664 2011-07-19 03:42:28Z nanang $ */ +/* + * 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-codec/l16.h> +#include <pjmedia/codec.h> +#include <pjmedia/errno.h> +#include <pjmedia/endpoint.h> +#include <pjmedia/plc.h> +#include <pjmedia/silencedet.h> +#include <pj/assert.h> +#include <pj/pool.h> +#include <pj/sock.h> +#include <pj/string.h> + + +/* + * Only build this file if PJMEDIA_HAS_L16_CODEC != 0 + */ +#if defined(PJMEDIA_HAS_L16_CODEC) && PJMEDIA_HAS_L16_CODEC != 0 + +#define PLC_DISABLED 0 + + +static const pj_str_t STR_L16 = { "L16", 3 }; + +/* To keep frame size below 1400 MTU, set ptime to 10ms for + * sampling rate > 35 KHz + */ +#define GET_PTIME(clock_rate) ((pj_uint16_t)(clock_rate > 35000 ? 10 : 20)) + + +/* Prototypes for L16 factory */ +static pj_status_t l16_test_alloc( pjmedia_codec_factory *factory, + const pjmedia_codec_info *id ); +static pj_status_t l16_default_attr( pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec_param *attr ); +static pj_status_t l16_enum_codecs (pjmedia_codec_factory *factory, + unsigned *count, + pjmedia_codec_info codecs[]); +static pj_status_t l16_alloc_codec( pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec **p_codec); +static pj_status_t l16_dealloc_codec( pjmedia_codec_factory *factory, + pjmedia_codec *codec ); + +/* Prototypes for L16 implementation. */ +static pj_status_t l16_init( pjmedia_codec *codec, + pj_pool_t *pool ); +static pj_status_t l16_open( pjmedia_codec *codec, + pjmedia_codec_param *attr ); +static pj_status_t l16_close( pjmedia_codec *codec ); +static pj_status_t l16_modify(pjmedia_codec *codec, + const pjmedia_codec_param *attr ); +static pj_status_t l16_parse(pjmedia_codec *codec, + void *pkt, + pj_size_t pkt_size, + const pj_timestamp *ts, + unsigned *frame_cnt, + pjmedia_frame frames[]); +static pj_status_t l16_encode( pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output); +static pj_status_t l16_decode( pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output); +#if !PLC_DISABLED +static pj_status_t l16_recover(pjmedia_codec *codec, + unsigned output_buf_len, + struct pjmedia_frame *output); +#endif + +/* Definition for L16 codec operations. */ +static pjmedia_codec_op l16_op = +{ + &l16_init, + &l16_open, + &l16_close, + &l16_modify, + &l16_parse, + &l16_encode, + &l16_decode, +#if !PLC_DISABLED + &l16_recover +#else + NULL +#endif +}; + +/* Definition for L16 codec factory operations. */ +static pjmedia_codec_factory_op l16_factory_op = +{ + &l16_test_alloc, + &l16_default_attr, + &l16_enum_codecs, + &l16_alloc_codec, + &l16_dealloc_codec, + &pjmedia_codec_l16_deinit +}; + +/* L16 factory private data */ +static struct l16_factory +{ + pjmedia_codec_factory base; + pjmedia_endpt *endpt; + pj_pool_t *pool; + pj_mutex_t *mutex; +} l16_factory; + + +/* L16 codec private data. */ +struct l16_data +{ + pj_pool_t *pool; + unsigned frame_size; /* Frame size, in bytes */ + unsigned clock_rate; /* Clock rate */ + +#if !PLC_DISABLED + pj_bool_t plc_enabled; + pjmedia_plc *plc; +#endif + pj_bool_t vad_enabled; + pjmedia_silence_det *vad; + pj_timestamp last_tx; +}; + + + +PJ_DEF(pj_status_t) pjmedia_codec_l16_init(pjmedia_endpt *endpt, + unsigned options) +{ + pjmedia_codec_mgr *codec_mgr; + pj_status_t status; + + + PJ_UNUSED_ARG(options); + + + if (l16_factory.endpt != NULL) { + /* Already initialized. */ + return PJ_SUCCESS; + } + + /* Init factory */ + l16_factory.base.op = &l16_factory_op; + l16_factory.base.factory_data = NULL; + l16_factory.endpt = endpt; + + /* Create pool */ + l16_factory.pool = pjmedia_endpt_create_pool(endpt, "l16", 4000, 4000); + if (!l16_factory.pool) + return PJ_ENOMEM; + + /* Create mutex. */ + status = pj_mutex_create_simple(l16_factory.pool, "l16", + &l16_factory.mutex); + if (status != PJ_SUCCESS) + goto on_error; + + /* Get the codec manager. */ + codec_mgr = pjmedia_endpt_get_codec_mgr(endpt); + if (!codec_mgr) { + return PJ_EINVALIDOP; + } + + /* Register codec factory to endpoint. */ + status = pjmedia_codec_mgr_register_factory(codec_mgr, + &l16_factory.base); + if (status != PJ_SUCCESS) + return status; + + + return PJ_SUCCESS; + +on_error: + if (l16_factory.mutex) { + pj_mutex_destroy(l16_factory.mutex); + l16_factory.mutex = NULL; + } + if (l16_factory.pool) { + pj_pool_release(l16_factory.pool); + l16_factory.pool = NULL; + } + return status; +} + +PJ_DEF(pj_status_t) pjmedia_codec_l16_deinit(void) +{ + pjmedia_codec_mgr *codec_mgr; + pj_status_t status; + + if (l16_factory.endpt == NULL) { + /* Not registered. */ + return PJ_SUCCESS; + } + + /* Lock mutex. */ + pj_mutex_lock(l16_factory.mutex); + + /* Get the codec manager. */ + codec_mgr = pjmedia_endpt_get_codec_mgr(l16_factory.endpt); + if (!codec_mgr) { + l16_factory.endpt = NULL; + pj_mutex_unlock(l16_factory.mutex); + return PJ_EINVALIDOP; + } + + /* Unregister L16 codec factory. */ + status = pjmedia_codec_mgr_unregister_factory(codec_mgr, + &l16_factory.base); + l16_factory.endpt = NULL; + + /* Destroy mutex. */ + pj_mutex_destroy(l16_factory.mutex); + l16_factory.mutex = NULL; + + + /* Release pool. */ + pj_pool_release(l16_factory.pool); + l16_factory.pool = NULL; + + + return status; +} + +static pj_status_t l16_test_alloc(pjmedia_codec_factory *factory, + const pjmedia_codec_info *id ) +{ + PJ_UNUSED_ARG(factory); + + if (pj_stricmp(&id->encoding_name, &STR_L16)==0) { + /* Match! */ + return PJ_SUCCESS; + } + + return -1; +} + +static pj_status_t l16_default_attr( pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec_param *attr ) +{ + PJ_UNUSED_ARG(factory); + + pj_bzero(attr, sizeof(pjmedia_codec_param)); + attr->info.pt = (pj_uint8_t)id->pt; + attr->info.clock_rate = id->clock_rate; + attr->info.channel_cnt = id->channel_cnt; + attr->info.avg_bps = id->clock_rate * id->channel_cnt * 16; + attr->info.max_bps = attr->info.avg_bps; + attr->info.pcm_bits_per_sample = 16; + + /* To keep frame size below 1400 MTU, set ptime to 10ms for + * sampling rate > 35 KHz + */ + attr->info.frm_ptime = GET_PTIME(id->clock_rate); + + attr->setting.frm_per_pkt = 1; + + attr->setting.vad = 1; +#if !PLC_DISABLED + attr->setting.plc = 1; +#endif + + return PJ_SUCCESS; +} + +static pj_status_t l16_enum_codecs( pjmedia_codec_factory *factory, + unsigned *max_count, + pjmedia_codec_info codecs[]) +{ + unsigned count = 0; + + PJ_UNUSED_ARG(factory); + + if (count < *max_count) { + /* Register 44100Hz 1 channel L16 codec */ + codecs[count].type = PJMEDIA_TYPE_AUDIO; + codecs[count].pt = PJMEDIA_RTP_PT_L16_1; + codecs[count].encoding_name = STR_L16; + codecs[count].clock_rate = 44100; + codecs[count].channel_cnt = 1; + ++count; + } + + if (count < *max_count) { + /* Register 44100Hz 2 channels L16 codec */ + codecs[count].type = PJMEDIA_TYPE_AUDIO; + codecs[count].pt = PJMEDIA_RTP_PT_L16_2; + codecs[count].encoding_name = STR_L16; + codecs[count].clock_rate = 44100; + codecs[count].channel_cnt = 2; + ++count; + } + + if (count < *max_count) { + /* 8KHz mono */ + codecs[count].type = PJMEDIA_TYPE_AUDIO; + codecs[count].pt = PJMEDIA_RTP_PT_L16_8KHZ_MONO; + codecs[count].encoding_name = STR_L16; + codecs[count].clock_rate = 8000; + codecs[count].channel_cnt = 1; + ++count; + } + + if (count < *max_count) { + /* 8KHz stereo */ + codecs[count].type = PJMEDIA_TYPE_AUDIO; + codecs[count].pt = PJMEDIA_RTP_PT_L16_8KHZ_STEREO; + codecs[count].encoding_name = STR_L16; + codecs[count].clock_rate = 8000; + codecs[count].channel_cnt = 2; + ++count; + } + +// disable some L16 modes +#if 0 + if (count < *max_count) { + /* 11025 Hz mono */ + codecs[count].type = PJMEDIA_TYPE_AUDIO; + codecs[count].pt = PJMEDIA_RTP_PT_L16_11KHZ_MONO; + codecs[count].encoding_name = STR_L16; + codecs[count].clock_rate = 11025; + codecs[count].channel_cnt = 1; + ++count; + } + + if (count < *max_count) { + /* 11025 Hz stereo */ + codecs[count].type = PJMEDIA_TYPE_AUDIO; + codecs[count].pt = PJMEDIA_RTP_PT_L16_11KHZ_STEREO; + codecs[count].encoding_name = STR_L16; + codecs[count].clock_rate = 11025; + codecs[count].channel_cnt = 2; + ++count; + } +#endif + + if (count < *max_count) { + /* 16000 Hz mono */ + codecs[count].type = PJMEDIA_TYPE_AUDIO; + codecs[count].pt = PJMEDIA_RTP_PT_L16_16KHZ_MONO; + codecs[count].encoding_name = STR_L16; + codecs[count].clock_rate = 16000; + codecs[count].channel_cnt = 1; + ++count; + } + + + if (count < *max_count) { + /* 16000 Hz stereo */ + codecs[count].type = PJMEDIA_TYPE_AUDIO; + codecs[count].pt = PJMEDIA_RTP_PT_L16_16KHZ_STEREO; + codecs[count].encoding_name = STR_L16; + codecs[count].clock_rate = 16000; + codecs[count].channel_cnt = 2; + ++count; + } + +// disable some L16 modes +#if 0 + if (count < *max_count) { + /* 22050 Hz mono */ + codecs[count].type = PJMEDIA_TYPE_AUDIO; + codecs[count].pt = PJMEDIA_RTP_PT_L16_22KHZ_MONO; + codecs[count].encoding_name = STR_L16; + codecs[count].clock_rate = 22050; + codecs[count].channel_cnt = 1; + ++count; + } + + + if (count < *max_count) { + /* 22050 Hz stereo */ + codecs[count].type = PJMEDIA_TYPE_AUDIO; + codecs[count].pt = PJMEDIA_RTP_PT_L16_22KHZ_STEREO; + codecs[count].encoding_name = STR_L16; + codecs[count].clock_rate = 22050; + codecs[count].channel_cnt = 2; + ++count; + } + + if (count < *max_count) { + /* 32000 Hz mono */ + codecs[count].type = PJMEDIA_TYPE_AUDIO; + codecs[count].pt = PJMEDIA_RTP_PT_L16_32KHZ_MONO; + codecs[count].encoding_name = STR_L16; + codecs[count].clock_rate = 32000; + codecs[count].channel_cnt = 1; + ++count; + } + + if (count < *max_count) { + /* 32000 Hz stereo */ + codecs[count].type = PJMEDIA_TYPE_AUDIO; + codecs[count].pt = PJMEDIA_RTP_PT_L16_32KHZ_STEREO; + codecs[count].encoding_name = STR_L16; + codecs[count].clock_rate = 32000; + codecs[count].channel_cnt = 2; + ++count; + } + + if (count < *max_count) { + /* 48KHz mono */ + codecs[count].type = PJMEDIA_TYPE_AUDIO; + codecs[count].pt = PJMEDIA_RTP_PT_L16_48KHZ_MONO; + codecs[count].encoding_name = STR_L16; + codecs[count].clock_rate = 48000; + codecs[count].channel_cnt = 1; + ++count; + } + + if (count < *max_count) { + /* 48KHz stereo */ + codecs[count].type = PJMEDIA_TYPE_AUDIO; + codecs[count].pt = PJMEDIA_RTP_PT_L16_48KHZ_STEREO; + codecs[count].encoding_name = STR_L16; + codecs[count].clock_rate = 48000; + codecs[count].channel_cnt = 2; + ++count; + } +#endif + + + *max_count = count; + + return PJ_SUCCESS; +} + +static pj_status_t l16_alloc_codec( pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec **p_codec) +{ + pjmedia_codec *codec = NULL; + struct l16_data *data; + unsigned ptime; + pj_pool_t *pool; + + pj_status_t status; + + PJ_ASSERT_RETURN(factory==&l16_factory.base, PJ_EINVAL); + + /* Lock mutex. */ + pj_mutex_lock(l16_factory.mutex); + + + pool = pjmedia_endpt_create_pool(l16_factory.endpt, "l16", 4000, 4000); + codec = PJ_POOL_ZALLOC_T(pool, pjmedia_codec); + codec->codec_data = pj_pool_alloc(pool, sizeof(struct l16_data)); + codec->factory = factory; + codec->op = &l16_op; + + /* Init private data */ + ptime = GET_PTIME(id->clock_rate); + data = (struct l16_data*) codec->codec_data; + data->frame_size = ptime * id->clock_rate * id->channel_cnt * 2 / 1000; + data->clock_rate = id->clock_rate; + data->pool = pool; + +#if !PLC_DISABLED + /* Create PLC */ + status = pjmedia_plc_create(pool, id->clock_rate, + data->frame_size >> 1, 0, + &data->plc); + if (status != PJ_SUCCESS) { + pj_mutex_unlock(l16_factory.mutex); + return status; + } +#endif + + /* Create silence detector */ + status = pjmedia_silence_det_create(pool, id->clock_rate, + data->frame_size >> 1, + &data->vad); + if (status != PJ_SUCCESS) { + pj_mutex_unlock(l16_factory.mutex); + return status; + } + + *p_codec = codec; + + /* Unlock mutex. */ + pj_mutex_unlock(l16_factory.mutex); + + return PJ_SUCCESS; +} + +static pj_status_t l16_dealloc_codec(pjmedia_codec_factory *factory, + pjmedia_codec *codec ) +{ + struct l16_data *data; + + PJ_ASSERT_RETURN(factory && codec, PJ_EINVAL); + PJ_ASSERT_RETURN(factory==&l16_factory.base, PJ_EINVAL); + + /* Lock mutex. */ + pj_mutex_lock(l16_factory.mutex); + + /* Just release codec data pool */ + data = (struct l16_data*) codec->codec_data; + pj_assert(data); + pj_pool_release(data->pool); + + /* Unlock mutex. */ + pj_mutex_unlock(l16_factory.mutex); + + return PJ_SUCCESS; +} + +static pj_status_t l16_init( pjmedia_codec *codec, pj_pool_t *pool ) +{ + /* There's nothing to do here really */ + PJ_UNUSED_ARG(codec); + PJ_UNUSED_ARG(pool); + + return PJ_SUCCESS; +} + +static pj_status_t l16_open(pjmedia_codec *codec, + pjmedia_codec_param *attr ) +{ + struct l16_data *data = NULL; + + PJ_ASSERT_RETURN(codec && codec->codec_data && attr, PJ_EINVAL); + + data = (struct l16_data*) codec->codec_data; + + data->vad_enabled = (attr->setting.vad != 0); +#if !PLC_DISABLED + data->plc_enabled = (attr->setting.plc != 0); +#endif + + return PJ_SUCCESS; +} + +static pj_status_t l16_close( pjmedia_codec *codec ) +{ + PJ_UNUSED_ARG(codec); + /* Nothing to do */ + return PJ_SUCCESS; +} + +static pj_status_t l16_modify(pjmedia_codec *codec, + const pjmedia_codec_param *attr ) +{ + struct l16_data *data = (struct l16_data*) codec->codec_data; + + pj_assert(data != NULL); + + data->vad_enabled = (attr->setting.vad != 0); +#if !PLC_DISABLED + data->plc_enabled = (attr->setting.plc != 0); +#endif + + return PJ_SUCCESS; +} + +static pj_status_t l16_parse( pjmedia_codec *codec, + void *pkt, + pj_size_t pkt_size, + const pj_timestamp *ts, + unsigned *frame_cnt, + pjmedia_frame frames[]) +{ + unsigned count = 0; + struct l16_data *data = (struct l16_data*) codec->codec_data; + + PJ_UNUSED_ARG(codec); + PJ_ASSERT_RETURN(frame_cnt, PJ_EINVAL); + + while (pkt_size >= data->frame_size && count < *frame_cnt) { + frames[count].type = PJMEDIA_FRAME_TYPE_AUDIO; + frames[count].buf = pkt; + frames[count].size = data->frame_size; + frames[count].timestamp.u64 = ts->u64 + (count * data->frame_size); + + pkt = ((char*)pkt) + data->frame_size; + pkt_size -= data->frame_size; + + ++count; + } + + *frame_cnt = count; + return PJ_SUCCESS; +} + +static pj_status_t l16_encode(pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output) +{ + struct l16_data *data = (struct l16_data*) codec->codec_data; + const pj_int16_t *samp = (const pj_int16_t*) input->buf; + const pj_int16_t *samp_end = samp + input->size/sizeof(pj_int16_t); + pj_int16_t *samp_out = (pj_int16_t*) output->buf; + + pj_assert(data && input && output); + + /* Check output buffer length */ + if (output_buf_len < input->size) + return PJMEDIA_CODEC_EFRMTOOSHORT; + + /* Detect silence */ + if (data->vad_enabled) { + pj_bool_t is_silence; + pj_int32_t silence_duration; + + silence_duration = pj_timestamp_diff32(&data->last_tx, + &input->timestamp); + + is_silence = pjmedia_silence_det_detect(data->vad, + (const pj_int16_t*) input->buf, + (input->size >> 1), + NULL); + if (is_silence && + (PJMEDIA_CODEC_MAX_SILENCE_PERIOD == -1 || + silence_duration < PJMEDIA_CODEC_MAX_SILENCE_PERIOD* + (int)data->clock_rate/1000)) + { + output->type = PJMEDIA_FRAME_TYPE_NONE; + output->buf = NULL; + output->size = 0; + output->timestamp = input->timestamp; + return PJ_SUCCESS; + } else { + data->last_tx = input->timestamp; + } + } + + /* Encode */ +#if defined(PJ_IS_LITTLE_ENDIAN) && PJ_IS_LITTLE_ENDIAN!=0 + while (samp!=samp_end) + *samp_out++ = pj_htons(*samp++); +#else + pjmedia_copy_samples(samp_out, samp, input->size >> 1); +#endif + + + /* Done */ + output->type = PJMEDIA_FRAME_TYPE_AUDIO; + output->size = input->size; + output->timestamp = input->timestamp; + + return PJ_SUCCESS; +} + +static pj_status_t l16_decode(pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output) +{ + struct l16_data *l16_data = (struct l16_data*) codec->codec_data; + const pj_int16_t *samp = (const pj_int16_t*) input->buf; + const pj_int16_t *samp_end = samp + input->size/sizeof(pj_int16_t); + pj_int16_t *samp_out = (pj_int16_t*) output->buf; + + pj_assert(l16_data != NULL); + PJ_ASSERT_RETURN(input && output, PJ_EINVAL); + + + /* Check output buffer length */ + if (output_buf_len < input->size) + return PJMEDIA_CODEC_EPCMTOOSHORT; + + + /* Decode */ +#if defined(PJ_IS_LITTLE_ENDIAN) && PJ_IS_LITTLE_ENDIAN!=0 + while (samp!=samp_end) + *samp_out++ = pj_htons(*samp++); +#else + pjmedia_copy_samples(samp_out, samp, input->size >> 1); +#endif + + + output->type = PJMEDIA_FRAME_TYPE_AUDIO; + output->size = input->size; + output->timestamp = input->timestamp; + +#if !PLC_DISABLED + if (l16_data->plc_enabled) + pjmedia_plc_save( l16_data->plc, (pj_int16_t*)output->buf); +#endif + + return PJ_SUCCESS; +} + +#if !PLC_DISABLED +/* + * Recover lost frame. + */ +static pj_status_t l16_recover(pjmedia_codec *codec, + unsigned output_buf_len, + struct pjmedia_frame *output) +{ + struct l16_data *data = (struct l16_data*) codec->codec_data; + + PJ_ASSERT_RETURN(data->plc_enabled, PJ_EINVALIDOP); + + PJ_ASSERT_RETURN(output_buf_len >= data->frame_size, + PJMEDIA_CODEC_EPCMTOOSHORT); + + pjmedia_plc_generate(data->plc, (pj_int16_t*)output->buf); + output->size = data->frame_size; + + return PJ_SUCCESS; +} +#endif + +#endif /* PJMEDIA_HAS_L16_CODEC */ + + diff --git a/pjmedia/src/pjmedia-codec/opencore_amrnb.c b/pjmedia/src/pjmedia-codec/opencore_amrnb.c new file mode 100644 index 0000000..9b6daa9 --- /dev/null +++ b/pjmedia/src/pjmedia-codec/opencore_amrnb.c @@ -0,0 +1,831 @@ +/* $Id: opencore_amrnb.c 3939 2012-01-10 05:38:40Z nanang $ */ +/* + * Copyright (C) 2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2011 Dan Arrhenius <dan@keystream.se> + * + * 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 + */ + +/* + * AMR-NB codec implementation with OpenCORE AMRNB library + */ +#include <pjmedia-codec/g722.h> +#include <pjmedia-codec/amr_sdp_match.h> +#include <pjmedia/codec.h> +#include <pjmedia/errno.h> +#include <pjmedia/endpoint.h> +#include <pjmedia/plc.h> +#include <pjmedia/port.h> +#include <pjmedia/silencedet.h> +#include <pj/assert.h> +#include <pj/log.h> +#include <pj/pool.h> +#include <pj/string.h> +#include <pj/os.h> +#include <pj/math.h> + +#if defined(PJMEDIA_HAS_OPENCORE_AMRNB_CODEC) && \ + (PJMEDIA_HAS_OPENCORE_AMRNB_CODEC != 0) + +#include <opencore-amrnb/interf_enc.h> +#include <opencore-amrnb/interf_dec.h> +#include <pjmedia-codec/amr_helper.h> +#include <pjmedia-codec/opencore_amrnb.h> + +#define THIS_FILE "opencore_amrnb.c" + +/* Tracing */ +#define PJ_TRACE 0 + +#if PJ_TRACE +# define TRACE_(expr) PJ_LOG(4,expr) +#else +# define TRACE_(expr) +#endif + +/* Use PJMEDIA PLC */ +#define USE_PJMEDIA_PLC 1 + + + +/* Prototypes for AMR-NB factory */ +static pj_status_t amr_test_alloc(pjmedia_codec_factory *factory, + const pjmedia_codec_info *id ); +static pj_status_t amr_default_attr(pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec_param *attr ); +static pj_status_t amr_enum_codecs(pjmedia_codec_factory *factory, + unsigned *count, + pjmedia_codec_info codecs[]); +static pj_status_t amr_alloc_codec(pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec **p_codec); +static pj_status_t amr_dealloc_codec(pjmedia_codec_factory *factory, + pjmedia_codec *codec ); + +/* Prototypes for AMR-NB implementation. */ +static pj_status_t amr_codec_init(pjmedia_codec *codec, + pj_pool_t *pool ); +static pj_status_t amr_codec_open(pjmedia_codec *codec, + pjmedia_codec_param *attr ); +static pj_status_t amr_codec_close(pjmedia_codec *codec ); +static pj_status_t amr_codec_modify(pjmedia_codec *codec, + const pjmedia_codec_param *attr ); +static pj_status_t amr_codec_parse(pjmedia_codec *codec, + void *pkt, + pj_size_t pkt_size, + const pj_timestamp *ts, + unsigned *frame_cnt, + pjmedia_frame frames[]); +static pj_status_t amr_codec_encode(pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output); +static pj_status_t amr_codec_decode(pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output); +static pj_status_t amr_codec_recover(pjmedia_codec *codec, + unsigned output_buf_len, + struct pjmedia_frame *output); + + + +/* Definition for AMR-NB codec operations. */ +static pjmedia_codec_op amr_op = +{ + &amr_codec_init, + &amr_codec_open, + &amr_codec_close, + &amr_codec_modify, + &amr_codec_parse, + &amr_codec_encode, + &amr_codec_decode, + &amr_codec_recover +}; + +/* Definition for AMR-NB codec factory operations. */ +static pjmedia_codec_factory_op amr_factory_op = +{ + &amr_test_alloc, + &amr_default_attr, + &amr_enum_codecs, + &amr_alloc_codec, + &amr_dealloc_codec, + &pjmedia_codec_opencore_amrnb_deinit +}; + + +/* AMR-NB factory */ +static struct amr_codec_factory +{ + pjmedia_codec_factory base; + pjmedia_endpt *endpt; + pj_pool_t *pool; +} amr_codec_factory; + + +/* AMR-NB codec private data. */ +struct amr_data +{ + pj_pool_t *pool; + void *encoder; + void *decoder; + pj_bool_t plc_enabled; + pj_bool_t vad_enabled; + int enc_mode; + pjmedia_codec_amr_pack_setting enc_setting; + pjmedia_codec_amr_pack_setting dec_setting; +#if USE_PJMEDIA_PLC + pjmedia_plc *plc; +#endif + pj_timestamp last_tx; +}; + +static pjmedia_codec_amrnb_config def_config = +{ + PJ_FALSE, /* octet align */ + 5900 /* bitrate */ +}; + + + +/* + * Initialize and register AMR-NB codec factory to pjmedia endpoint. + */ +PJ_DEF(pj_status_t) pjmedia_codec_opencore_amrnb_init( pjmedia_endpt *endpt ) +{ + pjmedia_codec_mgr *codec_mgr; + pj_str_t codec_name; + pj_status_t status; + + if (amr_codec_factory.pool != NULL) + return PJ_SUCCESS; + + /* Create AMR-NB codec factory. */ + amr_codec_factory.base.op = &amr_factory_op; + amr_codec_factory.base.factory_data = NULL; + amr_codec_factory.endpt = endpt; + + amr_codec_factory.pool = pjmedia_endpt_create_pool(endpt, "amrnb", 1000, + 1000); + if (!amr_codec_factory.pool) + return PJ_ENOMEM; + + /* Get the codec manager. */ + codec_mgr = pjmedia_endpt_get_codec_mgr(endpt); + if (!codec_mgr) { + status = PJ_EINVALIDOP; + goto on_error; + } + + /* Register format match callback. */ + pj_cstr(&codec_name, "AMR"); + status = pjmedia_sdp_neg_register_fmt_match_cb( + &codec_name, + &pjmedia_codec_amr_match_sdp); + if (status != PJ_SUCCESS) + goto on_error; + + /* Register codec factory to endpoint. */ + status = pjmedia_codec_mgr_register_factory(codec_mgr, + &amr_codec_factory.base); + if (status != PJ_SUCCESS) + goto on_error; + + /* Done. */ + return PJ_SUCCESS; + +on_error: + pj_pool_release(amr_codec_factory.pool); + amr_codec_factory.pool = NULL; + return status; +} + + +/* + * Unregister AMR-NB codec factory from pjmedia endpoint and deinitialize + * the AMR-NB codec library. + */ +PJ_DEF(pj_status_t) pjmedia_codec_opencore_amrnb_deinit(void) +{ + pjmedia_codec_mgr *codec_mgr; + pj_status_t status; + + if (amr_codec_factory.pool == NULL) + return PJ_SUCCESS; + + /* Get the codec manager. */ + codec_mgr = pjmedia_endpt_get_codec_mgr(amr_codec_factory.endpt); + if (!codec_mgr) { + pj_pool_release(amr_codec_factory.pool); + amr_codec_factory.pool = NULL; + return PJ_EINVALIDOP; + } + + /* Unregister AMR-NB codec factory. */ + status = pjmedia_codec_mgr_unregister_factory(codec_mgr, + &amr_codec_factory.base); + + /* Destroy pool. */ + pj_pool_release(amr_codec_factory.pool); + amr_codec_factory.pool = NULL; + + return status; +} + + +PJ_DEF(pj_status_t) pjmedia_codec_opencore_amrnb_set_config( + const pjmedia_codec_amrnb_config *config) +{ + unsigned nbitrates; + + + def_config = *config; + + /* Normalize bitrate. */ + nbitrates = PJ_ARRAY_SIZE(pjmedia_codec_amrnb_bitrates); + if (def_config.bitrate < pjmedia_codec_amrnb_bitrates[0]) + def_config.bitrate = pjmedia_codec_amrnb_bitrates[0]; + else if (def_config.bitrate > pjmedia_codec_amrnb_bitrates[nbitrates-1]) + def_config.bitrate = pjmedia_codec_amrnb_bitrates[nbitrates-1]; + else + { + unsigned i; + + for (i = 0; i < nbitrates; ++i) { + if (def_config.bitrate <= pjmedia_codec_amrnb_bitrates[i]) + break; + } + def_config.bitrate = pjmedia_codec_amrnb_bitrates[i]; + } + + return PJ_SUCCESS; +} + +/* + * Check if factory can allocate the specified codec. + */ +static pj_status_t amr_test_alloc( pjmedia_codec_factory *factory, + const pjmedia_codec_info *info ) +{ + PJ_UNUSED_ARG(factory); + + /* Check payload type. */ + if (info->pt != PJMEDIA_RTP_PT_AMR) + return PJMEDIA_CODEC_EUNSUP; + + /* Ignore the rest, since it's static payload type. */ + + return PJ_SUCCESS; +} + +/* + * Generate default attribute. + */ +static pj_status_t amr_default_attr( pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec_param *attr ) +{ + PJ_UNUSED_ARG(factory); + PJ_UNUSED_ARG(id); + + pj_bzero(attr, sizeof(pjmedia_codec_param)); + attr->info.clock_rate = 8000; + attr->info.channel_cnt = 1; + attr->info.avg_bps = def_config.bitrate; + attr->info.max_bps = pjmedia_codec_amrnb_bitrates[7]; + attr->info.pcm_bits_per_sample = 16; + attr->info.frm_ptime = 20; + attr->info.pt = PJMEDIA_RTP_PT_AMR; + + attr->setting.frm_per_pkt = 2; + attr->setting.vad = 1; + attr->setting.plc = 1; + + if (def_config.octet_align) { + attr->setting.dec_fmtp.cnt = 1; + attr->setting.dec_fmtp.param[0].name = pj_str("octet-align"); + attr->setting.dec_fmtp.param[0].val = pj_str("1"); + } + + /* Default all other flag bits disabled. */ + + return PJ_SUCCESS; +} + + +/* + * Enum codecs supported by this factory (i.e. only AMR-NB!). + */ +static pj_status_t amr_enum_codecs( pjmedia_codec_factory *factory, + unsigned *count, + pjmedia_codec_info codecs[]) +{ + PJ_UNUSED_ARG(factory); + PJ_ASSERT_RETURN(codecs && *count > 0, PJ_EINVAL); + + pj_bzero(&codecs[0], sizeof(pjmedia_codec_info)); + codecs[0].encoding_name = pj_str("AMR"); + codecs[0].pt = PJMEDIA_RTP_PT_AMR; + codecs[0].type = PJMEDIA_TYPE_AUDIO; + codecs[0].clock_rate = 8000; + codecs[0].channel_cnt = 1; + + *count = 1; + + return PJ_SUCCESS; +} + + +/* + * Allocate a new AMR-NB codec instance. + */ +static pj_status_t amr_alloc_codec( pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec **p_codec) +{ + pj_pool_t *pool; + pjmedia_codec *codec; + struct amr_data *amr_data; + pj_status_t status; + + PJ_ASSERT_RETURN(factory && id && p_codec, PJ_EINVAL); + PJ_ASSERT_RETURN(factory == &amr_codec_factory.base, PJ_EINVAL); + + pool = pjmedia_endpt_create_pool(amr_codec_factory.endpt, "amrnb-inst", + 512, 512); + + codec = PJ_POOL_ZALLOC_T(pool, pjmedia_codec); + PJ_ASSERT_RETURN(codec != NULL, PJ_ENOMEM); + codec->op = &amr_op; + codec->factory = factory; + + amr_data = PJ_POOL_ZALLOC_T(pool, struct amr_data); + codec->codec_data = amr_data; + amr_data->pool = pool; + +#if USE_PJMEDIA_PLC + /* Create PLC */ + status = pjmedia_plc_create(pool, 8000, 160, 0, &amr_data->plc); + if (status != PJ_SUCCESS) { + return status; + } +#else + PJ_UNUSED_ARG(status); +#endif + *p_codec = codec; + return PJ_SUCCESS; +} + + +/* + * Free codec. + */ +static pj_status_t amr_dealloc_codec( pjmedia_codec_factory *factory, + pjmedia_codec *codec ) +{ + struct amr_data *amr_data; + + PJ_ASSERT_RETURN(factory && codec, PJ_EINVAL); + PJ_ASSERT_RETURN(factory == &amr_codec_factory.base, PJ_EINVAL); + + amr_data = (struct amr_data*) codec->codec_data; + + /* Close codec, if it's not closed. */ + amr_codec_close(codec); + + pj_pool_release(amr_data->pool); + amr_data = NULL; + + return PJ_SUCCESS; +} + +/* + * Init codec. + */ +static pj_status_t amr_codec_init( pjmedia_codec *codec, + pj_pool_t *pool ) +{ + PJ_UNUSED_ARG(codec); + PJ_UNUSED_ARG(pool); + return PJ_SUCCESS; +} + + +/* + * Open codec. + */ +static pj_status_t amr_codec_open( pjmedia_codec *codec, + pjmedia_codec_param *attr ) +{ + struct amr_data *amr_data = (struct amr_data*) codec->codec_data; + pjmedia_codec_amr_pack_setting *setting; + unsigned i; + pj_uint8_t octet_align = 0; + pj_int8_t enc_mode; + const pj_str_t STR_FMTP_OCTET_ALIGN = {"octet-align", 11}; + + PJ_ASSERT_RETURN(codec && attr, PJ_EINVAL); + PJ_ASSERT_RETURN(amr_data != NULL, PJ_EINVALIDOP); + + enc_mode = pjmedia_codec_amr_get_mode(attr->info.avg_bps); + pj_assert(enc_mode >= 0 && enc_mode <= 7); + + /* Check octet-align */ + for (i = 0; i < attr->setting.dec_fmtp.cnt; ++i) { + if (pj_stricmp(&attr->setting.dec_fmtp.param[i].name, + &STR_FMTP_OCTET_ALIGN) == 0) + { + octet_align = (pj_uint8_t) + (pj_strtoul(&attr->setting.dec_fmtp.param[i].val)); + break; + } + } + + /* Check mode-set */ + for (i = 0; i < attr->setting.enc_fmtp.cnt; ++i) { + const pj_str_t STR_FMTP_MODE_SET = {"mode-set", 8}; + + if (pj_stricmp(&attr->setting.enc_fmtp.param[i].name, + &STR_FMTP_MODE_SET) == 0) + { + const char *p; + pj_size_t l; + pj_int8_t diff = 99; + + /* Encoding mode is chosen based on local default mode setting: + * - if local default mode is included in the mode-set, use it + * - otherwise, find the closest mode to local default mode; + * if there are two closest modes, prefer to use the higher + * one, e.g: local default mode is 4, the mode-set param + * contains '2,3,5,6', then 5 will be chosen. + */ + p = pj_strbuf(&attr->setting.enc_fmtp.param[i].val); + l = pj_strlen(&attr->setting.enc_fmtp.param[i].val); + while (l--) { + if (*p>='0' && *p<='7') { + pj_int8_t tmp = *p - '0' - enc_mode; + + if (PJ_ABS(diff) > PJ_ABS(tmp) || + (PJ_ABS(diff) == PJ_ABS(tmp) && tmp > diff)) + { + diff = tmp; + if (diff == 0) break; + } + } + ++p; + } + PJ_ASSERT_RETURN(diff != 99, PJMEDIA_CODEC_EFAILED); + + enc_mode = enc_mode + diff; + + break; + } + } + + amr_data->vad_enabled = (attr->setting.vad != 0); + amr_data->plc_enabled = (attr->setting.plc != 0); + amr_data->enc_mode = enc_mode; + + amr_data->encoder = Encoder_Interface_init(amr_data->vad_enabled); + if (amr_data->encoder == NULL) { + TRACE_((THIS_FILE, "Encoder_Interface_init() failed")); + amr_codec_close(codec); + return PJMEDIA_CODEC_EFAILED; + } + setting = &amr_data->enc_setting; + pj_bzero(setting, sizeof(pjmedia_codec_amr_pack_setting)); + setting->amr_nb = 1; + setting->reorder = 0; + setting->octet_aligned = octet_align; + setting->cmr = 15; + + amr_data->decoder = Decoder_Interface_init(); + if (amr_data->decoder == NULL) { + TRACE_((THIS_FILE, "Decoder_Interface_init() failed")); + amr_codec_close(codec); + return PJMEDIA_CODEC_EFAILED; + } + setting = &amr_data->dec_setting; + pj_bzero(setting, sizeof(pjmedia_codec_amr_pack_setting)); + setting->amr_nb = 1; + setting->reorder = 0; + setting->octet_aligned = octet_align; + + TRACE_((THIS_FILE, "AMR-NB codec allocated: vad=%d, plc=%d, bitrate=%d", + amr_data->vad_enabled, amr_data->plc_enabled, + pjmedia_codec_amrnb_bitrates[amr_data->enc_mode])); + return PJ_SUCCESS; +} + + +/* + * Close codec. + */ +static pj_status_t amr_codec_close( pjmedia_codec *codec ) +{ + struct amr_data *amr_data; + + PJ_ASSERT_RETURN(codec, PJ_EINVAL); + + amr_data = (struct amr_data*) codec->codec_data; + PJ_ASSERT_RETURN(amr_data != NULL, PJ_EINVALIDOP); + + if (amr_data->encoder) { + Encoder_Interface_exit(amr_data->encoder); + amr_data->encoder = NULL; + } + + if (amr_data->decoder) { + Decoder_Interface_exit(amr_data->decoder); + amr_data->decoder = NULL; + } + + TRACE_((THIS_FILE, "AMR-NB codec closed")); + return PJ_SUCCESS; +} + + +/* + * Modify codec settings. + */ +static pj_status_t amr_codec_modify( pjmedia_codec *codec, + const pjmedia_codec_param *attr ) +{ + struct amr_data *amr_data = (struct amr_data*) codec->codec_data; + pj_bool_t prev_vad_state; + + pj_assert(amr_data != NULL); + pj_assert(amr_data->encoder != NULL && amr_data->decoder != NULL); + + prev_vad_state = amr_data->vad_enabled; + amr_data->vad_enabled = (attr->setting.vad != 0); + amr_data->plc_enabled = (attr->setting.plc != 0); + + if (prev_vad_state != amr_data->vad_enabled) { + /* Reinit AMR encoder to update VAD setting */ + TRACE_((THIS_FILE, "Reiniting AMR encoder to update VAD setting.")); + Encoder_Interface_exit(amr_data->encoder); + amr_data->encoder = Encoder_Interface_init(amr_data->vad_enabled); + if (amr_data->encoder == NULL) { + TRACE_((THIS_FILE, "Encoder_Interface_init() failed")); + amr_codec_close(codec); + return PJMEDIA_CODEC_EFAILED; + } + } + + TRACE_((THIS_FILE, "AMR-NB codec modified: vad=%d, plc=%d", + amr_data->vad_enabled, amr_data->plc_enabled)); + return PJ_SUCCESS; +} + + +/* + * Get frames in the packet. + */ +static pj_status_t amr_codec_parse( pjmedia_codec *codec, + void *pkt, + pj_size_t pkt_size, + const pj_timestamp *ts, + unsigned *frame_cnt, + pjmedia_frame frames[]) +{ + struct amr_data *amr_data = (struct amr_data*) codec->codec_data; + pj_uint8_t cmr; + pj_status_t status; + + status = pjmedia_codec_amr_parse(pkt, pkt_size, ts, &amr_data->dec_setting, + frames, frame_cnt, &cmr); + if (status != PJ_SUCCESS) + return status; + + /* Check for Change Mode Request. */ + if (cmr <= 7 && amr_data->enc_mode != cmr) { + amr_data->enc_mode = cmr; + TRACE_((THIS_FILE, "AMR-NB encoder switched mode to %d (%dbps)", + amr_data->enc_mode, + pjmedia_codec_amrnb_bitrates[amr_data->enc_mode])); + } + + return PJ_SUCCESS; +} + + +/* + * Encode frame. + */ +static pj_status_t amr_codec_encode( pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output) +{ + struct amr_data *amr_data = (struct amr_data*) codec->codec_data; + unsigned char *bitstream; + pj_int16_t *speech; + unsigned nsamples, samples_per_frame; + enum {MAX_FRAMES_PER_PACKET = 16}; + pjmedia_frame frames[MAX_FRAMES_PER_PACKET]; + pj_uint8_t *p; + unsigned i, out_size = 0, nframes = 0; + pj_size_t payload_len; + unsigned dtx_cnt, sid_cnt; + pj_status_t status; + int size; + + pj_assert(amr_data != NULL); + PJ_ASSERT_RETURN(input && output, PJ_EINVAL); + + nsamples = input->size >> 1; + samples_per_frame = 160; + PJ_ASSERT_RETURN(nsamples % samples_per_frame == 0, + PJMEDIA_CODEC_EPCMFRMINLEN); + + nframes = nsamples / samples_per_frame; + PJ_ASSERT_RETURN(nframes <= MAX_FRAMES_PER_PACKET, + PJMEDIA_CODEC_EFRMTOOSHORT); + + /* Encode the frames */ + speech = (pj_int16_t*)input->buf; + bitstream = (unsigned char*)output->buf; + while (nsamples >= samples_per_frame) { + size = Encoder_Interface_Encode (amr_data->encoder, amr_data->enc_mode, + speech, bitstream, 0); + if (size == 0) { + output->size = 0; + output->buf = NULL; + output->type = PJMEDIA_FRAME_TYPE_NONE; + TRACE_((THIS_FILE, "AMR-NB encode() failed")); + return PJMEDIA_CODEC_EFAILED; + } + nsamples -= 160; + speech += samples_per_frame; + bitstream += size; + out_size += size; + TRACE_((THIS_FILE, "AMR-NB encode(): mode=%d, size=%d", + amr_data->enc_mode, out_size)); + } + + /* Pack payload */ + p = (pj_uint8_t*)output->buf + output_buf_len - out_size; + pj_memmove(p, output->buf, out_size); + dtx_cnt = sid_cnt = 0; + for (i = 0; i < nframes; ++i) { + pjmedia_codec_amr_bit_info *info = (pjmedia_codec_amr_bit_info*) + &frames[i].bit_info; + info->frame_type = (pj_uint8_t)((*p >> 3) & 0x0F); + info->good_quality = (pj_uint8_t)((*p >> 2) & 0x01); + info->mode = (pj_int8_t)amr_data->enc_mode; + info->start_bit = 0; + frames[i].buf = p + 1; + frames[i].size = (info->frame_type <= 8)? + pjmedia_codec_amrnb_framelen[info->frame_type] : 0; + p += frames[i].size + 1; + + /* Count the number of SID and DTX frames */ + if (info->frame_type == 15) /* DTX*/ + ++dtx_cnt; + else if (info->frame_type == 8) /* SID */ + ++sid_cnt; + } + + /* VA generates DTX frames as DTX+SID frames switching quickly and it + * seems that the SID frames occur too often (assuming the purpose is + * only for keeping NAT alive?). So let's modify the behavior a bit. + * Only an SID frame will be sent every PJMEDIA_CODEC_MAX_SILENCE_PERIOD + * milliseconds. + */ + if (sid_cnt + dtx_cnt == nframes) { + pj_int32_t dtx_duration; + + dtx_duration = pj_timestamp_diff32(&amr_data->last_tx, + &input->timestamp); + if (PJMEDIA_CODEC_MAX_SILENCE_PERIOD == -1 || + dtx_duration < PJMEDIA_CODEC_MAX_SILENCE_PERIOD*8000/1000) + { + output->size = 0; + output->type = PJMEDIA_FRAME_TYPE_NONE; + output->timestamp = input->timestamp; + return PJ_SUCCESS; + } + } + + payload_len = output_buf_len; + + status = pjmedia_codec_amr_pack(frames, nframes, &amr_data->enc_setting, + output->buf, &payload_len); + if (status != PJ_SUCCESS) { + output->size = 0; + output->buf = NULL; + output->type = PJMEDIA_FRAME_TYPE_NONE; + TRACE_((THIS_FILE, "Failed to pack AMR payload, status=%d", status)); + return status; + } + + output->size = payload_len; + output->type = PJMEDIA_FRAME_TYPE_AUDIO; + output->timestamp = input->timestamp; + + amr_data->last_tx = input->timestamp; + + return PJ_SUCCESS; +} + + +/* + * Decode frame. + */ +static pj_status_t amr_codec_decode( pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output) +{ + struct amr_data *amr_data = (struct amr_data*) codec->codec_data; + pjmedia_frame input_; + pjmedia_codec_amr_bit_info *info; + /* VA AMR-NB decoding buffer: AMR-NB max frame size + 1 byte header. */ + unsigned char bitstream[32]; + + pj_assert(amr_data != NULL); + PJ_ASSERT_RETURN(input && output, PJ_EINVAL); + + if (output_buf_len < 320) + return PJMEDIA_CODEC_EPCMTOOSHORT; + + input_.buf = &bitstream[1]; + input_.size = 31; /* AMR-NB max frame size */ + pjmedia_codec_amr_predecode(input, &amr_data->dec_setting, &input_); + info = (pjmedia_codec_amr_bit_info*)&input_.bit_info; + + /* VA AMRNB decoder requires frame info in the first byte. */ + bitstream[0] = (info->frame_type << 3) | (info->good_quality << 2); + + TRACE_((THIS_FILE, "AMR-NB decode(): mode=%d, ft=%d, size=%d", + info->mode, info->frame_type, input_.size)); + + /* Decode */ + Decoder_Interface_Decode(amr_data->decoder, bitstream, + (pj_int16_t*)output->buf, 0); + + output->size = 320; + output->type = PJMEDIA_FRAME_TYPE_AUDIO; + output->timestamp = input->timestamp; + +#if USE_PJMEDIA_PLC + if (amr_data->plc_enabled) + pjmedia_plc_save(amr_data->plc, (pj_int16_t*)output->buf); +#endif + + return PJ_SUCCESS; +} + + +/* + * Recover lost frame. + */ +#if USE_PJMEDIA_PLC +/* + * Recover lost frame. + */ +static pj_status_t amr_codec_recover( pjmedia_codec *codec, + unsigned output_buf_len, + struct pjmedia_frame *output) +{ + struct amr_data *amr_data = codec->codec_data; + + TRACE_((THIS_FILE, "amr_codec_recover")); + + PJ_ASSERT_RETURN(amr_data->plc_enabled, PJ_EINVALIDOP); + + PJ_ASSERT_RETURN(output_buf_len >= 320, PJMEDIA_CODEC_EPCMTOOSHORT); + + pjmedia_plc_generate(amr_data->plc, (pj_int16_t*)output->buf); + + output->size = 320; + output->type = PJMEDIA_FRAME_TYPE_AUDIO; + + return PJ_SUCCESS; +} +#endif + +#if defined(_MSC_VER) && PJMEDIA_AUTO_LINK_OPENCORE_AMR_LIBS +# if PJMEDIA_OPENCORE_AMR_BUILT_WITH_GCC +# pragma comment( lib, "libopencore-amrnb.a") +# else +# error Unsupported OpenCORE AMR library, fix here +# endif +#endif + +#endif diff --git a/pjmedia/src/pjmedia-codec/passthrough.c b/pjmedia/src/pjmedia-codec/passthrough.c new file mode 100644 index 0000000..d315993 --- /dev/null +++ b/pjmedia/src/pjmedia-codec/passthrough.c @@ -0,0 +1,1054 @@ +/* $Id: passthrough.c 4082 2012-04-24 13:09:14Z bennylp $ */ +/* + * 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-codec/passthrough.h> +#include <pjmedia-codec/amr_sdp_match.h> +#include <pjmedia/codec.h> +#include <pjmedia/errno.h> +#include <pjmedia/endpoint.h> +#include <pjmedia/port.h> +#include <pj/assert.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_PASSTHROUGH_CODECS != 0 + */ +#if defined(PJMEDIA_HAS_PASSTHROUGH_CODECS) && PJMEDIA_HAS_PASSTHROUGH_CODECS!=0 + +#define THIS_FILE "passthrough.c" + + +/* Prototypes for passthrough codecs factory */ +static pj_status_t test_alloc( pjmedia_codec_factory *factory, + const pjmedia_codec_info *id ); +static pj_status_t default_attr( pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec_param *attr ); +static pj_status_t enum_codecs( pjmedia_codec_factory *factory, + unsigned *count, + pjmedia_codec_info codecs[]); +static pj_status_t alloc_codec( pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec **p_codec); +static pj_status_t dealloc_codec( pjmedia_codec_factory *factory, + pjmedia_codec *codec ); + +/* Prototypes for passthrough codecs implementation. */ +static pj_status_t codec_init( pjmedia_codec *codec, + pj_pool_t *pool ); +static pj_status_t codec_open( pjmedia_codec *codec, + pjmedia_codec_param *attr ); +static pj_status_t codec_close( pjmedia_codec *codec ); +static pj_status_t codec_modify(pjmedia_codec *codec, + const pjmedia_codec_param *attr ); +static pj_status_t codec_parse( pjmedia_codec *codec, + void *pkt, + pj_size_t pkt_size, + const pj_timestamp *ts, + unsigned *frame_cnt, + pjmedia_frame frames[]); +static pj_status_t codec_encode( pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output); +static pj_status_t codec_decode( pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output); +static pj_status_t codec_recover( pjmedia_codec *codec, + unsigned output_buf_len, + struct pjmedia_frame *output); + +/* Definition for passthrough codecs operations. */ +static pjmedia_codec_op codec_op = +{ + &codec_init, + &codec_open, + &codec_close, + &codec_modify, + &codec_parse, + &codec_encode, + &codec_decode, + &codec_recover +}; + +/* Definition for passthrough codecs factory operations. */ +static pjmedia_codec_factory_op codec_factory_op = +{ + &test_alloc, + &default_attr, + &enum_codecs, + &alloc_codec, + &dealloc_codec, + &pjmedia_codec_passthrough_deinit +}; + +/* Passthrough codecs factory */ +static struct codec_factory { + pjmedia_codec_factory base; + pjmedia_endpt *endpt; + pj_pool_t *pool; + pj_mutex_t *mutex; +} codec_factory; + +/* Passthrough codecs private data. */ +typedef struct codec_private { + pj_pool_t *pool; /**< Pool for each instance. */ + int codec_idx; /**< Codec index. */ + void *codec_setting; /**< Specific codec setting. */ + pj_uint16_t avg_frame_size; /**< Average of frame size. */ + unsigned samples_per_frame; /**< Samples per frame, for iLBC + this can be 240 or 160, can + only be known after codec is + opened. */ +} codec_private_t; + + + +/* CUSTOM CALLBACKS */ + +/* Parse frames from a packet. Default behaviour of frame parsing is + * just separating frames based on calculating frame length derived + * from bitrate. Implement this callback when the default behaviour is + * unapplicable. + */ +typedef pj_status_t (*parse_cb)(codec_private_t *codec_data, void *pkt, + pj_size_t pkt_size, const pj_timestamp *ts, + unsigned *frame_cnt, pjmedia_frame frames[]); + +/* Pack frames into a packet. Default behaviour of packing frames is + * just stacking the frames with octet aligned without adding any + * payload header. Implement this callback when the default behaviour is + * unapplicable. + */ +typedef pj_status_t (*pack_cb)(codec_private_t *codec_data, + const struct pjmedia_frame_ext *input, + unsigned output_buf_len, + struct pjmedia_frame *output); + + +/* Custom callback implementations. */ +static pj_status_t parse_amr( codec_private_t *codec_data, void *pkt, + pj_size_t pkt_size, const pj_timestamp *ts, + unsigned *frame_cnt, pjmedia_frame frames[]); +static pj_status_t pack_amr ( codec_private_t *codec_data, + const struct pjmedia_frame_ext *input, + unsigned output_buf_len, + struct pjmedia_frame *output); + + +/* Passthrough codec implementation descriptions. */ +static struct codec_desc { + int enabled; /* Is this codec enabled? */ + const char *name; /* Codec name. */ + pj_uint8_t pt; /* Payload type. */ + pjmedia_format_id fmt_id; /* Source format. */ + unsigned clock_rate; /* Codec's clock rate. */ + unsigned channel_count; /* Codec's channel count. */ + unsigned samples_per_frame; /* Codec's samples count. */ + unsigned def_bitrate; /* Default bitrate of this codec. */ + unsigned max_bitrate; /* Maximum bitrate of this codec. */ + pj_uint8_t frm_per_pkt; /* Default num of frames per packet.*/ + pj_uint8_t vad; /* VAD enabled/disabled. */ + pj_uint8_t plc; /* PLC enabled/disabled. */ + parse_cb parse; /* Callback to parse bitstream. */ + pack_cb pack; /* Callback to pack bitstream. */ + pjmedia_codec_fmtp dec_fmtp; /* Decoder's fmtp params. */ +} + +codec_desc[] = +{ +# if PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR + {1, "AMR", PJMEDIA_RTP_PT_AMR, PJMEDIA_FORMAT_AMR, + 8000, 1, 160, + 7400, 12200, 2, 1, 1, + &parse_amr, &pack_amr + /*, {1, {{{"octet-align", 11}, {"1", 1}}} } */ + }, +# endif + +# if PJMEDIA_HAS_PASSTHROUGH_CODEC_G729 + {1, "G729", PJMEDIA_RTP_PT_G729, PJMEDIA_FORMAT_G729, + 8000, 1, 80, + 8000, 8000, 2, 1, 1 + }, +# endif + +# if PJMEDIA_HAS_PASSTHROUGH_CODEC_ILBC + {1, "iLBC", PJMEDIA_RTP_PT_ILBC, PJMEDIA_FORMAT_ILBC, + 8000, 1, 240, + 13333, 15200, 1, 1, 1, + NULL, NULL, + {1, {{{"mode", 4}, {"30", 2}}} } + }, +# endif + +# if PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMU + {1, "PCMU", PJMEDIA_RTP_PT_PCMU, PJMEDIA_FORMAT_PCMU, + 8000, 1, 80, + 64000, 64000, 2, 1, 1 + }, +# endif + +# if PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMA + {1, "PCMA", PJMEDIA_RTP_PT_PCMA, PJMEDIA_FORMAT_PCMA, + 8000, 1, 80, + 64000, 64000, 2, 1, 1 + }, +# endif +}; + + +#if PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR + +#include <pjmedia-codec/amr_helper.h> + +typedef struct amr_settings_t { + pjmedia_codec_amr_pack_setting enc_setting; + pjmedia_codec_amr_pack_setting dec_setting; + pj_int8_t enc_mode; +} amr_settings_t; + + +/* Pack AMR payload */ +static pj_status_t pack_amr ( codec_private_t *codec_data, + const struct pjmedia_frame_ext *input, + unsigned output_buf_len, + struct pjmedia_frame *output) +{ + enum {MAX_FRAMES_PER_PACKET = PJMEDIA_MAX_FRAME_DURATION_MS / 20}; + + pjmedia_frame frames[MAX_FRAMES_PER_PACKET]; + amr_settings_t* setting = (amr_settings_t*)codec_data->codec_setting; + pjmedia_codec_amr_pack_setting *enc_setting = &setting->enc_setting; + pj_uint8_t SID_FT; + unsigned i; + + pj_assert(input->subframe_cnt <= MAX_FRAMES_PER_PACKET); + + SID_FT = (pj_uint8_t)(enc_setting->amr_nb? 8 : 9); + + /* Get frames */ + for (i = 0; i < input->subframe_cnt; ++i) { + pjmedia_frame_ext_subframe *sf; + pjmedia_codec_amr_bit_info *info; + unsigned len; + + sf = pjmedia_frame_ext_get_subframe(input, i); + len = (sf->bitlen + 7) >> 3; + + info = (pjmedia_codec_amr_bit_info*) &frames[i].bit_info; + pj_bzero(info, sizeof(*info)); + + if (len == 0) { + /* DTX */ + info->frame_type = 15; + } else { + info->frame_type = pjmedia_codec_amr_get_mode2(enc_setting->amr_nb, + len); + } + info->good_quality = 1; + info->mode = setting->enc_mode; + if (info->frame_type == SID_FT) + info->STI = (sf->data[4] >> 4) & 1; + + frames[i].buf = sf->data; + frames[i].size = len; + } + + output->size = output_buf_len; + + return pjmedia_codec_amr_pack(frames, input->subframe_cnt, enc_setting, + output->buf, &output->size); +} + + +/* Parse AMR payload into frames. */ +static pj_status_t parse_amr(codec_private_t *codec_data, void *pkt, + pj_size_t pkt_size, const pj_timestamp *ts, + unsigned *frame_cnt, pjmedia_frame frames[]) +{ + amr_settings_t* s = (amr_settings_t*)codec_data->codec_setting; + pjmedia_codec_amr_pack_setting *setting; + pj_status_t status; + pj_uint8_t cmr; + + setting = &s->dec_setting; + + status = pjmedia_codec_amr_parse(pkt, pkt_size, ts, setting, frames, + frame_cnt, &cmr); + if (status != PJ_SUCCESS) + return status; + + // CMR is not supported for now. + /* Check Change Mode Request. */ + //if ((setting->amr_nb && cmr <= 7) || (!setting->amr_nb && cmr <= 8)) { + // s->enc_mode = cmr; + //} + + return PJ_SUCCESS; +} + +#endif /* PJMEDIA_HAS_PASSTROUGH_CODEC_AMR */ + + +/* + * Initialize and register passthrough codec factory to pjmedia endpoint. + */ +PJ_DEF(pj_status_t) pjmedia_codec_passthrough_init( pjmedia_endpt *endpt ) +{ + pjmedia_codec_mgr *codec_mgr; + pj_str_t codec_name; + pj_status_t status; + + if (codec_factory.pool != NULL) { + /* Already initialized. */ + return PJ_EEXISTS; + } + + /* Create passthrough codec factory. */ + codec_factory.base.op = &codec_factory_op; + codec_factory.base.factory_data = NULL; + codec_factory.endpt = endpt; + + codec_factory.pool = pjmedia_endpt_create_pool(endpt, "Passthrough codecs", + 4000, 4000); + if (!codec_factory.pool) + return PJ_ENOMEM; + + /* Create mutex. */ + status = pj_mutex_create_simple(codec_factory.pool, "Passthrough codecs", + &codec_factory.mutex); + if (status != PJ_SUCCESS) + goto on_error; + + /* Get the codec manager. */ + codec_mgr = pjmedia_endpt_get_codec_mgr(endpt); + if (!codec_mgr) { + status = PJ_EINVALIDOP; + goto on_error; + } + + /* Register format match callback. */ +#if PJMEDIA_HAS_PASSTROUGH_CODEC_AMR + pj_cstr(&codec_name, "AMR"); + status = pjmedia_sdp_neg_register_fmt_match_cb( + &codec_name, + &pjmedia_codec_amr_match_sdp); + if (status != PJ_SUCCESS) + goto on_error; +#endif + + /* Suppress compile warning */ + PJ_UNUSED_ARG(codec_name); + + /* Register codec factory to endpoint. */ + status = pjmedia_codec_mgr_register_factory(codec_mgr, + &codec_factory.base); + if (status != PJ_SUCCESS) + goto on_error; + + /* Done. */ + return PJ_SUCCESS; + +on_error: + pj_pool_release(codec_factory.pool); + codec_factory.pool = NULL; + return status; +} + +/* + * Initialize and register passthrough codec factory to pjmedia endpoint. + */ +PJ_DEF(pj_status_t) pjmedia_codec_passthrough_init2( + pjmedia_endpt *endpt, + const pjmedia_codec_passthrough_setting *setting) +{ + if (codec_factory.pool != NULL) { + /* Already initialized. */ + return PJ_EEXISTS; + } + + if (setting != NULL) { + unsigned i; + + /* Enable/disable codecs based on the specified encoding formats */ + for (i = 0; i < PJ_ARRAY_SIZE(codec_desc); ++i) { + pj_bool_t enabled = PJ_FALSE; + unsigned j; + + for (j = 0; j < setting->fmt_cnt && !enabled; ++j) { + if ((pj_uint32_t)codec_desc[i].fmt_id == setting->fmts[j].id) + enabled = PJ_TRUE; + } + + codec_desc[i].enabled = enabled; + } + +#if PJMEDIA_HAS_PASSTHROUGH_CODEC_ILBC + /* Update iLBC codec description based on default mode setting. */ + for (i = 0; i < PJ_ARRAY_SIZE(codec_desc); ++i) { + if (codec_desc[i].enabled && + codec_desc[i].fmt_id == PJMEDIA_FORMAT_ILBC) + { + codec_desc[i].samples_per_frame = + (setting->ilbc_mode == 20? 160 : 240); + codec_desc[i].def_bitrate = + (setting->ilbc_mode == 20? 15200 : 13333); + pj_strset2(&codec_desc[i].dec_fmtp.param[0].val, + (setting->ilbc_mode == 20? "20" : "30")); + break; + } + } +#endif + } + + return pjmedia_codec_passthrough_init(endpt); +} + +/* + * Unregister passthrough codecs factory from pjmedia endpoint. + */ +PJ_DEF(pj_status_t) pjmedia_codec_passthrough_deinit(void) +{ + pjmedia_codec_mgr *codec_mgr; + unsigned i; + pj_status_t status; + + if (codec_factory.pool == NULL) { + /* Already deinitialized */ + return PJ_SUCCESS; + } + + pj_mutex_lock(codec_factory.mutex); + + /* Get the codec manager. */ + codec_mgr = pjmedia_endpt_get_codec_mgr(codec_factory.endpt); + if (!codec_mgr) { + pj_pool_release(codec_factory.pool); + codec_factory.pool = NULL; + return PJ_EINVALIDOP; + } + + /* Unregister passthrough codecs factory. */ + status = pjmedia_codec_mgr_unregister_factory(codec_mgr, + &codec_factory.base); + + /* Destroy mutex. */ + pj_mutex_destroy(codec_factory.mutex); + + /* Destroy pool. */ + pj_pool_release(codec_factory.pool); + codec_factory.pool = NULL; + + /* Re-enable all codecs in the codec_desc. */ + for (i = 0; i < PJ_ARRAY_SIZE(codec_desc); ++i) { + codec_desc[i].enabled = PJ_TRUE; + } + + return status; +} + +/* + * Check if factory can allocate the specified codec. + */ +static pj_status_t test_alloc( pjmedia_codec_factory *factory, + const pjmedia_codec_info *info ) +{ + unsigned i; + + PJ_UNUSED_ARG(factory); + + /* Type MUST be audio. */ + if (info->type != PJMEDIA_TYPE_AUDIO) + return PJMEDIA_CODEC_EUNSUP; + + for (i = 0; i < PJ_ARRAY_SIZE(codec_desc); ++i) { + pj_str_t name = pj_str((char*)codec_desc[i].name); + if ((pj_stricmp(&info->encoding_name, &name) == 0) && + (info->clock_rate == (unsigned)codec_desc[i].clock_rate) && + (info->channel_cnt == (unsigned)codec_desc[i].channel_count) && + (codec_desc[i].enabled)) + { + return PJ_SUCCESS; + } + } + + /* Unsupported, or mode is disabled. */ + return PJMEDIA_CODEC_EUNSUP; +} + +/* + * Generate default attribute. + */ +static pj_status_t default_attr ( pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec_param *attr ) +{ + unsigned i; + + PJ_ASSERT_RETURN(factory==&codec_factory.base, PJ_EINVAL); + + pj_bzero(attr, sizeof(pjmedia_codec_param)); + + for (i = 0; i < PJ_ARRAY_SIZE(codec_desc); ++i) { + pj_str_t name = pj_str((char*)codec_desc[i].name); + if ((pj_stricmp(&id->encoding_name, &name) == 0) && + (id->clock_rate == (unsigned)codec_desc[i].clock_rate) && + (id->channel_cnt == (unsigned)codec_desc[i].channel_count) && + (id->pt == (unsigned)codec_desc[i].pt)) + { + attr->info.pt = (pj_uint8_t)id->pt; + attr->info.channel_cnt = codec_desc[i].channel_count; + attr->info.clock_rate = codec_desc[i].clock_rate; + attr->info.avg_bps = codec_desc[i].def_bitrate; + attr->info.max_bps = codec_desc[i].max_bitrate; + attr->info.pcm_bits_per_sample = 16; + attr->info.frm_ptime = (pj_uint16_t) + (codec_desc[i].samples_per_frame * 1000 / + codec_desc[i].channel_count / + codec_desc[i].clock_rate); + attr->info.fmt_id = codec_desc[i].fmt_id; + + /* Default flags. */ + attr->setting.frm_per_pkt = codec_desc[i].frm_per_pkt; + attr->setting.plc = codec_desc[i].plc; + attr->setting.penh= 0; + attr->setting.vad = codec_desc[i].vad; + attr->setting.cng = attr->setting.vad; + attr->setting.dec_fmtp = codec_desc[i].dec_fmtp; + + if (attr->setting.vad == 0) { +#if PJMEDIA_HAS_PASSTHROUGH_CODEC_G729 + if (id->pt == PJMEDIA_RTP_PT_G729) { + /* Signal G729 Annex B is being disabled */ + attr->setting.dec_fmtp.cnt = 1; + pj_strset2(&attr->setting.dec_fmtp.param[0].name, "annexb"); + pj_strset2(&attr->setting.dec_fmtp.param[0].val, "no"); + } +#endif + } + + return PJ_SUCCESS; + } + } + + return PJMEDIA_CODEC_EUNSUP; +} + +/* + * Enum codecs supported by this factory. + */ +static pj_status_t enum_codecs( pjmedia_codec_factory *factory, + unsigned *count, + pjmedia_codec_info codecs[]) +{ + unsigned max; + unsigned i; + + PJ_UNUSED_ARG(factory); + PJ_ASSERT_RETURN(codecs && *count > 0, PJ_EINVAL); + + max = *count; + + for (i = 0, *count = 0; i < PJ_ARRAY_SIZE(codec_desc) && *count < max; ++i) + { + if (!codec_desc[i].enabled) + continue; + + pj_bzero(&codecs[*count], sizeof(pjmedia_codec_info)); + codecs[*count].encoding_name = pj_str((char*)codec_desc[i].name); + codecs[*count].pt = codec_desc[i].pt; + codecs[*count].type = PJMEDIA_TYPE_AUDIO; + codecs[*count].clock_rate = codec_desc[i].clock_rate; + codecs[*count].channel_cnt = codec_desc[i].channel_count; + + ++*count; + } + + return PJ_SUCCESS; +} + +/* + * Allocate a new codec instance. + */ +static pj_status_t alloc_codec( pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec **p_codec) +{ + codec_private_t *codec_data; + pjmedia_codec *codec; + int idx; + pj_pool_t *pool; + unsigned i; + + PJ_ASSERT_RETURN(factory && id && p_codec, PJ_EINVAL); + PJ_ASSERT_RETURN(factory == &codec_factory.base, PJ_EINVAL); + + pj_mutex_lock(codec_factory.mutex); + + /* Find codec's index */ + idx = -1; + for (i = 0; i < PJ_ARRAY_SIZE(codec_desc); ++i) { + pj_str_t name = pj_str((char*)codec_desc[i].name); + if ((pj_stricmp(&id->encoding_name, &name) == 0) && + (id->clock_rate == (unsigned)codec_desc[i].clock_rate) && + (id->channel_cnt == (unsigned)codec_desc[i].channel_count) && + (codec_desc[i].enabled)) + { + idx = i; + break; + } + } + if (idx == -1) { + *p_codec = NULL; + return PJMEDIA_CODEC_EUNSUP; + } + + /* Create pool for codec instance */ + pool = pjmedia_endpt_create_pool(codec_factory.endpt, "passthroughcodec", + 512, 512); + codec = PJ_POOL_ZALLOC_T(pool, pjmedia_codec); + codec->op = &codec_op; + codec->factory = factory; + codec->codec_data = PJ_POOL_ZALLOC_T(pool, codec_private_t); + codec_data = (codec_private_t*) codec->codec_data; + codec_data->pool = pool; + codec_data->codec_idx = idx; + + pj_mutex_unlock(codec_factory.mutex); + + *p_codec = codec; + return PJ_SUCCESS; +} + +/* + * Free codec. + */ +static pj_status_t dealloc_codec( pjmedia_codec_factory *factory, + pjmedia_codec *codec ) +{ + codec_private_t *codec_data; + + PJ_ASSERT_RETURN(factory && codec, PJ_EINVAL); + PJ_ASSERT_RETURN(factory == &codec_factory.base, PJ_EINVAL); + + /* Close codec, if it's not closed. */ + codec_data = (codec_private_t*) codec->codec_data; + codec_close(codec); + + pj_pool_release(codec_data->pool); + + return PJ_SUCCESS; +} + +/* + * Init codec. + */ +static pj_status_t codec_init( pjmedia_codec *codec, + pj_pool_t *pool ) +{ + PJ_UNUSED_ARG(codec); + PJ_UNUSED_ARG(pool); + return PJ_SUCCESS; +} + +/* + * Open codec. + */ +static pj_status_t codec_open( pjmedia_codec *codec, + pjmedia_codec_param *attr ) +{ + codec_private_t *codec_data = (codec_private_t*) codec->codec_data; + struct codec_desc *desc = &codec_desc[codec_data->codec_idx]; + pj_pool_t *pool; + int i, j; + + pool = codec_data->pool; + + /* Cache samples per frame value */ + codec_data->samples_per_frame = desc->samples_per_frame; + + /* Calculate bitstream size */ + i = attr->info.avg_bps * codec_data->samples_per_frame; + j = desc->clock_rate << 3; + codec_data->avg_frame_size = (pj_uint16_t)(i / j); + if (i % j) ++codec_data->avg_frame_size; + +#if PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR + /* Init AMR settings */ + if (desc->pt == PJMEDIA_RTP_PT_AMR || desc->pt == PJMEDIA_RTP_PT_AMRWB) { + amr_settings_t *s; + pj_uint8_t octet_align = 0; + pj_int8_t enc_mode; + + enc_mode = pjmedia_codec_amr_get_mode(attr->info.avg_bps); + pj_assert(enc_mode >= 0 && enc_mode <= 8); + + for (i = 0; i < attr->setting.dec_fmtp.cnt; ++i) { + const pj_str_t STR_FMTP_OCTET_ALIGN = {"octet-align", 11}; + + /* Fetch octet-align setting. It should be fine to fetch only + * the decoder, since encoder & decoder must use the same setting + * (RFC 4867 section 8.3.1). + */ + if (pj_stricmp(&attr->setting.dec_fmtp.param[i].name, + &STR_FMTP_OCTET_ALIGN) == 0) + { + octet_align=(pj_uint8_t) + (pj_strtoul(&attr->setting.dec_fmtp.param[i].val)); + break; + } + } + + for (i = 0; i < attr->setting.enc_fmtp.cnt; ++i) { + const pj_str_t STR_FMTP_MODE_SET = {"mode-set", 8}; + + /* mode-set, encoding mode is chosen based on local default mode + * setting: + * - if local default mode is included in the mode-set, use it + * - otherwise, find the closest mode to local default mode; + * if there are two closest modes, prefer to use the higher + * one, e.g: local default mode is 4, the mode-set param + * contains '2,3,5,6', then 5 will be chosen. + */ + if (pj_stricmp(&attr->setting.enc_fmtp.param[i].name, + &STR_FMTP_MODE_SET) == 0) + { + const char *p; + pj_size_t l; + pj_int8_t diff = 99; + + p = pj_strbuf(&attr->setting.enc_fmtp.param[i].val); + l = pj_strlen(&attr->setting.enc_fmtp.param[i].val); + + while (l--) { + if ((desc->pt==PJMEDIA_RTP_PT_AMR && *p>='0' && *p<='7') || + (desc->pt==PJMEDIA_RTP_PT_AMRWB && *p>='0' && *p<='8')) + { + pj_int8_t tmp = (pj_int8_t)(*p - '0' - enc_mode); + + if (PJ_ABS(diff) > PJ_ABS(tmp) || + (PJ_ABS(diff) == PJ_ABS(tmp) && tmp > diff)) + { + diff = tmp; + if (diff == 0) break; + } + } + ++p; + } + + if (diff == 99) + return PJMEDIA_CODEC_EFAILED; + + enc_mode = (pj_int8_t)(enc_mode + diff); + + break; + } + } + + s = PJ_POOL_ZALLOC_T(pool, amr_settings_t); + codec_data->codec_setting = s; + + s->enc_mode = enc_mode; + if (s->enc_mode < 0) + return PJMEDIA_CODEC_EINMODE; + + s->enc_setting.amr_nb = (pj_uint8_t)(desc->pt == PJMEDIA_RTP_PT_AMR); + s->enc_setting.octet_aligned = octet_align; + s->enc_setting.reorder = PJ_FALSE; /* Note this! passthrough codec + doesn't do sensitivity bits + reordering */ + s->enc_setting.cmr = 15; + + s->dec_setting.amr_nb = (pj_uint8_t)(desc->pt == PJMEDIA_RTP_PT_AMR); + s->dec_setting.octet_aligned = octet_align; + s->dec_setting.reorder = PJ_FALSE; /* Note this! passthrough codec + doesn't do sensitivity bits + reordering */ + + /* Return back bitrate info to application */ + attr->info.avg_bps = s->enc_setting.amr_nb? + pjmedia_codec_amrnb_bitrates[s->enc_mode]: + pjmedia_codec_amrwb_bitrates[s->enc_mode]; + } +#endif + +#if PJMEDIA_HAS_PASSTHROUGH_CODEC_ILBC + /* Init iLBC settings */ + if (desc->pt == PJMEDIA_RTP_PT_ILBC) + { + enum { DEFAULT_MODE = 30 }; + static pj_str_t STR_MODE = {"mode", 4}; + pj_uint16_t dec_fmtp_mode = DEFAULT_MODE, + enc_fmtp_mode = DEFAULT_MODE; + + /* Get decoder mode */ + for (i = 0; i < attr->setting.dec_fmtp.cnt; ++i) { + if (pj_stricmp(&attr->setting.dec_fmtp.param[i].name, &STR_MODE) == 0) + { + dec_fmtp_mode = (pj_uint16_t) + pj_strtoul(&attr->setting.dec_fmtp.param[i].val); + break; + } + } + + /* Decoder mode must be set */ + PJ_ASSERT_RETURN(dec_fmtp_mode == 20 || dec_fmtp_mode == 30, + PJMEDIA_CODEC_EINMODE); + + /* Get encoder mode */ + for (i = 0; i < attr->setting.enc_fmtp.cnt; ++i) { + if (pj_stricmp(&attr->setting.enc_fmtp.param[i].name, &STR_MODE) == 0) + { + enc_fmtp_mode = (pj_uint16_t) + pj_strtoul(&attr->setting.enc_fmtp.param[i].val); + break; + } + } + + PJ_ASSERT_RETURN(enc_fmtp_mode==20 || enc_fmtp_mode==30, + PJMEDIA_CODEC_EINMODE); + + /* Both sides of a bi-directional session MUST use the same "mode" value. + * In this point, possible values are only 20 or 30, so when encoder and + * decoder modes are not same, just use the default mode, it is 30. + */ + if (enc_fmtp_mode != dec_fmtp_mode) { + enc_fmtp_mode = dec_fmtp_mode = DEFAULT_MODE; + PJ_LOG(4,(pool->obj_name, + "Normalized iLBC encoder and decoder modes to %d", + DEFAULT_MODE)); + } + + /* Update some attributes based on negotiated mode. */ + attr->info.avg_bps = (dec_fmtp_mode == 30? 13333 : 15200); + attr->info.frm_ptime = dec_fmtp_mode; + + /* Override average frame size */ + codec_data->avg_frame_size = (dec_fmtp_mode == 30? 50 : 38); + + /* Override samples per frame */ + codec_data->samples_per_frame = (dec_fmtp_mode == 30? 240 : 160); + } +#endif + + return PJ_SUCCESS; +} + +/* + * Close codec. + */ +static pj_status_t codec_close( pjmedia_codec *codec ) +{ + PJ_UNUSED_ARG(codec); + + return PJ_SUCCESS; +} + + +/* + * Modify codec settings. + */ +static pj_status_t codec_modify( pjmedia_codec *codec, + const pjmedia_codec_param *attr ) +{ + /* Not supported yet. */ + PJ_UNUSED_ARG(codec); + PJ_UNUSED_ARG(attr); + + return PJ_ENOTSUP; +} + +/* + * Get frames in the packet. + */ +static pj_status_t codec_parse( pjmedia_codec *codec, + void *pkt, + pj_size_t pkt_size, + const pj_timestamp *ts, + unsigned *frame_cnt, + pjmedia_frame frames[]) +{ + codec_private_t *codec_data = (codec_private_t*) codec->codec_data; + struct codec_desc *desc = &codec_desc[codec_data->codec_idx]; + unsigned count = 0; + + PJ_ASSERT_RETURN(frame_cnt, PJ_EINVAL); + + if (desc->parse != NULL) { + return desc->parse(codec_data, pkt, pkt_size, ts, frame_cnt, frames); + } + + while (pkt_size >= codec_data->avg_frame_size && count < *frame_cnt) { + frames[count].type = PJMEDIA_FRAME_TYPE_AUDIO; + frames[count].buf = pkt; + frames[count].size = codec_data->avg_frame_size; + frames[count].timestamp.u64 = ts->u64 + + count * codec_data->samples_per_frame; + + pkt = (pj_uint8_t*)pkt + codec_data->avg_frame_size; + pkt_size -= codec_data->avg_frame_size; + + ++count; + } + + if (pkt_size && count < *frame_cnt) { + frames[count].type = PJMEDIA_FRAME_TYPE_AUDIO; + frames[count].buf = pkt; + frames[count].size = pkt_size; + frames[count].timestamp.u64 = ts->u64 + + count * codec_data->samples_per_frame; + ++count; + } + + *frame_cnt = count; + return PJ_SUCCESS; +} + +/* + * Encode frames. + */ +static pj_status_t codec_encode( pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output) +{ + codec_private_t *codec_data = (codec_private_t*) codec->codec_data; + struct codec_desc *desc = &codec_desc[codec_data->codec_idx]; + const pjmedia_frame_ext *input_ = (const pjmedia_frame_ext*) input; + + pj_assert(input && input->type == PJMEDIA_FRAME_TYPE_EXTENDED); + + if (desc->pack != NULL) { + desc->pack(codec_data, input_, output_buf_len, output); + } else { + if (input_->subframe_cnt == 0) { + /* DTX */ + output->buf = NULL; + output->size = 0; + output->type = PJMEDIA_FRAME_TYPE_NONE; + } else { + unsigned i; + pj_uint8_t *p = output->buf; + + output->type = PJMEDIA_FRAME_TYPE_AUDIO; + output->size = 0; + + for (i = 0; i < input_->subframe_cnt; ++i) { + pjmedia_frame_ext_subframe *sf; + unsigned sf_len; + + sf = pjmedia_frame_ext_get_subframe(input_, i); + pj_assert(sf); + + sf_len = (sf->bitlen + 7) >> 3; + + pj_memcpy(p, sf->data, sf_len); + p += sf_len; + output->size += sf_len; + + /* If there is SID or DTX frame, break the loop. */ + if (desc->pt == PJMEDIA_RTP_PT_G729 && + sf_len < codec_data->avg_frame_size) + { + break; + } + + } + } + } + + output->timestamp = input->timestamp; + + return PJ_SUCCESS; +} + +/* + * Decode frame. + */ +static pj_status_t codec_decode( pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output) +{ + codec_private_t *codec_data = (codec_private_t*) codec->codec_data; +#if PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR + struct codec_desc *desc = &codec_desc[codec_data->codec_idx]; +#endif + pjmedia_frame_ext *output_ = (pjmedia_frame_ext*) output; + + pj_assert(input); + PJ_UNUSED_ARG(output_buf_len); + +#if PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR + /* Need to rearrange the AMR bitstream, since the bitstream may not be + * started from bit 0 or may need to be reordered from sensitivity order + * into encoder bits order. + */ + if (desc->pt == PJMEDIA_RTP_PT_AMR || desc->pt == PJMEDIA_RTP_PT_AMRWB) { + pjmedia_frame input_; + pjmedia_codec_amr_pack_setting *setting; + + setting = &((amr_settings_t*)codec_data->codec_setting)->dec_setting; + + input_ = *input; + pjmedia_codec_amr_predecode(input, setting, &input_); + + pjmedia_frame_ext_append_subframe(output_, input_.buf, + (pj_uint16_t)(input_.size << 3), + (pj_uint16_t)codec_data->samples_per_frame); + output->timestamp = input->timestamp; + + return PJ_SUCCESS; + } +#endif + + pjmedia_frame_ext_append_subframe(output_, input->buf, + (pj_uint16_t)(input->size << 3), + (pj_uint16_t)codec_data->samples_per_frame); + output->timestamp = input->timestamp; + + return PJ_SUCCESS; +} + +/* + * Recover lost frame. + */ +static pj_status_t codec_recover( pjmedia_codec *codec, + unsigned output_buf_len, + struct pjmedia_frame *output) +{ + codec_private_t *codec_data = (codec_private_t*) codec->codec_data; + pjmedia_frame_ext *output_ = (pjmedia_frame_ext*) output; + + PJ_UNUSED_ARG(output_buf_len); + + pjmedia_frame_ext_append_subframe(output_, NULL, 0, + (pj_uint16_t)codec_data->samples_per_frame); + + return PJ_SUCCESS; +} + +#endif /* PJMEDIA_HAS_PASSTHROUGH_CODECS */ + diff --git a/pjmedia/src/pjmedia-codec/speex_codec.c b/pjmedia/src/pjmedia-codec/speex_codec.c new file mode 100644 index 0000000..4623ef5 --- /dev/null +++ b/pjmedia/src/pjmedia-codec/speex_codec.c @@ -0,0 +1,997 @@ +/* $Id: speex_codec.c 3664 2011-07-19 03:42:28Z nanang $ */ +/* + * 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-codec/speex.h> +#include <pjmedia/codec.h> +#include <pjmedia/errno.h> +#include <pjmedia/endpoint.h> +#include <pjmedia/port.h> +#include <speex/speex.h> +#include <pj/assert.h> +#include <pj/log.h> +#include <pj/pool.h> +#include <pj/string.h> +#include <pj/os.h> + +/* + * Only build this file if PJMEDIA_HAS_SPEEX_CODEC != 0 + */ +#if defined(PJMEDIA_HAS_SPEEX_CODEC) && PJMEDIA_HAS_SPEEX_CODEC!=0 + + +#define THIS_FILE "speex_codec.c" + +/* Prototypes for Speex factory */ +static pj_status_t spx_test_alloc( pjmedia_codec_factory *factory, + const pjmedia_codec_info *id ); +static pj_status_t spx_default_attr( pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec_param *attr ); +static pj_status_t spx_enum_codecs( pjmedia_codec_factory *factory, + unsigned *count, + pjmedia_codec_info codecs[]); +static pj_status_t spx_alloc_codec( pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec **p_codec); +static pj_status_t spx_dealloc_codec( pjmedia_codec_factory *factory, + pjmedia_codec *codec ); + +/* Prototypes for Speex implementation. */ +static pj_status_t spx_codec_init( pjmedia_codec *codec, + pj_pool_t *pool ); +static pj_status_t spx_codec_open( pjmedia_codec *codec, + pjmedia_codec_param *attr ); +static pj_status_t spx_codec_close( pjmedia_codec *codec ); +static pj_status_t spx_codec_modify(pjmedia_codec *codec, + const pjmedia_codec_param *attr ); +static pj_status_t spx_codec_parse( pjmedia_codec *codec, + void *pkt, + pj_size_t pkt_size, + const pj_timestamp *ts, + unsigned *frame_cnt, + pjmedia_frame frames[]); +static pj_status_t spx_codec_encode( pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output); +static pj_status_t spx_codec_decode( pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output); +static pj_status_t spx_codec_recover(pjmedia_codec *codec, + unsigned output_buf_len, + struct pjmedia_frame *output); + +/* Definition for Speex codec operations. */ +static pjmedia_codec_op spx_op = +{ + &spx_codec_init, + &spx_codec_open, + &spx_codec_close, + &spx_codec_modify, + &spx_codec_parse, + &spx_codec_encode, + &spx_codec_decode, + &spx_codec_recover +}; + +/* Definition for Speex codec factory operations. */ +static pjmedia_codec_factory_op spx_factory_op = +{ + &spx_test_alloc, + &spx_default_attr, + &spx_enum_codecs, + &spx_alloc_codec, + &spx_dealloc_codec, + &pjmedia_codec_speex_deinit +}; + +/* Index to Speex parameter. */ +enum +{ + PARAM_NB, /* Index for narrowband parameter. */ + PARAM_WB, /* Index for wideband parameter. */ + PARAM_UWB, /* Index for ultra-wideband parameter */ +}; + +/* Speex default parameter */ +struct speex_param +{ + int enabled; /* Is this mode enabled? */ + const SpeexMode *mode; /* Speex mode. */ + int pt; /* Payload type. */ + unsigned clock_rate; /* Default sampling rate to be used.*/ + int quality; /* Default encoder quality. */ + int complexity; /* Default encoder complexity. */ + int samples_per_frame; /* Samples per frame. */ + int framesize; /* Frame size for current mode. */ + int bitrate; /* Bit rate for current mode. */ + int max_bitrate; /* Max bit rate for current mode. */ +}; + +/* Speex factory */ +static struct spx_factory +{ + pjmedia_codec_factory base; + pjmedia_endpt *endpt; + pj_pool_t *pool; + pj_mutex_t *mutex; + pjmedia_codec codec_list; + struct speex_param speex_param[3]; + +} spx_factory; + +/* Speex codec private data. */ +struct spx_private +{ + int param_id; /**< Index to speex param. */ + + void *enc; /**< Encoder state. */ + SpeexBits enc_bits; /**< Encoder bits. */ + void *dec; /**< Decoder state. */ + SpeexBits dec_bits; /**< Decoder bits. */ +}; + + +/* + * Get codec bitrate and frame size. + */ +static pj_status_t get_speex_info( struct speex_param *p ) +{ + void *state; + int tmp; + + /* Create temporary encoder */ + state = speex_encoder_init(p->mode); + if (!state) + return PJMEDIA_CODEC_EFAILED; + + /* Set the quality */ + if (p->quality != -1) + speex_encoder_ctl(state, SPEEX_SET_QUALITY, &p->quality); + + /* Sampling rate. */ + speex_encoder_ctl(state, SPEEX_SET_SAMPLING_RATE, &p->clock_rate); + + /* VAD off to have max bitrate */ + tmp = 0; + speex_encoder_ctl(state, SPEEX_SET_VAD, &tmp); + + /* Complexity. */ + if (p->complexity != -1) + speex_encoder_ctl(state, SPEEX_SET_COMPLEXITY, &p->complexity); + + /* Now get the frame size */ + speex_encoder_ctl(state, SPEEX_GET_FRAME_SIZE, &p->samples_per_frame); + + /* Now get the average bitrate */ + speex_encoder_ctl(state, SPEEX_GET_BITRATE, &p->bitrate); + + /* Calculate framesize. */ + p->framesize = p->bitrate * 20 / 1000; + + /* Now get the maximum bitrate by using maximum quality (=10) */ + tmp = 10; + speex_encoder_ctl(state, SPEEX_SET_QUALITY, &tmp); + speex_encoder_ctl(state, SPEEX_GET_BITRATE, &p->max_bitrate); + + /* Destroy encoder. */ + speex_encoder_destroy(state); + + return PJ_SUCCESS; +} + +/* + * Initialize and register Speex codec factory to pjmedia endpoint. + */ +PJ_DEF(pj_status_t) pjmedia_codec_speex_init( pjmedia_endpt *endpt, + unsigned options, + int quality, + int complexity ) +{ + pjmedia_codec_mgr *codec_mgr; + unsigned i; + pj_status_t status; + + if (spx_factory.pool != NULL) { + /* Already initialized. */ + return PJ_SUCCESS; + } + + /* Get defaults */ + if (quality < 0) quality = PJMEDIA_CODEC_SPEEX_DEFAULT_QUALITY; + if (complexity < 0) complexity = PJMEDIA_CODEC_SPEEX_DEFAULT_COMPLEXITY; + + /* Validate quality & complexity */ + PJ_ASSERT_RETURN(quality >= 0 && quality <= 10, PJ_EINVAL); + PJ_ASSERT_RETURN(complexity >= 1 && complexity <= 10, PJ_EINVAL); + + /* Create Speex codec factory. */ + spx_factory.base.op = &spx_factory_op; + spx_factory.base.factory_data = NULL; + spx_factory.endpt = endpt; + + spx_factory.pool = pjmedia_endpt_create_pool(endpt, "speex", + 4000, 4000); + if (!spx_factory.pool) + return PJ_ENOMEM; + + pj_list_init(&spx_factory.codec_list); + + /* Create mutex. */ + status = pj_mutex_create_simple(spx_factory.pool, "speex", + &spx_factory.mutex); + if (status != PJ_SUCCESS) + goto on_error; + + /* Initialize default Speex parameter. */ + spx_factory.speex_param[PARAM_NB].enabled = + ((options & PJMEDIA_SPEEX_NO_NB) == 0); + spx_factory.speex_param[PARAM_NB].pt = PJMEDIA_RTP_PT_SPEEX_NB; + spx_factory.speex_param[PARAM_NB].mode = speex_lib_get_mode(SPEEX_MODEID_NB); + spx_factory.speex_param[PARAM_NB].clock_rate = 8000; + spx_factory.speex_param[PARAM_NB].quality = quality; + spx_factory.speex_param[PARAM_NB].complexity = complexity; + + spx_factory.speex_param[PARAM_WB].enabled = + ((options & PJMEDIA_SPEEX_NO_WB) == 0); + spx_factory.speex_param[PARAM_WB].pt = PJMEDIA_RTP_PT_SPEEX_WB; + spx_factory.speex_param[PARAM_WB].mode = speex_lib_get_mode(SPEEX_MODEID_WB); + spx_factory.speex_param[PARAM_WB].clock_rate = 16000; + spx_factory.speex_param[PARAM_WB].quality = quality; + spx_factory.speex_param[PARAM_WB].complexity = complexity; + + spx_factory.speex_param[PARAM_UWB].enabled = + ((options & PJMEDIA_SPEEX_NO_UWB) == 0); + spx_factory.speex_param[PARAM_UWB].pt = PJMEDIA_RTP_PT_SPEEX_UWB; + spx_factory.speex_param[PARAM_UWB].mode = speex_lib_get_mode(SPEEX_MODEID_UWB); + spx_factory.speex_param[PARAM_UWB].clock_rate = 32000; + spx_factory.speex_param[PARAM_UWB].quality = quality; + spx_factory.speex_param[PARAM_UWB].complexity = complexity; + + /* Somehow quality <=4 is broken in linux. */ + if (quality <= 4 && quality >= 0) { + PJ_LOG(5,(THIS_FILE, "Adjusting quality to 5 for uwb")); + spx_factory.speex_param[PARAM_UWB].quality = 5; + } + + /* Get codec framesize and avg bitrate for each mode. */ + for (i=0; i<PJ_ARRAY_SIZE(spx_factory.speex_param); ++i) { + status = get_speex_info(&spx_factory.speex_param[i]); + } + + /* Get the codec manager. */ + codec_mgr = pjmedia_endpt_get_codec_mgr(endpt); + if (!codec_mgr) { + status = PJ_EINVALIDOP; + goto on_error; + } + + /* Register codec factory to endpoint. */ + status = pjmedia_codec_mgr_register_factory(codec_mgr, + &spx_factory.base); + if (status != PJ_SUCCESS) + goto on_error; + + /* Done. */ + return PJ_SUCCESS; + +on_error: + pj_pool_release(spx_factory.pool); + spx_factory.pool = NULL; + return status; +} + + +/* + * Initialize with default settings. + */ +PJ_DEF(pj_status_t) pjmedia_codec_speex_init_default(pjmedia_endpt *endpt) +{ + return pjmedia_codec_speex_init(endpt, 0, -1, -1); +} + +/* + * Change the settings of Speex codec. + */ +PJ_DEF(pj_status_t) pjmedia_codec_speex_set_param(unsigned clock_rate, + int quality, + int complexity) +{ + unsigned i; + + /* Get defaults */ + if (quality < 0) quality = PJMEDIA_CODEC_SPEEX_DEFAULT_QUALITY; + if (complexity < 0) complexity = PJMEDIA_CODEC_SPEEX_DEFAULT_COMPLEXITY; + + /* Validate quality & complexity */ + PJ_ASSERT_RETURN(quality >= 0 && quality <= 10, PJ_EINVAL); + PJ_ASSERT_RETURN(complexity >= 1 && complexity <= 10, PJ_EINVAL); + + /* Apply the settings */ + for (i=0; i<PJ_ARRAY_SIZE(spx_factory.speex_param); ++i) { + if (spx_factory.speex_param[i].clock_rate == clock_rate) { + pj_status_t status; + + spx_factory.speex_param[i].quality = quality; + spx_factory.speex_param[i].complexity = complexity; + + /* Somehow quality<=4 is broken in linux. */ + if (i == PARAM_UWB && quality <= 4 && quality >= 0) { + PJ_LOG(5,(THIS_FILE, "Adjusting quality to 5 for uwb")); + spx_factory.speex_param[PARAM_UWB].quality = 5; + } + + status = get_speex_info(&spx_factory.speex_param[i]); + + return status; + } + } + + return PJ_EINVAL; +} + +/* + * Unregister Speex codec factory from pjmedia endpoint and deinitialize + * the Speex codec library. + */ +PJ_DEF(pj_status_t) pjmedia_codec_speex_deinit(void) +{ + pjmedia_codec_mgr *codec_mgr; + pj_status_t status; + + if (spx_factory.pool == NULL) { + /* Already deinitialized */ + return PJ_SUCCESS; + } + + pj_mutex_lock(spx_factory.mutex); + + /* We don't want to deinit if there's outstanding codec. */ + /* This is silly, as we'll always have codec in the list if + we ever allocate a codec! A better behavior maybe is to + deallocate all codecs in the list. + if (!pj_list_empty(&spx_factory.codec_list)) { + pj_mutex_unlock(spx_factory.mutex); + return PJ_EBUSY; + } + */ + + /* Get the codec manager. */ + codec_mgr = pjmedia_endpt_get_codec_mgr(spx_factory.endpt); + if (!codec_mgr) { + pj_pool_release(spx_factory.pool); + spx_factory.pool = NULL; + return PJ_EINVALIDOP; + } + + /* Unregister Speex codec factory. */ + status = pjmedia_codec_mgr_unregister_factory(codec_mgr, + &spx_factory.base); + + /* Destroy mutex. */ + pj_mutex_destroy(spx_factory.mutex); + + /* Destroy pool. */ + pj_pool_release(spx_factory.pool); + spx_factory.pool = NULL; + + return status; +} + +/* + * Check if factory can allocate the specified codec. + */ +static pj_status_t spx_test_alloc( pjmedia_codec_factory *factory, + const pjmedia_codec_info *info ) +{ + const pj_str_t speex_tag = { "speex", 5}; + unsigned i; + + PJ_UNUSED_ARG(factory); + + /* Type MUST be audio. */ + if (info->type != PJMEDIA_TYPE_AUDIO) + return PJMEDIA_CODEC_EUNSUP; + + /* Check encoding name. */ + if (pj_stricmp(&info->encoding_name, &speex_tag) != 0) + return PJMEDIA_CODEC_EUNSUP; + + /* Check clock-rate */ + for (i=0; i<PJ_ARRAY_SIZE(spx_factory.speex_param); ++i) { + if (info->clock_rate == spx_factory.speex_param[i].clock_rate) { + /* Okay, let's Speex! */ + return PJ_SUCCESS; + } + } + + + /* Unsupported, or mode is disabled. */ + return PJMEDIA_CODEC_EUNSUP; +} + +/* + * Generate default attribute. + */ +static pj_status_t spx_default_attr (pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec_param *attr ) +{ + + PJ_ASSERT_RETURN(factory==&spx_factory.base, PJ_EINVAL); + + pj_bzero(attr, sizeof(pjmedia_codec_param)); + attr->info.pt = (pj_uint8_t)id->pt; + attr->info.channel_cnt = 1; + + if (id->clock_rate <= 8000) { + attr->info.clock_rate = spx_factory.speex_param[PARAM_NB].clock_rate; + attr->info.avg_bps = spx_factory.speex_param[PARAM_NB].bitrate; + attr->info.max_bps = spx_factory.speex_param[PARAM_NB].max_bitrate; + + } else if (id->clock_rate <= 16000) { + attr->info.clock_rate = spx_factory.speex_param[PARAM_WB].clock_rate; + attr->info.avg_bps = spx_factory.speex_param[PARAM_WB].bitrate; + attr->info.max_bps = spx_factory.speex_param[PARAM_WB].max_bitrate; + + } else { + /* Wow.. somebody is doing ultra-wideband. Cool...! */ + attr->info.clock_rate = spx_factory.speex_param[PARAM_UWB].clock_rate; + attr->info.avg_bps = spx_factory.speex_param[PARAM_UWB].bitrate; + attr->info.max_bps = spx_factory.speex_param[PARAM_UWB].max_bitrate; + } + + attr->info.pcm_bits_per_sample = 16; + attr->info.frm_ptime = 20; + attr->info.pt = (pj_uint8_t)id->pt; + + attr->setting.frm_per_pkt = 1; + + /* Default flags. */ + attr->setting.cng = 1; + attr->setting.plc = 1; + attr->setting.penh =1 ; + attr->setting.vad = 1; + + return PJ_SUCCESS; +} + +/* + * Enum codecs supported by this factory (i.e. only Speex!). + */ +static pj_status_t spx_enum_codecs(pjmedia_codec_factory *factory, + unsigned *count, + pjmedia_codec_info codecs[]) +{ + unsigned max; + int i; /* Must be signed */ + + PJ_UNUSED_ARG(factory); + PJ_ASSERT_RETURN(codecs && *count > 0, PJ_EINVAL); + + max = *count; + *count = 0; + + /* + * We return three codecs here, and in this order: + * - ultra-wideband, wideband, and narrowband. + */ + for (i=PJ_ARRAY_SIZE(spx_factory.speex_param)-1; i>=0 && *count<max; --i) { + + if (!spx_factory.speex_param[i].enabled) + continue; + + pj_bzero(&codecs[*count], sizeof(pjmedia_codec_info)); + codecs[*count].encoding_name = pj_str("speex"); + codecs[*count].pt = spx_factory.speex_param[i].pt; + codecs[*count].type = PJMEDIA_TYPE_AUDIO; + codecs[*count].clock_rate = spx_factory.speex_param[i].clock_rate; + codecs[*count].channel_cnt = 1; + + ++*count; + } + + return PJ_SUCCESS; +} + +/* + * Allocate a new Speex codec instance. + */ +static pj_status_t spx_alloc_codec( pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec **p_codec) +{ + pjmedia_codec *codec; + struct spx_private *spx; + + PJ_ASSERT_RETURN(factory && id && p_codec, PJ_EINVAL); + PJ_ASSERT_RETURN(factory == &spx_factory.base, PJ_EINVAL); + + + pj_mutex_lock(spx_factory.mutex); + + /* Get free nodes, if any. */ + if (!pj_list_empty(&spx_factory.codec_list)) { + codec = spx_factory.codec_list.next; + pj_list_erase(codec); + } else { + codec = PJ_POOL_ZALLOC_T(spx_factory.pool, pjmedia_codec); + PJ_ASSERT_RETURN(codec != NULL, PJ_ENOMEM); + codec->op = &spx_op; + codec->factory = factory; + codec->codec_data = pj_pool_alloc(spx_factory.pool, + sizeof(struct spx_private)); + } + + pj_mutex_unlock(spx_factory.mutex); + + spx = (struct spx_private*) codec->codec_data; + spx->enc = NULL; + spx->dec = NULL; + + if (id->clock_rate <= 8000) + spx->param_id = PARAM_NB; + else if (id->clock_rate <= 16000) + spx->param_id = PARAM_WB; + else + spx->param_id = PARAM_UWB; + + *p_codec = codec; + return PJ_SUCCESS; +} + +/* + * Free codec. + */ +static pj_status_t spx_dealloc_codec( pjmedia_codec_factory *factory, + pjmedia_codec *codec ) +{ + struct spx_private *spx; + + PJ_ASSERT_RETURN(factory && codec, PJ_EINVAL); + PJ_ASSERT_RETURN(factory == &spx_factory.base, PJ_EINVAL); + + /* Close codec, if it's not closed. */ + spx = (struct spx_private*) codec->codec_data; + if (spx->enc != NULL || spx->dec != NULL) { + spx_codec_close(codec); + } + + /* Put in the free list. */ + pj_mutex_lock(spx_factory.mutex); + pj_list_push_front(&spx_factory.codec_list, codec); + pj_mutex_unlock(spx_factory.mutex); + + return PJ_SUCCESS; +} + +/* + * Init codec. + */ +static pj_status_t spx_codec_init( pjmedia_codec *codec, + pj_pool_t *pool ) +{ + PJ_UNUSED_ARG(codec); + PJ_UNUSED_ARG(pool); + return PJ_SUCCESS; +} + +/* + * Open codec. + */ +static pj_status_t spx_codec_open( pjmedia_codec *codec, + pjmedia_codec_param *attr ) +{ + struct spx_private *spx; + int id, tmp; + + spx = (struct spx_private*) codec->codec_data; + id = spx->param_id; + + /* + * Create and initialize encoder. + */ + spx->enc = speex_encoder_init(spx_factory.speex_param[id].mode); + if (!spx->enc) + return PJMEDIA_CODEC_EFAILED; + speex_bits_init(&spx->enc_bits); + + /* Set the quality*/ + if (spx_factory.speex_param[id].quality != -1) { + speex_encoder_ctl(spx->enc, SPEEX_SET_QUALITY, + &spx_factory.speex_param[id].quality); + } + + /* Sampling rate. */ + tmp = attr->info.clock_rate; + speex_encoder_ctl(spx->enc, SPEEX_SET_SAMPLING_RATE, + &spx_factory.speex_param[id].clock_rate); + + /* VAD */ + tmp = (attr->setting.vad != 0); + speex_encoder_ctl(spx->enc, SPEEX_SET_VAD, &tmp); + speex_encoder_ctl(spx->enc, SPEEX_SET_DTX, &tmp); + + /* Complexity */ + if (spx_factory.speex_param[id].complexity != -1) { + speex_encoder_ctl(spx->enc, SPEEX_SET_COMPLEXITY, + &spx_factory.speex_param[id].complexity); + } + + /* + * Create and initialize decoder. + */ + spx->dec = speex_decoder_init(spx_factory.speex_param[id].mode); + if (!spx->dec) { + spx_codec_close(codec); + return PJMEDIA_CODEC_EFAILED; + } + speex_bits_init(&spx->dec_bits); + + /* Sampling rate. */ + speex_decoder_ctl(spx->dec, SPEEX_SET_SAMPLING_RATE, + &spx_factory.speex_param[id].clock_rate); + + /* PENH */ + tmp = attr->setting.penh; + speex_decoder_ctl(spx->dec, SPEEX_SET_ENH, &tmp); + + return PJ_SUCCESS; +} + +/* + * Close codec. + */ +static pj_status_t spx_codec_close( pjmedia_codec *codec ) +{ + struct spx_private *spx; + + spx = (struct spx_private*) codec->codec_data; + + /* Destroy encoder*/ + if (spx->enc) { + speex_encoder_destroy( spx->enc ); + spx->enc = NULL; + speex_bits_destroy( &spx->enc_bits ); + } + + /* Destroy decoder */ + if (spx->dec) { + speex_decoder_destroy( spx->dec); + spx->dec = NULL; + speex_bits_destroy( &spx->dec_bits ); + } + + return PJ_SUCCESS; +} + + +/* + * Modify codec settings. + */ +static pj_status_t spx_codec_modify(pjmedia_codec *codec, + const pjmedia_codec_param *attr ) +{ + struct spx_private *spx; + int tmp; + + spx = (struct spx_private*) codec->codec_data; + + /* VAD */ + tmp = (attr->setting.vad != 0); + speex_encoder_ctl(spx->enc, SPEEX_SET_VAD, &tmp); + speex_encoder_ctl(spx->enc, SPEEX_SET_DTX, &tmp); + + /* PENH */ + tmp = attr->setting.penh; + speex_decoder_ctl(spx->dec, SPEEX_SET_ENH, &tmp); + + return PJ_SUCCESS; +} + +#if 0 +# define TRACE__(args) PJ_LOG(5,args) +#else +# define TRACE__(args) +#endif + +#undef THIS_FUNC +#define THIS_FUNC "speex_get_next_frame" + +#define NB_SUBMODES 16 +#define NB_SUBMODE_BITS 4 + +#define SB_SUBMODES 8 +#define SB_SUBMODE_BITS 3 + +/* This function will iterate frames & submodes in the Speex bits. + * Returns 0 if a frame found, otherwise returns -1. + */ +int speex_get_next_frame(SpeexBits *bits) +{ + static const int inband_skip_table[NB_SUBMODES] = + {1, 1, 4, 4, 4, 4, 4, 4, 8, 8, 16, 16, 32, 32, 64, 64 }; + static const int wb_skip_table[SB_SUBMODES] = + {SB_SUBMODE_BITS+1, 36, 112, 192, 352, -1, -1, -1}; + + unsigned submode; + unsigned nb_count = 0; + + while (speex_bits_remaining(bits) >= 5) { + unsigned wb_count = 0; + unsigned bit_ptr = bits->bitPtr; + unsigned char_ptr = bits->charPtr; + + /* WB frame */ + while ((speex_bits_remaining(bits) >= 4) + && speex_bits_unpack_unsigned(bits, 1)) + { + int advance; + + submode = speex_bits_unpack_unsigned(bits, 3); + advance = wb_skip_table[submode]; + if (advance < 0) { + TRACE__((THIS_FUNC, "Invalid mode encountered. " + "The stream is corrupted.")); + return -1; + } + TRACE__((THIS_FUNC, "WB layer skipped: %d bits", advance)); + advance -= (SB_SUBMODE_BITS+1); + speex_bits_advance(bits, advance); + + bit_ptr = bits->bitPtr; + char_ptr = bits->charPtr; + + /* Consecutive subband frames may not exceed 2 frames */ + if (++wb_count > 2) + return -1; + } + + /* End of bits, return the frame */ + if (speex_bits_remaining(bits) < 4) { + TRACE__((THIS_FUNC, "End of stream")); + return 0; + } + + /* Stop iteration, return the frame */ + if (nb_count > 0) { + bits->bitPtr = bit_ptr; + bits->charPtr = char_ptr; + return 0; + } + + /* Get control bits */ + submode = speex_bits_unpack_unsigned(bits, 4); + TRACE__((THIS_FUNC, "Control bits: %d at %d", + submode, bits->charPtr*8+bits->bitPtr)); + + if (submode == 15) { + TRACE__((THIS_FUNC, "Found submode: terminator")); + return -1; + } else if (submode == 14) { + /* in-band signal; next 4 bits contain signal id */ + submode = speex_bits_unpack_unsigned(bits, 4); + TRACE__((THIS_FUNC, "Found submode: in-band %d bits", + inband_skip_table[submode])); + speex_bits_advance(bits, inband_skip_table[submode]); + } else if (submode == 13) { + /* user in-band; next 5 bits contain msg len */ + submode = speex_bits_unpack_unsigned(bits, 5); + TRACE__((THIS_FUNC, "Found submode: user-band %d bytes", submode)); + speex_bits_advance(bits, submode * 8); + } else if (submode > 8) { + TRACE__((THIS_FUNC, "Unknown sub-mode %d", submode)); + return -1; + } else { + /* NB frame */ + unsigned int advance = submode; + speex_mode_query(&speex_nb_mode, SPEEX_SUBMODE_BITS_PER_FRAME, &advance); + if (advance < 0) { + TRACE__((THIS_FUNC, "Invalid mode encountered. " + "The stream is corrupted.")); + return -1; + } + TRACE__((THIS_FUNC, "Submode %d: %d bits", submode, advance)); + advance -= (NB_SUBMODE_BITS+1); + speex_bits_advance(bits, advance); + + ++nb_count; + } + } + + return 0; +} + + +/* + * Get frames in the packet. + */ +static pj_status_t spx_codec_parse( pjmedia_codec *codec, + void *pkt, + pj_size_t pkt_size, + const pj_timestamp *ts, + unsigned *frame_cnt, + pjmedia_frame frames[]) +{ + struct spx_private *spx = (struct spx_private*) codec->codec_data; + unsigned samples_per_frame; + unsigned count = 0; + int char_ptr = 0; + int bit_ptr = 0; + + samples_per_frame=spx_factory.speex_param[spx->param_id].samples_per_frame; + + /* Copy the data into the speex bit-stream */ + speex_bits_read_from(&spx->dec_bits, (char*)pkt, pkt_size); + + while (speex_get_next_frame(&spx->dec_bits) == 0 && + spx->dec_bits.charPtr != char_ptr) + { + frames[count].buf = (char*)pkt + char_ptr; + /* Bit info contains start bit offset of the frame */ + frames[count].bit_info = bit_ptr; + frames[count].type = PJMEDIA_FRAME_TYPE_AUDIO; + frames[count].timestamp.u64 = ts->u64 + count * samples_per_frame; + frames[count].size = spx->dec_bits.charPtr - char_ptr; + if (spx->dec_bits.bitPtr) + ++frames[count].size; + + bit_ptr = spx->dec_bits.bitPtr; + char_ptr = spx->dec_bits.charPtr; + + ++count; + } + + *frame_cnt = count; + + return PJ_SUCCESS; +} + +/* + * Encode frames. + */ +static pj_status_t spx_codec_encode( pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output) +{ + struct spx_private *spx; + unsigned samples_per_frame; + int tx = 0; + spx_int16_t *pcm_in = (spx_int16_t*)input->buf; + unsigned nsamples; + + spx = (struct spx_private*) codec->codec_data; + + if (input->type != PJMEDIA_FRAME_TYPE_AUDIO) { + output->size = 0; + output->buf = NULL; + output->timestamp = input->timestamp; + output->type = input->type; + return PJ_SUCCESS; + } + + nsamples = input->size >> 1; + samples_per_frame=spx_factory.speex_param[spx->param_id].samples_per_frame; + + PJ_ASSERT_RETURN(nsamples % samples_per_frame == 0, + PJMEDIA_CODEC_EPCMFRMINLEN); + + /* Flush all the bits in the struct so we can encode a new frame */ + speex_bits_reset(&spx->enc_bits); + + /* Encode the frames */ + while (nsamples >= samples_per_frame) { + tx += speex_encode_int(spx->enc, pcm_in, &spx->enc_bits); + pcm_in += samples_per_frame; + nsamples -= samples_per_frame; + } + + /* Check if we need not to transmit the frame (DTX) */ + if (tx == 0) { + output->buf = NULL; + output->size = 0; + output->timestamp.u64 = input->timestamp.u64; + output->type = PJMEDIA_FRAME_TYPE_NONE; + return PJ_SUCCESS; + } + + /* Check size. */ + pj_assert(speex_bits_nbytes(&spx->enc_bits) <= (int)output_buf_len); + + /* Copy the bits to an array of char that can be written */ + output->size = speex_bits_write(&spx->enc_bits, + (char*)output->buf, output_buf_len); + output->type = PJMEDIA_FRAME_TYPE_AUDIO; + output->timestamp = input->timestamp; + + return PJ_SUCCESS; +} + +/* + * Decode frame. + */ +static pj_status_t spx_codec_decode( pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output) +{ + struct spx_private *spx; + unsigned samples_per_frame; + + spx = (struct spx_private*) codec->codec_data; + samples_per_frame=spx_factory.speex_param[spx->param_id].samples_per_frame; + + PJ_ASSERT_RETURN(output_buf_len >= samples_per_frame << 1, + PJMEDIA_CODEC_EPCMTOOSHORT); + + if (input->type != PJMEDIA_FRAME_TYPE_AUDIO) { + pjmedia_zero_samples((pj_int16_t*)output->buf, samples_per_frame); + output->size = samples_per_frame << 1; + output->timestamp.u64 = input->timestamp.u64; + output->type = PJMEDIA_FRAME_TYPE_AUDIO; + return PJ_SUCCESS; + } + + /* Copy the data into the bit-stream struct */ + speex_bits_read_from(&spx->dec_bits, (char*)input->buf, input->size); + + /* Set Speex dec_bits pointer to the start bit of the frame */ + speex_bits_advance(&spx->dec_bits, input->bit_info); + + /* Decode the data */ + speex_decode_int(spx->dec, &spx->dec_bits, (spx_int16_t*)output->buf); + + output->type = PJMEDIA_FRAME_TYPE_AUDIO; + output->size = samples_per_frame << 1; + output->timestamp.u64 = input->timestamp.u64; + + return PJ_SUCCESS; +} + +/* + * Recover lost frame. + */ +static pj_status_t spx_codec_recover(pjmedia_codec *codec, + unsigned output_buf_len, + struct pjmedia_frame *output) +{ + struct spx_private *spx; + unsigned count; + + /* output_buf_len is unreferenced when building in Release mode */ + PJ_UNUSED_ARG(output_buf_len); + + spx = (struct spx_private*) codec->codec_data; + + count = spx_factory.speex_param[spx->param_id].clock_rate * 20 / 1000; + pj_assert(count <= output_buf_len/2); + + /* Recover packet loss */ + speex_decode_int(spx->dec, NULL, (spx_int16_t*) output->buf); + + output->size = count * 2; + + return PJ_SUCCESS; +} + + +#endif /* PJMEDIA_HAS_SPEEX_CODEC */ |