diff options
Diffstat (limited to 'pjmedia/src/pjmedia-codec/ffmpeg_vid_codecs.c')
-rw-r--r-- | pjmedia/src/pjmedia-codec/ffmpeg_vid_codecs.c | 1778 |
1 files changed, 1778 insertions, 0 deletions
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 00000000..960267db --- /dev/null +++ b/pjmedia/src/pjmedia-codec/ffmpeg_vid_codecs.c @@ -0,0 +1,1778 @@ +/* $Id$ */ +/* + * Copyright (C) 2010-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjmedia-codec/ffmpeg_vid_codecs.h> +#include <pjmedia-codec/h263_packetizer.h> +#include <pjmedia-codec/h264_packetizer.h> +#include <pjmedia/errno.h> +#include <pjmedia/vid_codec_util.h> +#include <pj/assert.h> +#include <pj/list.h> +#include <pj/log.h> +#include <pj/math.h> +#include <pj/pool.h> +#include <pj/string.h> +#include <pj/os.h> + + +/* + * Only build this file if PJMEDIA_HAS_FFMPEG_VID_CODEC != 0 and + * PJMEDIA_HAS_VIDEO != 0 + */ +#if defined(PJMEDIA_HAS_FFMPEG_VID_CODEC) && \ + PJMEDIA_HAS_FFMPEG_VID_CODEC != 0 && \ + defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0) + +#define THIS_FILE "ffmpeg_vid_codecs.c" + +#define LIBAVCODEC_VER_AT_LEAST(major,minor) (LIBAVCODEC_VERSION_MAJOR > major || \ + (LIBAVCODEC_VERSION_MAJOR == major && \ + LIBAVCODEC_VERSION_MINOR >= minor)) + +#include "../pjmedia/ffmpeg_util.h" +#include <libavcodec/avcodec.h> +#include <libavformat/avformat.h> +#if LIBAVCODEC_VER_AT_LEAST(53,20) + /* Needed by 264 so far, on libavcodec 53.20 */ +# include <libavutil/opt.h> +#endif + + +/* 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_set_string3(ctx->priv_data, "profile", profile, 0, NULL)) + { + PJ_LOG(3, (THIS_FILE, "Failed to set H264 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_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_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_set_string3(ctx->priv_data, "preset", "veryfast", 0, NULL)) + { + PJ_LOG(3, (THIS_FILE, "Failed to set x264 preset 'veryfast'")); + } + if (av_set_string3(ctx->priv_data, "tune", "animation+zerolatency", + 0, NULL)) + { + 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; + + avcodec_init(); + avcodec_register_all(); + av_log_set_level(AV_LOG_ERROR); + + /* Enum FFMPEG codecs */ + for (c=av_codec_next(NULL); c; c=av_codec_next(c)) { + ffmpeg_codec_desc *desc; + pjmedia_format_id fmt_id; + int codec_info_idx; + +#if LIBAVCODEC_VERSION_MAJOR <= 52 +# define AVMEDIA_TYPE_VIDEO CODEC_TYPE_VIDEO +#endif + if (c->type != AVMEDIA_TYPE_VIDEO) + continue; + + /* Video encoder and decoder are usually implemented in separate + * AVCodec instances. While the codec attributes (e.g: raw formats, + * supported fps) are in the encoder. + */ + + //PJ_LOG(3, (THIS_FILE, "%s", c->name)); + status = CodecID_to_pjmedia_format_id(c->id, &fmt_id); + /* Skip if format ID is unknown */ + if (status != PJ_SUCCESS) + continue; + + codec_info_idx = find_codec_idx_by_fmt_id(fmt_id); + /* Skip if codec is unwanted by this wrapper (not listed in + * the codec info array) + */ + if (codec_info_idx < 0) + continue; + + desc = &codec_desc[codec_info_idx]; + + /* Skip duplicated codec implementation */ + if ((c->encode && (desc->info.dir & PJMEDIA_DIR_ENCODING)) || + (c->decode && (desc->info.dir & PJMEDIA_DIR_DECODING))) + { + continue; + } + + /* Get raw/decoded format ids in the encoder */ + if (c->pix_fmts && c->encode) { + pjmedia_format_id raw_fmt[PJMEDIA_VID_CODEC_MAX_DEC_FMT_CNT]; + unsigned raw_fmt_cnt = 0; + unsigned raw_fmt_cnt_should_be = 0; + const enum PixelFormat *p = c->pix_fmts; + + for(;(p && *p != -1) && + (raw_fmt_cnt < PJMEDIA_VID_CODEC_MAX_DEC_FMT_CNT); + ++p) + { + pjmedia_format_id fmt_id; + + raw_fmt_cnt_should_be++; + status = PixelFormat_to_pjmedia_format_id(*p, &fmt_id); + if (status != PJ_SUCCESS) { + PJ_LOG(6, (THIS_FILE, "Unrecognized ffmpeg pixel " + "format %d", *p)); + continue; + } + + //raw_fmt[raw_fmt_cnt++] = fmt_id; + /* 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 (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 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; + + 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) + 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; + 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]]; + } + + /* 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 + } + + 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; + 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 */ + |