From cd283c8825c9a94400f27735acb1c9385e90ffc8 Mon Sep 17 00:00:00 2001 From: Nanang Izzuddin Date: Tue, 19 Jul 2011 03:42:28 +0000 Subject: Re #1326: Initial code integration from branch 2.0-dev to trunk as "2.0-pre-alpha-svn". git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@3664 74dad513-b988-da41-8d7b-12977e46ad98 --- pjmedia/src/pjmedia-codec/audio_codecs.c | 112 ++ pjmedia/src/pjmedia-codec/ffmpeg_codecs.c | 1492 +++++++++++++++++++++++++++ pjmedia/src/pjmedia-codec/g722.c | 3 +- pjmedia/src/pjmedia-codec/g7221.c | 3 +- pjmedia/src/pjmedia-codec/gsm.c | 3 +- pjmedia/src/pjmedia-codec/h263_packetizer.c | 287 ++++++ pjmedia/src/pjmedia-codec/h264_packetizer.c | 530 ++++++++++ pjmedia/src/pjmedia-codec/ilbc.c | 3 +- pjmedia/src/pjmedia-codec/ipp_codecs.c | 3 +- pjmedia/src/pjmedia-codec/l16.c | 3 +- pjmedia/src/pjmedia-codec/passthrough.c | 3 +- pjmedia/src/pjmedia-codec/speex_codec.c | 3 +- 12 files changed, 2437 insertions(+), 8 deletions(-) create mode 100644 pjmedia/src/pjmedia-codec/audio_codecs.c create mode 100644 pjmedia/src/pjmedia-codec/ffmpeg_codecs.c create mode 100644 pjmedia/src/pjmedia-codec/h263_packetizer.c create mode 100644 pjmedia/src/pjmedia-codec/h264_packetizer.c (limited to 'pjmedia/src/pjmedia-codec') diff --git a/pjmedia/src/pjmedia-codec/audio_codecs.c b/pjmedia/src/pjmedia-codec/audio_codecs.c new file mode 100644 index 00000000..a9e0700a --- /dev/null +++ b/pjmedia/src/pjmedia-codec/audio_codecs.c @@ -0,0 +1,112 @@ +/* $Id$ */ +/* + * Copyright (C) 2011-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include + +PJ_DEF(void) pjmedia_audio_codec_config_default(pjmedia_audio_codec_config*cfg) +{ + pj_bzero(cfg, sizeof(*cfg)); + cfg->speex.option = 0; + cfg->speex.quality = PJMEDIA_CODEC_SPEEX_DEFAULT_QUALITY; + cfg->speex.complexity = PJMEDIA_CODEC_SPEEX_DEFAULT_COMPLEXITY; + cfg->ilbc.mode = 30; + cfg->passthrough.setting.ilbc_mode = cfg->ilbc.mode; +} + +PJ_DEF(pj_status_t) +pjmedia_codec_register_audio_codecs(pjmedia_endpt *endpt, + const pjmedia_audio_codec_config *c) +{ + pjmedia_audio_codec_config default_cfg; + pj_status_t status; + + PJ_ASSERT_RETURN(endpt, PJ_EINVAL); + if (!c) { + pjmedia_audio_codec_config_default(&default_cfg); + c = &default_cfg; + } + + PJ_ASSERT_RETURN(c->ilbc.mode==20 || c->ilbc.mode==30, PJ_EINVAL); + +#if PJMEDIA_HAS_PASSTHROUGH_CODECS + status = pjmedia_codec_passthrough_init2(endpt, &c->passthough.ilbc); + if (status != PJ_SUCCESS) + return status; +#endif + +#if PJMEDIA_HAS_SPEEX_CODEC + /* Register speex. */ + status = pjmedia_codec_speex_init(endpt, c->speex.option, + c->speex.quality, + c->speex.complexity); + if (status != PJ_SUCCESS) + return status; +#endif + +#if PJMEDIA_HAS_ILBC_CODEC + /* Register iLBC. */ + status = pjmedia_codec_ilbc_init( endpt, c->ilbc.mode); + if (status != PJ_SUCCESS) + return status; +#endif /* PJMEDIA_HAS_ILBC_CODEC */ + +#if PJMEDIA_HAS_GSM_CODEC + /* Register GSM */ + status = pjmedia_codec_gsm_init(endpt); + if (status != PJ_SUCCESS) + return status; +#endif /* PJMEDIA_HAS_GSM_CODEC */ + +#if PJMEDIA_HAS_G711_CODEC + /* Register PCMA and PCMU */ + status = pjmedia_codec_g711_init(endpt); + if (status != PJ_SUCCESS) + return status; +#endif /* PJMEDIA_HAS_G711_CODEC */ + +#if PJMEDIA_HAS_G722_CODEC + status = pjmedia_codec_g722_init(endpt ); + if (status != PJ_SUCCESS) + return status; +#endif /* PJMEDIA_HAS_G722_CODEC */ + +#if PJMEDIA_HAS_INTEL_IPP + /* Register IPP codecs */ + status = pjmedia_codec_ipp_init(endpt); + if (status != PJ_SUCCESS) + return status; +#endif /* PJMEDIA_HAS_INTEL_IPP */ + +#if PJMEDIA_HAS_G7221_CODEC + /* Register G722.1 codecs */ + status = pjmedia_codec_g7221_init(endpt); + if (status != PJ_SUCCESS) + return status; +#endif /* PJMEDIA_HAS_G7221_CODEC */ + +#if PJMEDIA_HAS_L16_CODEC + /* Register L16 family codecs */ + status = pjmedia_codec_l16_init(endpt, 0); + if (status != PJ_SUCCESS) + return status; +#endif /* PJMEDIA_HAS_L16_CODEC */ + + return PJ_SUCCESS; +} + diff --git a/pjmedia/src/pjmedia-codec/ffmpeg_codecs.c b/pjmedia/src/pjmedia-codec/ffmpeg_codecs.c new file mode 100644 index 00000000..ddf02c1a --- /dev/null +++ b/pjmedia/src/pjmedia-codec/ffmpeg_codecs.c @@ -0,0 +1,1492 @@ +/* $Id$ */ +/* + * Copyright (C) 2010 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* + * Only build this file if PJMEDIA_HAS_FFMPEG_CODEC != 0 + */ +#if defined(PJMEDIA_HAS_FFMPEG_CODEC) && PJMEDIA_HAS_FFMPEG_CODEC != 0 + +#define THIS_FILE "ffmpeg_codecs.c" + +#include "../pjmedia/ffmpeg_util.h" +#include +#include + + +/* Prototypes for FFMPEG codecs factory */ +static pj_status_t ffmpeg_test_alloc( pjmedia_vid_codec_factory *factory, + const pjmedia_vid_codec_info *id ); +static pj_status_t ffmpeg_default_attr( pjmedia_vid_codec_factory *factory, + const pjmedia_vid_codec_info *info, + pjmedia_vid_codec_param *attr ); +static pj_status_t ffmpeg_enum_codecs( pjmedia_vid_codec_factory *factory, + unsigned *count, + pjmedia_vid_codec_info codecs[]); +static pj_status_t ffmpeg_alloc_codec( pjmedia_vid_codec_factory *factory, + const pjmedia_vid_codec_info *info, + pjmedia_vid_codec **p_codec); +static pj_status_t ffmpeg_dealloc_codec( pjmedia_vid_codec_factory *factory, + pjmedia_vid_codec *codec ); + +/* Prototypes for FFMPEG codecs implementation. */ +static pj_status_t ffmpeg_codec_init( pjmedia_vid_codec *codec, + pj_pool_t *pool ); +static pj_status_t ffmpeg_codec_open( pjmedia_vid_codec *codec, + pjmedia_vid_codec_param *attr ); +static pj_status_t ffmpeg_codec_close( pjmedia_vid_codec *codec ); +static pj_status_t ffmpeg_codec_modify(pjmedia_vid_codec *codec, + const pjmedia_vid_codec_param *attr ); +static pj_status_t ffmpeg_codec_get_param(pjmedia_vid_codec *codec, + pjmedia_vid_codec_param *param); +static pj_status_t ffmpeg_packetize ( pjmedia_vid_codec *codec, + pj_uint8_t *buf, + pj_size_t buf_len, + unsigned *pos, + const pj_uint8_t **payload, + pj_size_t *payload_len); +static pj_status_t ffmpeg_unpacketize(pjmedia_vid_codec *codec, + const pj_uint8_t *payload, + pj_size_t payload_len, + pj_uint8_t *buf, + pj_size_t buf_len, + unsigned *pos); +static pj_status_t ffmpeg_codec_encode( pjmedia_vid_codec *codec, + const pjmedia_frame *input, + unsigned output_buf_len, + pjmedia_frame *output); +static pj_status_t ffmpeg_codec_decode( pjmedia_vid_codec *codec, + const pjmedia_frame *input, + unsigned output_buf_len, + pjmedia_frame *output); +static pj_status_t ffmpeg_codec_recover( pjmedia_vid_codec *codec, + unsigned output_buf_len, + pjmedia_frame *output); + +/* Definition for FFMPEG codecs operations. */ +static pjmedia_vid_codec_op ffmpeg_op = +{ + &ffmpeg_codec_init, + &ffmpeg_codec_open, + &ffmpeg_codec_close, + &ffmpeg_codec_modify, + &ffmpeg_codec_get_param, + &ffmpeg_packetize, + &ffmpeg_unpacketize, + &ffmpeg_codec_encode, + &ffmpeg_codec_decode, + NULL //&ffmpeg_codec_recover +}; + +/* Definition for FFMPEG codecs factory operations. */ +static pjmedia_vid_codec_factory_op ffmpeg_factory_op = +{ + &ffmpeg_test_alloc, + &ffmpeg_default_attr, + &ffmpeg_enum_codecs, + &ffmpeg_alloc_codec, + &ffmpeg_dealloc_codec +}; + + +/* FFMPEG codecs factory */ +static struct ffmpeg_factory { + pjmedia_vid_codec_factory base; + pjmedia_vid_codec_mgr *mgr; + pj_pool_factory *pf; + pj_pool_t *pool; + pj_mutex_t *mutex; +} ffmpeg_factory; + + +typedef struct ffmpeg_codec_desc ffmpeg_codec_desc; + + +/* FFMPEG codecs private data. */ +typedef struct ffmpeg_private +{ + const ffmpeg_codec_desc *desc; + pjmedia_vid_codec_param param; /**< Codec param */ + pj_pool_t *pool; /**< Pool for each instance */ + pj_timestamp last_tx; /**< Timestamp of last + transmit */ + + /* Format info and apply format param */ + const pjmedia_video_format_info *enc_vfi; + pjmedia_video_apply_fmt_param enc_vafp; + const pjmedia_video_format_info *dec_vfi; + pjmedia_video_apply_fmt_param dec_vafp; + + /* The ffmpeg codec states. */ + AVCodec *enc; + AVCodec *dec; + AVCodecContext *enc_ctx; + AVCodecContext *dec_ctx; + + /* The ffmpeg decoder cannot set the output format, so format conversion + * may be needed for post-decoding. + */ + enum PixelFormat expected_dec_fmt; + /**< Expected output format of + ffmpeg decoder */ + + void *data; /**< Codec specific data */ +} ffmpeg_private; + + +/* Shortcuts for packetize & unpacketize function declaration, + * as it has long params and is reused many times! + */ +#define FUNC_PACKETIZE(name) \ + pj_status_t(name)(ffmpeg_private *ff, pj_uint8_t *bits, \ + pj_size_t bits_len, unsigned *bits_pos, \ + const pj_uint8_t **payload, pj_size_t *payload_len) + +#define FUNC_UNPACKETIZE(name) \ + pj_status_t(name)(ffmpeg_private *ff, const pj_uint8_t *payload, \ + pj_size_t payload_len, pj_uint8_t *bits, \ + pj_size_t bits_len, unsigned *bits_pos) + +#define FUNC_FMT_MATCH(name) \ + pj_status_t(name)(pj_pool_t *pool, \ + pjmedia_sdp_media *offer, unsigned o_fmt_idx, \ + pjmedia_sdp_media *answer, unsigned a_fmt_idx, \ + unsigned option) + + +/* Type definition of codec specific functions */ +typedef FUNC_PACKETIZE(*func_packetize); +typedef FUNC_UNPACKETIZE(*func_unpacketize); +typedef pj_status_t (*func_preopen) (ffmpeg_private *ff); +typedef pj_status_t (*func_postopen) (ffmpeg_private *ff); +typedef FUNC_FMT_MATCH(*func_sdp_fmt_match); + + +/* FFMPEG codec info */ +struct ffmpeg_codec_desc +{ + /* Predefined info */ + pjmedia_vid_codec_info info; + pjmedia_format_id base_fmt_id; /**< Some codecs may be exactly + same or compatible with + another codec, base format + will tell the initializer + to copy this codec desc + from its base format */ + pj_uint32_t avg_bps; + pj_uint32_t max_bps; + func_packetize packetize; + func_unpacketize unpacketize; + func_preopen preopen; + func_preopen postopen; + func_sdp_fmt_match sdp_fmt_match; + pjmedia_codec_fmtp dec_fmtp; + + /* Init time defined info */ + pj_bool_t enabled; + AVCodec *enc; + AVCodec *dec; +}; + + +/* H264 constants */ +#define PROFILE_H264_BASELINE 66 +#define PROFILE_H264_MAIN 77 + +/* Codec specific functions */ +static pj_status_t h264_preopen(ffmpeg_private *ff); +static pj_status_t h264_postopen(ffmpeg_private *ff); +static pj_status_t h263_preopen(ffmpeg_private *ff); +static FUNC_PACKETIZE(h264_packetize); +static FUNC_UNPACKETIZE(h264_unpacketize); +static FUNC_PACKETIZE(h263_packetize); +static FUNC_UNPACKETIZE(h263_unpacketize); + + +/* Internal codec info */ +ffmpeg_codec_desc codec_desc[] = +{ + { + {PJMEDIA_FORMAT_H264, PJMEDIA_RTP_PT_H264, {"H264",4}, + {"Constrained Baseline (level=30, pack=1)", 39}}, + 0, 128000, 1000000, + &h264_packetize, &h264_unpacketize, &h264_preopen, &h264_postopen, + &pjmedia_vid_codec_h264_match_sdp, + /* Leading space for better compatibility (strange indeed!) */ + {2, { {{"profile-level-id",16}, {"42e01e",6}}, + {{" packetization-mode",19}, {"1",1}}, } }, + }, + { + {PJMEDIA_FORMAT_H264, PJMEDIA_RTP_PT_H264_RSV1, {"H264",4}, + {"Baseline (level=30, pack=1)", 27}}, + PJMEDIA_FORMAT_H264, 128000, 1000000, + &h264_packetize, &h264_unpacketize, &h264_preopen, &h264_postopen, + &pjmedia_vid_codec_h264_match_sdp, + {2, { {{"profile-level-id",16}, {"42001e",6}}, + {{" packetization-mode",19}, {"1",1}}, } }, + }, + { + {PJMEDIA_FORMAT_H263P, PJMEDIA_RTP_PT_H263P, {"H263-1998",9}}, + PJMEDIA_FORMAT_H263, 1000000, 2000000, + &h263_packetize, &h263_unpacketize, &h263_preopen, NULL, NULL, + {2, { {{"CIF",3}, {"1",1}}, + {{"QCIF",4}, {"1",1}}, } }, + }, + { + {PJMEDIA_FORMAT_H263, PJMEDIA_RTP_PT_H263, {"H263",4}}, + PJMEDIA_FORMAT_H263, 1000000, 2000000, + &h263_packetize, &h263_unpacketize, &h263_preopen, NULL, NULL, + {2, { {{"CIF",3}, {"1",1}}, + {{"QCIF",4}, {"1",1}}, } }, + }, + { + {PJMEDIA_FORMAT_H263, PJMEDIA_RTP_PT_H263, {"H263",4}}, + }, + { + {PJMEDIA_FORMAT_H261, PJMEDIA_RTP_PT_H261, {"H261",4}}, + }, + { + {PJMEDIA_FORMAT_MJPEG, PJMEDIA_RTP_PT_JPEG, {"JPEG",4}}, + }, + { + {PJMEDIA_FORMAT_MPEG4, 0, {"MP4V",4}}, + }, + { + {PJMEDIA_FORMAT_XVID, 0, {"XVID",4}}, + PJMEDIA_FORMAT_MPEG4, + }, +}; + + +typedef struct h264_data +{ + pjmedia_vid_codec_h264_fmtp fmtp; + pjmedia_h264_packetizer *pktz; +} h264_data; + + +static pj_status_t h264_preopen(ffmpeg_private *ff) +{ + h264_data *data; + pjmedia_h264_packetizer_cfg pktz_cfg; + pj_status_t status; + + data = PJ_POOL_ZALLOC_T(ff->pool, h264_data); + ff->data = data; + + /* Parse remote fmtp */ + status = pjmedia_vid_codec_h264_parse_fmtp(&ff->param.enc_fmtp, + &data->fmtp); + if (status != PJ_SUCCESS) + return status; + + /* Create packetizer */ + pktz_cfg.mtu = ff->param.enc_mtu; +#if 0 + if (data->fmtp.packetization_mode == 0) + pktz_cfg.mode = PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL; + else if (data->fmtp.packetization_mode == 1) + pktz_cfg.mode = PJMEDIA_H264_PACKETIZER_MODE_NON_INTERLEAVED; + else + return PJ_ENOTSUP; +#else + if (data->fmtp.packetization_mode!= + PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL && + data->fmtp.packetization_mode!= + PJMEDIA_H264_PACKETIZER_MODE_NON_INTERLEAVED) + { + return PJ_ENOTSUP; + } + /* Better always send in single NAL mode for better compatibility */ + pktz_cfg.mode = PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL; +#endif + + status = pjmedia_h264_packetizer_create(ff->pool, &pktz_cfg, &data->pktz); + if (status != PJ_SUCCESS) + return status; + + /* Apply SDP fmtp to format in codec param */ + status = pjmedia_vid_codec_h264_apply_fmtp(&ff->param); + if (status != PJ_SUCCESS) + return status; + + if (ff->param.dir & PJMEDIA_DIR_ENCODING) { + AVCodecContext *ctx = ff->enc_ctx; + + /* Apply profile. Note that, for x264 backend, ffmpeg doesn't seem to + * use this profile param field, so let's try to apply it "manually". + */ + ctx->profile = data->fmtp.profile_idc; + if (ctx->profile == PROFILE_H264_BASELINE) { + /* Baseline profile settings (the most used profile in + * conversational/real-time communications). + */ + ctx->coder_type = FF_CODER_TYPE_VLC; + ctx->max_b_frames = 0; + ctx->flags2 &= ~(CODEC_FLAG2_WPRED | CODEC_FLAG2_8X8DCT); + ctx->weighted_p_pred = 0; + } else if (ctx->profile == PROFILE_H264_MAIN) { + ctx->flags2 &= ~CODEC_FLAG2_8X8DCT; + } + + /* Apply profile constraint bits. */ + // The x264 doesn't seem to support non-constrained (baseline) profile + // so this shouldn't be a problem (for now). + //PJ_TODO(set_h264_constraint_bits_properly_in_ffmpeg); + if (data->fmtp.profile_iop) { +#if defined(FF_PROFILE_H264_CONSTRAINED) + ctx->profile |= FF_PROFILE_H264_CONSTRAINED; +#endif + } + + /* Apply profile level. */ + ctx->level = data->fmtp.level; + + /* Libx264 rejects the "broken" ffmpeg defaults, so just change some */ + ctx->me_range = 16; + ctx->max_qdiff = 4; + ctx->qmin = 20; + ctx->qmax = 32; + ctx->qcompress = 0.6f; + + ctx->rtp_payload_size = ff->param.enc_mtu; + } + + if (ff->param.dir & PJMEDIA_DIR_DECODING) { + AVCodecContext *ctx = ff->dec_ctx; + + /* Apply the "sprop-parameter-sets" fmtp from remote SDP to + * extradata of ffmpeg codec context. + */ + if (data->fmtp.sprop_param_sets_len) { + ctx->extradata_size = data->fmtp.sprop_param_sets_len; + ctx->extradata = data->fmtp.sprop_param_sets; + } + } + + return PJ_SUCCESS; +} + +static pj_status_t h264_postopen(ffmpeg_private *ff) +{ + h264_data *data = (h264_data*)ff->data; + PJ_UNUSED_ARG(data); + return PJ_SUCCESS; +} + + +static FUNC_PACKETIZE(h264_packetize) +{ + h264_data *data = (h264_data*)ff->data; + return pjmedia_h264_packetize(data->pktz, bits, bits_len, bits_pos, + payload, payload_len); +} + +static FUNC_UNPACKETIZE(h264_unpacketize) +{ + h264_data *data = (h264_data*)ff->data; + return pjmedia_h264_unpacketize(data->pktz, payload, payload_len, + bits, bits_len, bits_pos); +} + + +typedef struct h263_data +{ + pjmedia_h263_packetizer *pktz; +} h263_data; + +/* H263 pre-open */ +static pj_status_t h263_preopen(ffmpeg_private *ff) +{ + h263_data *data; + pjmedia_h263_packetizer_cfg pktz_cfg; + pj_status_t status; + + data = PJ_POOL_ZALLOC_T(ff->pool, h263_data); + ff->data = data; + + /* Create packetizer */ + pktz_cfg.mtu = ff->param.enc_mtu; + pktz_cfg.mode = PJMEDIA_H263_PACKETIZER_MODE_RFC4629; + status = pjmedia_h263_packetizer_create(ff->pool, &pktz_cfg, &data->pktz); + if (status != PJ_SUCCESS) + return status; + + /* Apply fmtp settings to codec param */ + status = pjmedia_vid_codec_h263_apply_fmtp(&ff->param); + + return status; +} + +static FUNC_PACKETIZE(h263_packetize) +{ + h263_data *data = (h263_data*)ff->data; + return pjmedia_h263_packetize(data->pktz, bits, bits_len, bits_pos, + payload, payload_len); +} + +static FUNC_UNPACKETIZE(h263_unpacketize) +{ + h263_data *data = (h263_data*)ff->data; + return pjmedia_h263_unpacketize(data->pktz, payload, payload_len, + bits, bits_len, bits_pos); +} + + +static const ffmpeg_codec_desc* find_codec_desc_by_info( + const pjmedia_vid_codec_info *info) +{ + int i; + + for (i=0; ienabled && + (desc->info.fmt_id == info->fmt_id) && + ((desc->info.dir & info->dir) == info->dir) && + (desc->info.pt == info->pt)) + { + return desc; + } + } + + return NULL; +} + + +static int find_codec_idx_by_fmt_id(pjmedia_format_id fmt_id) +{ + int i; + for (i=0; itype != AVMEDIA_TYPE_VIDEO) + continue; + + /* Video encoder and decoder are usually implemented in separate + * AVCodec instances. While the codec attributes (e.g: raw formats, + * supported fps) are in the encoder. + */ + + //PJ_LOG(3, (THIS_FILE, "%s", c->name)); + status = CodecID_to_pjmedia_format_id(c->id, &fmt_id); + /* Skip if format ID is unknown */ + if (status != PJ_SUCCESS) + continue; + + codec_info_idx = find_codec_idx_by_fmt_id(fmt_id); + /* Skip if codec is unwanted by this wrapper (not listed in + * the codec info array) + */ + if (codec_info_idx < 0) + continue; + + desc = &codec_desc[codec_info_idx]; + + /* Skip duplicated codec implementation */ + if ((c->encode && (desc->info.dir & PJMEDIA_DIR_ENCODING)) || + (c->decode && (desc->info.dir & PJMEDIA_DIR_DECODING))) + { + continue; + } + + /* Get raw/decoded format ids in the encoder */ + if (c->pix_fmts && c->encode) { + pjmedia_format_id raw_fmt[PJMEDIA_VID_CODEC_MAX_DEC_FMT_CNT]; + unsigned raw_fmt_cnt = 0; + unsigned raw_fmt_cnt_should_be = 0; + const enum PixelFormat *p = c->pix_fmts; + + for(;(p && *p != -1) && + (raw_fmt_cnt < PJMEDIA_VID_CODEC_MAX_DEC_FMT_CNT); + ++p) + { + pjmedia_format_id fmt_id; + + raw_fmt_cnt_should_be++; + status = PixelFormat_to_pjmedia_format_id(*p, &fmt_id); + if (status != PJ_SUCCESS) { + PJ_LOG(6, (THIS_FILE, "Unrecognized ffmpeg pixel " + "format %d", *p)); + continue; + } + raw_fmt[raw_fmt_cnt++] = fmt_id; + } + + if (raw_fmt_cnt == 0) { + PJ_LOG(5, (THIS_FILE, "No recognized raw format " + "for codec [%s/%s], codec ignored", + c->name, c->long_name)); + /* Skip this encoder */ + continue; + } + + if (raw_fmt_cnt < raw_fmt_cnt_should_be) { + PJ_LOG(6, (THIS_FILE, "Codec [%s/%s] have %d raw formats, " + "recognized only %d raw formats", + c->name, c->long_name, + raw_fmt_cnt_should_be, raw_fmt_cnt)); + } + + desc->info.dec_fmt_id_cnt = raw_fmt_cnt; + pj_memcpy(desc->info.dec_fmt_id, raw_fmt, + sizeof(raw_fmt[0])*raw_fmt_cnt); + } + + /* Get supported framerates */ + if (c->supported_framerates) { + const AVRational *fr = c->supported_framerates; + while ((fr->num != 0 || fr->den != 0) && + desc->info.fps_cnt < PJMEDIA_VID_CODEC_MAX_FPS_CNT) + { + desc->info.fps[desc->info.fps_cnt].num = fr->num; + desc->info.fps[desc->info.fps_cnt].denum = fr->den; + ++desc->info.fps_cnt; + ++fr; + } + } + + /* Get ffmpeg encoder instance */ + if (c->encode && !desc->enc) { + desc->info.dir |= PJMEDIA_DIR_ENCODING; + desc->enc = c; + } + + /* Get ffmpeg decoder instance */ + if (c->decode && !desc->dec) { + desc->info.dir |= PJMEDIA_DIR_DECODING; + desc->dec = c; + } + + /* Enable this codec when any ffmpeg codec instance are recognized + * and the supported raw formats info has been collected. + */ + if ((desc->dec || desc->enc) && desc->info.dec_fmt_id_cnt) + { + desc->enabled = PJ_TRUE; + } + + /* Normalize default value of clock rate */ + if (desc->info.clock_rate == 0) + desc->info.clock_rate = 90000; + + /* Set RTP packetization support flag in the codec info */ + desc->info.has_rtp_pack = (desc->packetize != NULL) && + (desc->unpacketize != NULL); + } + + /* Review all codecs for applying base format, registering format match for + * SDP negotiation, etc. + */ + for (i = 0; i < PJ_ARRAY_SIZE(codec_desc); ++i) { + ffmpeg_codec_desc *desc = &codec_desc[i]; + + /* Init encoder/decoder description from base format */ + if (desc->base_fmt_id && (!desc->dec || !desc->enc)) { + ffmpeg_codec_desc *base_desc = NULL; + int base_desc_idx; + pjmedia_dir copied_dir = PJMEDIA_DIR_NONE; + + base_desc_idx = find_codec_idx_by_fmt_id(desc->base_fmt_id); + if (base_desc_idx != -1) + base_desc = &codec_desc[base_desc_idx]; + if (!base_desc || !base_desc->enabled) + continue; + + /* Copy description from base codec */ + if (!desc->info.dec_fmt_id_cnt) { + desc->info.dec_fmt_id_cnt = base_desc->info.dec_fmt_id_cnt; + pj_memcpy(desc->info.dec_fmt_id, base_desc->info.dec_fmt_id, + sizeof(pjmedia_format_id)*desc->info.dec_fmt_id_cnt); + } + if (!desc->info.fps_cnt) { + desc->info.fps_cnt = base_desc->info.fps_cnt; + pj_memcpy(desc->info.fps, base_desc->info.fps, + sizeof(desc->info.fps[0])*desc->info.fps_cnt); + } + if (!desc->info.clock_rate) { + desc->info.clock_rate = base_desc->info.clock_rate; + } + if (!desc->dec && base_desc->dec) { + copied_dir |= PJMEDIA_DIR_DECODING; + desc->dec = base_desc->dec; + } + if (!desc->enc && base_desc->enc) { + copied_dir |= PJMEDIA_DIR_ENCODING; + desc->enc = base_desc->enc; + } + + desc->info.dir |= copied_dir; + desc->enabled = (desc->info.dir != PJMEDIA_DIR_NONE); + desc->info.has_rtp_pack = (desc->packetize != NULL) && + (desc->unpacketize != NULL); + + if (copied_dir != PJMEDIA_DIR_NONE) { + const char *dir_name[] = {NULL, "encoder", "decoder", "codec"}; + PJ_LOG(5, (THIS_FILE, "The %.*s %s is using base codec (%.*s)", + desc->info.encoding_name.slen, + desc->info.encoding_name.ptr, + dir_name[copied_dir], + base_desc->info.encoding_name.slen, + base_desc->info.encoding_name.ptr)); + } + } + + /* Registering format match for SDP negotiation */ + if (desc->sdp_fmt_match) { + status = pjmedia_sdp_neg_register_fmt_match_cb( + &desc->info.encoding_name, + desc->sdp_fmt_match); + pj_assert(status == PJ_SUCCESS); + } + } + + /* Register codec factory to codec manager. */ + status = pjmedia_vid_codec_mgr_register_factory(mgr, + &ffmpeg_factory.base); + if (status != PJ_SUCCESS) + goto on_error; + + ffmpeg_factory.pool = pool; + + /* Done. */ + return PJ_SUCCESS; + +on_error: + pj_pool_release(pool); + return status; +} + +/* + * Unregister FFMPEG codecs factory from pjmedia endpoint. + */ +PJ_DEF(pj_status_t) pjmedia_codec_ffmpeg_deinit(void) +{ + pj_status_t status = PJ_SUCCESS; + + if (ffmpeg_factory.pool == NULL) { + /* Already deinitialized */ + return PJ_SUCCESS; + } + + pj_mutex_lock(ffmpeg_factory.mutex); + + /* Unregister FFMPEG codecs factory. */ + status = pjmedia_vid_codec_mgr_unregister_factory(ffmpeg_factory.mgr, + &ffmpeg_factory.base); + + /* Destroy mutex. */ + pj_mutex_destroy(ffmpeg_factory.mutex); + + /* Destroy pool. */ + pj_pool_release(ffmpeg_factory.pool); + ffmpeg_factory.pool = NULL; + + return status; +} + + +/* + * Check if factory can allocate the specified codec. + */ +static pj_status_t ffmpeg_test_alloc( pjmedia_vid_codec_factory *factory, + const pjmedia_vid_codec_info *info ) +{ + const ffmpeg_codec_desc *desc; + + PJ_ASSERT_RETURN(factory==&ffmpeg_factory.base, PJ_EINVAL); + PJ_ASSERT_RETURN(info, PJ_EINVAL); + + desc = find_codec_desc_by_info(info); + if (!desc) { + return PJMEDIA_CODEC_EUNSUP; + } + + return PJ_SUCCESS; +} + +/* + * Generate default attribute. + */ +static pj_status_t ffmpeg_default_attr( pjmedia_vid_codec_factory *factory, + const pjmedia_vid_codec_info *info, + pjmedia_vid_codec_param *attr ) +{ + const ffmpeg_codec_desc *desc; + + PJ_ASSERT_RETURN(factory==&ffmpeg_factory.base, PJ_EINVAL); + PJ_ASSERT_RETURN(info && attr, PJ_EINVAL); + + desc = find_codec_desc_by_info(info); + if (!desc) { + return PJMEDIA_CODEC_EUNSUP; + } + + pj_bzero(attr, sizeof(pjmedia_vid_codec_param)); + + /* Direction */ + attr->dir = desc->info.dir; + + /* Encoded format */ + pjmedia_format_init_video(&attr->enc_fmt, desc->info.fmt_id, + 352, 288, 30000, 1001); + + /* Decoded format */ + pjmedia_format_init_video(&attr->dec_fmt, desc->info.dec_fmt_id[0], + //352, 288, 30000, 1001); + 720, 576, 30000, 1001); + + /* Decoding fmtp */ + attr->dec_fmtp = desc->dec_fmtp; + + /* Bitrate */ + attr->enc_fmt.det.vid.avg_bps = desc->avg_bps; + attr->enc_fmt.det.vid.max_bps = desc->max_bps; + + /* MTU */ + attr->enc_mtu = PJMEDIA_MAX_MTU; + + return PJ_SUCCESS; +} + +/* + * Enum codecs supported by this factory. + */ +static pj_status_t ffmpeg_enum_codecs( pjmedia_vid_codec_factory *factory, + unsigned *count, + pjmedia_vid_codec_info codecs[]) +{ + unsigned i, max_cnt; + + PJ_ASSERT_RETURN(codecs && *count > 0, PJ_EINVAL); + PJ_ASSERT_RETURN(factory == &ffmpeg_factory.base, PJ_EINVAL); + + max_cnt = PJ_MIN(*count, PJ_ARRAY_SIZE(codec_desc)); + *count = 0; + + for (i=0; iop = &ffmpeg_op; + codec->factory = factory; + ff = PJ_POOL_ZALLOC_T(pool, ffmpeg_private); + if (!ff) { + status = PJ_ENOMEM; + goto on_error; + } + codec->codec_data = ff; + ff->pool = pool; + ff->enc = desc->enc; + ff->dec = desc->dec; + ff->desc = desc; + + *p_codec = codec; + return PJ_SUCCESS; + +on_error: + if (pool) + pj_pool_release(pool); + return status; +} + +/* + * Free codec. + */ +static pj_status_t ffmpeg_dealloc_codec( pjmedia_vid_codec_factory *factory, + pjmedia_vid_codec *codec ) +{ + ffmpeg_private *ff; + pj_pool_t *pool; + + PJ_ASSERT_RETURN(factory && codec, PJ_EINVAL); + PJ_ASSERT_RETURN(factory == &ffmpeg_factory.base, PJ_EINVAL); + + /* Close codec, if it's not closed. */ + ff = (ffmpeg_private*) codec->codec_data; + pool = ff->pool; + codec->codec_data = NULL; + pj_pool_release(pool); + + return PJ_SUCCESS; +} + +/* + * Init codec. + */ +static pj_status_t ffmpeg_codec_init( pjmedia_vid_codec *codec, + pj_pool_t *pool ) +{ + PJ_UNUSED_ARG(codec); + PJ_UNUSED_ARG(pool); + return PJ_SUCCESS; +} + +static void print_ffmpeg_err(int err) +{ +#if LIBAVCODEC_VERSION_MAJOR > 52 || \ + (LIBAVCODEC_VERSION_MAJOR >= 52 && LIBAVCODEC_VERSION_MINOR >= 72) + char errbuf[512]; + if (av_strerror(err, errbuf, sizeof(errbuf)) >= 0) + PJ_LOG(5, (THIS_FILE, "ffmpeg err %d: %s", err, errbuf)); +#else + PJ_LOG(5, (THIS_FILE, "ffmpeg err %d", err)); +#endif + +} + +static enum PixelFormat dec_get_format(struct AVCodecContext *s, + const enum PixelFormat * fmt) +{ + ffmpeg_private *ff = (ffmpeg_private*)s->opaque; + enum PixelFormat def_fmt = *fmt; + + while (*fmt != -1) { + if (*fmt == ff->expected_dec_fmt) + return *fmt; + ++fmt; + } + + pj_assert(!"Inconsistency in supported formats"); + return def_fmt; +} + + +static pj_status_t open_ffmpeg_codec(ffmpeg_private *ff, + pj_mutex_t *ff_mutex) +{ + enum PixelFormat pix_fmt; + pjmedia_video_format_detail *vfd; + pj_bool_t enc_opened = PJ_FALSE, dec_opened = PJ_FALSE; + pj_status_t status; + + /* Get decoded pixel format */ + status = pjmedia_format_id_to_PixelFormat(ff->param.dec_fmt.id, + &pix_fmt); + if (status != PJ_SUCCESS) + return status; + ff->expected_dec_fmt = pix_fmt; + + /* Get video format detail for shortcut access to encoded format */ + vfd = pjmedia_format_get_video_format_detail(&ff->param.enc_fmt, + PJ_TRUE); + + /* Allocate ffmpeg codec context */ + if (ff->param.dir & PJMEDIA_DIR_ENCODING) { + ff->enc_ctx = avcodec_alloc_context(); + if (ff->enc_ctx == NULL) + goto on_error; + } + if (ff->param.dir & PJMEDIA_DIR_DECODING) { + ff->dec_ctx = avcodec_alloc_context(); + if (ff->dec_ctx == NULL) + goto on_error; + } + + /* Let the codec apply specific settings before the codec opened */ + if (ff->desc->preopen) { + status = (*ff->desc->preopen)(ff); + if (status != PJ_SUCCESS) + goto on_error; + } + + if (ff->param.dir & PJMEDIA_DIR_ENCODING) { + AVCodecContext *ctx = ff->enc_ctx; + int err; + + /* Init common settings */ + ctx->pix_fmt = pix_fmt; + ctx->width = vfd->size.w; + ctx->height = vfd->size.h; + ctx->time_base.num = vfd->fps.denum; + ctx->time_base.den = vfd->fps.num; + if (vfd->avg_bps) { + ctx->bit_rate = vfd->avg_bps; + if (vfd->max_bps > vfd->avg_bps) + ctx->bit_rate_tolerance = vfd->max_bps - vfd->avg_bps; + } + ctx->strict_std_compliance = FF_COMPLIANCE_STRICT; + ctx->workaround_bugs = FF_BUG_AUTODETECT; + ctx->opaque = ff; + + /* Set no delay, note that this may cause some codec functionals + * not working (e.g: rate control). + */ +#if LIBAVCODEC_VERSION_MAJOR > 52 || \ + (LIBAVCODEC_VERSION_MAJOR >= 52 && LIBAVCODEC_VERSION_MINOR >= 113) + ctx->rc_lookahead = 0; +#endif + + /* Open ffmpeg codec */ + pj_mutex_lock(ff_mutex); + err = avcodec_open(ctx, ff->enc); + pj_mutex_unlock(ff_mutex); + if (err < 0) { + print_ffmpeg_err(err); + status = PJMEDIA_CODEC_EFAILED; + goto on_error; + } + enc_opened = PJ_TRUE; + } + + if (ff->param.dir & PJMEDIA_DIR_DECODING) { + AVCodecContext *ctx = ff->dec_ctx; + int err; + + /* Init common settings */ + /* Width/height may be overriden by ffmpeg after first decoding. */ + ctx->width = ctx->coded_width = ff->param.dec_fmt.det.vid.size.w; + ctx->height = ctx->coded_height = ff->param.dec_fmt.det.vid.size.h; + ctx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL; + ctx->workaround_bugs = FF_BUG_AUTODETECT; + ctx->opaque = ff; + ctx->get_format = &dec_get_format; + + /* Open ffmpeg codec */ + pj_mutex_lock(ff_mutex); + err = avcodec_open(ctx, ff->dec); + pj_mutex_unlock(ff_mutex); + if (err < 0) { + print_ffmpeg_err(err); + status = PJMEDIA_CODEC_EFAILED; + goto on_error; + } + dec_opened = PJ_TRUE; + } + + /* Let the codec apply specific settings after the codec opened */ + if (ff->desc->postopen) { + status = (*ff->desc->postopen)(ff); + if (status != PJ_SUCCESS) + goto on_error; + } + + return PJ_SUCCESS; + +on_error: + if (ff->enc_ctx) { + if (enc_opened) + avcodec_close(ff->enc_ctx); + av_free(ff->enc_ctx); + ff->enc_ctx = NULL; + } + if (ff->dec_ctx) { + if (dec_opened) + avcodec_close(ff->dec_ctx); + av_free(ff->dec_ctx); + ff->dec_ctx = NULL; + } + return status; +} + +/* + * Open codec. + */ +static pj_status_t ffmpeg_codec_open( pjmedia_vid_codec *codec, + pjmedia_vid_codec_param *attr ) +{ + ffmpeg_private *ff; + pj_status_t status; + pj_mutex_t *ff_mutex; + + PJ_ASSERT_RETURN(codec && attr, PJ_EINVAL); + ff = (ffmpeg_private*)codec->codec_data; + + pj_memcpy(&ff->param, attr, sizeof(*attr)); + + /* Open the codec */ + ff_mutex = ((struct ffmpeg_factory*)codec->factory)->mutex; + status = open_ffmpeg_codec(ff, ff_mutex); + if (status != PJ_SUCCESS) + goto on_error; + + /* Init format info and apply-param of decoder */ + ff->dec_vfi = pjmedia_get_video_format_info(NULL, ff->param.dec_fmt.id); + if (!ff->dec_vfi) { + status = PJ_EINVAL; + goto on_error; + } + pj_bzero(&ff->dec_vafp, sizeof(ff->dec_vafp)); + ff->dec_vafp.size = ff->param.dec_fmt.det.vid.size; + ff->dec_vafp.buffer = NULL; + status = (*ff->dec_vfi->apply_fmt)(ff->dec_vfi, &ff->dec_vafp); + if (status != PJ_SUCCESS) { + goto on_error; + } + + /* Init format info and apply-param of encoder */ + ff->enc_vfi = pjmedia_get_video_format_info(NULL, ff->param.dec_fmt.id); + if (!ff->enc_vfi) { + status = PJ_EINVAL; + goto on_error; + } + pj_bzero(&ff->enc_vafp, sizeof(ff->enc_vafp)); + ff->enc_vafp.size = ff->param.enc_fmt.det.vid.size; + ff->enc_vafp.buffer = NULL; + status = (*ff->enc_vfi->apply_fmt)(ff->enc_vfi, &ff->enc_vafp); + if (status != PJ_SUCCESS) { + goto on_error; + } + + /* Update codec attributes, e.g: encoding format may be changed by + * SDP fmtp negotiation. + */ + pj_memcpy(attr, &ff->param, sizeof(*attr)); + + return PJ_SUCCESS; + +on_error: + ffmpeg_codec_close(codec); + return status; +} + +/* + * Close codec. + */ +static pj_status_t ffmpeg_codec_close( pjmedia_vid_codec *codec ) +{ + ffmpeg_private *ff; + pj_mutex_t *ff_mutex; + + PJ_ASSERT_RETURN(codec, PJ_EINVAL); + ff = (ffmpeg_private*)codec->codec_data; + ff_mutex = ((struct ffmpeg_factory*)codec->factory)->mutex; + + pj_mutex_lock(ff_mutex); + if (ff->enc_ctx) { + avcodec_close(ff->enc_ctx); + av_free(ff->enc_ctx); + } + if (ff->dec_ctx && ff->dec_ctx!=ff->enc_ctx) { + avcodec_close(ff->dec_ctx); + av_free(ff->dec_ctx); + } + ff->enc_ctx = NULL; + ff->dec_ctx = NULL; + pj_mutex_unlock(ff_mutex); + + return PJ_SUCCESS; +} + + +/* + * Modify codec settings. + */ +static pj_status_t ffmpeg_codec_modify( pjmedia_vid_codec *codec, + const pjmedia_vid_codec_param *attr) +{ + ffmpeg_private *ff = (ffmpeg_private*)codec->codec_data; + + PJ_UNUSED_ARG(attr); + PJ_UNUSED_ARG(ff); + + return PJ_ENOTSUP; +} + +static pj_status_t ffmpeg_codec_get_param(pjmedia_vid_codec *codec, + pjmedia_vid_codec_param *param) +{ + ffmpeg_private *ff; + + PJ_ASSERT_RETURN(codec && param, PJ_EINVAL); + + ff = (ffmpeg_private*)codec->codec_data; + pj_memcpy(param, &ff->param, sizeof(*param)); + + return PJ_SUCCESS; +} + + +static pj_status_t ffmpeg_packetize ( pjmedia_vid_codec *codec, + pj_uint8_t *bits, + pj_size_t bits_len, + unsigned *bits_pos, + const pj_uint8_t **payload, + pj_size_t *payload_len) +{ + ffmpeg_private *ff = (ffmpeg_private*)codec->codec_data; + + if (ff->desc->packetize) { + return (*ff->desc->packetize)(ff, bits, bits_len, bits_pos, + payload, payload_len); + } + + return PJ_ENOTSUP; +} + +static pj_status_t ffmpeg_unpacketize(pjmedia_vid_codec *codec, + const pj_uint8_t *payload, + pj_size_t payload_len, + pj_uint8_t *bits, + pj_size_t bits_len, + unsigned *bits_pos) +{ + ffmpeg_private *ff = (ffmpeg_private*)codec->codec_data; + + if (ff->desc->unpacketize) { + return (*ff->desc->unpacketize)(ff, payload, payload_len, + bits, bits_len, bits_pos); + } + + return PJ_ENOTSUP; +} + + +/* + * Encode frames. + */ +static pj_status_t ffmpeg_codec_encode( pjmedia_vid_codec *codec, + const pjmedia_frame *input, + unsigned output_buf_len, + pjmedia_frame *output) +{ + ffmpeg_private *ff = (ffmpeg_private*)codec->codec_data; + pj_uint8_t *p = (pj_uint8_t*)input->buf; + AVFrame avframe; + pj_uint8_t *out_buf = (pj_uint8_t*)output->buf; + int out_buf_len = output_buf_len; + int err; + //AVRational src_timebase; + /* For some reasons (e.g: SSE/MMX usage), the avcodec_encode_video() must + * have stack aligned to 16 bytes. Let's try to be safe by preparing the + * 16-bytes aligned stack here, in case it's not managed by the ffmpeg. + */ + PJ_ALIGN_DATA(pj_uint32_t i[4], 16); + + if ((long)i & 0xF) { + PJ_LOG(2,(THIS_FILE, "Stack alignment fails")); + } + + /* Check if encoder has been opened */ + PJ_ASSERT_RETURN(ff->enc_ctx, PJ_EINVALIDOP); + + avcodec_get_frame_defaults(&avframe); + + // Let ffmpeg manage the timestamps + /* + src_timebase.num = 1; + src_timebase.den = ff->desc->info.clock_rate; + avframe.pts = av_rescale_q(input->timestamp.u64, src_timebase, + ff->enc_ctx->time_base); + */ + + for (i[0] = 0; i[0] < ff->enc_vfi->plane_cnt; ++i[0]) { + avframe.data[i[0]] = p; + avframe.linesize[i[0]] = ff->enc_vafp.strides[i[0]]; + p += ff->enc_vafp.plane_bytes[i[0]]; + } + + err = avcodec_encode_video(ff->enc_ctx, out_buf, out_buf_len, &avframe); + if (err < 0) { + print_ffmpeg_err(err); + return PJMEDIA_CODEC_EFAILED; + } else { + output->size = err; + } + + return PJ_SUCCESS; +} + +/* + * Decode frame. + */ +static pj_status_t ffmpeg_codec_decode( pjmedia_vid_codec *codec, + const pjmedia_frame *input, + unsigned output_buf_len, + pjmedia_frame *output) +{ + ffmpeg_private *ff = (ffmpeg_private*)codec->codec_data; + AVFrame avframe; + AVPacket avpacket; + int err, got_picture; + + /* Check if decoder has been opened */ + PJ_ASSERT_RETURN(ff->dec_ctx, PJ_EINVALIDOP); + + /* Reset output frame bit info */ + output->bit_info = 0; + + /* Validate output buffer size */ + // Do this validation later after getting decoding result, where the real + // decoded size will be assured. + //if (ff->dec_vafp.framebytes > output_buf_len) + //return PJ_ETOOSMALL; + + /* Init frame to receive the decoded data, the ffmpeg codec context will + * automatically provide the decoded buffer (single buffer used for the + * whole decoding session, and seems to be freed when the codec context + * closed). + */ + avcodec_get_frame_defaults(&avframe); + + /* Init packet, the container of the encoded data */ + av_init_packet(&avpacket); + avpacket.data = (pj_uint8_t*)input->buf; + avpacket.size = input->size; + + /* ffmpeg warns: + * - input buffer padding, at least FF_INPUT_BUFFER_PADDING_SIZE + * - null terminated + * Normally, encoded buffer is allocated more than needed, so lets just + * bzero the input buffer end/pad, hope it will be just fine. + */ + pj_bzero(avpacket.data+avpacket.size, FF_INPUT_BUFFER_PADDING_SIZE); + + output->bit_info = 0; + output->timestamp = input->timestamp; + +#if LIBAVCODEC_VERSION_MAJOR > 52 || \ + (LIBAVCODEC_VERSION_MAJOR >= 52 && LIBAVCODEC_VERSION_MINOR >= 72) + //avpacket.flags = AV_PKT_FLAG_KEY; +#else + avpacket.flags = 0; +#endif + +#if LIBAVCODEC_VERSION_MAJOR > 52 || \ + (LIBAVCODEC_VERSION_MAJOR >= 52 && LIBAVCODEC_VERSION_MINOR >= 72) + err = avcodec_decode_video2(ff->dec_ctx, &avframe, + &got_picture, &avpacket); +#else + err = avcodec_decode_video(ff->dec_ctx, &avframe, + &got_picture, avpacket.data, avpacket.size); +#endif + if (err < 0) { + output->type = PJMEDIA_FRAME_TYPE_NONE; + output->size = 0; + print_ffmpeg_err(err); + return PJMEDIA_CODEC_EFAILED; + } else if (got_picture) { + pjmedia_video_apply_fmt_param *vafp = &ff->dec_vafp; + pj_uint8_t *q = (pj_uint8_t*)output->buf; + unsigned i; + + /* Decoder output format is set by libavcodec, in case it is different + * to the configured param. + */ + if (ff->dec_ctx->pix_fmt != ff->expected_dec_fmt || + ff->dec_ctx->width != (int)vafp->size.w || + ff->dec_ctx->height != (int)vafp->size.h) + { + pjmedia_format_id new_fmt_id; + pj_status_t status; + + /* Get current raw format id from ffmpeg decoder context */ + status = PixelFormat_to_pjmedia_format_id(ff->dec_ctx->pix_fmt, + &new_fmt_id); + if (status != PJ_SUCCESS) + return status; + + /* Update decoder format in param */ + ff->param.dec_fmt.id = new_fmt_id; + ff->param.dec_fmt.det.vid.size.w = ff->dec_ctx->width; + ff->param.dec_fmt.det.vid.size.h = ff->dec_ctx->height; + + /* Re-init format info and apply-param of decoder */ + ff->dec_vfi = pjmedia_get_video_format_info(NULL, ff->param.dec_fmt.id); + if (!ff->dec_vfi) + return PJ_ENOTSUP; + pj_bzero(&ff->dec_vafp, sizeof(ff->dec_vafp)); + ff->dec_vafp.size = ff->param.dec_fmt.det.vid.size; + ff->dec_vafp.buffer = NULL; + status = (*ff->dec_vfi->apply_fmt)(ff->dec_vfi, &ff->dec_vafp); + if (status != PJ_SUCCESS) + return status; + + /* Broadcast event */ + if (pjmedia_event_publisher_has_sub(&codec->epub)) { + pjmedia_event event; + + pjmedia_event_init(&event, PJMEDIA_EVENT_FMT_CHANGED, + &input->timestamp, &codec->epub); + event.data.fmt_changed.dir = PJMEDIA_DIR_DECODING; + pj_memcpy(&event.data.fmt_changed.new_fmt, &ff->param.dec_fmt, + sizeof(ff->param.dec_fmt)); + pjmedia_event_publish(&codec->epub, &event); + } + } + + /* Check provided buffer size */ + if (vafp->framebytes > output_buf_len) + return PJ_ETOOSMALL; + + /* Get the decoded data */ + for (i = 0; i < ff->dec_vfi->plane_cnt; ++i) { + pj_uint8_t *p = avframe.data[i]; + + /* The decoded data may contain padding */ + if (avframe.linesize[i]!=vafp->strides[i]) { + /* Padding exists, copy line by line */ + pj_uint8_t *q_end; + + q_end = q+vafp->plane_bytes[i]; + while(q < q_end) { + pj_memcpy(q, p, vafp->strides[i]); + q += vafp->strides[i]; + p += avframe.linesize[i]; + } + } else { + /* No padding, copy the whole plane */ + pj_memcpy(q, p, vafp->plane_bytes[i]); + q += vafp->plane_bytes[i]; + } + } + + output->type = PJMEDIA_FRAME_TYPE_VIDEO; + output->size = vafp->framebytes; + + /* Check if we got key frame */ + if (avframe.key_frame && pjmedia_event_publisher_has_sub(&codec->epub)) + { + pjmedia_event event; + + pjmedia_event_init(&event, PJMEDIA_EVENT_KEY_FRAME_FOUND, + &output->timestamp, &codec->epub); + pjmedia_event_publish(&codec->epub, &event); + } + } else { + output->type = PJMEDIA_FRAME_TYPE_NONE; + output->size = 0; + } + + return PJ_SUCCESS; +} + +/* + * Recover lost frame. + */ +static pj_status_t ffmpeg_codec_recover( pjmedia_vid_codec *codec, + unsigned output_buf_len, + pjmedia_frame *output) +{ + PJ_UNUSED_ARG(codec); + PJ_UNUSED_ARG(output_buf_len); + PJ_UNUSED_ARG(output); + + return PJ_ENOTSUP; +} + +#ifdef _MSC_VER +# pragma comment( lib, "avcodec.lib") +#endif + +#endif /* PJMEDIA_HAS_FFMPEG_CODEC */ + diff --git a/pjmedia/src/pjmedia-codec/g722.c b/pjmedia/src/pjmedia-codec/g722.c index 8cd332d8..acddc142 100644 --- a/pjmedia/src/pjmedia-codec/g722.c +++ b/pjmedia/src/pjmedia-codec/g722.c @@ -122,7 +122,8 @@ static pjmedia_codec_factory_op g722_factory_op = &g722_default_attr, &g722_enum_codecs, &g722_alloc_codec, - &g722_dealloc_codec + &g722_dealloc_codec, + &pjmedia_codec_g722_deinit }; /* G722 factory */ diff --git a/pjmedia/src/pjmedia-codec/g7221.c b/pjmedia/src/pjmedia-codec/g7221.c index f1aa8ab8..44ecabc9 100644 --- a/pjmedia/src/pjmedia-codec/g7221.c +++ b/pjmedia/src/pjmedia-codec/g7221.c @@ -115,7 +115,8 @@ static pjmedia_codec_factory_op codec_factory_op = &default_attr, &enum_codecs, &alloc_codec, - &dealloc_codec + &dealloc_codec, + &pjmedia_codec_g7221_deinit }; diff --git a/pjmedia/src/pjmedia-codec/gsm.c b/pjmedia/src/pjmedia-codec/gsm.c index ea4bb63a..43149b78 100644 --- a/pjmedia/src/pjmedia-codec/gsm.c +++ b/pjmedia/src/pjmedia-codec/gsm.c @@ -117,7 +117,8 @@ static pjmedia_codec_factory_op gsm_factory_op = &gsm_default_attr, &gsm_enum_codecs, &gsm_alloc_codec, - &gsm_dealloc_codec + &gsm_dealloc_codec, + &pjmedia_codec_gsm_deinit }; /* GSM factory */ diff --git a/pjmedia/src/pjmedia-codec/h263_packetizer.c b/pjmedia/src/pjmedia-codec/h263_packetizer.c new file mode 100644 index 00000000..149c2e64 --- /dev/null +++ b/pjmedia/src/pjmedia-codec/h263_packetizer.c @@ -0,0 +1,287 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include + +#define THIS_FILE "h263_packetizer.c" + + +/* H.263 packetizer definition */ +struct pjmedia_h263_packetizer { + /* Current settings */ + pjmedia_h263_packetizer_cfg cfg; + + /* Unpacketizer state */ + unsigned unpack_last_sync_pos; + pj_bool_t unpack_prev_lost; +}; + + +/* + * Find synchronization point (PSC, slice, GSBC, EOS, EOSBS) in H.263 + * bitstream. + */ +static pj_uint8_t* find_sync_point(pj_uint8_t *data, + pj_size_t data_len) +{ + pj_uint8_t *p = data, *end = data+data_len-1; + + while (p < end && (*p || *(p+1))) + ++p; + + if (p == end) + return NULL; + + return p; +} + + +/* + * Find synchronization point (PSC, slice, GSBC, EOS, EOSBS) in H.263 + * bitstream, in reversed manner. + */ +static pj_uint8_t* find_sync_point_rev(pj_uint8_t *data, + pj_size_t data_len) +{ + pj_uint8_t *p = data+data_len-2; + + while (p >= data && (*p || *(p+1))) + --p; + + if (p < data) + return (data + data_len); + + return p; +} + + +/* + * Create H263 packetizer. + */ +PJ_DEF(pj_status_t) pjmedia_h263_packetizer_create( + pj_pool_t *pool, + const pjmedia_h263_packetizer_cfg *cfg, + pjmedia_h263_packetizer **p) +{ + pjmedia_h263_packetizer *p_; + + PJ_ASSERT_RETURN(pool && p, PJ_EINVAL); + + if (cfg && cfg->mode != PJMEDIA_H263_PACKETIZER_MODE_RFC4629) + return PJ_ENOTSUP; + + p_ = PJ_POOL_ZALLOC_T(pool, pjmedia_h263_packetizer); + if (cfg) { + pj_memcpy(&p_->cfg, cfg, sizeof(*cfg)); + } else { + p_->cfg.mode = PJMEDIA_H263_PACKETIZER_MODE_RFC4629; + p_->cfg.mtu = PJMEDIA_MAX_MTU; + } + + *p = p_; + + return PJ_SUCCESS; +} + + +/* + * Generate an RTP payload from H.263 frame bitstream, in-place processing. + */ +PJ_DEF(pj_status_t) pjmedia_h263_packetize(pjmedia_h263_packetizer *pktz, + pj_uint8_t *bits, + pj_size_t bits_len, + unsigned *pos, + const pj_uint8_t **payload, + pj_size_t *payload_len) +{ + pj_uint8_t *p, *end; + + pj_assert(pktz && bits && pos && payload && payload_len); + pj_assert(*pos <= bits_len); + + p = bits + *pos; + end = bits + bits_len; + + /* Put two octets payload header */ + if ((end-p > 2) && *p==0 && *(p+1)==0) { + /* The bitstream starts with synchronization point, just override + * the two zero octets (sync point mark) for payload header. + */ + *p = 0x04; + } else { + /* Not started in synchronization point, we will use two octets + * preceeding the bitstream for payload header! + */ + + if (*pos < 2) { + /* Invalid H263 bitstream, it's not started with PSC */ + return PJ_EINVAL; + } + + p -= 2; + *p = 0; + } + *(p+1) = 0; + + /* When bitstream truncation needed because of payload length/MTU + * limitation, try to use sync point for the payload boundary. + */ + if (end-p > pktz->cfg.mtu) { + end = find_sync_point_rev(p+2, pktz->cfg.mtu-2); + } + + *payload = p; + *payload_len = end-p; + *pos = end - bits; + + return PJ_SUCCESS; +} + + +/* + * Append an RTP payload to a H.263 picture bitstream. + */ +PJ_DEF(pj_status_t) pjmedia_h263_unpacketize (pjmedia_h263_packetizer *pktz, + const pj_uint8_t *payload, + pj_size_t payload_len, + pj_uint8_t *bits, + pj_size_t bits_size, + unsigned *pos) +{ + pj_uint8_t P, V, PLEN; + const pj_uint8_t *p = payload; + pj_uint8_t *q; + + q = bits + *pos; + + /* Check if this is a missing/lost packet */ + if (payload == NULL) { + pktz->unpack_prev_lost = PJ_TRUE; + return PJ_SUCCESS; + } + + /* H263 payload header size is two octets */ + if (payload_len < 2) { + /* Invalid bitstream, discard this payload */ + pktz->unpack_prev_lost = PJ_TRUE; + return PJ_EINVAL; + } + + /* Reset last sync point for every new picture bitstream */ + if (*pos == 0) + pktz->unpack_last_sync_pos = 0; + + /* Get payload header info */ + P = *p & 0x04; + V = *p & 0x02; + PLEN = ((*p & 0x01) << 5) + ((*(p+1) & 0xF8)>>3); + + /* Get start bitstream pointer */ + p += 2; /* Skip payload header */ + if (V) + p += 1; /* Skip VRC data */ + if (PLEN) + p += PLEN; /* Skip extra picture header data */ + + /* Get bitstream length */ + if (payload_len > (pj_size_t)(p - payload)) { + payload_len -= (p - payload); + } else { + /* Invalid bitstream, discard this payload */ + pktz->unpack_prev_lost = PJ_TRUE; + return PJ_EINVAL; + } + + /* Validate bitstream length */ + if (bits_size < *pos + payload_len + 2) { + /* Insufficient bistream buffer, discard this payload */ + pj_assert(!"Insufficient H.263 bitstream buffer"); + pktz->unpack_prev_lost = PJ_TRUE; + return PJ_ETOOSMALL; + } + + /* Start writing bitstream */ + + /* No sync point flag */ + if (!P) { + if (*pos == 0) { + /* Previous packet must be lost */ + pktz->unpack_prev_lost = PJ_TRUE; + + /* If there is extra picture header, let's use it. */ + if (PLEN) { + /* Write two zero octets for PSC */ + *q++ = 0; + *q++ = 0; + /* Copy the picture header */ + p -= PLEN; + pj_memcpy(q, p, PLEN); + p += PLEN; + q += PLEN; + } + } else if (pktz->unpack_prev_lost) { + /* If prev packet was lost, revert the bitstream pointer to + * the last sync point. + */ + pj_assert(pktz->unpack_last_sync_pos <= *pos); + q = bits + pktz->unpack_last_sync_pos; + } + + /* There was packet lost, see if this payload contain sync point + * (usable data). + */ + if (pktz->unpack_prev_lost) { + pj_uint8_t *sync; + sync = find_sync_point((pj_uint8_t*)p, payload_len); + if (sync) { + /* Got sync point, update P/sync-point flag */ + P = 1; + /* Skip the two zero octets */ + sync += 2; + /* Update payload length and start bitstream pointer */ + payload_len -= (sync - p); + p = sync; + } else { + /* No sync point in it, just discard this payload */ + return PJ_EIGNORED; + } + } + } + + /* Write two zero octets when payload flagged with sync point */ + if (P) { + pktz->unpack_last_sync_pos = q - bits; + *q++ = 0; + *q++ = 0; + } + + /* Write the payload to the bitstream */ + pj_memcpy(q, p, payload_len); + q += payload_len; + + /* Update the bitstream writing offset */ + *pos = q - bits; + + pktz->unpack_prev_lost = PJ_FALSE; + + return PJ_SUCCESS; +} diff --git a/pjmedia/src/pjmedia-codec/h264_packetizer.c b/pjmedia/src/pjmedia-codec/h264_packetizer.c new file mode 100644 index 00000000..8eb22d6f --- /dev/null +++ b/pjmedia/src/pjmedia-codec/h264_packetizer.c @@ -0,0 +1,530 @@ +/* $Id$ */ +/* + * Copyright (C) 2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include + +#define THIS_FILE "h264_packetizer.c" + +#define DBG_PACKETIZE 0 +#define DBG_UNPACKETIZE 0 + + +/* H.264 packetizer definition */ +struct pjmedia_h264_packetizer +{ + /* Current settings */ + pjmedia_h264_packetizer_cfg cfg; + + /* Unpacketizer state */ + unsigned unpack_last_sync_pos; + pj_bool_t unpack_prev_lost; +}; + + +/* Enumeration of H.264 NAL unit types */ +enum +{ + NAL_TYPE_SINGLE_NAL_MIN = 1, + NAL_TYPE_SINGLE_NAL_MAX = 23, + NAL_TYPE_STAP_A = 24, + NAL_TYPE_FU_A = 28, +}; + + +/* + * Find next NAL unit from the specified H.264 bitstream data. + */ +static pj_uint8_t* find_next_nal_unit(pj_uint8_t *start, + pj_uint8_t *end) +{ + pj_uint8_t *p = start; + + /* Simply lookup "0x000001" pattern */ + while (p <= end-3 && (p[0] || p[1] || p[2]!=1)) + ++p; + + if (p > end-3) + /* No more NAL unit in this bitstream */ + return NULL; + + /* Include 8 bits leading zero */ + if (p>start && *(p-1)==0) + return (p-1); + + return p; +} + + +/* + * Create H264 packetizer. + */ +PJ_DEF(pj_status_t) pjmedia_h264_packetizer_create( + pj_pool_t *pool, + const pjmedia_h264_packetizer_cfg *cfg, + pjmedia_h264_packetizer **p) +{ + pjmedia_h264_packetizer *p_; + + PJ_ASSERT_RETURN(pool && p, PJ_EINVAL); + + if (cfg && + cfg->mode != PJMEDIA_H264_PACKETIZER_MODE_NON_INTERLEAVED && + cfg->mode != PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL) + { + return PJ_ENOTSUP; + } + + p_ = PJ_POOL_ZALLOC_T(pool, pjmedia_h264_packetizer); + if (cfg) { + pj_memcpy(&p_->cfg, cfg, sizeof(*cfg)); + } else { + p_->cfg.mode = PJMEDIA_H264_PACKETIZER_MODE_NON_INTERLEAVED; + p_->cfg.mtu = PJMEDIA_MAX_MTU; + } + + *p = p_; + + return PJ_SUCCESS; +} + + + +/* + * Generate an RTP payload from H.264 frame bitstream, in-place processing. + */ +PJ_DEF(pj_status_t) pjmedia_h264_packetize(pjmedia_h264_packetizer *pktz, + pj_uint8_t *buf, + pj_size_t buf_len, + unsigned *pos, + const pj_uint8_t **payload, + pj_size_t *payload_len) +{ + pj_uint8_t *nal_start = NULL, *nal_end = NULL, *nal_octet = NULL; + pj_uint8_t *p, *end; + enum { + HEADER_SIZE_FU_A = 2, + HEADER_SIZE_STAP_A = 3, + }; + enum { MAX_NALS_IN_AGGR = 32 }; + +#if DBG_PACKETIZE + if (*pos == 0 && buf_len) { + PJ_LOG(3, ("h264pack", "<< Start packing new frame >>")); + } +#endif + + p = buf + *pos; + end = buf + buf_len; + + /* Find NAL unit startcode */ + if (end-p >= 4) + nal_start = find_next_nal_unit(p, p+4); + if (nal_start) { + /* Get NAL unit octet pointer */ + while (*nal_start++ == 0); + nal_octet = nal_start; + } else { + /* This NAL unit is being fragmented */ + nal_start = p; + } + + /* Get end of NAL unit */ + p = nal_start+pktz->cfg.mtu+1; + if (p > end || pktz->cfg.mode==PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL) + p = end; + nal_end = find_next_nal_unit(nal_start, p); + if (!nal_end) + nal_end = p; + + /* Validate MTU vs NAL length on single NAL unit packetization */ + if ((pktz->cfg.mode==PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL) && + nal_end - nal_start > pktz->cfg.mtu) + { + //pj_assert(!"MTU too small for H.264 single NAL packetization mode"); + PJ_LOG(2,("h264_packetizer.c", + "MTU too small for H.264 (required=%u, MTU=%u)", + nal_end - nal_start, pktz->cfg.mtu)); + return PJ_ETOOSMALL; + } + + /* Evaluate the proper payload format structure */ + + /* Fragmentation (FU-A) packet */ + if ((pktz->cfg.mode != PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL) && + (!nal_octet || nal_end-nal_start > pktz->cfg.mtu)) + { + pj_uint8_t NRI, TYPE; + + if (nal_octet) { + /* We have NAL unit octet, so this is the first fragment */ + NRI = (*nal_octet & 0x60) >> 5; + TYPE = *nal_octet & 0x1F; + + /* Skip nal_octet in nal_start to be overriden by FU header */ + ++nal_start; + } else { + /* Not the first fragment, get NRI and NAL unit type + * from the previous fragment. + */ + p = nal_start - pktz->cfg.mtu; + NRI = (*p & 0x60) >> 5; + TYPE = *(p+1) & 0x1F; + } + + /* Init FU indicator (one octet: F+NRI+TYPE) */ + p = nal_start - HEADER_SIZE_FU_A; + *p = (NRI << 5) | NAL_TYPE_FU_A; + ++p; + + /* Init FU header (one octed: S+E+R+TYPE) */ + *p = TYPE; + if (nal_octet) + *p |= (1 << 7); /* S bit flag = start of fragmentation */ + if (nal_end-nal_start+HEADER_SIZE_FU_A <= pktz->cfg.mtu) + *p |= (1 << 6); /* E bit flag = end of fragmentation */ + + /* Set payload, payload length */ + *payload = nal_start - HEADER_SIZE_FU_A; + if (nal_end-nal_start+HEADER_SIZE_FU_A > pktz->cfg.mtu) + *payload_len = pktz->cfg.mtu; + else + *payload_len = nal_end - nal_start + HEADER_SIZE_FU_A; + *pos = *payload + *payload_len - buf; + +#if DBG_PACKETIZE + PJ_LOG(3, ("h264pack", "Packetized fragmented H264 NAL unit " + "(pos=%d, type=%d, NRI=%d, S=%d, E=%d, len=%d/%d)", + *payload-buf, TYPE, NRI, *p>>7, (*p>>6)&1, *payload_len, + buf_len)); +#endif + + return PJ_SUCCESS; + } + + /* Aggregation (STAP-A) packet */ + if ((pktz->cfg.mode != PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL) && + (nal_end != end) && + (nal_end - nal_start + HEADER_SIZE_STAP_A) < pktz->cfg.mtu) + { + int total_size; + unsigned nal_cnt = 1; + pj_uint8_t *nal[MAX_NALS_IN_AGGR]; + pj_size_t nal_size[MAX_NALS_IN_AGGR]; + pj_uint8_t NRI; + + pj_assert(nal_octet); + + /* Init the first NAL unit in the packet */ + nal[0] = nal_start; + nal_size[0] = nal_end - nal_start; + total_size = nal_size[0] + HEADER_SIZE_STAP_A; + NRI = (*nal_octet & 0x60) >> 5; + + /* Populate next NAL units */ + while (nal_cnt < MAX_NALS_IN_AGGR) { + pj_uint8_t *tmp_end; + + /* Find start address of the next NAL unit */ + p = nal[nal_cnt-1] + nal_size[nal_cnt-1]; + while (*p++ == 0); + nal[nal_cnt] = p; + + /* Find end address of the next NAL unit */ + tmp_end = p + (pktz->cfg.mtu - total_size); + if (tmp_end > end) + tmp_end = end; + p = find_next_nal_unit(p+1, tmp_end); + if (p) { + nal_size[nal_cnt] = p - nal[nal_cnt]; + } else { + break; + } + + /* Update total payload size (2 octet NAL size + NAL) */ + total_size += (2 + nal_size[nal_cnt]); + if (total_size <= pktz->cfg.mtu) { + pj_uint8_t tmp_nri; + + /* Get maximum NRI of the aggregated NAL units */ + tmp_nri = (*(nal[nal_cnt]-1) & 0x60) >> 5; + if (tmp_nri > NRI) + NRI = tmp_nri; + } else { + break; + } + + ++nal_cnt; + } + + /* Only use STAP-A when we found more than one NAL units */ + if (nal_cnt > 1) { + unsigned i; + + /* Init STAP-A NAL header (F+NRI+TYPE) */ + p = nal[0] - HEADER_SIZE_STAP_A; + *p++ = (NRI << 5) | NAL_TYPE_STAP_A; + + /* Append all populated NAL units into payload (SIZE+NAL) */ + for (i = 0; i < nal_cnt; ++i) { + /* Put size (2 octets in network order) */ + pj_assert(nal_size[i] <= 0xFFFF); + *p++ = (pj_uint8_t)(nal_size[i] >> 8); + *p++ = (pj_uint8_t)(nal_size[i] & 0xFF); + + /* Append NAL unit, watchout memmove()-ing bitstream! */ + if (p != nal[i]) + pj_memmove(p, nal[i], nal_size[i]); + p += nal_size[i]; + } + + /* Set payload, payload length, and pos */ + *payload = nal[0] - HEADER_SIZE_STAP_A; + pj_assert(*payload >= buf+*pos); + *payload_len = p - *payload; + *pos = nal[nal_cnt-1] + nal_size[nal_cnt-1] - buf; + +#if DBG_PACKETIZE + PJ_LOG(3, ("h264pack", "Packetized aggregation of " + "%d H264 NAL units (pos=%d, NRI=%d len=%d/%d)", + nal_cnt, *payload-buf, NRI, *payload_len, buf_len)); +#endif + + return PJ_SUCCESS; + } + } + + /* Single NAL unit packet */ + pj_assert(nal_octet); + + *payload = nal_start; + *payload_len = nal_end - nal_start; + *pos = nal_end - buf; + +#if DBG_PACKETIZE + PJ_LOG(3, ("h264pack", "Packetized single H264 NAL unit " + "(pos=%d, type=%d, NRI=%d, len=%d/%d)", + nal_start-buf, *nal_octet&0x1F, (*nal_octet&0x60)>>5, + *payload_len, buf_len)); +#endif + + return PJ_SUCCESS; +} + + +/* + * Append RTP payload to a H.264 picture bitstream. Note that the only + * payload format that cares about packet lost is the NAL unit + * fragmentation format (FU-A/B), so we will only manage the "prev_lost" + * state for the FU-A/B packets. + */ +PJ_DEF(pj_status_t) pjmedia_h264_unpacketize(pjmedia_h264_packetizer *pktz, + const pj_uint8_t *payload, + pj_size_t payload_len, + pj_uint8_t *bits, + pj_size_t bits_len, + unsigned *bits_pos) +{ + const pj_uint8_t nal_start_code[3] = {0, 0, 1}; + enum { MIN_PAYLOAD_SIZE = 2 }; + pj_uint8_t nal_type; + + PJ_UNUSED_ARG(pktz); + +#if DBG_UNPACKETIZE + if (*bits_pos == 0 && payload_len) { + PJ_LOG(3, ("h264unpack", ">> Start unpacking new frame <<")); + } +#endif + + /* Check if this is a missing/lost packet */ + if (payload == NULL) { + pktz->unpack_prev_lost = PJ_TRUE; + return PJ_SUCCESS; + } + + /* H264 payload size */ + if (payload_len < MIN_PAYLOAD_SIZE) { + /* Invalid bitstream, discard this payload */ + pktz->unpack_prev_lost = PJ_TRUE; + return PJ_EINVAL; + } + + /* Reset last sync point for every new picture bitstream */ + if (*bits_pos == 0) + pktz->unpack_last_sync_pos = 0; + + nal_type = *payload & 0x1F; + if (nal_type >= NAL_TYPE_SINGLE_NAL_MIN && + nal_type <= NAL_TYPE_SINGLE_NAL_MAX) + { + /* Single NAL unit packet */ + pj_uint8_t *p = bits + *bits_pos; + + /* Validate bitstream length */ + if (bits_len-*bits_pos < payload_len+PJ_ARRAY_SIZE(nal_start_code)) { + /* Insufficient bistream buffer, discard this payload */ + pj_assert(!"Insufficient H.263 bitstream buffer"); + return PJ_ETOOSMALL; + } + + /* Write NAL unit start code */ + pj_memcpy(p, &nal_start_code, PJ_ARRAY_SIZE(nal_start_code)); + p += PJ_ARRAY_SIZE(nal_start_code); + + /* Write NAL unit */ + pj_memcpy(p, payload, payload_len); + p += payload_len; + + /* Update the bitstream writing offset */ + *bits_pos = p - bits; + pktz->unpack_last_sync_pos = *bits_pos; + +#if DBG_UNPACKETIZE + PJ_LOG(3, ("h264unpack", "Unpacked single H264 NAL unit " + "(type=%d, NRI=%d, len=%d)", + nal_type, (*payload&0x60)>>5, payload_len)); +#endif + + } + else if (nal_type == NAL_TYPE_STAP_A) + { + /* Aggregation packet */ + pj_uint8_t *p, *p_end; + const pj_uint8_t *q, *q_end; + unsigned cnt = 0; + + /* Validate bitstream length */ + if (bits_len - *bits_pos < payload_len + 32) { + /* Insufficient bistream buffer, discard this payload */ + pj_assert(!"Insufficient H.263 bitstream buffer"); + return PJ_ETOOSMALL; + } + + /* Fill bitstream */ + p = bits + *bits_pos; + p_end = bits + bits_len; + q = payload + 1; + q_end = payload + payload_len; + while (q < q_end && p < p_end) { + pj_uint16_t tmp_nal_size; + + /* Write NAL unit start code */ + pj_memcpy(p, &nal_start_code, PJ_ARRAY_SIZE(nal_start_code)); + p += PJ_ARRAY_SIZE(nal_start_code); + + /* Get NAL unit size */ + tmp_nal_size = (*q << 8) | *(q+1); + q += 2; + if (q + tmp_nal_size > q_end) { + /* Invalid bitstream, discard the rest of the payload */ + return PJ_EINVAL; + } + + /* Write NAL unit */ + pj_memcpy(p, q, tmp_nal_size); + p += tmp_nal_size; + q += tmp_nal_size; + ++cnt; + + /* Update the bitstream writing offset */ + *bits_pos = p - bits; + pktz->unpack_last_sync_pos = *bits_pos; + } + +#if DBG_UNPACKETIZE + PJ_LOG(3, ("h264unpack", "Unpacked %d H264 NAL units (len=%d)", + cnt, payload_len)); +#endif + + } + else if (nal_type == NAL_TYPE_FU_A) + { + /* Fragmentation packet */ + pj_uint8_t *p; + const pj_uint8_t *q = payload; + pj_uint8_t NRI, TYPE, S, E; + + p = bits + *bits_pos; + + /* Validate bitstream length */ + if (bits_len-*bits_pos < payload_len+PJ_ARRAY_SIZE(nal_start_code)) { + /* Insufficient bistream buffer, drop this packet */ + pj_assert(!"Insufficient H.263 bitstream buffer"); + pktz->unpack_prev_lost = PJ_TRUE; + return PJ_ETOOSMALL; + } + + /* Get info */ + S = *(q+1) & 0x80; /* Start bit flag */ + E = *(q+1) & 0x40; /* End bit flag */ + TYPE = *(q+1) & 0x1f; + NRI = (*q & 0x60) >> 5; + + /* Fill bitstream */ + if (S) { + /* This is the first part, write NAL unit start code */ + pj_memcpy(p, &nal_start_code, PJ_ARRAY_SIZE(nal_start_code)); + p += PJ_ARRAY_SIZE(nal_start_code); + + /* Write NAL unit octet */ + *p++ = (NRI << 5) | TYPE; + } else if (pktz->unpack_prev_lost) { + /* If prev packet was lost, revert the bitstream pointer to + * the last sync point. + */ + pj_assert(pktz->unpack_last_sync_pos <= *bits_pos); + *bits_pos = pktz->unpack_last_sync_pos; + /* And discard this payload (and the following fragmentation + * payloads carrying this same NAL unit. + */ + return PJ_EIGNORED; + } + q += 2; + + /* Write NAL unit */ + pj_memcpy(p, q, payload_len - 2); + p += (payload_len - 2); + + /* Update the bitstream writing offset */ + *bits_pos = p - bits; + if (E) { + /* Update the sync pos only if the end bit flag is set */ + pktz->unpack_last_sync_pos = *bits_pos; + } + +#if DBG_UNPACKETIZE + PJ_LOG(3, ("h264unpack", "Unpacked fragmented H264 NAL unit " + "(type=%d, NRI=%d, len=%d)", + TYPE, NRI, payload_len)); +#endif + + } else { + *bits_pos = 0; + return PJ_ENOTSUP; + } + + pktz->unpack_prev_lost = PJ_FALSE; + + return PJ_SUCCESS; +} diff --git a/pjmedia/src/pjmedia-codec/ilbc.c b/pjmedia/src/pjmedia-codec/ilbc.c index f30b5f09..c595477f 100644 --- a/pjmedia/src/pjmedia-codec/ilbc.c +++ b/pjmedia/src/pjmedia-codec/ilbc.c @@ -113,7 +113,8 @@ static pjmedia_codec_factory_op ilbc_factory_op = &ilbc_default_attr, &ilbc_enum_codecs, &ilbc_alloc_codec, - &ilbc_dealloc_codec + &ilbc_dealloc_codec, + &pjmedia_codec_ilbc_deinit }; /* iLBC factory */ diff --git a/pjmedia/src/pjmedia-codec/ipp_codecs.c b/pjmedia/src/pjmedia-codec/ipp_codecs.c index 023cdc7a..f5690eee 100644 --- a/pjmedia/src/pjmedia-codec/ipp_codecs.c +++ b/pjmedia/src/pjmedia-codec/ipp_codecs.c @@ -103,7 +103,8 @@ static pjmedia_codec_factory_op ipp_factory_op = &ipp_default_attr, &ipp_enum_codecs, &ipp_alloc_codec, - &ipp_dealloc_codec + &ipp_dealloc_codec, + &pjmedia_codec_ipp_deinit }; /* IPP codecs factory */ diff --git a/pjmedia/src/pjmedia-codec/l16.c b/pjmedia/src/pjmedia-codec/l16.c index 3be8a1b6..71b72009 100644 --- a/pjmedia/src/pjmedia-codec/l16.c +++ b/pjmedia/src/pjmedia-codec/l16.c @@ -112,7 +112,8 @@ static pjmedia_codec_factory_op l16_factory_op = &l16_default_attr, &l16_enum_codecs, &l16_alloc_codec, - &l16_dealloc_codec + &l16_dealloc_codec, + &pjmedia_codec_l16_deinit }; /* L16 factory private data */ diff --git a/pjmedia/src/pjmedia-codec/passthrough.c b/pjmedia/src/pjmedia-codec/passthrough.c index 73eed891..8ded8e2a 100644 --- a/pjmedia/src/pjmedia-codec/passthrough.c +++ b/pjmedia/src/pjmedia-codec/passthrough.c @@ -98,7 +98,8 @@ static pjmedia_codec_factory_op codec_factory_op = &default_attr, &enum_codecs, &alloc_codec, - &dealloc_codec + &dealloc_codec, + &pjmedia_codec_passthrough_deinit }; /* Passthrough codecs factory */ diff --git a/pjmedia/src/pjmedia-codec/speex_codec.c b/pjmedia/src/pjmedia-codec/speex_codec.c index eff88235..d5cc0a57 100644 --- a/pjmedia/src/pjmedia-codec/speex_codec.c +++ b/pjmedia/src/pjmedia-codec/speex_codec.c @@ -99,7 +99,8 @@ static pjmedia_codec_factory_op spx_factory_op = &spx_default_attr, &spx_enum_codecs, &spx_alloc_codec, - &spx_dealloc_codec + &spx_dealloc_codec, + &pjmedia_codec_speex_deinit }; /* Index to Speex parameter. */ -- cgit v1.2.3