diff options
author | Nanang Izzuddin <nanang@teluu.com> | 2011-07-19 03:42:28 +0000 |
---|---|---|
committer | Nanang Izzuddin <nanang@teluu.com> | 2011-07-19 03:42:28 +0000 |
commit | cd283c8825c9a94400f27735acb1c9385e90ffc8 (patch) | |
tree | 56d5722310fa8957ce5d1ba7cbd137cf8802dcc7 /pjmedia/src | |
parent | ed8f8d08abba9040f769e922aa0c1adbde86fbbc (diff) |
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
Diffstat (limited to 'pjmedia/src')
81 files changed, 18717 insertions, 510 deletions
diff --git a/pjmedia/src/pjmedia-audiodev/audiodev.c b/pjmedia/src/pjmedia-audiodev/audiodev.c index cb1ebbe2..437ee584 100644 --- a/pjmedia/src/pjmedia-audiodev/audiodev.c +++ b/pjmedia/src/pjmedia-audiodev/audiodev.c @@ -145,7 +145,7 @@ PJ_DEF(const char*) pjmedia_aud_dev_cap_name(pjmedia_aud_dev_cap cap, break; } - if (i==32) { + if (i==PJ_ARRAY_SIZE(cap_infos)) { *p_desc = "??"; return "??"; } @@ -190,9 +190,11 @@ static pj_status_t get_cap_pointer(const pjmedia_aud_param *param, case PJMEDIA_AUD_DEV_CAP_EC_TAIL: FIELD_INFO(ec_tail_ms); break; + /* vad is no longer in "fmt" in 2.0. case PJMEDIA_AUD_DEV_CAP_VAD: FIELD_INFO(ext_fmt.vad); break; + */ case PJMEDIA_AUD_DEV_CAP_CNG: FIELD_INFO(cng_enabled); break; diff --git a/pjmedia/src/pjmedia-audiodev/wmme_dev.c b/pjmedia/src/pjmedia-audiodev/wmme_dev.c index 9c7d4452..3a010226 100644 --- a/pjmedia/src/pjmedia-audiodev/wmme_dev.c +++ b/pjmedia/src/pjmedia-audiodev/wmme_dev.c @@ -372,12 +372,12 @@ static void build_dev_info(UINT deviceId, struct wmme_dev_info *wdi, /* Extended formats */ wdi->info.caps |= PJMEDIA_AUD_DEV_CAP_EXT_FORMAT; wdi->info.ext_fmt_cnt = 2; - wdi->info.ext_fmt[0].id = PJMEDIA_FORMAT_PCMU; - wdi->info.ext_fmt[0].bitrate = 64000; - wdi->info.ext_fmt[0].vad = 0; - wdi->info.ext_fmt[1].id = PJMEDIA_FORMAT_PCMA; - wdi->info.ext_fmt[1].bitrate = 64000; - wdi->info.ext_fmt[1].vad = 0; + pjmedia_format_init_audio(&wdi->info.ext_fmt[0], + PJMEDIA_FORMAT_PCMU, 8000, 1, 8, + 20000, 64000, 64000); + pjmedia_format_init_audio(&wdi->info.ext_fmt[0], + PJMEDIA_FORMAT_PCMA, 8000, 1, 8, + 20000, 64000, 64000); } /* API: init factory */ diff --git a/pjmedia/src/pjmedia-codec/audio_codecs.c b/pjmedia/src/pjmedia-codec/audio_codecs.c new file mode 100644 index 00000000..a9e0700a --- /dev/null +++ b/pjmedia/src/pjmedia-codec/audio_codecs.c @@ -0,0 +1,112 @@ +/* $Id$ */ +/* + * Copyright (C) 2011-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjmedia-codec.h> +#include <pjmedia/g711.h> + +PJ_DEF(void) pjmedia_audio_codec_config_default(pjmedia_audio_codec_config*cfg) +{ + pj_bzero(cfg, sizeof(*cfg)); + cfg->speex.option = 0; + cfg->speex.quality = PJMEDIA_CODEC_SPEEX_DEFAULT_QUALITY; + cfg->speex.complexity = PJMEDIA_CODEC_SPEEX_DEFAULT_COMPLEXITY; + cfg->ilbc.mode = 30; + cfg->passthrough.setting.ilbc_mode = cfg->ilbc.mode; +} + +PJ_DEF(pj_status_t) +pjmedia_codec_register_audio_codecs(pjmedia_endpt *endpt, + const pjmedia_audio_codec_config *c) +{ + pjmedia_audio_codec_config default_cfg; + pj_status_t status; + + PJ_ASSERT_RETURN(endpt, PJ_EINVAL); + if (!c) { + pjmedia_audio_codec_config_default(&default_cfg); + c = &default_cfg; + } + + PJ_ASSERT_RETURN(c->ilbc.mode==20 || c->ilbc.mode==30, PJ_EINVAL); + +#if PJMEDIA_HAS_PASSTHROUGH_CODECS + status = pjmedia_codec_passthrough_init2(endpt, &c->passthough.ilbc); + if (status != PJ_SUCCESS) + return status; +#endif + +#if PJMEDIA_HAS_SPEEX_CODEC + /* Register speex. */ + status = pjmedia_codec_speex_init(endpt, c->speex.option, + c->speex.quality, + c->speex.complexity); + if (status != PJ_SUCCESS) + return status; +#endif + +#if PJMEDIA_HAS_ILBC_CODEC + /* Register iLBC. */ + status = pjmedia_codec_ilbc_init( endpt, c->ilbc.mode); + if (status != PJ_SUCCESS) + return status; +#endif /* PJMEDIA_HAS_ILBC_CODEC */ + +#if PJMEDIA_HAS_GSM_CODEC + /* Register GSM */ + status = pjmedia_codec_gsm_init(endpt); + if (status != PJ_SUCCESS) + return status; +#endif /* PJMEDIA_HAS_GSM_CODEC */ + +#if PJMEDIA_HAS_G711_CODEC + /* Register PCMA and PCMU */ + status = pjmedia_codec_g711_init(endpt); + if (status != PJ_SUCCESS) + return status; +#endif /* PJMEDIA_HAS_G711_CODEC */ + +#if PJMEDIA_HAS_G722_CODEC + status = pjmedia_codec_g722_init(endpt ); + if (status != PJ_SUCCESS) + return status; +#endif /* PJMEDIA_HAS_G722_CODEC */ + +#if PJMEDIA_HAS_INTEL_IPP + /* Register IPP codecs */ + status = pjmedia_codec_ipp_init(endpt); + if (status != PJ_SUCCESS) + return status; +#endif /* PJMEDIA_HAS_INTEL_IPP */ + +#if PJMEDIA_HAS_G7221_CODEC + /* Register G722.1 codecs */ + status = pjmedia_codec_g7221_init(endpt); + if (status != PJ_SUCCESS) + return status; +#endif /* PJMEDIA_HAS_G7221_CODEC */ + +#if PJMEDIA_HAS_L16_CODEC + /* Register L16 family codecs */ + status = pjmedia_codec_l16_init(endpt, 0); + if (status != PJ_SUCCESS) + return status; +#endif /* PJMEDIA_HAS_L16_CODEC */ + + return PJ_SUCCESS; +} + diff --git a/pjmedia/src/pjmedia-codec/ffmpeg_codecs.c b/pjmedia/src/pjmedia-codec/ffmpeg_codecs.c new file mode 100644 index 00000000..ddf02c1a --- /dev/null +++ b/pjmedia/src/pjmedia-codec/ffmpeg_codecs.c @@ -0,0 +1,1492 @@ +/* $Id$ */ +/* + * Copyright (C) 2010 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjmedia-codec/ffmpeg_codecs.h> +#include <pjmedia-codec/h263_packetizer.h> +#include <pjmedia-codec/h264_packetizer.h> +#include <pjmedia/errno.h> +#include <pjmedia/vid_codec_util.h> +#include <pj/assert.h> +#include <pj/list.h> +#include <pj/log.h> +#include <pj/math.h> +#include <pj/pool.h> +#include <pj/string.h> +#include <pj/os.h> + + +/* + * Only build this file if PJMEDIA_HAS_FFMPEG_CODEC != 0 + */ +#if defined(PJMEDIA_HAS_FFMPEG_CODEC) && PJMEDIA_HAS_FFMPEG_CODEC != 0 + +#define THIS_FILE "ffmpeg_codecs.c" + +#include "../pjmedia/ffmpeg_util.h" +#include <libavcodec/avcodec.h> +#include <libavformat/avformat.h> + + +/* Prototypes for FFMPEG codecs factory */ +static pj_status_t ffmpeg_test_alloc( pjmedia_vid_codec_factory *factory, + const pjmedia_vid_codec_info *id ); +static pj_status_t ffmpeg_default_attr( pjmedia_vid_codec_factory *factory, + const pjmedia_vid_codec_info *info, + pjmedia_vid_codec_param *attr ); +static pj_status_t ffmpeg_enum_codecs( pjmedia_vid_codec_factory *factory, + unsigned *count, + pjmedia_vid_codec_info codecs[]); +static pj_status_t ffmpeg_alloc_codec( pjmedia_vid_codec_factory *factory, + const pjmedia_vid_codec_info *info, + pjmedia_vid_codec **p_codec); +static pj_status_t ffmpeg_dealloc_codec( pjmedia_vid_codec_factory *factory, + pjmedia_vid_codec *codec ); + +/* Prototypes for FFMPEG codecs implementation. */ +static pj_status_t ffmpeg_codec_init( pjmedia_vid_codec *codec, + pj_pool_t *pool ); +static pj_status_t ffmpeg_codec_open( pjmedia_vid_codec *codec, + pjmedia_vid_codec_param *attr ); +static pj_status_t ffmpeg_codec_close( pjmedia_vid_codec *codec ); +static pj_status_t ffmpeg_codec_modify(pjmedia_vid_codec *codec, + const pjmedia_vid_codec_param *attr ); +static pj_status_t ffmpeg_codec_get_param(pjmedia_vid_codec *codec, + pjmedia_vid_codec_param *param); +static pj_status_t ffmpeg_packetize ( pjmedia_vid_codec *codec, + pj_uint8_t *buf, + pj_size_t buf_len, + unsigned *pos, + const pj_uint8_t **payload, + pj_size_t *payload_len); +static pj_status_t ffmpeg_unpacketize(pjmedia_vid_codec *codec, + const pj_uint8_t *payload, + pj_size_t payload_len, + pj_uint8_t *buf, + pj_size_t buf_len, + unsigned *pos); +static pj_status_t ffmpeg_codec_encode( pjmedia_vid_codec *codec, + const pjmedia_frame *input, + unsigned output_buf_len, + pjmedia_frame *output); +static pj_status_t ffmpeg_codec_decode( pjmedia_vid_codec *codec, + const pjmedia_frame *input, + unsigned output_buf_len, + pjmedia_frame *output); +static pj_status_t ffmpeg_codec_recover( pjmedia_vid_codec *codec, + unsigned output_buf_len, + pjmedia_frame *output); + +/* Definition for FFMPEG codecs operations. */ +static pjmedia_vid_codec_op ffmpeg_op = +{ + &ffmpeg_codec_init, + &ffmpeg_codec_open, + &ffmpeg_codec_close, + &ffmpeg_codec_modify, + &ffmpeg_codec_get_param, + &ffmpeg_packetize, + &ffmpeg_unpacketize, + &ffmpeg_codec_encode, + &ffmpeg_codec_decode, + NULL //&ffmpeg_codec_recover +}; + +/* Definition for FFMPEG codecs factory operations. */ +static pjmedia_vid_codec_factory_op ffmpeg_factory_op = +{ + &ffmpeg_test_alloc, + &ffmpeg_default_attr, + &ffmpeg_enum_codecs, + &ffmpeg_alloc_codec, + &ffmpeg_dealloc_codec +}; + + +/* FFMPEG codecs factory */ +static struct ffmpeg_factory { + pjmedia_vid_codec_factory base; + pjmedia_vid_codec_mgr *mgr; + pj_pool_factory *pf; + pj_pool_t *pool; + pj_mutex_t *mutex; +} ffmpeg_factory; + + +typedef struct ffmpeg_codec_desc ffmpeg_codec_desc; + + +/* FFMPEG codecs private data. */ +typedef struct ffmpeg_private +{ + const ffmpeg_codec_desc *desc; + pjmedia_vid_codec_param param; /**< Codec param */ + pj_pool_t *pool; /**< Pool for each instance */ + pj_timestamp last_tx; /**< Timestamp of last + transmit */ + + /* Format info and apply format param */ + const pjmedia_video_format_info *enc_vfi; + pjmedia_video_apply_fmt_param enc_vafp; + const pjmedia_video_format_info *dec_vfi; + pjmedia_video_apply_fmt_param dec_vafp; + + /* The ffmpeg codec states. */ + AVCodec *enc; + AVCodec *dec; + AVCodecContext *enc_ctx; + AVCodecContext *dec_ctx; + + /* The ffmpeg decoder cannot set the output format, so format conversion + * may be needed for post-decoding. + */ + enum PixelFormat expected_dec_fmt; + /**< Expected output format of + ffmpeg decoder */ + + void *data; /**< Codec specific data */ +} ffmpeg_private; + + +/* Shortcuts for packetize & unpacketize function declaration, + * as it has long params and is reused many times! + */ +#define FUNC_PACKETIZE(name) \ + pj_status_t(name)(ffmpeg_private *ff, pj_uint8_t *bits, \ + pj_size_t bits_len, unsigned *bits_pos, \ + const pj_uint8_t **payload, pj_size_t *payload_len) + +#define FUNC_UNPACKETIZE(name) \ + pj_status_t(name)(ffmpeg_private *ff, const pj_uint8_t *payload, \ + pj_size_t payload_len, pj_uint8_t *bits, \ + pj_size_t bits_len, unsigned *bits_pos) + +#define FUNC_FMT_MATCH(name) \ + pj_status_t(name)(pj_pool_t *pool, \ + pjmedia_sdp_media *offer, unsigned o_fmt_idx, \ + pjmedia_sdp_media *answer, unsigned a_fmt_idx, \ + unsigned option) + + +/* Type definition of codec specific functions */ +typedef FUNC_PACKETIZE(*func_packetize); +typedef FUNC_UNPACKETIZE(*func_unpacketize); +typedef pj_status_t (*func_preopen) (ffmpeg_private *ff); +typedef pj_status_t (*func_postopen) (ffmpeg_private *ff); +typedef FUNC_FMT_MATCH(*func_sdp_fmt_match); + + +/* FFMPEG codec info */ +struct ffmpeg_codec_desc +{ + /* Predefined info */ + pjmedia_vid_codec_info info; + pjmedia_format_id base_fmt_id; /**< Some codecs may be exactly + same or compatible with + another codec, base format + will tell the initializer + to copy this codec desc + from its base format */ + pj_uint32_t avg_bps; + pj_uint32_t max_bps; + func_packetize packetize; + func_unpacketize unpacketize; + func_preopen preopen; + func_preopen postopen; + func_sdp_fmt_match sdp_fmt_match; + pjmedia_codec_fmtp dec_fmtp; + + /* Init time defined info */ + pj_bool_t enabled; + AVCodec *enc; + AVCodec *dec; +}; + + +/* H264 constants */ +#define PROFILE_H264_BASELINE 66 +#define PROFILE_H264_MAIN 77 + +/* Codec specific functions */ +static pj_status_t h264_preopen(ffmpeg_private *ff); +static pj_status_t h264_postopen(ffmpeg_private *ff); +static pj_status_t h263_preopen(ffmpeg_private *ff); +static FUNC_PACKETIZE(h264_packetize); +static FUNC_UNPACKETIZE(h264_unpacketize); +static FUNC_PACKETIZE(h263_packetize); +static FUNC_UNPACKETIZE(h263_unpacketize); + + +/* Internal codec info */ +ffmpeg_codec_desc codec_desc[] = +{ + { + {PJMEDIA_FORMAT_H264, PJMEDIA_RTP_PT_H264, {"H264",4}, + {"Constrained Baseline (level=30, pack=1)", 39}}, + 0, 128000, 1000000, + &h264_packetize, &h264_unpacketize, &h264_preopen, &h264_postopen, + &pjmedia_vid_codec_h264_match_sdp, + /* Leading space for better compatibility (strange indeed!) */ + {2, { {{"profile-level-id",16}, {"42e01e",6}}, + {{" packetization-mode",19}, {"1",1}}, } }, + }, + { + {PJMEDIA_FORMAT_H264, PJMEDIA_RTP_PT_H264_RSV1, {"H264",4}, + {"Baseline (level=30, pack=1)", 27}}, + PJMEDIA_FORMAT_H264, 128000, 1000000, + &h264_packetize, &h264_unpacketize, &h264_preopen, &h264_postopen, + &pjmedia_vid_codec_h264_match_sdp, + {2, { {{"profile-level-id",16}, {"42001e",6}}, + {{" packetization-mode",19}, {"1",1}}, } }, + }, + { + {PJMEDIA_FORMAT_H263P, PJMEDIA_RTP_PT_H263P, {"H263-1998",9}}, + PJMEDIA_FORMAT_H263, 1000000, 2000000, + &h263_packetize, &h263_unpacketize, &h263_preopen, NULL, NULL, + {2, { {{"CIF",3}, {"1",1}}, + {{"QCIF",4}, {"1",1}}, } }, + }, + { + {PJMEDIA_FORMAT_H263, PJMEDIA_RTP_PT_H263, {"H263",4}}, + PJMEDIA_FORMAT_H263, 1000000, 2000000, + &h263_packetize, &h263_unpacketize, &h263_preopen, NULL, NULL, + {2, { {{"CIF",3}, {"1",1}}, + {{"QCIF",4}, {"1",1}}, } }, + }, + { + {PJMEDIA_FORMAT_H263, PJMEDIA_RTP_PT_H263, {"H263",4}}, + }, + { + {PJMEDIA_FORMAT_H261, PJMEDIA_RTP_PT_H261, {"H261",4}}, + }, + { + {PJMEDIA_FORMAT_MJPEG, PJMEDIA_RTP_PT_JPEG, {"JPEG",4}}, + }, + { + {PJMEDIA_FORMAT_MPEG4, 0, {"MP4V",4}}, + }, + { + {PJMEDIA_FORMAT_XVID, 0, {"XVID",4}}, + PJMEDIA_FORMAT_MPEG4, + }, +}; + + +typedef struct h264_data +{ + pjmedia_vid_codec_h264_fmtp fmtp; + pjmedia_h264_packetizer *pktz; +} h264_data; + + +static pj_status_t h264_preopen(ffmpeg_private *ff) +{ + h264_data *data; + pjmedia_h264_packetizer_cfg pktz_cfg; + pj_status_t status; + + data = PJ_POOL_ZALLOC_T(ff->pool, h264_data); + ff->data = data; + + /* Parse remote fmtp */ + status = pjmedia_vid_codec_h264_parse_fmtp(&ff->param.enc_fmtp, + &data->fmtp); + if (status != PJ_SUCCESS) + return status; + + /* Create packetizer */ + pktz_cfg.mtu = ff->param.enc_mtu; +#if 0 + if (data->fmtp.packetization_mode == 0) + pktz_cfg.mode = PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL; + else if (data->fmtp.packetization_mode == 1) + pktz_cfg.mode = PJMEDIA_H264_PACKETIZER_MODE_NON_INTERLEAVED; + else + return PJ_ENOTSUP; +#else + if (data->fmtp.packetization_mode!= + PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL && + data->fmtp.packetization_mode!= + PJMEDIA_H264_PACKETIZER_MODE_NON_INTERLEAVED) + { + return PJ_ENOTSUP; + } + /* Better always send in single NAL mode for better compatibility */ + pktz_cfg.mode = PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL; +#endif + + status = pjmedia_h264_packetizer_create(ff->pool, &pktz_cfg, &data->pktz); + if (status != PJ_SUCCESS) + return status; + + /* Apply SDP fmtp to format in codec param */ + status = pjmedia_vid_codec_h264_apply_fmtp(&ff->param); + if (status != PJ_SUCCESS) + return status; + + if (ff->param.dir & PJMEDIA_DIR_ENCODING) { + AVCodecContext *ctx = ff->enc_ctx; + + /* Apply profile. Note that, for x264 backend, ffmpeg doesn't seem to + * use this profile param field, so let's try to apply it "manually". + */ + ctx->profile = data->fmtp.profile_idc; + if (ctx->profile == PROFILE_H264_BASELINE) { + /* Baseline profile settings (the most used profile in + * conversational/real-time communications). + */ + ctx->coder_type = FF_CODER_TYPE_VLC; + ctx->max_b_frames = 0; + ctx->flags2 &= ~(CODEC_FLAG2_WPRED | CODEC_FLAG2_8X8DCT); + ctx->weighted_p_pred = 0; + } else if (ctx->profile == PROFILE_H264_MAIN) { + ctx->flags2 &= ~CODEC_FLAG2_8X8DCT; + } + + /* Apply profile constraint bits. */ + // The x264 doesn't seem to support non-constrained (baseline) profile + // so this shouldn't be a problem (for now). + //PJ_TODO(set_h264_constraint_bits_properly_in_ffmpeg); + if (data->fmtp.profile_iop) { +#if defined(FF_PROFILE_H264_CONSTRAINED) + ctx->profile |= FF_PROFILE_H264_CONSTRAINED; +#endif + } + + /* Apply profile level. */ + ctx->level = data->fmtp.level; + + /* Libx264 rejects the "broken" ffmpeg defaults, so just change some */ + ctx->me_range = 16; + ctx->max_qdiff = 4; + ctx->qmin = 20; + ctx->qmax = 32; + ctx->qcompress = 0.6f; + + ctx->rtp_payload_size = ff->param.enc_mtu; + } + + if (ff->param.dir & PJMEDIA_DIR_DECODING) { + AVCodecContext *ctx = ff->dec_ctx; + + /* Apply the "sprop-parameter-sets" fmtp from remote SDP to + * extradata of ffmpeg codec context. + */ + if (data->fmtp.sprop_param_sets_len) { + ctx->extradata_size = data->fmtp.sprop_param_sets_len; + ctx->extradata = data->fmtp.sprop_param_sets; + } + } + + return PJ_SUCCESS; +} + +static pj_status_t h264_postopen(ffmpeg_private *ff) +{ + h264_data *data = (h264_data*)ff->data; + PJ_UNUSED_ARG(data); + return PJ_SUCCESS; +} + + +static FUNC_PACKETIZE(h264_packetize) +{ + h264_data *data = (h264_data*)ff->data; + return pjmedia_h264_packetize(data->pktz, bits, bits_len, bits_pos, + payload, payload_len); +} + +static FUNC_UNPACKETIZE(h264_unpacketize) +{ + h264_data *data = (h264_data*)ff->data; + return pjmedia_h264_unpacketize(data->pktz, payload, payload_len, + bits, bits_len, bits_pos); +} + + +typedef struct h263_data +{ + pjmedia_h263_packetizer *pktz; +} h263_data; + +/* H263 pre-open */ +static pj_status_t h263_preopen(ffmpeg_private *ff) +{ + h263_data *data; + pjmedia_h263_packetizer_cfg pktz_cfg; + pj_status_t status; + + data = PJ_POOL_ZALLOC_T(ff->pool, h263_data); + ff->data = data; + + /* Create packetizer */ + pktz_cfg.mtu = ff->param.enc_mtu; + pktz_cfg.mode = PJMEDIA_H263_PACKETIZER_MODE_RFC4629; + status = pjmedia_h263_packetizer_create(ff->pool, &pktz_cfg, &data->pktz); + if (status != PJ_SUCCESS) + return status; + + /* Apply fmtp settings to codec param */ + status = pjmedia_vid_codec_h263_apply_fmtp(&ff->param); + + return status; +} + +static FUNC_PACKETIZE(h263_packetize) +{ + h263_data *data = (h263_data*)ff->data; + return pjmedia_h263_packetize(data->pktz, bits, bits_len, bits_pos, + payload, payload_len); +} + +static FUNC_UNPACKETIZE(h263_unpacketize) +{ + h263_data *data = (h263_data*)ff->data; + return pjmedia_h263_unpacketize(data->pktz, payload, payload_len, + bits, bits_len, bits_pos); +} + + +static const ffmpeg_codec_desc* find_codec_desc_by_info( + const pjmedia_vid_codec_info *info) +{ + int i; + + for (i=0; i<PJ_ARRAY_SIZE(codec_desc); ++i) { + ffmpeg_codec_desc *desc = &codec_desc[i]; + + if (desc->enabled && + (desc->info.fmt_id == info->fmt_id) && + ((desc->info.dir & info->dir) == info->dir) && + (desc->info.pt == info->pt)) + { + return desc; + } + } + + return NULL; +} + + +static int find_codec_idx_by_fmt_id(pjmedia_format_id fmt_id) +{ + int i; + for (i=0; i<PJ_ARRAY_SIZE(codec_desc); ++i) { + if (codec_desc[i].info.fmt_id == fmt_id) + return i; + } + + return -1; +} + + +/* + * Initialize and register FFMPEG codec factory to pjmedia endpoint. + */ +PJ_DEF(pj_status_t) pjmedia_codec_ffmpeg_init(pjmedia_vid_codec_mgr *mgr, + pj_pool_factory *pf) +{ + pj_pool_t *pool; + AVCodec *c; + pj_status_t status; + unsigned i; + + if (ffmpeg_factory.pool != NULL) { + /* Already initialized. */ + return PJ_SUCCESS; + } + + if (!mgr) mgr = pjmedia_vid_codec_mgr_instance(); + PJ_ASSERT_RETURN(mgr, PJ_EINVAL); + + /* Create FFMPEG codec factory. */ + ffmpeg_factory.base.op = &ffmpeg_factory_op; + ffmpeg_factory.base.factory_data = NULL; + ffmpeg_factory.mgr = mgr; + ffmpeg_factory.pf = pf; + + pool = pj_pool_create(pf, "ffmpeg codec factory", 256, 256, NULL); + if (!pool) + return PJ_ENOMEM; + + /* Create mutex. */ + status = pj_mutex_create_simple(pool, "ffmpeg codec factory", + &ffmpeg_factory.mutex); + if (status != PJ_SUCCESS) + goto on_error; + + avcodec_init(); + avcodec_register_all(); + av_log_set_level(AV_LOG_ERROR); + + /* Enum FFMPEG codecs */ + for (c=av_codec_next(NULL); c; c=av_codec_next(c)) + { + ffmpeg_codec_desc *desc; + pjmedia_format_id fmt_id; + int codec_info_idx; + +#if LIBAVCODEC_VERSION_MAJOR <= 52 +# define AVMEDIA_TYPE_VIDEO CODEC_TYPE_VIDEO +#endif + if (c->type != AVMEDIA_TYPE_VIDEO) + continue; + + /* Video encoder and decoder are usually implemented in separate + * AVCodec instances. While the codec attributes (e.g: raw formats, + * supported fps) are in the encoder. + */ + + //PJ_LOG(3, (THIS_FILE, "%s", c->name)); + status = CodecID_to_pjmedia_format_id(c->id, &fmt_id); + /* Skip if format ID is unknown */ + if (status != PJ_SUCCESS) + continue; + + codec_info_idx = find_codec_idx_by_fmt_id(fmt_id); + /* Skip if codec is unwanted by this wrapper (not listed in + * the codec info array) + */ + if (codec_info_idx < 0) + continue; + + desc = &codec_desc[codec_info_idx]; + + /* Skip duplicated codec implementation */ + if ((c->encode && (desc->info.dir & PJMEDIA_DIR_ENCODING)) || + (c->decode && (desc->info.dir & PJMEDIA_DIR_DECODING))) + { + continue; + } + + /* Get raw/decoded format ids in the encoder */ + if (c->pix_fmts && c->encode) { + pjmedia_format_id raw_fmt[PJMEDIA_VID_CODEC_MAX_DEC_FMT_CNT]; + unsigned raw_fmt_cnt = 0; + unsigned raw_fmt_cnt_should_be = 0; + const enum PixelFormat *p = c->pix_fmts; + + for(;(p && *p != -1) && + (raw_fmt_cnt < PJMEDIA_VID_CODEC_MAX_DEC_FMT_CNT); + ++p) + { + pjmedia_format_id fmt_id; + + raw_fmt_cnt_should_be++; + status = PixelFormat_to_pjmedia_format_id(*p, &fmt_id); + if (status != PJ_SUCCESS) { + PJ_LOG(6, (THIS_FILE, "Unrecognized ffmpeg pixel " + "format %d", *p)); + continue; + } + raw_fmt[raw_fmt_cnt++] = fmt_id; + } + + if (raw_fmt_cnt == 0) { + PJ_LOG(5, (THIS_FILE, "No recognized raw format " + "for codec [%s/%s], codec ignored", + c->name, c->long_name)); + /* Skip this encoder */ + continue; + } + + if (raw_fmt_cnt < raw_fmt_cnt_should_be) { + PJ_LOG(6, (THIS_FILE, "Codec [%s/%s] have %d raw formats, " + "recognized only %d raw formats", + c->name, c->long_name, + raw_fmt_cnt_should_be, raw_fmt_cnt)); + } + + desc->info.dec_fmt_id_cnt = raw_fmt_cnt; + pj_memcpy(desc->info.dec_fmt_id, raw_fmt, + sizeof(raw_fmt[0])*raw_fmt_cnt); + } + + /* Get supported framerates */ + if (c->supported_framerates) { + const AVRational *fr = c->supported_framerates; + while ((fr->num != 0 || fr->den != 0) && + desc->info.fps_cnt < PJMEDIA_VID_CODEC_MAX_FPS_CNT) + { + desc->info.fps[desc->info.fps_cnt].num = fr->num; + desc->info.fps[desc->info.fps_cnt].denum = fr->den; + ++desc->info.fps_cnt; + ++fr; + } + } + + /* Get ffmpeg encoder instance */ + if (c->encode && !desc->enc) { + desc->info.dir |= PJMEDIA_DIR_ENCODING; + desc->enc = c; + } + + /* Get ffmpeg decoder instance */ + if (c->decode && !desc->dec) { + desc->info.dir |= PJMEDIA_DIR_DECODING; + desc->dec = c; + } + + /* Enable this codec when any ffmpeg codec instance are recognized + * and the supported raw formats info has been collected. + */ + if ((desc->dec || desc->enc) && desc->info.dec_fmt_id_cnt) + { + desc->enabled = PJ_TRUE; + } + + /* Normalize default value of clock rate */ + if (desc->info.clock_rate == 0) + desc->info.clock_rate = 90000; + + /* Set RTP packetization support flag in the codec info */ + desc->info.has_rtp_pack = (desc->packetize != NULL) && + (desc->unpacketize != NULL); + } + + /* Review all codecs for applying base format, registering format match for + * SDP negotiation, etc. + */ + for (i = 0; i < PJ_ARRAY_SIZE(codec_desc); ++i) { + ffmpeg_codec_desc *desc = &codec_desc[i]; + + /* Init encoder/decoder description from base format */ + if (desc->base_fmt_id && (!desc->dec || !desc->enc)) { + ffmpeg_codec_desc *base_desc = NULL; + int base_desc_idx; + pjmedia_dir copied_dir = PJMEDIA_DIR_NONE; + + base_desc_idx = find_codec_idx_by_fmt_id(desc->base_fmt_id); + if (base_desc_idx != -1) + base_desc = &codec_desc[base_desc_idx]; + if (!base_desc || !base_desc->enabled) + continue; + + /* Copy description from base codec */ + if (!desc->info.dec_fmt_id_cnt) { + desc->info.dec_fmt_id_cnt = base_desc->info.dec_fmt_id_cnt; + pj_memcpy(desc->info.dec_fmt_id, base_desc->info.dec_fmt_id, + sizeof(pjmedia_format_id)*desc->info.dec_fmt_id_cnt); + } + if (!desc->info.fps_cnt) { + desc->info.fps_cnt = base_desc->info.fps_cnt; + pj_memcpy(desc->info.fps, base_desc->info.fps, + sizeof(desc->info.fps[0])*desc->info.fps_cnt); + } + if (!desc->info.clock_rate) { + desc->info.clock_rate = base_desc->info.clock_rate; + } + if (!desc->dec && base_desc->dec) { + copied_dir |= PJMEDIA_DIR_DECODING; + desc->dec = base_desc->dec; + } + if (!desc->enc && base_desc->enc) { + copied_dir |= PJMEDIA_DIR_ENCODING; + desc->enc = base_desc->enc; + } + + desc->info.dir |= copied_dir; + desc->enabled = (desc->info.dir != PJMEDIA_DIR_NONE); + desc->info.has_rtp_pack = (desc->packetize != NULL) && + (desc->unpacketize != NULL); + + if (copied_dir != PJMEDIA_DIR_NONE) { + const char *dir_name[] = {NULL, "encoder", "decoder", "codec"}; + PJ_LOG(5, (THIS_FILE, "The %.*s %s is using base codec (%.*s)", + desc->info.encoding_name.slen, + desc->info.encoding_name.ptr, + dir_name[copied_dir], + base_desc->info.encoding_name.slen, + base_desc->info.encoding_name.ptr)); + } + } + + /* Registering format match for SDP negotiation */ + if (desc->sdp_fmt_match) { + status = pjmedia_sdp_neg_register_fmt_match_cb( + &desc->info.encoding_name, + desc->sdp_fmt_match); + pj_assert(status == PJ_SUCCESS); + } + } + + /* Register codec factory to codec manager. */ + status = pjmedia_vid_codec_mgr_register_factory(mgr, + &ffmpeg_factory.base); + if (status != PJ_SUCCESS) + goto on_error; + + ffmpeg_factory.pool = pool; + + /* Done. */ + return PJ_SUCCESS; + +on_error: + pj_pool_release(pool); + return status; +} + +/* + * Unregister FFMPEG codecs factory from pjmedia endpoint. + */ +PJ_DEF(pj_status_t) pjmedia_codec_ffmpeg_deinit(void) +{ + pj_status_t status = PJ_SUCCESS; + + if (ffmpeg_factory.pool == NULL) { + /* Already deinitialized */ + return PJ_SUCCESS; + } + + pj_mutex_lock(ffmpeg_factory.mutex); + + /* Unregister FFMPEG codecs factory. */ + status = pjmedia_vid_codec_mgr_unregister_factory(ffmpeg_factory.mgr, + &ffmpeg_factory.base); + + /* Destroy mutex. */ + pj_mutex_destroy(ffmpeg_factory.mutex); + + /* Destroy pool. */ + pj_pool_release(ffmpeg_factory.pool); + ffmpeg_factory.pool = NULL; + + return status; +} + + +/* + * Check if factory can allocate the specified codec. + */ +static pj_status_t ffmpeg_test_alloc( pjmedia_vid_codec_factory *factory, + const pjmedia_vid_codec_info *info ) +{ + const ffmpeg_codec_desc *desc; + + PJ_ASSERT_RETURN(factory==&ffmpeg_factory.base, PJ_EINVAL); + PJ_ASSERT_RETURN(info, PJ_EINVAL); + + desc = find_codec_desc_by_info(info); + if (!desc) { + return PJMEDIA_CODEC_EUNSUP; + } + + return PJ_SUCCESS; +} + +/* + * Generate default attribute. + */ +static pj_status_t ffmpeg_default_attr( pjmedia_vid_codec_factory *factory, + const pjmedia_vid_codec_info *info, + pjmedia_vid_codec_param *attr ) +{ + const ffmpeg_codec_desc *desc; + + PJ_ASSERT_RETURN(factory==&ffmpeg_factory.base, PJ_EINVAL); + PJ_ASSERT_RETURN(info && attr, PJ_EINVAL); + + desc = find_codec_desc_by_info(info); + if (!desc) { + return PJMEDIA_CODEC_EUNSUP; + } + + pj_bzero(attr, sizeof(pjmedia_vid_codec_param)); + + /* Direction */ + attr->dir = desc->info.dir; + + /* Encoded format */ + pjmedia_format_init_video(&attr->enc_fmt, desc->info.fmt_id, + 352, 288, 30000, 1001); + + /* Decoded format */ + pjmedia_format_init_video(&attr->dec_fmt, desc->info.dec_fmt_id[0], + //352, 288, 30000, 1001); + 720, 576, 30000, 1001); + + /* Decoding fmtp */ + attr->dec_fmtp = desc->dec_fmtp; + + /* Bitrate */ + attr->enc_fmt.det.vid.avg_bps = desc->avg_bps; + attr->enc_fmt.det.vid.max_bps = desc->max_bps; + + /* MTU */ + attr->enc_mtu = PJMEDIA_MAX_MTU; + + return PJ_SUCCESS; +} + +/* + * Enum codecs supported by this factory. + */ +static pj_status_t ffmpeg_enum_codecs( pjmedia_vid_codec_factory *factory, + unsigned *count, + pjmedia_vid_codec_info codecs[]) +{ + unsigned i, max_cnt; + + PJ_ASSERT_RETURN(codecs && *count > 0, PJ_EINVAL); + PJ_ASSERT_RETURN(factory == &ffmpeg_factory.base, PJ_EINVAL); + + max_cnt = PJ_MIN(*count, PJ_ARRAY_SIZE(codec_desc)); + *count = 0; + + for (i=0; i<max_cnt; ++i) { + if (codec_desc[i].enabled) { + pj_memcpy(&codecs[*count], &codec_desc[i].info, + sizeof(pjmedia_vid_codec_info)); + (*count)++; + } + } + + return PJ_SUCCESS; +} + +/* + * Allocate a new codec instance. + */ +static pj_status_t ffmpeg_alloc_codec( pjmedia_vid_codec_factory *factory, + const pjmedia_vid_codec_info *info, + pjmedia_vid_codec **p_codec) +{ + ffmpeg_private *ff; + const ffmpeg_codec_desc *desc; + pjmedia_vid_codec *codec; + pj_pool_t *pool = NULL; + pj_status_t status = PJ_SUCCESS; + + PJ_ASSERT_RETURN(factory && info && p_codec, PJ_EINVAL); + PJ_ASSERT_RETURN(factory == &ffmpeg_factory.base, PJ_EINVAL); + + desc = find_codec_desc_by_info(info); + if (!desc) { + return PJMEDIA_CODEC_EUNSUP; + } + + /* Create pool for codec instance */ + pool = pj_pool_create(ffmpeg_factory.pf, "ffmpeg codec", 512, 512, NULL); + codec = PJ_POOL_ALLOC_T(pool, pjmedia_vid_codec); + if (!codec) { + status = PJ_ENOMEM; + goto on_error; + } + pjmedia_vid_codec_reset(codec, PJMEDIA_SIG_VID_CODEC_FFMPEG); + codec->op = &ffmpeg_op; + codec->factory = factory; + ff = PJ_POOL_ZALLOC_T(pool, ffmpeg_private); + if (!ff) { + status = PJ_ENOMEM; + goto on_error; + } + codec->codec_data = ff; + ff->pool = pool; + ff->enc = desc->enc; + ff->dec = desc->dec; + ff->desc = desc; + + *p_codec = codec; + return PJ_SUCCESS; + +on_error: + if (pool) + pj_pool_release(pool); + return status; +} + +/* + * Free codec. + */ +static pj_status_t ffmpeg_dealloc_codec( pjmedia_vid_codec_factory *factory, + pjmedia_vid_codec *codec ) +{ + ffmpeg_private *ff; + pj_pool_t *pool; + + PJ_ASSERT_RETURN(factory && codec, PJ_EINVAL); + PJ_ASSERT_RETURN(factory == &ffmpeg_factory.base, PJ_EINVAL); + + /* Close codec, if it's not closed. */ + ff = (ffmpeg_private*) codec->codec_data; + pool = ff->pool; + codec->codec_data = NULL; + pj_pool_release(pool); + + return PJ_SUCCESS; +} + +/* + * Init codec. + */ +static pj_status_t ffmpeg_codec_init( pjmedia_vid_codec *codec, + pj_pool_t *pool ) +{ + PJ_UNUSED_ARG(codec); + PJ_UNUSED_ARG(pool); + return PJ_SUCCESS; +} + +static void print_ffmpeg_err(int err) +{ +#if LIBAVCODEC_VERSION_MAJOR > 52 || \ + (LIBAVCODEC_VERSION_MAJOR >= 52 && LIBAVCODEC_VERSION_MINOR >= 72) + char errbuf[512]; + if (av_strerror(err, errbuf, sizeof(errbuf)) >= 0) + PJ_LOG(5, (THIS_FILE, "ffmpeg err %d: %s", err, errbuf)); +#else + PJ_LOG(5, (THIS_FILE, "ffmpeg err %d", err)); +#endif + +} + +static enum PixelFormat dec_get_format(struct AVCodecContext *s, + const enum PixelFormat * fmt) +{ + ffmpeg_private *ff = (ffmpeg_private*)s->opaque; + enum PixelFormat def_fmt = *fmt; + + while (*fmt != -1) { + if (*fmt == ff->expected_dec_fmt) + return *fmt; + ++fmt; + } + + pj_assert(!"Inconsistency in supported formats"); + return def_fmt; +} + + +static pj_status_t open_ffmpeg_codec(ffmpeg_private *ff, + pj_mutex_t *ff_mutex) +{ + enum PixelFormat pix_fmt; + pjmedia_video_format_detail *vfd; + pj_bool_t enc_opened = PJ_FALSE, dec_opened = PJ_FALSE; + pj_status_t status; + + /* Get decoded pixel format */ + status = pjmedia_format_id_to_PixelFormat(ff->param.dec_fmt.id, + &pix_fmt); + if (status != PJ_SUCCESS) + return status; + ff->expected_dec_fmt = pix_fmt; + + /* Get video format detail for shortcut access to encoded format */ + vfd = pjmedia_format_get_video_format_detail(&ff->param.enc_fmt, + PJ_TRUE); + + /* Allocate ffmpeg codec context */ + if (ff->param.dir & PJMEDIA_DIR_ENCODING) { + ff->enc_ctx = avcodec_alloc_context(); + if (ff->enc_ctx == NULL) + goto on_error; + } + if (ff->param.dir & PJMEDIA_DIR_DECODING) { + ff->dec_ctx = avcodec_alloc_context(); + if (ff->dec_ctx == NULL) + goto on_error; + } + + /* Let the codec apply specific settings before the codec opened */ + if (ff->desc->preopen) { + status = (*ff->desc->preopen)(ff); + if (status != PJ_SUCCESS) + goto on_error; + } + + if (ff->param.dir & PJMEDIA_DIR_ENCODING) { + AVCodecContext *ctx = ff->enc_ctx; + int err; + + /* Init common settings */ + ctx->pix_fmt = pix_fmt; + ctx->width = vfd->size.w; + ctx->height = vfd->size.h; + ctx->time_base.num = vfd->fps.denum; + ctx->time_base.den = vfd->fps.num; + if (vfd->avg_bps) { + ctx->bit_rate = vfd->avg_bps; + if (vfd->max_bps > vfd->avg_bps) + ctx->bit_rate_tolerance = vfd->max_bps - vfd->avg_bps; + } + ctx->strict_std_compliance = FF_COMPLIANCE_STRICT; + ctx->workaround_bugs = FF_BUG_AUTODETECT; + ctx->opaque = ff; + + /* Set no delay, note that this may cause some codec functionals + * not working (e.g: rate control). + */ +#if LIBAVCODEC_VERSION_MAJOR > 52 || \ + (LIBAVCODEC_VERSION_MAJOR >= 52 && LIBAVCODEC_VERSION_MINOR >= 113) + ctx->rc_lookahead = 0; +#endif + + /* Open ffmpeg codec */ + pj_mutex_lock(ff_mutex); + err = avcodec_open(ctx, ff->enc); + pj_mutex_unlock(ff_mutex); + if (err < 0) { + print_ffmpeg_err(err); + status = PJMEDIA_CODEC_EFAILED; + goto on_error; + } + enc_opened = PJ_TRUE; + } + + if (ff->param.dir & PJMEDIA_DIR_DECODING) { + AVCodecContext *ctx = ff->dec_ctx; + int err; + + /* Init common settings */ + /* Width/height may be overriden by ffmpeg after first decoding. */ + ctx->width = ctx->coded_width = ff->param.dec_fmt.det.vid.size.w; + ctx->height = ctx->coded_height = ff->param.dec_fmt.det.vid.size.h; + ctx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL; + ctx->workaround_bugs = FF_BUG_AUTODETECT; + ctx->opaque = ff; + ctx->get_format = &dec_get_format; + + /* Open ffmpeg codec */ + pj_mutex_lock(ff_mutex); + err = avcodec_open(ctx, ff->dec); + pj_mutex_unlock(ff_mutex); + if (err < 0) { + print_ffmpeg_err(err); + status = PJMEDIA_CODEC_EFAILED; + goto on_error; + } + dec_opened = PJ_TRUE; + } + + /* Let the codec apply specific settings after the codec opened */ + if (ff->desc->postopen) { + status = (*ff->desc->postopen)(ff); + if (status != PJ_SUCCESS) + goto on_error; + } + + return PJ_SUCCESS; + +on_error: + if (ff->enc_ctx) { + if (enc_opened) + avcodec_close(ff->enc_ctx); + av_free(ff->enc_ctx); + ff->enc_ctx = NULL; + } + if (ff->dec_ctx) { + if (dec_opened) + avcodec_close(ff->dec_ctx); + av_free(ff->dec_ctx); + ff->dec_ctx = NULL; + } + return status; +} + +/* + * Open codec. + */ +static pj_status_t ffmpeg_codec_open( pjmedia_vid_codec *codec, + pjmedia_vid_codec_param *attr ) +{ + ffmpeg_private *ff; + pj_status_t status; + pj_mutex_t *ff_mutex; + + PJ_ASSERT_RETURN(codec && attr, PJ_EINVAL); + ff = (ffmpeg_private*)codec->codec_data; + + pj_memcpy(&ff->param, attr, sizeof(*attr)); + + /* Open the codec */ + ff_mutex = ((struct ffmpeg_factory*)codec->factory)->mutex; + status = open_ffmpeg_codec(ff, ff_mutex); + if (status != PJ_SUCCESS) + goto on_error; + + /* Init format info and apply-param of decoder */ + ff->dec_vfi = pjmedia_get_video_format_info(NULL, ff->param.dec_fmt.id); + if (!ff->dec_vfi) { + status = PJ_EINVAL; + goto on_error; + } + pj_bzero(&ff->dec_vafp, sizeof(ff->dec_vafp)); + ff->dec_vafp.size = ff->param.dec_fmt.det.vid.size; + ff->dec_vafp.buffer = NULL; + status = (*ff->dec_vfi->apply_fmt)(ff->dec_vfi, &ff->dec_vafp); + if (status != PJ_SUCCESS) { + goto on_error; + } + + /* Init format info and apply-param of encoder */ + ff->enc_vfi = pjmedia_get_video_format_info(NULL, ff->param.dec_fmt.id); + if (!ff->enc_vfi) { + status = PJ_EINVAL; + goto on_error; + } + pj_bzero(&ff->enc_vafp, sizeof(ff->enc_vafp)); + ff->enc_vafp.size = ff->param.enc_fmt.det.vid.size; + ff->enc_vafp.buffer = NULL; + status = (*ff->enc_vfi->apply_fmt)(ff->enc_vfi, &ff->enc_vafp); + if (status != PJ_SUCCESS) { + goto on_error; + } + + /* Update codec attributes, e.g: encoding format may be changed by + * SDP fmtp negotiation. + */ + pj_memcpy(attr, &ff->param, sizeof(*attr)); + + return PJ_SUCCESS; + +on_error: + ffmpeg_codec_close(codec); + return status; +} + +/* + * Close codec. + */ +static pj_status_t ffmpeg_codec_close( pjmedia_vid_codec *codec ) +{ + ffmpeg_private *ff; + pj_mutex_t *ff_mutex; + + PJ_ASSERT_RETURN(codec, PJ_EINVAL); + ff = (ffmpeg_private*)codec->codec_data; + ff_mutex = ((struct ffmpeg_factory*)codec->factory)->mutex; + + pj_mutex_lock(ff_mutex); + if (ff->enc_ctx) { + avcodec_close(ff->enc_ctx); + av_free(ff->enc_ctx); + } + if (ff->dec_ctx && ff->dec_ctx!=ff->enc_ctx) { + avcodec_close(ff->dec_ctx); + av_free(ff->dec_ctx); + } + ff->enc_ctx = NULL; + ff->dec_ctx = NULL; + pj_mutex_unlock(ff_mutex); + + return PJ_SUCCESS; +} + + +/* + * Modify codec settings. + */ +static pj_status_t ffmpeg_codec_modify( pjmedia_vid_codec *codec, + const pjmedia_vid_codec_param *attr) +{ + ffmpeg_private *ff = (ffmpeg_private*)codec->codec_data; + + PJ_UNUSED_ARG(attr); + PJ_UNUSED_ARG(ff); + + return PJ_ENOTSUP; +} + +static pj_status_t ffmpeg_codec_get_param(pjmedia_vid_codec *codec, + pjmedia_vid_codec_param *param) +{ + ffmpeg_private *ff; + + PJ_ASSERT_RETURN(codec && param, PJ_EINVAL); + + ff = (ffmpeg_private*)codec->codec_data; + pj_memcpy(param, &ff->param, sizeof(*param)); + + return PJ_SUCCESS; +} + + +static pj_status_t ffmpeg_packetize ( pjmedia_vid_codec *codec, + pj_uint8_t *bits, + pj_size_t bits_len, + unsigned *bits_pos, + const pj_uint8_t **payload, + pj_size_t *payload_len) +{ + ffmpeg_private *ff = (ffmpeg_private*)codec->codec_data; + + if (ff->desc->packetize) { + return (*ff->desc->packetize)(ff, bits, bits_len, bits_pos, + payload, payload_len); + } + + return PJ_ENOTSUP; +} + +static pj_status_t ffmpeg_unpacketize(pjmedia_vid_codec *codec, + const pj_uint8_t *payload, + pj_size_t payload_len, + pj_uint8_t *bits, + pj_size_t bits_len, + unsigned *bits_pos) +{ + ffmpeg_private *ff = (ffmpeg_private*)codec->codec_data; + + if (ff->desc->unpacketize) { + return (*ff->desc->unpacketize)(ff, payload, payload_len, + bits, bits_len, bits_pos); + } + + return PJ_ENOTSUP; +} + + +/* + * Encode frames. + */ +static pj_status_t ffmpeg_codec_encode( pjmedia_vid_codec *codec, + const pjmedia_frame *input, + unsigned output_buf_len, + pjmedia_frame *output) +{ + ffmpeg_private *ff = (ffmpeg_private*)codec->codec_data; + pj_uint8_t *p = (pj_uint8_t*)input->buf; + AVFrame avframe; + pj_uint8_t *out_buf = (pj_uint8_t*)output->buf; + int out_buf_len = output_buf_len; + int err; + //AVRational src_timebase; + /* For some reasons (e.g: SSE/MMX usage), the avcodec_encode_video() must + * have stack aligned to 16 bytes. Let's try to be safe by preparing the + * 16-bytes aligned stack here, in case it's not managed by the ffmpeg. + */ + PJ_ALIGN_DATA(pj_uint32_t i[4], 16); + + if ((long)i & 0xF) { + PJ_LOG(2,(THIS_FILE, "Stack alignment fails")); + } + + /* Check if encoder has been opened */ + PJ_ASSERT_RETURN(ff->enc_ctx, PJ_EINVALIDOP); + + avcodec_get_frame_defaults(&avframe); + + // Let ffmpeg manage the timestamps + /* + src_timebase.num = 1; + src_timebase.den = ff->desc->info.clock_rate; + avframe.pts = av_rescale_q(input->timestamp.u64, src_timebase, + ff->enc_ctx->time_base); + */ + + for (i[0] = 0; i[0] < ff->enc_vfi->plane_cnt; ++i[0]) { + avframe.data[i[0]] = p; + avframe.linesize[i[0]] = ff->enc_vafp.strides[i[0]]; + p += ff->enc_vafp.plane_bytes[i[0]]; + } + + err = avcodec_encode_video(ff->enc_ctx, out_buf, out_buf_len, &avframe); + if (err < 0) { + print_ffmpeg_err(err); + return PJMEDIA_CODEC_EFAILED; + } else { + output->size = err; + } + + return PJ_SUCCESS; +} + +/* + * Decode frame. + */ +static pj_status_t ffmpeg_codec_decode( pjmedia_vid_codec *codec, + const pjmedia_frame *input, + unsigned output_buf_len, + pjmedia_frame *output) +{ + ffmpeg_private *ff = (ffmpeg_private*)codec->codec_data; + AVFrame avframe; + AVPacket avpacket; + int err, got_picture; + + /* Check if decoder has been opened */ + PJ_ASSERT_RETURN(ff->dec_ctx, PJ_EINVALIDOP); + + /* Reset output frame bit info */ + output->bit_info = 0; + + /* Validate output buffer size */ + // Do this validation later after getting decoding result, where the real + // decoded size will be assured. + //if (ff->dec_vafp.framebytes > output_buf_len) + //return PJ_ETOOSMALL; + + /* Init frame to receive the decoded data, the ffmpeg codec context will + * automatically provide the decoded buffer (single buffer used for the + * whole decoding session, and seems to be freed when the codec context + * closed). + */ + avcodec_get_frame_defaults(&avframe); + + /* Init packet, the container of the encoded data */ + av_init_packet(&avpacket); + avpacket.data = (pj_uint8_t*)input->buf; + avpacket.size = input->size; + + /* ffmpeg warns: + * - input buffer padding, at least FF_INPUT_BUFFER_PADDING_SIZE + * - null terminated + * Normally, encoded buffer is allocated more than needed, so lets just + * bzero the input buffer end/pad, hope it will be just fine. + */ + pj_bzero(avpacket.data+avpacket.size, FF_INPUT_BUFFER_PADDING_SIZE); + + output->bit_info = 0; + output->timestamp = input->timestamp; + +#if LIBAVCODEC_VERSION_MAJOR > 52 || \ + (LIBAVCODEC_VERSION_MAJOR >= 52 && LIBAVCODEC_VERSION_MINOR >= 72) + //avpacket.flags = AV_PKT_FLAG_KEY; +#else + avpacket.flags = 0; +#endif + +#if LIBAVCODEC_VERSION_MAJOR > 52 || \ + (LIBAVCODEC_VERSION_MAJOR >= 52 && LIBAVCODEC_VERSION_MINOR >= 72) + err = avcodec_decode_video2(ff->dec_ctx, &avframe, + &got_picture, &avpacket); +#else + err = avcodec_decode_video(ff->dec_ctx, &avframe, + &got_picture, avpacket.data, avpacket.size); +#endif + if (err < 0) { + output->type = PJMEDIA_FRAME_TYPE_NONE; + output->size = 0; + print_ffmpeg_err(err); + return PJMEDIA_CODEC_EFAILED; + } else if (got_picture) { + pjmedia_video_apply_fmt_param *vafp = &ff->dec_vafp; + pj_uint8_t *q = (pj_uint8_t*)output->buf; + unsigned i; + + /* Decoder output format is set by libavcodec, in case it is different + * to the configured param. + */ + if (ff->dec_ctx->pix_fmt != ff->expected_dec_fmt || + ff->dec_ctx->width != (int)vafp->size.w || + ff->dec_ctx->height != (int)vafp->size.h) + { + pjmedia_format_id new_fmt_id; + pj_status_t status; + + /* Get current raw format id from ffmpeg decoder context */ + status = PixelFormat_to_pjmedia_format_id(ff->dec_ctx->pix_fmt, + &new_fmt_id); + if (status != PJ_SUCCESS) + return status; + + /* Update decoder format in param */ + ff->param.dec_fmt.id = new_fmt_id; + ff->param.dec_fmt.det.vid.size.w = ff->dec_ctx->width; + ff->param.dec_fmt.det.vid.size.h = ff->dec_ctx->height; + + /* Re-init format info and apply-param of decoder */ + ff->dec_vfi = pjmedia_get_video_format_info(NULL, ff->param.dec_fmt.id); + if (!ff->dec_vfi) + return PJ_ENOTSUP; + pj_bzero(&ff->dec_vafp, sizeof(ff->dec_vafp)); + ff->dec_vafp.size = ff->param.dec_fmt.det.vid.size; + ff->dec_vafp.buffer = NULL; + status = (*ff->dec_vfi->apply_fmt)(ff->dec_vfi, &ff->dec_vafp); + if (status != PJ_SUCCESS) + return status; + + /* Broadcast event */ + if (pjmedia_event_publisher_has_sub(&codec->epub)) { + pjmedia_event event; + + pjmedia_event_init(&event, PJMEDIA_EVENT_FMT_CHANGED, + &input->timestamp, &codec->epub); + event.data.fmt_changed.dir = PJMEDIA_DIR_DECODING; + pj_memcpy(&event.data.fmt_changed.new_fmt, &ff->param.dec_fmt, + sizeof(ff->param.dec_fmt)); + pjmedia_event_publish(&codec->epub, &event); + } + } + + /* Check provided buffer size */ + if (vafp->framebytes > output_buf_len) + return PJ_ETOOSMALL; + + /* Get the decoded data */ + for (i = 0; i < ff->dec_vfi->plane_cnt; ++i) { + pj_uint8_t *p = avframe.data[i]; + + /* The decoded data may contain padding */ + if (avframe.linesize[i]!=vafp->strides[i]) { + /* Padding exists, copy line by line */ + pj_uint8_t *q_end; + + q_end = q+vafp->plane_bytes[i]; + while(q < q_end) { + pj_memcpy(q, p, vafp->strides[i]); + q += vafp->strides[i]; + p += avframe.linesize[i]; + } + } else { + /* No padding, copy the whole plane */ + pj_memcpy(q, p, vafp->plane_bytes[i]); + q += vafp->plane_bytes[i]; + } + } + + output->type = PJMEDIA_FRAME_TYPE_VIDEO; + output->size = vafp->framebytes; + + /* Check if we got key frame */ + if (avframe.key_frame && pjmedia_event_publisher_has_sub(&codec->epub)) + { + pjmedia_event event; + + pjmedia_event_init(&event, PJMEDIA_EVENT_KEY_FRAME_FOUND, + &output->timestamp, &codec->epub); + pjmedia_event_publish(&codec->epub, &event); + } + } else { + output->type = PJMEDIA_FRAME_TYPE_NONE; + output->size = 0; + } + + return PJ_SUCCESS; +} + +/* + * Recover lost frame. + */ +static pj_status_t ffmpeg_codec_recover( pjmedia_vid_codec *codec, + unsigned output_buf_len, + pjmedia_frame *output) +{ + PJ_UNUSED_ARG(codec); + PJ_UNUSED_ARG(output_buf_len); + PJ_UNUSED_ARG(output); + + return PJ_ENOTSUP; +} + +#ifdef _MSC_VER +# pragma comment( lib, "avcodec.lib") +#endif + +#endif /* PJMEDIA_HAS_FFMPEG_CODEC */ + diff --git a/pjmedia/src/pjmedia-codec/g722.c b/pjmedia/src/pjmedia-codec/g722.c index 8cd332d8..acddc142 100644 --- a/pjmedia/src/pjmedia-codec/g722.c +++ b/pjmedia/src/pjmedia-codec/g722.c @@ -122,7 +122,8 @@ static pjmedia_codec_factory_op g722_factory_op = &g722_default_attr, &g722_enum_codecs, &g722_alloc_codec, - &g722_dealloc_codec + &g722_dealloc_codec, + &pjmedia_codec_g722_deinit }; /* G722 factory */ diff --git a/pjmedia/src/pjmedia-codec/g7221.c b/pjmedia/src/pjmedia-codec/g7221.c index f1aa8ab8..44ecabc9 100644 --- a/pjmedia/src/pjmedia-codec/g7221.c +++ b/pjmedia/src/pjmedia-codec/g7221.c @@ -115,7 +115,8 @@ static pjmedia_codec_factory_op codec_factory_op = &default_attr, &enum_codecs, &alloc_codec, - &dealloc_codec + &dealloc_codec, + &pjmedia_codec_g7221_deinit }; diff --git a/pjmedia/src/pjmedia-codec/gsm.c b/pjmedia/src/pjmedia-codec/gsm.c index ea4bb63a..43149b78 100644 --- a/pjmedia/src/pjmedia-codec/gsm.c +++ b/pjmedia/src/pjmedia-codec/gsm.c @@ -117,7 +117,8 @@ static pjmedia_codec_factory_op gsm_factory_op = &gsm_default_attr, &gsm_enum_codecs, &gsm_alloc_codec, - &gsm_dealloc_codec + &gsm_dealloc_codec, + &pjmedia_codec_gsm_deinit }; /* GSM factory */ diff --git a/pjmedia/src/pjmedia-codec/h263_packetizer.c b/pjmedia/src/pjmedia-codec/h263_packetizer.c new file mode 100644 index 00000000..149c2e64 --- /dev/null +++ b/pjmedia/src/pjmedia-codec/h263_packetizer.c @@ -0,0 +1,287 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjmedia-codec/h263_packetizer.h> +#include <pjmedia/types.h> +#include <pj/assert.h> +#include <pj/errno.h> +#include <pj/string.h> + +#define THIS_FILE "h263_packetizer.c" + + +/* H.263 packetizer definition */ +struct pjmedia_h263_packetizer { + /* Current settings */ + pjmedia_h263_packetizer_cfg cfg; + + /* Unpacketizer state */ + unsigned unpack_last_sync_pos; + pj_bool_t unpack_prev_lost; +}; + + +/* + * Find synchronization point (PSC, slice, GSBC, EOS, EOSBS) in H.263 + * bitstream. + */ +static pj_uint8_t* find_sync_point(pj_uint8_t *data, + pj_size_t data_len) +{ + pj_uint8_t *p = data, *end = data+data_len-1; + + while (p < end && (*p || *(p+1))) + ++p; + + if (p == end) + return NULL; + + return p; +} + + +/* + * Find synchronization point (PSC, slice, GSBC, EOS, EOSBS) in H.263 + * bitstream, in reversed manner. + */ +static pj_uint8_t* find_sync_point_rev(pj_uint8_t *data, + pj_size_t data_len) +{ + pj_uint8_t *p = data+data_len-2; + + while (p >= data && (*p || *(p+1))) + --p; + + if (p < data) + return (data + data_len); + + return p; +} + + +/* + * Create H263 packetizer. + */ +PJ_DEF(pj_status_t) pjmedia_h263_packetizer_create( + pj_pool_t *pool, + const pjmedia_h263_packetizer_cfg *cfg, + pjmedia_h263_packetizer **p) +{ + pjmedia_h263_packetizer *p_; + + PJ_ASSERT_RETURN(pool && p, PJ_EINVAL); + + if (cfg && cfg->mode != PJMEDIA_H263_PACKETIZER_MODE_RFC4629) + return PJ_ENOTSUP; + + p_ = PJ_POOL_ZALLOC_T(pool, pjmedia_h263_packetizer); + if (cfg) { + pj_memcpy(&p_->cfg, cfg, sizeof(*cfg)); + } else { + p_->cfg.mode = PJMEDIA_H263_PACKETIZER_MODE_RFC4629; + p_->cfg.mtu = PJMEDIA_MAX_MTU; + } + + *p = p_; + + return PJ_SUCCESS; +} + + +/* + * Generate an RTP payload from H.263 frame bitstream, in-place processing. + */ +PJ_DEF(pj_status_t) pjmedia_h263_packetize(pjmedia_h263_packetizer *pktz, + pj_uint8_t *bits, + pj_size_t bits_len, + unsigned *pos, + const pj_uint8_t **payload, + pj_size_t *payload_len) +{ + pj_uint8_t *p, *end; + + pj_assert(pktz && bits && pos && payload && payload_len); + pj_assert(*pos <= bits_len); + + p = bits + *pos; + end = bits + bits_len; + + /* Put two octets payload header */ + if ((end-p > 2) && *p==0 && *(p+1)==0) { + /* The bitstream starts with synchronization point, just override + * the two zero octets (sync point mark) for payload header. + */ + *p = 0x04; + } else { + /* Not started in synchronization point, we will use two octets + * preceeding the bitstream for payload header! + */ + + if (*pos < 2) { + /* Invalid H263 bitstream, it's not started with PSC */ + return PJ_EINVAL; + } + + p -= 2; + *p = 0; + } + *(p+1) = 0; + + /* When bitstream truncation needed because of payload length/MTU + * limitation, try to use sync point for the payload boundary. + */ + if (end-p > pktz->cfg.mtu) { + end = find_sync_point_rev(p+2, pktz->cfg.mtu-2); + } + + *payload = p; + *payload_len = end-p; + *pos = end - bits; + + return PJ_SUCCESS; +} + + +/* + * Append an RTP payload to a H.263 picture bitstream. + */ +PJ_DEF(pj_status_t) pjmedia_h263_unpacketize (pjmedia_h263_packetizer *pktz, + const pj_uint8_t *payload, + pj_size_t payload_len, + pj_uint8_t *bits, + pj_size_t bits_size, + unsigned *pos) +{ + pj_uint8_t P, V, PLEN; + const pj_uint8_t *p = payload; + pj_uint8_t *q; + + q = bits + *pos; + + /* Check if this is a missing/lost packet */ + if (payload == NULL) { + pktz->unpack_prev_lost = PJ_TRUE; + return PJ_SUCCESS; + } + + /* H263 payload header size is two octets */ + if (payload_len < 2) { + /* Invalid bitstream, discard this payload */ + pktz->unpack_prev_lost = PJ_TRUE; + return PJ_EINVAL; + } + + /* Reset last sync point for every new picture bitstream */ + if (*pos == 0) + pktz->unpack_last_sync_pos = 0; + + /* Get payload header info */ + P = *p & 0x04; + V = *p & 0x02; + PLEN = ((*p & 0x01) << 5) + ((*(p+1) & 0xF8)>>3); + + /* Get start bitstream pointer */ + p += 2; /* Skip payload header */ + if (V) + p += 1; /* Skip VRC data */ + if (PLEN) + p += PLEN; /* Skip extra picture header data */ + + /* Get bitstream length */ + if (payload_len > (pj_size_t)(p - payload)) { + payload_len -= (p - payload); + } else { + /* Invalid bitstream, discard this payload */ + pktz->unpack_prev_lost = PJ_TRUE; + return PJ_EINVAL; + } + + /* Validate bitstream length */ + if (bits_size < *pos + payload_len + 2) { + /* Insufficient bistream buffer, discard this payload */ + pj_assert(!"Insufficient H.263 bitstream buffer"); + pktz->unpack_prev_lost = PJ_TRUE; + return PJ_ETOOSMALL; + } + + /* Start writing bitstream */ + + /* No sync point flag */ + if (!P) { + if (*pos == 0) { + /* Previous packet must be lost */ + pktz->unpack_prev_lost = PJ_TRUE; + + /* If there is extra picture header, let's use it. */ + if (PLEN) { + /* Write two zero octets for PSC */ + *q++ = 0; + *q++ = 0; + /* Copy the picture header */ + p -= PLEN; + pj_memcpy(q, p, PLEN); + p += PLEN; + q += PLEN; + } + } else if (pktz->unpack_prev_lost) { + /* If prev packet was lost, revert the bitstream pointer to + * the last sync point. + */ + pj_assert(pktz->unpack_last_sync_pos <= *pos); + q = bits + pktz->unpack_last_sync_pos; + } + + /* There was packet lost, see if this payload contain sync point + * (usable data). + */ + if (pktz->unpack_prev_lost) { + pj_uint8_t *sync; + sync = find_sync_point((pj_uint8_t*)p, payload_len); + if (sync) { + /* Got sync point, update P/sync-point flag */ + P = 1; + /* Skip the two zero octets */ + sync += 2; + /* Update payload length and start bitstream pointer */ + payload_len -= (sync - p); + p = sync; + } else { + /* No sync point in it, just discard this payload */ + return PJ_EIGNORED; + } + } + } + + /* Write two zero octets when payload flagged with sync point */ + if (P) { + pktz->unpack_last_sync_pos = q - bits; + *q++ = 0; + *q++ = 0; + } + + /* Write the payload to the bitstream */ + pj_memcpy(q, p, payload_len); + q += payload_len; + + /* Update the bitstream writing offset */ + *pos = q - bits; + + pktz->unpack_prev_lost = PJ_FALSE; + + return PJ_SUCCESS; +} diff --git a/pjmedia/src/pjmedia-codec/h264_packetizer.c b/pjmedia/src/pjmedia-codec/h264_packetizer.c new file mode 100644 index 00000000..8eb22d6f --- /dev/null +++ b/pjmedia/src/pjmedia-codec/h264_packetizer.c @@ -0,0 +1,530 @@ +/* $Id$ */ +/* + * Copyright (C) 2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjmedia-codec/h264_packetizer.h> +#include <pjmedia/types.h> +#include <pj/assert.h> +#include <pj/errno.h> +#include <pj/log.h> +#include <pj/pool.h> +#include <pj/string.h> + +#define THIS_FILE "h264_packetizer.c" + +#define DBG_PACKETIZE 0 +#define DBG_UNPACKETIZE 0 + + +/* H.264 packetizer definition */ +struct pjmedia_h264_packetizer +{ + /* Current settings */ + pjmedia_h264_packetizer_cfg cfg; + + /* Unpacketizer state */ + unsigned unpack_last_sync_pos; + pj_bool_t unpack_prev_lost; +}; + + +/* Enumeration of H.264 NAL unit types */ +enum +{ + NAL_TYPE_SINGLE_NAL_MIN = 1, + NAL_TYPE_SINGLE_NAL_MAX = 23, + NAL_TYPE_STAP_A = 24, + NAL_TYPE_FU_A = 28, +}; + + +/* + * Find next NAL unit from the specified H.264 bitstream data. + */ +static pj_uint8_t* find_next_nal_unit(pj_uint8_t *start, + pj_uint8_t *end) +{ + pj_uint8_t *p = start; + + /* Simply lookup "0x000001" pattern */ + while (p <= end-3 && (p[0] || p[1] || p[2]!=1)) + ++p; + + if (p > end-3) + /* No more NAL unit in this bitstream */ + return NULL; + + /* Include 8 bits leading zero */ + if (p>start && *(p-1)==0) + return (p-1); + + return p; +} + + +/* + * Create H264 packetizer. + */ +PJ_DEF(pj_status_t) pjmedia_h264_packetizer_create( + pj_pool_t *pool, + const pjmedia_h264_packetizer_cfg *cfg, + pjmedia_h264_packetizer **p) +{ + pjmedia_h264_packetizer *p_; + + PJ_ASSERT_RETURN(pool && p, PJ_EINVAL); + + if (cfg && + cfg->mode != PJMEDIA_H264_PACKETIZER_MODE_NON_INTERLEAVED && + cfg->mode != PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL) + { + return PJ_ENOTSUP; + } + + p_ = PJ_POOL_ZALLOC_T(pool, pjmedia_h264_packetizer); + if (cfg) { + pj_memcpy(&p_->cfg, cfg, sizeof(*cfg)); + } else { + p_->cfg.mode = PJMEDIA_H264_PACKETIZER_MODE_NON_INTERLEAVED; + p_->cfg.mtu = PJMEDIA_MAX_MTU; + } + + *p = p_; + + return PJ_SUCCESS; +} + + + +/* + * Generate an RTP payload from H.264 frame bitstream, in-place processing. + */ +PJ_DEF(pj_status_t) pjmedia_h264_packetize(pjmedia_h264_packetizer *pktz, + pj_uint8_t *buf, + pj_size_t buf_len, + unsigned *pos, + const pj_uint8_t **payload, + pj_size_t *payload_len) +{ + pj_uint8_t *nal_start = NULL, *nal_end = NULL, *nal_octet = NULL; + pj_uint8_t *p, *end; + enum { + HEADER_SIZE_FU_A = 2, + HEADER_SIZE_STAP_A = 3, + }; + enum { MAX_NALS_IN_AGGR = 32 }; + +#if DBG_PACKETIZE + if (*pos == 0 && buf_len) { + PJ_LOG(3, ("h264pack", "<< Start packing new frame >>")); + } +#endif + + p = buf + *pos; + end = buf + buf_len; + + /* Find NAL unit startcode */ + if (end-p >= 4) + nal_start = find_next_nal_unit(p, p+4); + if (nal_start) { + /* Get NAL unit octet pointer */ + while (*nal_start++ == 0); + nal_octet = nal_start; + } else { + /* This NAL unit is being fragmented */ + nal_start = p; + } + + /* Get end of NAL unit */ + p = nal_start+pktz->cfg.mtu+1; + if (p > end || pktz->cfg.mode==PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL) + p = end; + nal_end = find_next_nal_unit(nal_start, p); + if (!nal_end) + nal_end = p; + + /* Validate MTU vs NAL length on single NAL unit packetization */ + if ((pktz->cfg.mode==PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL) && + nal_end - nal_start > pktz->cfg.mtu) + { + //pj_assert(!"MTU too small for H.264 single NAL packetization mode"); + PJ_LOG(2,("h264_packetizer.c", + "MTU too small for H.264 (required=%u, MTU=%u)", + nal_end - nal_start, pktz->cfg.mtu)); + return PJ_ETOOSMALL; + } + + /* Evaluate the proper payload format structure */ + + /* Fragmentation (FU-A) packet */ + if ((pktz->cfg.mode != PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL) && + (!nal_octet || nal_end-nal_start > pktz->cfg.mtu)) + { + pj_uint8_t NRI, TYPE; + + if (nal_octet) { + /* We have NAL unit octet, so this is the first fragment */ + NRI = (*nal_octet & 0x60) >> 5; + TYPE = *nal_octet & 0x1F; + + /* Skip nal_octet in nal_start to be overriden by FU header */ + ++nal_start; + } else { + /* Not the first fragment, get NRI and NAL unit type + * from the previous fragment. + */ + p = nal_start - pktz->cfg.mtu; + NRI = (*p & 0x60) >> 5; + TYPE = *(p+1) & 0x1F; + } + + /* Init FU indicator (one octet: F+NRI+TYPE) */ + p = nal_start - HEADER_SIZE_FU_A; + *p = (NRI << 5) | NAL_TYPE_FU_A; + ++p; + + /* Init FU header (one octed: S+E+R+TYPE) */ + *p = TYPE; + if (nal_octet) + *p |= (1 << 7); /* S bit flag = start of fragmentation */ + if (nal_end-nal_start+HEADER_SIZE_FU_A <= pktz->cfg.mtu) + *p |= (1 << 6); /* E bit flag = end of fragmentation */ + + /* Set payload, payload length */ + *payload = nal_start - HEADER_SIZE_FU_A; + if (nal_end-nal_start+HEADER_SIZE_FU_A > pktz->cfg.mtu) + *payload_len = pktz->cfg.mtu; + else + *payload_len = nal_end - nal_start + HEADER_SIZE_FU_A; + *pos = *payload + *payload_len - buf; + +#if DBG_PACKETIZE + PJ_LOG(3, ("h264pack", "Packetized fragmented H264 NAL unit " + "(pos=%d, type=%d, NRI=%d, S=%d, E=%d, len=%d/%d)", + *payload-buf, TYPE, NRI, *p>>7, (*p>>6)&1, *payload_len, + buf_len)); +#endif + + return PJ_SUCCESS; + } + + /* Aggregation (STAP-A) packet */ + if ((pktz->cfg.mode != PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL) && + (nal_end != end) && + (nal_end - nal_start + HEADER_SIZE_STAP_A) < pktz->cfg.mtu) + { + int total_size; + unsigned nal_cnt = 1; + pj_uint8_t *nal[MAX_NALS_IN_AGGR]; + pj_size_t nal_size[MAX_NALS_IN_AGGR]; + pj_uint8_t NRI; + + pj_assert(nal_octet); + + /* Init the first NAL unit in the packet */ + nal[0] = nal_start; + nal_size[0] = nal_end - nal_start; + total_size = nal_size[0] + HEADER_SIZE_STAP_A; + NRI = (*nal_octet & 0x60) >> 5; + + /* Populate next NAL units */ + while (nal_cnt < MAX_NALS_IN_AGGR) { + pj_uint8_t *tmp_end; + + /* Find start address of the next NAL unit */ + p = nal[nal_cnt-1] + nal_size[nal_cnt-1]; + while (*p++ == 0); + nal[nal_cnt] = p; + + /* Find end address of the next NAL unit */ + tmp_end = p + (pktz->cfg.mtu - total_size); + if (tmp_end > end) + tmp_end = end; + p = find_next_nal_unit(p+1, tmp_end); + if (p) { + nal_size[nal_cnt] = p - nal[nal_cnt]; + } else { + break; + } + + /* Update total payload size (2 octet NAL size + NAL) */ + total_size += (2 + nal_size[nal_cnt]); + if (total_size <= pktz->cfg.mtu) { + pj_uint8_t tmp_nri; + + /* Get maximum NRI of the aggregated NAL units */ + tmp_nri = (*(nal[nal_cnt]-1) & 0x60) >> 5; + if (tmp_nri > NRI) + NRI = tmp_nri; + } else { + break; + } + + ++nal_cnt; + } + + /* Only use STAP-A when we found more than one NAL units */ + if (nal_cnt > 1) { + unsigned i; + + /* Init STAP-A NAL header (F+NRI+TYPE) */ + p = nal[0] - HEADER_SIZE_STAP_A; + *p++ = (NRI << 5) | NAL_TYPE_STAP_A; + + /* Append all populated NAL units into payload (SIZE+NAL) */ + for (i = 0; i < nal_cnt; ++i) { + /* Put size (2 octets in network order) */ + pj_assert(nal_size[i] <= 0xFFFF); + *p++ = (pj_uint8_t)(nal_size[i] >> 8); + *p++ = (pj_uint8_t)(nal_size[i] & 0xFF); + + /* Append NAL unit, watchout memmove()-ing bitstream! */ + if (p != nal[i]) + pj_memmove(p, nal[i], nal_size[i]); + p += nal_size[i]; + } + + /* Set payload, payload length, and pos */ + *payload = nal[0] - HEADER_SIZE_STAP_A; + pj_assert(*payload >= buf+*pos); + *payload_len = p - *payload; + *pos = nal[nal_cnt-1] + nal_size[nal_cnt-1] - buf; + +#if DBG_PACKETIZE + PJ_LOG(3, ("h264pack", "Packetized aggregation of " + "%d H264 NAL units (pos=%d, NRI=%d len=%d/%d)", + nal_cnt, *payload-buf, NRI, *payload_len, buf_len)); +#endif + + return PJ_SUCCESS; + } + } + + /* Single NAL unit packet */ + pj_assert(nal_octet); + + *payload = nal_start; + *payload_len = nal_end - nal_start; + *pos = nal_end - buf; + +#if DBG_PACKETIZE + PJ_LOG(3, ("h264pack", "Packetized single H264 NAL unit " + "(pos=%d, type=%d, NRI=%d, len=%d/%d)", + nal_start-buf, *nal_octet&0x1F, (*nal_octet&0x60)>>5, + *payload_len, buf_len)); +#endif + + return PJ_SUCCESS; +} + + +/* + * Append RTP payload to a H.264 picture bitstream. Note that the only + * payload format that cares about packet lost is the NAL unit + * fragmentation format (FU-A/B), so we will only manage the "prev_lost" + * state for the FU-A/B packets. + */ +PJ_DEF(pj_status_t) pjmedia_h264_unpacketize(pjmedia_h264_packetizer *pktz, + const pj_uint8_t *payload, + pj_size_t payload_len, + pj_uint8_t *bits, + pj_size_t bits_len, + unsigned *bits_pos) +{ + const pj_uint8_t nal_start_code[3] = {0, 0, 1}; + enum { MIN_PAYLOAD_SIZE = 2 }; + pj_uint8_t nal_type; + + PJ_UNUSED_ARG(pktz); + +#if DBG_UNPACKETIZE + if (*bits_pos == 0 && payload_len) { + PJ_LOG(3, ("h264unpack", ">> Start unpacking new frame <<")); + } +#endif + + /* Check if this is a missing/lost packet */ + if (payload == NULL) { + pktz->unpack_prev_lost = PJ_TRUE; + return PJ_SUCCESS; + } + + /* H264 payload size */ + if (payload_len < MIN_PAYLOAD_SIZE) { + /* Invalid bitstream, discard this payload */ + pktz->unpack_prev_lost = PJ_TRUE; + return PJ_EINVAL; + } + + /* Reset last sync point for every new picture bitstream */ + if (*bits_pos == 0) + pktz->unpack_last_sync_pos = 0; + + nal_type = *payload & 0x1F; + if (nal_type >= NAL_TYPE_SINGLE_NAL_MIN && + nal_type <= NAL_TYPE_SINGLE_NAL_MAX) + { + /* Single NAL unit packet */ + pj_uint8_t *p = bits + *bits_pos; + + /* Validate bitstream length */ + if (bits_len-*bits_pos < payload_len+PJ_ARRAY_SIZE(nal_start_code)) { + /* Insufficient bistream buffer, discard this payload */ + pj_assert(!"Insufficient H.263 bitstream buffer"); + return PJ_ETOOSMALL; + } + + /* Write NAL unit start code */ + pj_memcpy(p, &nal_start_code, PJ_ARRAY_SIZE(nal_start_code)); + p += PJ_ARRAY_SIZE(nal_start_code); + + /* Write NAL unit */ + pj_memcpy(p, payload, payload_len); + p += payload_len; + + /* Update the bitstream writing offset */ + *bits_pos = p - bits; + pktz->unpack_last_sync_pos = *bits_pos; + +#if DBG_UNPACKETIZE + PJ_LOG(3, ("h264unpack", "Unpacked single H264 NAL unit " + "(type=%d, NRI=%d, len=%d)", + nal_type, (*payload&0x60)>>5, payload_len)); +#endif + + } + else if (nal_type == NAL_TYPE_STAP_A) + { + /* Aggregation packet */ + pj_uint8_t *p, *p_end; + const pj_uint8_t *q, *q_end; + unsigned cnt = 0; + + /* Validate bitstream length */ + if (bits_len - *bits_pos < payload_len + 32) { + /* Insufficient bistream buffer, discard this payload */ + pj_assert(!"Insufficient H.263 bitstream buffer"); + return PJ_ETOOSMALL; + } + + /* Fill bitstream */ + p = bits + *bits_pos; + p_end = bits + bits_len; + q = payload + 1; + q_end = payload + payload_len; + while (q < q_end && p < p_end) { + pj_uint16_t tmp_nal_size; + + /* Write NAL unit start code */ + pj_memcpy(p, &nal_start_code, PJ_ARRAY_SIZE(nal_start_code)); + p += PJ_ARRAY_SIZE(nal_start_code); + + /* Get NAL unit size */ + tmp_nal_size = (*q << 8) | *(q+1); + q += 2; + if (q + tmp_nal_size > q_end) { + /* Invalid bitstream, discard the rest of the payload */ + return PJ_EINVAL; + } + + /* Write NAL unit */ + pj_memcpy(p, q, tmp_nal_size); + p += tmp_nal_size; + q += tmp_nal_size; + ++cnt; + + /* Update the bitstream writing offset */ + *bits_pos = p - bits; + pktz->unpack_last_sync_pos = *bits_pos; + } + +#if DBG_UNPACKETIZE + PJ_LOG(3, ("h264unpack", "Unpacked %d H264 NAL units (len=%d)", + cnt, payload_len)); +#endif + + } + else if (nal_type == NAL_TYPE_FU_A) + { + /* Fragmentation packet */ + pj_uint8_t *p; + const pj_uint8_t *q = payload; + pj_uint8_t NRI, TYPE, S, E; + + p = bits + *bits_pos; + + /* Validate bitstream length */ + if (bits_len-*bits_pos < payload_len+PJ_ARRAY_SIZE(nal_start_code)) { + /* Insufficient bistream buffer, drop this packet */ + pj_assert(!"Insufficient H.263 bitstream buffer"); + pktz->unpack_prev_lost = PJ_TRUE; + return PJ_ETOOSMALL; + } + + /* Get info */ + S = *(q+1) & 0x80; /* Start bit flag */ + E = *(q+1) & 0x40; /* End bit flag */ + TYPE = *(q+1) & 0x1f; + NRI = (*q & 0x60) >> 5; + + /* Fill bitstream */ + if (S) { + /* This is the first part, write NAL unit start code */ + pj_memcpy(p, &nal_start_code, PJ_ARRAY_SIZE(nal_start_code)); + p += PJ_ARRAY_SIZE(nal_start_code); + + /* Write NAL unit octet */ + *p++ = (NRI << 5) | TYPE; + } else if (pktz->unpack_prev_lost) { + /* If prev packet was lost, revert the bitstream pointer to + * the last sync point. + */ + pj_assert(pktz->unpack_last_sync_pos <= *bits_pos); + *bits_pos = pktz->unpack_last_sync_pos; + /* And discard this payload (and the following fragmentation + * payloads carrying this same NAL unit. + */ + return PJ_EIGNORED; + } + q += 2; + + /* Write NAL unit */ + pj_memcpy(p, q, payload_len - 2); + p += (payload_len - 2); + + /* Update the bitstream writing offset */ + *bits_pos = p - bits; + if (E) { + /* Update the sync pos only if the end bit flag is set */ + pktz->unpack_last_sync_pos = *bits_pos; + } + +#if DBG_UNPACKETIZE + PJ_LOG(3, ("h264unpack", "Unpacked fragmented H264 NAL unit " + "(type=%d, NRI=%d, len=%d)", + TYPE, NRI, payload_len)); +#endif + + } else { + *bits_pos = 0; + return PJ_ENOTSUP; + } + + pktz->unpack_prev_lost = PJ_FALSE; + + return PJ_SUCCESS; +} diff --git a/pjmedia/src/pjmedia-codec/ilbc.c b/pjmedia/src/pjmedia-codec/ilbc.c index f30b5f09..c595477f 100644 --- a/pjmedia/src/pjmedia-codec/ilbc.c +++ b/pjmedia/src/pjmedia-codec/ilbc.c @@ -113,7 +113,8 @@ static pjmedia_codec_factory_op ilbc_factory_op = &ilbc_default_attr, &ilbc_enum_codecs, &ilbc_alloc_codec, - &ilbc_dealloc_codec + &ilbc_dealloc_codec, + &pjmedia_codec_ilbc_deinit }; /* iLBC factory */ diff --git a/pjmedia/src/pjmedia-codec/ipp_codecs.c b/pjmedia/src/pjmedia-codec/ipp_codecs.c index 023cdc7a..f5690eee 100644 --- a/pjmedia/src/pjmedia-codec/ipp_codecs.c +++ b/pjmedia/src/pjmedia-codec/ipp_codecs.c @@ -103,7 +103,8 @@ static pjmedia_codec_factory_op ipp_factory_op = &ipp_default_attr, &ipp_enum_codecs, &ipp_alloc_codec, - &ipp_dealloc_codec + &ipp_dealloc_codec, + &pjmedia_codec_ipp_deinit }; /* IPP codecs factory */ diff --git a/pjmedia/src/pjmedia-codec/l16.c b/pjmedia/src/pjmedia-codec/l16.c index 3be8a1b6..71b72009 100644 --- a/pjmedia/src/pjmedia-codec/l16.c +++ b/pjmedia/src/pjmedia-codec/l16.c @@ -112,7 +112,8 @@ static pjmedia_codec_factory_op l16_factory_op = &l16_default_attr, &l16_enum_codecs, &l16_alloc_codec, - &l16_dealloc_codec + &l16_dealloc_codec, + &pjmedia_codec_l16_deinit }; /* L16 factory private data */ diff --git a/pjmedia/src/pjmedia-codec/passthrough.c b/pjmedia/src/pjmedia-codec/passthrough.c index 73eed891..8ded8e2a 100644 --- a/pjmedia/src/pjmedia-codec/passthrough.c +++ b/pjmedia/src/pjmedia-codec/passthrough.c @@ -98,7 +98,8 @@ static pjmedia_codec_factory_op codec_factory_op = &default_attr, &enum_codecs, &alloc_codec, - &dealloc_codec + &dealloc_codec, + &pjmedia_codec_passthrough_deinit }; /* Passthrough codecs factory */ diff --git a/pjmedia/src/pjmedia-codec/speex_codec.c b/pjmedia/src/pjmedia-codec/speex_codec.c index eff88235..d5cc0a57 100644 --- a/pjmedia/src/pjmedia-codec/speex_codec.c +++ b/pjmedia/src/pjmedia-codec/speex_codec.c @@ -99,7 +99,8 @@ static pjmedia_codec_factory_op spx_factory_op = &spx_default_attr, &spx_enum_codecs, &spx_alloc_codec, - &spx_dealloc_codec + &spx_dealloc_codec, + &pjmedia_codec_speex_deinit }; /* Index to Speex parameter. */ diff --git a/pjmedia/src/pjmedia-videodev/colorbar_dev.c b/pjmedia/src/pjmedia-videodev/colorbar_dev.c new file mode 100644 index 00000000..7d6c348f --- /dev/null +++ b/pjmedia/src/pjmedia-videodev/colorbar_dev.c @@ -0,0 +1,622 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2010 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjmedia-videodev/videodev_imp.h> +#include <pj/assert.h> +#include <pj/log.h> +#include <pj/os.h> +#include <pj/rand.h> + +#if PJMEDIA_VIDEO_DEV_HAS_CBAR_SRC + +#define THIS_FILE "colorbar_dev.c" +#define DEFAULT_CLOCK_RATE 90000 +#define DEFAULT_WIDTH 352 //640 +#define DEFAULT_HEIGHT 288 //480 +#define DEFAULT_FPS 25 + +/* cbar_ device info */ +struct cbar_dev_info +{ + pjmedia_vid_dev_info info; +}; + +/* cbar_ factory */ +struct cbar_factory +{ + pjmedia_vid_dev_factory base; + pj_pool_t *pool; + pj_pool_factory *pf; + + unsigned dev_count; + struct cbar_dev_info *dev_info; +}; + +struct cbar_fmt_info { + pjmedia_format_id fmt_id; /* Format ID */ + + /* Info for packed formats. */ + unsigned c_offset[3]; /* Color component offset, + in bytes */ + unsigned c_stride[3]; /* Color component stride, + or distance between two + consecutive same color + components, in bytes */ +}; + +/* Colorbar video source supports */ +static struct cbar_fmt_info cbar_fmts[] = +{ + /* Packed formats */ + { PJMEDIA_FORMAT_YUY2, {0, 1, 3}, {2, 4, 4} }, + { PJMEDIA_FORMAT_UYVY, {1, 0, 2}, {2, 4, 4} }, + { PJMEDIA_FORMAT_YVYU, {0, 3, 1}, {2, 4, 4} }, + { PJMEDIA_FORMAT_RGBA, {0, 1, 2}, {4, 4, 4} }, + { PJMEDIA_FORMAT_RGB24, {0, 1, 2}, {3, 3, 3} }, + { PJMEDIA_FORMAT_BGRA, {2, 1, 0}, {4, 4, 4} }, + + /* Planar formats */ + { PJMEDIA_FORMAT_YV12 }, + { PJMEDIA_FORMAT_I420 }, + { PJMEDIA_FORMAT_I420JPEG }, + { PJMEDIA_FORMAT_I422JPEG }, +}; + +/* Video stream. */ +struct cbar_stream +{ + pjmedia_vid_dev_stream base; /**< Base stream */ + pjmedia_vid_dev_param param; /**< Settings */ + pj_pool_t *pool; /**< Memory pool. */ + + pjmedia_vid_dev_cb vid_cb; /**< Stream callback. */ + void *user_data; /**< Application data. */ + + const struct cbar_fmt_info *cbfi; + const pjmedia_video_format_info *vfi; + pjmedia_video_apply_fmt_param vafp; + pj_uint8_t *first_line[PJMEDIA_MAX_VIDEO_PLANES]; + pj_timestamp ts; + unsigned ts_inc; +}; + + +/* Prototypes */ +static pj_status_t cbar_factory_init(pjmedia_vid_dev_factory *f); +static pj_status_t cbar_factory_destroy(pjmedia_vid_dev_factory *f); +static pj_status_t cbar_factory_refresh(pjmedia_vid_dev_factory *f); +static unsigned cbar_factory_get_dev_count(pjmedia_vid_dev_factory *f); +static pj_status_t cbar_factory_get_dev_info(pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_info *info); +static pj_status_t cbar_factory_default_param(pj_pool_t *pool, + pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_param *param); +static pj_status_t cbar_factory_create_stream( + pjmedia_vid_dev_factory *f, + pjmedia_vid_dev_param *param, + const pjmedia_vid_dev_cb *cb, + void *user_data, + pjmedia_vid_dev_stream **p_vid_strm); + +static pj_status_t cbar_stream_get_param(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_param *param); +static pj_status_t cbar_stream_get_cap(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_cap cap, + void *value); +static pj_status_t cbar_stream_set_cap(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_cap cap, + const void *value); +static pj_status_t cbar_stream_get_frame(pjmedia_vid_dev_stream *strm, + pjmedia_frame *frame); +static pj_status_t cbar_stream_start(pjmedia_vid_dev_stream *strm); +static pj_status_t cbar_stream_stop(pjmedia_vid_dev_stream *strm); +static pj_status_t cbar_stream_destroy(pjmedia_vid_dev_stream *strm); + +/* Operations */ +static pjmedia_vid_dev_factory_op factory_op = +{ + &cbar_factory_init, + &cbar_factory_destroy, + &cbar_factory_get_dev_count, + &cbar_factory_get_dev_info, + &cbar_factory_default_param, + &cbar_factory_create_stream, + &cbar_factory_refresh +}; + +static pjmedia_vid_dev_stream_op stream_op = +{ + &cbar_stream_get_param, + &cbar_stream_get_cap, + &cbar_stream_set_cap, + &cbar_stream_start, + &cbar_stream_get_frame, + NULL, + &cbar_stream_stop, + &cbar_stream_destroy +}; + + +/**************************************************************************** + * Factory operations + */ +/* + * Init cbar_ video driver. + */ +pjmedia_vid_dev_factory* pjmedia_cbar_factory(pj_pool_factory *pf) +{ + struct cbar_factory *f; + pj_pool_t *pool; + + pool = pj_pool_create(pf, "cbar video", 512, 512, NULL); + f = PJ_POOL_ZALLOC_T(pool, struct cbar_factory); + f->pf = pf; + f->pool = pool; + f->base.op = &factory_op; + + return &f->base; +} + + +/* API: init factory */ +static pj_status_t cbar_factory_init(pjmedia_vid_dev_factory *f) +{ + struct cbar_factory *cf = (struct cbar_factory*)f; + struct cbar_dev_info *ddi; + unsigned i; + + cf->dev_count = 1; + cf->dev_info = (struct cbar_dev_info*) + pj_pool_calloc(cf->pool, cf->dev_count, + sizeof(struct cbar_dev_info)); + + ddi = &cf->dev_info[0]; + pj_bzero(ddi, sizeof(*ddi)); + pj_ansi_strncpy(ddi->info.name, "Colorbar generator", + sizeof(ddi->info.name)); + ddi->info.driver[sizeof(ddi->info.driver)-1] = '\0'; + pj_ansi_strncpy(ddi->info.driver, "Colorbar", sizeof(ddi->info.driver)); + ddi->info.driver[sizeof(ddi->info.driver)-1] = '\0'; + ddi->info.dir = PJMEDIA_DIR_CAPTURE; + ddi->info.has_callback = PJ_FALSE; + + ddi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT; + ddi->info.fmt_cnt = sizeof(cbar_fmts)/sizeof(cbar_fmts[0]); + for (i = 0; i < ddi->info.fmt_cnt; i++) { + pjmedia_format *fmt = &ddi->info.fmt[i]; + pjmedia_format_init_video(fmt, cbar_fmts[i].fmt_id, + DEFAULT_WIDTH, DEFAULT_HEIGHT, + DEFAULT_FPS, 1); + } + + PJ_LOG(4, (THIS_FILE, "Colorbar video src initialized with %d device(s):", + cf->dev_count)); + for (i = 0; i < cf->dev_count; i++) { + PJ_LOG(4, (THIS_FILE, "%2d: %s", i, cf->dev_info[i].info.name)); + } + + return PJ_SUCCESS; +} + +/* API: destroy factory */ +static pj_status_t cbar_factory_destroy(pjmedia_vid_dev_factory *f) +{ + struct cbar_factory *cf = (struct cbar_factory*)f; + pj_pool_t *pool = cf->pool; + + cf->pool = NULL; + pj_pool_release(pool); + + return PJ_SUCCESS; +} + +/* API: refresh the list of devices */ +static pj_status_t cbar_factory_refresh(pjmedia_vid_dev_factory *f) +{ + PJ_UNUSED_ARG(f); + return PJ_SUCCESS; +} + +/* API: get number of devices */ +static unsigned cbar_factory_get_dev_count(pjmedia_vid_dev_factory *f) +{ + struct cbar_factory *cf = (struct cbar_factory*)f; + return cf->dev_count; +} + +/* API: get device info */ +static pj_status_t cbar_factory_get_dev_info(pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_info *info) +{ + struct cbar_factory *cf = (struct cbar_factory*)f; + + PJ_ASSERT_RETURN(index < cf->dev_count, PJMEDIA_EVID_INVDEV); + + pj_memcpy(info, &cf->dev_info[index].info, sizeof(*info)); + + return PJ_SUCCESS; +} + +/* API: create default device parameter */ +static pj_status_t cbar_factory_default_param(pj_pool_t *pool, + pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_param *param) +{ + struct cbar_factory *cf = (struct cbar_factory*)f; + struct cbar_dev_info *di = &cf->dev_info[index]; + + PJ_ASSERT_RETURN(index < cf->dev_count, PJMEDIA_EVID_INVDEV); + + PJ_UNUSED_ARG(pool); + + pj_bzero(param, sizeof(*param)); + param->dir = PJMEDIA_DIR_CAPTURE; + param->cap_id = index; + param->rend_id = PJMEDIA_VID_INVALID_DEV; + param->flags = PJMEDIA_VID_DEV_CAP_FORMAT; + param->clock_rate = DEFAULT_CLOCK_RATE; + pj_memcpy(¶m->fmt, &di->info.fmt[0], sizeof(param->fmt)); + + return PJ_SUCCESS; +} + +static const struct cbar_fmt_info* get_cbar_fmt_info(pjmedia_format_id id) +{ + unsigned i; + + for (i = 0; i < sizeof(cbar_fmts)/sizeof(cbar_fmts[0]); i++) { + if (cbar_fmts[i].fmt_id == id) + return &cbar_fmts[i]; + } + + return NULL; +} + +static void fill_first_line(pj_uint8_t *first_lines[], + const struct cbar_fmt_info *cbfi, + const pjmedia_video_format_info *vfi, + const pjmedia_video_apply_fmt_param *vafp) +{ + typedef pj_uint8_t color_comp_t[3]; + color_comp_t rgb_colors[] = + { + {255,255,255}, {255,255,0}, {0,255,255}, {0,255,0}, + {255,0,255}, {255,0,0}, {0,0,255}, {0,0,0} + }; + color_comp_t yuv_colors[] = + { + //{235,128,128}, {162,44,142}, {131,156,44}, {112,72,58}, + //{84,184,198}, {65,100,212}, {35,212,114}, {16,128,128} + {235,128,128}, {210,16,146}, {170,166,16}, {145,54,34}, + {106,202,222}, {81,90,240}, {41,240,110}, {16,128,128} + }; + + unsigned i, j, k; + + if (vfi->plane_cnt == 1) { + /* Packed */ + + for (i = 0; i < 8; ++i) { + /* iterate bars */ + for (j = 0; j < 3; ++j) { + /* iterate color components */ + pj_uint8_t *p = NULL, c; + unsigned bar_width, inc_p; + + if (vfi->color_model == PJMEDIA_COLOR_MODEL_RGB) + c = rgb_colors[i][j]; + else + c = yuv_colors[i][j]; + + bar_width = vafp->size.w/8; + bar_width /= (cbfi->c_stride[j] * 8 / vfi->bpp); + inc_p = cbfi->c_stride[j]; + p = first_lines[0] + bar_width*i*inc_p + cbfi->c_offset[j]; + + /* draw this color */ + for (k = 0; k < bar_width; ++k) { + *p = c; + p += inc_p; + } + } + } + + } else if (vfi->plane_cnt == 3) { + + for (i = 0; i < 8; ++i) { + /* iterate bars */ + for (j = 0; j < 3; ++j) { + /* iterate planes/color components */ + pj_uint8_t *p = NULL, c; + unsigned bar_width; + + if (vfi->color_model == PJMEDIA_COLOR_MODEL_RGB) + c = rgb_colors[i][j]; + else + c = yuv_colors[i][j]; + + bar_width = vafp->strides[j]/8; + p = first_lines[j] + bar_width*i; + + /* draw this plane/color */ + for (k = 0; k < bar_width; ++k) + *p++ = c; + } + } + } +} + +/* API: create stream */ +static pj_status_t cbar_factory_create_stream( + pjmedia_vid_dev_factory *f, + pjmedia_vid_dev_param *param, + const pjmedia_vid_dev_cb *cb, + void *user_data, + pjmedia_vid_dev_stream **p_vid_strm) +{ + struct cbar_factory *cf = (struct cbar_factory*)f; + pj_pool_t *pool; + struct cbar_stream *strm; + const pjmedia_video_format_detail *vfd; + const pjmedia_video_format_info *vfi; + pjmedia_video_apply_fmt_param vafp; + const struct cbar_fmt_info *cbfi; + unsigned i; + + PJ_ASSERT_RETURN(f && param && p_vid_strm, PJ_EINVAL); + PJ_ASSERT_RETURN(param->fmt.type == PJMEDIA_TYPE_VIDEO && + param->fmt.detail_type == PJMEDIA_FORMAT_DETAIL_VIDEO && + param->dir == PJMEDIA_DIR_CAPTURE, + PJ_EINVAL); + + pj_bzero(&vafp, sizeof(vafp)); + + vfd = pjmedia_format_get_video_format_detail(¶m->fmt, PJ_TRUE); + vfi = pjmedia_get_video_format_info(NULL, param->fmt.id); + cbfi = get_cbar_fmt_info(param->fmt.id); + if (!vfi || !cbfi) + return PJMEDIA_EVID_BADFORMAT; + + vafp.size = param->fmt.det.vid.size; + if (vfi->apply_fmt(vfi, &vafp) != PJ_SUCCESS) + return PJMEDIA_EVID_BADFORMAT; + + /* Create and Initialize stream descriptor */ + pool = pj_pool_create(cf->pf, "cbar-dev", 512, 512, NULL); + PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); + + strm = PJ_POOL_ZALLOC_T(pool, struct cbar_stream); + pj_memcpy(&strm->param, param, sizeof(*param)); + strm->pool = pool; + pj_memcpy(&strm->vid_cb, cb, sizeof(*cb)); + strm->user_data = user_data; + strm->vfi = vfi; + strm->cbfi = cbfi; + pj_memcpy(&strm->vafp, &vafp, sizeof(vafp)); + strm->ts_inc = PJMEDIA_SPF2(param->clock_rate, &vfd->fps, 1); + pjmedia_event_publisher_init(&strm->base.epub, PJMEDIA_SIG_VID_DEV_COLORBAR); + + for (i = 0; i < vfi->plane_cnt; ++i) { + strm->first_line[i] = pj_pool_alloc(pool, vafp.strides[i]); + pj_memset(strm->first_line[i], 255, vafp.strides[i]); + } + + fill_first_line(strm->first_line, strm->cbfi, vfi, &strm->vafp); + + /* Apply the remaining settings */ +/* if (param->flags & PJMEDIA_VID_DEV_CAP_INPUT_SCALE) { + cbar_stream_set_cap(&strm->base, + PJMEDIA_VID_DEV_CAP_INPUT_SCALE, + ¶m->fmt); + } +*/ + /* Done */ + strm->base.op = &stream_op; + *p_vid_strm = &strm->base; + + return PJ_SUCCESS; +} + +/* API: Get stream info. */ +static pj_status_t cbar_stream_get_param(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_param *pi) +{ + struct cbar_stream *strm = (struct cbar_stream*)s; + + PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL); + + pj_memcpy(pi, &strm->param, sizeof(*pi)); + +/* if (cbar_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_INPUT_SCALE, + &pi->fmt.info_size) == PJ_SUCCESS) + { + pi->flags |= PJMEDIA_VID_DEV_CAP_INPUT_SCALE; + } +*/ + return PJ_SUCCESS; +} + +/* API: get capability */ +static pj_status_t cbar_stream_get_cap(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_cap cap, + void *pval) +{ + struct cbar_stream *strm = (struct cbar_stream*)s; + + PJ_UNUSED_ARG(strm); + + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + if (cap==PJMEDIA_VID_DEV_CAP_INPUT_SCALE) + { + return PJMEDIA_EVID_INVCAP; +// return PJ_SUCCESS; + } else { + return PJMEDIA_EVID_INVCAP; + } +} + +/* API: set capability */ +static pj_status_t cbar_stream_set_cap(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_cap cap, + const void *pval) +{ + struct cbar_stream *strm = (struct cbar_stream*)s; + + PJ_UNUSED_ARG(strm); + + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + if (cap==PJMEDIA_VID_DEV_CAP_INPUT_SCALE) + { + return PJ_SUCCESS; + } + + return PJMEDIA_EVID_INVCAP; +} + +static pj_status_t spectrum_run(struct cbar_stream *d, pj_uint8_t *p, + pj_size_t size) +{ + unsigned i; + pj_uint8_t *ptr = p; + pj_time_val tv; + + PJ_UNUSED_ARG(size); + + /* Subsequent lines */ + for (i=0; i<d->vfi->plane_cnt; ++i) { + pj_uint8_t *plane_end; + + plane_end = ptr + d->vafp.plane_bytes[i]; + while (ptr < plane_end) { + pj_memcpy(ptr, d->first_line[i], d->vafp.strides[i]); + ptr += d->vafp.strides[i]; + } + } + + /* blinking dot */ + pj_gettimeofday(&tv); + if (tv.msec < 660) { + enum { DOT_SIZE = 8 }; + pj_uint8_t dot_clr_rgb[3] = {255, 255, 255}; + pj_uint8_t dot_clr_yuv[3] = {235, 128, 128}; + + if (d->vfi->plane_cnt == 1) { + for (i = 0; i < 3; ++i) { + pj_uint8_t *ptr; + unsigned j, k, inc_ptr; + pj_size_t dot_size = DOT_SIZE; + + dot_size /= (d->cbfi->c_stride[i] * 8 / d->vfi->bpp); + inc_ptr = d->cbfi->c_stride[i]; + for (j = 0; j < dot_size; ++j) { + ptr = p + d->vafp.strides[0]*(dot_size+j+1) - + 2*dot_size*inc_ptr + d->cbfi->c_offset[i]; + for (k = 0; k < dot_size; ++k) { + if (d->vfi->color_model == PJMEDIA_COLOR_MODEL_RGB) + *ptr = dot_clr_rgb[i]; + else + *ptr = dot_clr_yuv[i]; + ptr += inc_ptr; + } + } + } + } else { + pj_size_t offset_p = 0; + + for (i = 0; i < 3; ++i) { + pj_uint8_t *ptr, c; + unsigned j; + pj_size_t dot_size = DOT_SIZE; + + if (d->vfi->color_model == PJMEDIA_COLOR_MODEL_RGB) + c = dot_clr_rgb[i]; + else + c = dot_clr_yuv[i]; + + dot_size /= (d->vafp.size.w / d->vafp.strides[i]); + ptr = p + offset_p + d->vafp.strides[i]*(dot_size+1) - + 2*dot_size; + for (j = 0; j < dot_size; ++j) { + pj_memset(ptr, c, dot_size); + ptr += d->vafp.strides[i]; + } + offset_p += d->vafp.plane_bytes[i]; + } + } + } + + return PJ_SUCCESS; +} + +/* API: Get frame from stream */ +static pj_status_t cbar_stream_get_frame(pjmedia_vid_dev_stream *strm, + pjmedia_frame *frame) +{ + struct cbar_stream *stream = (struct cbar_stream*)strm; + + frame->timestamp = stream->ts; + stream->ts.u64 += stream->ts_inc; + return spectrum_run(stream, frame->buf, frame->size); +} + +/* API: Start stream. */ +static pj_status_t cbar_stream_start(pjmedia_vid_dev_stream *strm) +{ + struct cbar_stream *stream = (struct cbar_stream*)strm; + + PJ_UNUSED_ARG(stream); + + PJ_LOG(4, (THIS_FILE, "Starting cbar video stream")); + + return PJ_SUCCESS; +} + +/* API: Stop stream. */ +static pj_status_t cbar_stream_stop(pjmedia_vid_dev_stream *strm) +{ + struct cbar_stream *stream = (struct cbar_stream*)strm; + + PJ_UNUSED_ARG(stream); + + PJ_LOG(4, (THIS_FILE, "Stopping cbar video stream")); + + return PJ_SUCCESS; +} + + +/* API: Destroy stream. */ +static pj_status_t cbar_stream_destroy(pjmedia_vid_dev_stream *strm) +{ + struct cbar_stream *stream = (struct cbar_stream*)strm; + + PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); + + cbar_stream_stop(strm); + + pj_pool_release(stream->pool); + + return PJ_SUCCESS; +} + +#endif /* PJMEDIA_VIDEO_DEV_HAS_CBAR_SRC */ diff --git a/pjmedia/src/pjmedia-videodev/dshow_dev.c b/pjmedia/src/pjmedia-videodev/dshow_dev.c new file mode 100644 index 00000000..c69393fa --- /dev/null +++ b/pjmedia/src/pjmedia-videodev/dshow_dev.c @@ -0,0 +1,1016 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2010 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjmedia-videodev/videodev_imp.h> +#include <pj/assert.h> +#include <pj/log.h> +#include <pj/os.h> +#include <pj/unicode.h> + +#if PJMEDIA_VIDEO_DEV_HAS_DSHOW + +#ifdef _MSC_VER +# pragma warning(push, 3) +#endif + +#include <windows.h> +#define COBJMACROS +#include <DShow.h> + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +#pragma comment(lib, "Strmiids.lib") +#pragma comment(lib, "Rpcrt4.lib") +#pragma comment(lib, "Quartz.lib") + +#define THIS_FILE "dshow_dev.c" +#define DEFAULT_CLOCK_RATE 90000 +#define DEFAULT_WIDTH 640 +#define DEFAULT_HEIGHT 480 +#define DEFAULT_FPS 25 + +/* Temporarily disable DirectShow renderer (VMR) */ +#define HAS_VMR 0 + +typedef void (*input_callback)(void *user_data, IMediaSample *pMediaSample); +typedef struct NullRenderer NullRenderer; +IBaseFilter* NullRenderer_Create(input_callback input_cb, + void *user_data); +typedef struct SourceFilter SourceFilter; +IBaseFilter* SourceFilter_Create(SourceFilter **pSrc); +HRESULT SourceFilter_Deliver(SourceFilter *src, void *buf, long size); +void SourceFilter_SetMediaType(SourceFilter *src, AM_MEDIA_TYPE *pmt); + +typedef struct dshow_fmt_info +{ + pjmedia_format_id pjmedia_format; + const GUID *dshow_format; +} dshow_fmt_info; + +static dshow_fmt_info dshow_fmts[] = +{ + {PJMEDIA_FORMAT_YUY2, &MEDIASUBTYPE_YUY2} , + {PJMEDIA_FORMAT_RGB24, &MEDIASUBTYPE_RGB24} , + {PJMEDIA_FORMAT_RGB32, &MEDIASUBTYPE_RGB32} , + {PJMEDIA_FORMAT_IYUV, &MEDIASUBTYPE_IYUV} , +}; + +/* dshow_ device info */ +struct dshow_dev_info +{ + pjmedia_vid_dev_info info; + unsigned dev_id; + WCHAR display_name[192]; +}; + +/* dshow_ factory */ +struct dshow_factory +{ + pjmedia_vid_dev_factory base; + pj_pool_t *pool; + pj_pool_t *dev_pool; + pj_pool_factory *pf; + + unsigned dev_count; + struct dshow_dev_info *dev_info; +}; + +/* Video stream. */ +struct dshow_stream +{ + pjmedia_vid_dev_stream base; /**< Base stream */ + pjmedia_vid_dev_param param; /**< Settings */ + pj_pool_t *pool; /**< Memory pool. */ + + pjmedia_vid_dev_cb vid_cb; /**< Stream callback. */ + void *user_data; /**< Application data. */ + + pj_bool_t quit_flag; + pj_bool_t rend_thread_exited; + pj_bool_t cap_thread_exited; + pj_bool_t cap_thread_initialized; + pj_thread_desc cap_thread_desc; + pj_thread_t *cap_thread; + + struct dshow_graph + { + IFilterGraph *filter_graph; + IMediaFilter *media_filter; + SourceFilter *csource_filter; + IBaseFilter *source_filter; + IBaseFilter *rend_filter; + AM_MEDIA_TYPE *mediatype; + } dgraph; + + pj_timestamp cap_ts; + unsigned cap_ts_inc; +}; + + +/* Prototypes */ +static pj_status_t dshow_factory_init(pjmedia_vid_dev_factory *f); +static pj_status_t dshow_factory_destroy(pjmedia_vid_dev_factory *f); +static pj_status_t dshow_factory_refresh(pjmedia_vid_dev_factory *f); +static unsigned dshow_factory_get_dev_count(pjmedia_vid_dev_factory *f); +static pj_status_t dshow_factory_get_dev_info(pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_info *info); +static pj_status_t dshow_factory_default_param(pj_pool_t *pool, + pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_param *param); +static pj_status_t dshow_factory_create_stream( + pjmedia_vid_dev_factory *f, + pjmedia_vid_dev_param *param, + const pjmedia_vid_dev_cb *cb, + void *user_data, + pjmedia_vid_dev_stream **p_vid_strm); + +static pj_status_t dshow_stream_get_param(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_param *param); +static pj_status_t dshow_stream_get_cap(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_cap cap, + void *value); +static pj_status_t dshow_stream_set_cap(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_cap cap, + const void *value); +static pj_status_t dshow_stream_start(pjmedia_vid_dev_stream *strm); +static pj_status_t dshow_stream_put_frame(pjmedia_vid_dev_stream *strm, + const pjmedia_frame *frame); +static pj_status_t dshow_stream_stop(pjmedia_vid_dev_stream *strm); +static pj_status_t dshow_stream_destroy(pjmedia_vid_dev_stream *strm); + +/* Operations */ +static pjmedia_vid_dev_factory_op factory_op = +{ + &dshow_factory_init, + &dshow_factory_destroy, + &dshow_factory_get_dev_count, + &dshow_factory_get_dev_info, + &dshow_factory_default_param, + &dshow_factory_create_stream, + &dshow_factory_refresh +}; + +static pjmedia_vid_dev_stream_op stream_op = +{ + &dshow_stream_get_param, + &dshow_stream_get_cap, + &dshow_stream_set_cap, + &dshow_stream_start, + NULL, + &dshow_stream_put_frame, + &dshow_stream_stop, + &dshow_stream_destroy +}; + + +/**************************************************************************** + * Factory operations + */ +/* + * Init dshow_ video driver. + */ +pjmedia_vid_dev_factory* pjmedia_dshow_factory(pj_pool_factory *pf) +{ + struct dshow_factory *f; + pj_pool_t *pool; + + pool = pj_pool_create(pf, "dshow video", 1000, 1000, NULL); + f = PJ_POOL_ZALLOC_T(pool, struct dshow_factory); + f->pf = pf; + f->pool = pool; + f->base.op = &factory_op; + + return &f->base; +} + +/* API: init factory */ +static pj_status_t dshow_factory_init(pjmedia_vid_dev_factory *f) +{ + CoInitializeEx(NULL, COINIT_MULTITHREADED); + + return dshow_factory_refresh(f); +} + +/* API: destroy factory */ +static pj_status_t dshow_factory_destroy(pjmedia_vid_dev_factory *f) +{ + struct dshow_factory *df = (struct dshow_factory*)f; + pj_pool_t *pool = df->pool; + + df->pool = NULL; + if (df->dev_pool) + pj_pool_release(df->dev_pool); + if (pool) + pj_pool_release(pool); + + CoUninitialize(); + + return PJ_SUCCESS; +} + +static HRESULT get_cap_device(struct dshow_factory *df, + unsigned id, + IBaseFilter **filter) +{ + IBindCtx *pbc; + HRESULT hr; + + hr = CreateBindCtx(0, &pbc); + if (SUCCEEDED (hr)) { + IMoniker *moniker; + DWORD pchEaten; + + hr = MkParseDisplayName(pbc, df->dev_info[id].display_name, + &pchEaten, &moniker); + if (SUCCEEDED(hr)) { + hr = IMoniker_BindToObject(moniker, pbc, NULL, + &IID_IBaseFilter, + (LPVOID *)filter); + IMoniker_Release(moniker); + } + IBindCtx_Release(pbc); + } + + return hr; +} + +static void enum_dev_cap(IBaseFilter *filter, + pjmedia_dir dir, + const GUID *dshow_format, + AM_MEDIA_TYPE **pMediatype, + IPin **pSrcpin, + pj_bool_t *sup_fmt) +{ + IEnumPins *pEnum; + AM_MEDIA_TYPE *mediatype = NULL; + HRESULT hr; + + if (pSrcpin) + *pSrcpin = NULL; + hr = IBaseFilter_EnumPins(filter, &pEnum); + if (SUCCEEDED(hr)) { + /* Loop through all the pins. */ + IPin *pPin = NULL; + + while (IEnumPins_Next(pEnum, 1, &pPin, NULL) == S_OK) { + PIN_DIRECTION pindirtmp; + + hr = IPin_QueryDirection(pPin, &pindirtmp); + if (hr != S_OK || pindirtmp != PINDIR_OUTPUT) { + if (SUCCEEDED(hr)) + IPin_Release(pPin); + continue; + } + + if (dir == PJMEDIA_DIR_CAPTURE) { + IAMStreamConfig *streamcaps; + + hr = IPin_QueryInterface(pPin, &IID_IAMStreamConfig, + (LPVOID *)&streamcaps); + if (SUCCEEDED(hr)) { + VIDEO_STREAM_CONFIG_CAPS vscc; + int i, isize, icount; + + IAMStreamConfig_GetNumberOfCapabilities(streamcaps, + &icount, &isize); + + for (i = 0; i < icount; i++) { + unsigned j, nformat; + RPC_STATUS rpcstatus, rpcstatus2; + + hr = IAMStreamConfig_GetStreamCaps(streamcaps, i, + &mediatype, + (BYTE *)&vscc); + if (FAILED (hr)) + continue; + + nformat = (dshow_format? 1: + sizeof(dshow_fmts)/sizeof(dshow_fmts[0])); + for (j = 0; j < nformat; j++) { + if (!dshow_format || j > 0) + dshow_format = dshow_fmts[j].dshow_format; + if (UuidCompare(&mediatype->subtype, + (UUID*)dshow_format, + &rpcstatus) == 0 && + rpcstatus == RPC_S_OK && + UuidCompare(&mediatype->formattype, + (UUID*)&FORMAT_VideoInfo, + &rpcstatus2) == 0 && + rpcstatus2 == RPC_S_OK) + { + if (sup_fmt) + sup_fmt[j] = PJ_TRUE; + if (pSrcpin) { + *pSrcpin = pPin; + *pMediatype = mediatype; + } + } + } + if (pSrcpin && *pSrcpin) + break; + } + IAMStreamConfig_Release(streamcaps); + } + } else { + *pSrcpin = pPin; + } + if (pSrcpin && *pSrcpin) + break; + IPin_Release(pPin); + } + IEnumPins_Release(pEnum); + } +} + +/* API: refresh the list of devices */ +static pj_status_t dshow_factory_refresh(pjmedia_vid_dev_factory *f) +{ + struct dshow_factory *df = (struct dshow_factory*)f; + struct dshow_dev_info *ddi; + int dev_count = 0; + unsigned c; + ICreateDevEnum *dev_enum = NULL; + IEnumMoniker *enum_cat = NULL; + IMoniker *moniker = NULL; + HRESULT hr; + ULONG fetched; + + if (df->dev_pool) { + pj_pool_release(df->dev_pool); + df->dev_pool = NULL; + } + + df->dev_count = 0; + df->dev_pool = pj_pool_create(df->pf, "dshow video", 500, 500, NULL); + + hr = CoCreateInstance(&CLSID_SystemDeviceEnum, NULL, + CLSCTX_INPROC_SERVER, &IID_ICreateDevEnum, + (void**)&dev_enum); + if (FAILED(hr) || + ICreateDevEnum_CreateClassEnumerator(dev_enum, + &CLSID_VideoInputDeviceCategory, &enum_cat, 0) != S_OK) + { + PJ_LOG(4,(THIS_FILE, "Windows found no video input devices")); + if (dev_enum) + ICreateDevEnum_Release(dev_enum); + dev_count = 0; + } else { + while (IEnumMoniker_Next(enum_cat, 1, &moniker, &fetched) == S_OK) { + dev_count++; + } + } + + /* Add renderer device */ + dev_count += 1; + df->dev_info = (struct dshow_dev_info*) + pj_pool_calloc(df->dev_pool, dev_count, + sizeof(struct dshow_dev_info)); + + if (dev_count > 1) { + IEnumMoniker_Reset(enum_cat); + while (IEnumMoniker_Next(enum_cat, 1, &moniker, &fetched) == S_OK) { + IPropertyBag *prop_bag; + + hr = IMoniker_BindToStorage(moniker, 0, 0, &IID_IPropertyBag, + (void**)&prop_bag); + if (SUCCEEDED(hr)) { + VARIANT var_name; + + VariantInit(&var_name); + hr = IPropertyBag_Read(prop_bag, L"FriendlyName", + &var_name, NULL); + if (SUCCEEDED(hr) && var_name.bstrVal) { + WCHAR *wszDisplayName = NULL; + IBaseFilter *filter; + + ddi = &df->dev_info[df->dev_count++]; + pj_bzero(ddi, sizeof(*ddi)); + pj_unicode_to_ansi(var_name.bstrVal, + wcslen(var_name.bstrVal), + ddi->info.name, + sizeof(ddi->info.name)); + + hr = IMoniker_GetDisplayName(moniker, NULL, NULL, + &wszDisplayName); + if (hr == S_OK && wszDisplayName) { + pj_memcpy(ddi->display_name, wszDisplayName, + (wcslen(wszDisplayName)+1) * sizeof(WCHAR)); + CoTaskMemFree(wszDisplayName); + } + + strncpy(ddi->info.driver, "dshow", + sizeof(ddi->info.driver)); + ddi->info.driver[sizeof(ddi->info.driver)-1] = '\0'; + ddi->info.dir = PJMEDIA_DIR_CAPTURE; + ddi->info.has_callback = PJ_TRUE; + + /* Set the device capabilities here */ + ddi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT; + + hr = get_cap_device(df, df->dev_count-1, &filter); + if (SUCCEEDED(hr)) { + unsigned j; + pj_bool_t sup_fmt[sizeof(dshow_fmts)/sizeof(dshow_fmts[0])]; + + pj_bzero(sup_fmt, sizeof(sup_fmt)); + enum_dev_cap(filter, ddi->info.dir, NULL, NULL, NULL, sup_fmt); + + ddi->info.fmt_cnt = 0; + for (j = 0; + j < sizeof(dshow_fmts)/sizeof(dshow_fmts[0]); + j++) + { + if (!sup_fmt[j]) + continue; + pjmedia_format_init_video( + &ddi->info.fmt[ddi->info.fmt_cnt++], + dshow_fmts[j].pjmedia_format, + DEFAULT_WIDTH, DEFAULT_HEIGHT, + DEFAULT_FPS, 1); + } + } + } + VariantClear(&var_name); + + IPropertyBag_Release(prop_bag); + } + IMoniker_Release(moniker); + } + + IEnumMoniker_Release(enum_cat); + ICreateDevEnum_Release(dev_enum); + } + +#if HAS_VMR + ddi = &df->dev_info[df->dev_count++]; + pj_bzero(ddi, sizeof(*ddi)); + pj_ansi_strncpy(ddi->info.name, "Video Mixing Renderer", + sizeof(ddi->info.name)); + ddi->info.name[sizeof(ddi->info.name)-1] = '\0'; + pj_ansi_strncpy(ddi->info.driver, "dshow", sizeof(ddi->info.driver)); + ddi->info.driver[sizeof(ddi->info.driver)-1] = '\0'; + ddi->info.dir = PJMEDIA_DIR_RENDER; + ddi->info.has_callback = PJ_FALSE; + ddi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT; +// TODO: +// ddi->info.caps |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW; + + ddi->info.fmt_cnt = 1; + pjmedia_format_init_video(&ddi->info.fmt[0], dshow_fmts[0].pjmedia_format, + DEFAULT_WIDTH, DEFAULT_HEIGHT, + DEFAULT_FPS, 1); +#endif + + PJ_LOG(4, (THIS_FILE, "DShow has %d devices:", + df->dev_count)); + for (c = 0; c < df->dev_count; ++c) { + PJ_LOG(4, (THIS_FILE, " dev_id %d: %s (%s)", + c, + df->dev_info[c].info.name, + df->dev_info[c].info.dir & PJMEDIA_DIR_CAPTURE ? + "capture" : "render")); + } + + return PJ_SUCCESS; +} + +/* API: get number of devices */ +static unsigned dshow_factory_get_dev_count(pjmedia_vid_dev_factory *f) +{ + struct dshow_factory *df = (struct dshow_factory*)f; + return df->dev_count; +} + +/* API: get device info */ +static pj_status_t dshow_factory_get_dev_info(pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_info *info) +{ + struct dshow_factory *df = (struct dshow_factory*)f; + + PJ_ASSERT_RETURN(index < df->dev_count, PJMEDIA_EVID_INVDEV); + + pj_memcpy(info, &df->dev_info[index].info, sizeof(*info)); + + return PJ_SUCCESS; +} + +/* API: create default device parameter */ +static pj_status_t dshow_factory_default_param(pj_pool_t *pool, + pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_param *param) +{ + struct dshow_factory *df = (struct dshow_factory*)f; + struct dshow_dev_info *di = &df->dev_info[index]; + + PJ_ASSERT_RETURN(index < df->dev_count, PJMEDIA_EVID_INVDEV); + + PJ_UNUSED_ARG(pool); + + pj_bzero(param, sizeof(*param)); + if (di->info.dir & PJMEDIA_DIR_CAPTURE) { + param->dir = PJMEDIA_DIR_CAPTURE; + param->cap_id = index; + param->rend_id = PJMEDIA_VID_INVALID_DEV; + } else if (di->info.dir & PJMEDIA_DIR_RENDER) { + param->dir = PJMEDIA_DIR_RENDER; + param->rend_id = index; + param->cap_id = PJMEDIA_VID_INVALID_DEV; + } else { + return PJMEDIA_EVID_INVDEV; + } + + /* Set the device capabilities here */ + param->clock_rate = DEFAULT_CLOCK_RATE; + param->flags = PJMEDIA_VID_DEV_CAP_FORMAT; + + pjmedia_format_copy(¶m->fmt, &di->info.fmt[0]); + + return PJ_SUCCESS; +} + +static void input_cb(void *user_data, IMediaSample *pMediaSample) +{ + struct dshow_stream *strm = (struct dshow_stream*)user_data; + unsigned char *buffer; + pjmedia_frame frame = {0}; + + if (strm->quit_flag) { + strm->cap_thread_exited = PJ_TRUE; + return; + } + + if (strm->cap_thread_initialized == 0 || !pj_thread_is_registered()) + { + pj_status_t status; + + status = pj_thread_register("ds_cap", strm->cap_thread_desc, + &strm->cap_thread); + if (status != PJ_SUCCESS) + return; + strm->cap_thread_initialized = 1; + PJ_LOG(5,(THIS_FILE, "Capture thread started")); + } + + IMediaSample_GetPointer(pMediaSample, &buffer); + + frame.type = PJMEDIA_TYPE_VIDEO; + IMediaSample_GetPointer(pMediaSample, (BYTE **)&frame.buf); + frame.size = IMediaSample_GetActualDataLength(pMediaSample); + frame.bit_info = 0; + frame.timestamp = strm->cap_ts; + strm->cap_ts.u64 += strm->cap_ts_inc; + if (strm->vid_cb.capture_cb) + (*strm->vid_cb.capture_cb)(&strm->base, strm->user_data, &frame); +} + +/* API: Put frame from stream */ +static pj_status_t dshow_stream_put_frame(pjmedia_vid_dev_stream *strm, + const pjmedia_frame *frame) +{ + struct dshow_stream *stream = (struct dshow_stream*)strm; + HRESULT hr; + + if (stream->quit_flag) { + stream->rend_thread_exited = PJ_TRUE; + return PJ_SUCCESS; + } + + hr = SourceFilter_Deliver(stream->dgraph.csource_filter, + frame->buf, frame->size); + if (FAILED(hr)) + return hr; + + return PJ_SUCCESS; +} + +static dshow_fmt_info* get_dshow_format_info(pjmedia_format_id id) +{ + unsigned i; + + for (i = 0; i < sizeof(dshow_fmts)/sizeof(dshow_fmts[0]); i++) { + if (dshow_fmts[i].pjmedia_format == id) + return &dshow_fmts[i]; + } + + return NULL; +} + +static pj_status_t create_filter_graph(pjmedia_dir dir, + unsigned id, + pj_bool_t use_def_size, + pj_bool_t use_def_fps, + struct dshow_factory *df, + struct dshow_stream *strm, + struct dshow_graph *graph) +{ + HRESULT hr; + IEnumPins *pEnum; + IPin *srcpin = NULL; + IPin *sinkpin = NULL; + AM_MEDIA_TYPE *mediatype= NULL, mtype; + VIDEOINFOHEADER *video_info, *vi = NULL; + pjmedia_video_format_detail *vfd; + const pjmedia_video_format_info *vfi; + + vfi = pjmedia_get_video_format_info(pjmedia_video_format_mgr_instance(), + strm->param.fmt.id); + if (!vfi) + return PJMEDIA_EVID_BADFORMAT; + + hr = CoCreateInstance(&CLSID_FilterGraph, NULL, CLSCTX_INPROC, + &IID_IFilterGraph, (LPVOID *)&graph->filter_graph); + if (FAILED(hr)) { + goto on_error; + } + + hr = IFilterGraph_QueryInterface(graph->filter_graph, &IID_IMediaFilter, + (LPVOID *)&graph->media_filter); + if (FAILED(hr)) { + goto on_error; + } + + if (dir == PJMEDIA_DIR_CAPTURE) { + hr = get_cap_device(df, id, &graph->source_filter); + if (FAILED(hr)) { + goto on_error; + } + } else { + graph->source_filter = SourceFilter_Create(&graph->csource_filter); + } + + hr = IFilterGraph_AddFilter(graph->filter_graph, graph->source_filter, + L"capture"); + if (FAILED(hr)) { + goto on_error; + } + + if (dir == PJMEDIA_DIR_CAPTURE) { + graph->rend_filter = NullRenderer_Create(input_cb, strm); + } else { + hr = CoCreateInstance(&CLSID_VideoMixingRenderer, NULL, + CLSCTX_INPROC, &IID_IBaseFilter, + (LPVOID *)&graph->rend_filter); + if (FAILED (hr)) { + goto on_error; + } + } + + IBaseFilter_EnumPins(graph->rend_filter, &pEnum); + if (SUCCEEDED(hr)) { + // Loop through all the pins + IPin *pPin = NULL; + + while (IEnumPins_Next(pEnum, 1, &pPin, NULL) == S_OK) { + PIN_DIRECTION pindirtmp; + + hr = IPin_QueryDirection(pPin, &pindirtmp); + if (hr == S_OK && pindirtmp == PINDIR_INPUT) { + sinkpin = pPin; + break; + } + IPin_Release(pPin); + } + IEnumPins_Release(pEnum); + } + + vfd = pjmedia_format_get_video_format_detail(&strm->param.fmt, PJ_TRUE); + + enum_dev_cap(graph->source_filter, dir, + get_dshow_format_info(strm->param.fmt.id)->dshow_format, + &mediatype, &srcpin, NULL); + graph->mediatype = mediatype; + + if (srcpin && dir == PJMEDIA_DIR_RENDER) { + mediatype = graph->mediatype = &mtype; + + memset (mediatype, 0, sizeof(AM_MEDIA_TYPE)); + mediatype->majortype = MEDIATYPE_Video; + mediatype->subtype = *(get_dshow_format_info(strm->param.fmt.id)-> + dshow_format); + mediatype->bFixedSizeSamples = TRUE; + mediatype->bTemporalCompression = FALSE; + + vi = (VIDEOINFOHEADER *) + CoTaskMemAlloc(sizeof(VIDEOINFOHEADER)); + memset (vi, 0, sizeof(VIDEOINFOHEADER)); + mediatype->formattype = FORMAT_VideoInfo; + mediatype->cbFormat = sizeof(VIDEOINFOHEADER); + mediatype->pbFormat = (BYTE *)vi; + + vi->rcSource.bottom = vfd->size.h; + vi->rcSource.right = vfd->size.w; + vi->rcTarget.bottom = vfd->size.h; + vi->rcTarget.right = vfd->size.w; + + vi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + vi->bmiHeader.biPlanes = 1; + vi->bmiHeader.biBitCount = vfi->bpp; + vi->bmiHeader.biCompression = strm->param.fmt.id; + } + + if (!srcpin || !sinkpin || !mediatype) { + hr = VFW_E_TYPE_NOT_ACCEPTED; + goto on_error; + } + video_info = (VIDEOINFOHEADER *) mediatype->pbFormat; + if (!use_def_size) { + video_info->bmiHeader.biWidth = vfd->size.w; + video_info->bmiHeader.biHeight = vfd->size.h; + } + if (!use_def_fps && vfd->fps.num != 0) + video_info->AvgTimePerFrame = (LONGLONG) (10000000 * + (double)vfd->fps.denum / + vfd->fps.num); + video_info->bmiHeader.biSizeImage = DIBSIZE(video_info->bmiHeader); + mediatype->lSampleSize = DIBSIZE(video_info->bmiHeader); + if (graph->csource_filter) + SourceFilter_SetMediaType(graph->csource_filter, + mediatype); + + hr = IFilterGraph_AddFilter(graph->filter_graph, + (IBaseFilter *)graph->rend_filter, + L"renderer"); + if (FAILED(hr)) + goto on_error; + + hr = IFilterGraph_ConnectDirect(graph->filter_graph, srcpin, sinkpin, + mediatype); + if (SUCCEEDED(hr) && (use_def_size || use_def_fps)) { + pjmedia_format_init_video(&strm->param.fmt, strm->param.fmt.id, + video_info->bmiHeader.biWidth, + video_info->bmiHeader.biHeight, + 10000000, + (unsigned)video_info->AvgTimePerFrame); + } + +on_error: + if (srcpin) + IPin_Release(srcpin); + if (sinkpin) + IPin_Release(sinkpin); + if (vi) + CoTaskMemFree(vi); + if (FAILED(hr)) { + char msg[80]; + if (AMGetErrorText(hr, msg, sizeof(msg))) { + PJ_LOG(4,(THIS_FILE, "Error creating filter graph: %s (hr=0x%x)", + msg, hr)); + } + return PJ_EUNKNOWN; + } + + return PJ_SUCCESS; +} + +static void destroy_filter_graph(struct dshow_stream * stream) +{ + if (stream->dgraph.source_filter) { + IBaseFilter_Release(stream->dgraph.source_filter); + stream->dgraph.source_filter = NULL; + } + if (stream->dgraph.rend_filter) { + IBaseFilter_Release(stream->dgraph.rend_filter); + stream->dgraph.rend_filter = NULL; + } + if (stream->dgraph.media_filter) { + IMediaFilter_Release(stream->dgraph.media_filter); + stream->dgraph.media_filter = NULL; + } + if (stream->dgraph.filter_graph) { + IFilterGraph_Release(stream->dgraph.filter_graph); + stream->dgraph.filter_graph = NULL; + } +} + +/* API: create stream */ +static pj_status_t dshow_factory_create_stream( + pjmedia_vid_dev_factory *f, + pjmedia_vid_dev_param *param, + const pjmedia_vid_dev_cb *cb, + void *user_data, + pjmedia_vid_dev_stream **p_vid_strm) +{ + struct dshow_factory *df = (struct dshow_factory*)f; + pj_pool_t *pool; + struct dshow_stream *strm; + pj_status_t status; + + PJ_ASSERT_RETURN(param->dir == PJMEDIA_DIR_CAPTURE || + param->dir == PJMEDIA_DIR_RENDER, PJ_EINVAL); + + if (!get_dshow_format_info(param->fmt.id)) + return PJMEDIA_EVID_BADFORMAT; + + /* Create and Initialize stream descriptor */ + pool = pj_pool_create(df->pf, "dshow-dev", 1000, 1000, NULL); + PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); + + strm = PJ_POOL_ZALLOC_T(pool, struct dshow_stream); + pj_memcpy(&strm->param, param, sizeof(*param)); + strm->pool = pool; + pj_memcpy(&strm->vid_cb, cb, sizeof(*cb)); + strm->user_data = user_data; + pjmedia_event_publisher_init(&strm->base.epub, PJMEDIA_SIG_VID_DEV_DSHOW); + + if (param->dir & PJMEDIA_DIR_CAPTURE) { + const pjmedia_video_format_detail *vfd; + + /* Create capture stream here */ + status = create_filter_graph(PJMEDIA_DIR_CAPTURE, param->cap_id, + PJ_FALSE, PJ_FALSE, df, strm, + &strm->dgraph); + if (status != PJ_SUCCESS) { + destroy_filter_graph(strm); + /* Try to use default fps */ + PJ_LOG(4,(THIS_FILE, "Trying to open dshow dev with default fps")); + status = create_filter_graph(PJMEDIA_DIR_CAPTURE, param->cap_id, + PJ_FALSE, PJ_TRUE, df, strm, + &strm->dgraph); + + if (status != PJ_SUCCESS) { + /* Still failed, now try to use default fps and size */ + destroy_filter_graph(strm); + /* Try to use default fps */ + PJ_LOG(4,(THIS_FILE, "Trying to open dshow dev with default " + "size & fps")); + status = create_filter_graph(PJMEDIA_DIR_CAPTURE, + param->cap_id, + PJ_TRUE, PJ_TRUE, df, strm, + &strm->dgraph); + } + + if (status != PJ_SUCCESS) + goto on_error; + pj_memcpy(param, &strm->param, sizeof(*param)); + } + + vfd = pjmedia_format_get_video_format_detail(¶m->fmt, PJ_TRUE); + strm->cap_ts_inc = PJMEDIA_SPF2(param->clock_rate, &vfd->fps, 1); + } else if (param->dir & PJMEDIA_DIR_RENDER) { + /* Create render stream here */ + status = create_filter_graph(PJMEDIA_DIR_RENDER, param->rend_id, + PJ_FALSE, PJ_FALSE, df, strm, + &strm->dgraph); + if (status != PJ_SUCCESS) + goto on_error; + } + + /* Apply the remaining settings */ + if (param->flags & PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW) { + dshow_stream_set_cap(&strm->base, + PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW, + ¶m->window); + } + + /* Done */ + strm->base.op = &stream_op; + *p_vid_strm = &strm->base; + + return PJ_SUCCESS; + +on_error: + dshow_stream_destroy((pjmedia_vid_dev_stream *)strm); + return status; +} + +/* API: Get stream info. */ +static pj_status_t dshow_stream_get_param(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_param *pi) +{ + struct dshow_stream *strm = (struct dshow_stream*)s; + + PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL); + + pj_memcpy(pi, &strm->param, sizeof(*pi)); + + if (dshow_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW, + &pi->window) == PJ_SUCCESS) + { + pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW; + } + + return PJ_SUCCESS; +} + +/* API: get capability */ +static pj_status_t dshow_stream_get_cap(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_cap cap, + void *pval) +{ + struct dshow_stream *strm = (struct dshow_stream*)s; + + PJ_UNUSED_ARG(strm); + + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + if (cap==PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW) + { + *(unsigned*)pval = 0; + return PJ_SUCCESS; + } else { + return PJMEDIA_EVID_INVCAP; + } +} + +/* API: set capability */ +static pj_status_t dshow_stream_set_cap(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_cap cap, + const void *pval) +{ + struct dshow_stream *strm = (struct dshow_stream*)s; + + PJ_UNUSED_ARG(strm); + + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + if (cap==PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW) + { + // set renderer's window here + return PJ_SUCCESS; + } + + return PJMEDIA_EVID_INVCAP; +} + +/* API: Start stream. */ +static pj_status_t dshow_stream_start(pjmedia_vid_dev_stream *strm) +{ + struct dshow_stream *stream = (struct dshow_stream*)strm; + HRESULT hr; + + stream->quit_flag = PJ_FALSE; + stream->cap_thread_exited = PJ_FALSE; + stream->rend_thread_exited = PJ_FALSE; + + hr = IMediaFilter_Run(stream->dgraph.media_filter, 0); + if (FAILED(hr)) { + char msg[80]; + if (AMGetErrorText(hr, msg, sizeof(msg))) { + PJ_LOG(4,(THIS_FILE, "Error starting media: %s", msg)); + } + return PJ_EUNKNOWN; + } + + PJ_LOG(4, (THIS_FILE, "Starting dshow video stream")); + + return PJ_SUCCESS; +} + +/* API: Stop stream. */ +static pj_status_t dshow_stream_stop(pjmedia_vid_dev_stream *strm) +{ + struct dshow_stream *stream = (struct dshow_stream*)strm; + unsigned i; + + stream->quit_flag = PJ_TRUE; + if (stream->cap_thread) { + for (i=0; !stream->cap_thread_exited && i<100; ++i) + pj_thread_sleep(10); + } + for (i=0; !stream->rend_thread_exited && i<100; ++i) + pj_thread_sleep(10); + + IMediaFilter_Stop(stream->dgraph.media_filter); + + PJ_LOG(4, (THIS_FILE, "Stopping dshow video stream")); + + return PJ_SUCCESS; +} + +/* API: Destroy stream. */ +static pj_status_t dshow_stream_destroy(pjmedia_vid_dev_stream *strm) +{ + struct dshow_stream *stream = (struct dshow_stream*)strm; + + PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); + + dshow_stream_stop(strm); + destroy_filter_graph(stream); + + pj_pool_release(stream->pool); + + return PJ_SUCCESS; +} + +#endif /* PJMEDIA_VIDEO_DEV_HAS_DSHOW */ diff --git a/pjmedia/src/pjmedia-videodev/dshowclasses.cpp b/pjmedia/src/pjmedia-videodev/dshowclasses.cpp new file mode 100644 index 00000000..bdddd01f --- /dev/null +++ b/pjmedia/src/pjmedia-videodev/dshowclasses.cpp @@ -0,0 +1,245 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2010 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <pjmedia-videodev/config.h> + +#if PJMEDIA_VIDEO_DEV_HAS_DSHOW + +#include <assert.h> +#include <streams.h> + +#if PJ_DEBUG +# pragma comment(lib, "Strmbasd.lib") +#else +# pragma comment(lib, "Strmbase.lib") +#endif + +typedef void (*input_callback)(void *user_data, IMediaSample *pMediaSample); + +const GUID CLSID_NullRenderer = {0xF9168C5E, 0xCEB2, 0x4FAA, {0xB6, 0xBF, + 0x32, 0x9B, 0xF3, 0x9F, 0xA1, 0xE4}}; + +const GUID CLSID_SourceFilter = {0xF9168C5E, 0xCEB2, 0x4FAA, {0xB6, 0xBF, + 0x32, 0x9B, 0xF3, 0x9F, 0xA1, 0xE5}}; + +class NullRenderer: public CBaseRenderer +{ +public: + NullRenderer(HRESULT *pHr); + virtual ~NullRenderer(); + + virtual HRESULT CheckMediaType(const CMediaType *pmt); + virtual HRESULT DoRenderSample(IMediaSample *pMediaSample); + + input_callback input_cb; + void *user_data; +}; + +class OutputPin: public CBaseOutputPin +{ +public: + OutputPin(CBaseFilter *pFilter, CCritSec *pLock, HRESULT *pHr); + ~OutputPin(); + + HRESULT Push(void *buf, long size); + + virtual HRESULT CheckMediaType(const CMediaType *pmt); + virtual HRESULT DecideBufferSize(IMemAllocator *pAlloc, + ALLOCATOR_PROPERTIES *ppropInputRequest); + + CMediaType mediaType; + long bufSize; +}; + +class SourceFilter: public CBaseFilter +{ +public: + SourceFilter(); + ~SourceFilter(); + + int GetPinCount(); + CBasePin* GetPin(int n); + +protected: + CCritSec lock; + OutputPin* outPin; +}; + +OutputPin::OutputPin(CBaseFilter *pFilter, CCritSec *pLock, HRESULT *pHr): + CBaseOutputPin("OutputPin", pFilter, pLock, pHr, L"OutputPin") +{ +} + +OutputPin::~OutputPin() +{ +} + +HRESULT OutputPin::CheckMediaType(const CMediaType *pmt) +{ + return S_OK; +} + +HRESULT OutputPin::DecideBufferSize(IMemAllocator *pAlloc, + ALLOCATOR_PROPERTIES *ppropInputRequest) +{ + ALLOCATOR_PROPERTIES properties; + + ppropInputRequest->cbBuffer = bufSize; + ppropInputRequest->cBuffers = 1; + + /* First set the buffer descriptions we're interested in */ + pAlloc->SetProperties(ppropInputRequest, &properties); + + return S_OK; +} + +HRESULT OutputPin::Push(void *buf, long size) +{ + HRESULT hr; + IMediaSample *pSample; + VIDEOINFOHEADER *vi; + AM_MEDIA_TYPE *pmt; + BYTE *dst_buf; + + /** + * Hold the critical section here as the pin might get disconnected + * during the Deliver() method call. + */ + m_pLock->Lock(); + + hr = GetDeliveryBuffer(&pSample, NULL, NULL, 0); + if (FAILED(hr)) + goto on_error; + + pSample->GetMediaType(&pmt); + if (pmt) { + mediaType.Set(*pmt); + bufSize = pmt->lSampleSize; + } + + pSample->GetPointer(&dst_buf); + vi = (VIDEOINFOHEADER *)mediaType.pbFormat; + if (vi->rcSource.right == vi->bmiHeader.biWidth) { + assert(pSample->GetSize() >= size); + memcpy(dst_buf, buf, size); + } else { + unsigned i, bpp; + unsigned dststride, srcstride; + BYTE *src_buf = (BYTE *)buf; + + bpp = size / abs(vi->bmiHeader.biHeight) / vi->rcSource.right; + dststride = vi->bmiHeader.biWidth * bpp; + srcstride = vi->rcSource.right * bpp; + for (i = abs(vi->bmiHeader.biHeight); i > 0; i--) { + memcpy(dst_buf, src_buf, srcstride); + dst_buf += dststride; + src_buf += srcstride; + } + } + pSample->SetActualDataLength(size); + + hr = Deliver(pSample); + + pSample->Release(); + +on_error: + m_pLock->Unlock(); + return hr; +} + +SourceFilter::SourceFilter(): CBaseFilter("SourceFilter", NULL, &lock, + CLSID_SourceFilter) +{ + HRESULT hr; + outPin = new OutputPin(this, &lock, &hr); +} + +SourceFilter::~SourceFilter() +{ +} + +int SourceFilter::GetPinCount() +{ + return 1; +} + +CBasePin* SourceFilter::GetPin(int n) +{ + return outPin; +} + +NullRenderer::NullRenderer(HRESULT *pHr): CBaseRenderer(CLSID_NullRenderer, + "NullRenderer", + NULL, pHr) +{ + input_cb = NULL; +} + +NullRenderer::~NullRenderer() +{ +} + +HRESULT NullRenderer::CheckMediaType(const CMediaType *pmt) +{ + return S_OK; +} + +HRESULT NullRenderer::DoRenderSample(IMediaSample *pMediaSample) +{ + if (input_cb) + input_cb(user_data, pMediaSample); + + return S_OK; +} + +extern "C" IBaseFilter* NullRenderer_Create(input_callback input_cb, + void *user_data) +{ + HRESULT hr; + NullRenderer *renderer = new NullRenderer(&hr); + renderer->AddRef(); + renderer->input_cb = input_cb; + renderer->user_data = user_data; + + return (CBaseFilter *)renderer; +} + +extern "C" IBaseFilter* SourceFilter_Create(SourceFilter **pSrc) +{ + SourceFilter *src = new SourceFilter(); + src->AddRef(); + *pSrc = src; + + return (CBaseFilter *)src; +} + +extern "C" HRESULT SourceFilter_Deliver(SourceFilter *src, + void *buf, long size) +{ + return ((OutputPin *)src->GetPin(0))->Push(buf, size); +} + +extern "C" void SourceFilter_SetMediaType(SourceFilter *src, + AM_MEDIA_TYPE *pmt) +{ + ((OutputPin *)src->GetPin(0))->mediaType.Set(*pmt); + ((OutputPin *)src->GetPin(0))->bufSize = pmt->lSampleSize; +} + +#endif /* PJMEDIA_VIDEO_DEV_HAS_DSHOW */ diff --git a/pjmedia/src/pjmedia-videodev/errno.c b/pjmedia/src/pjmedia-videodev/errno.c new file mode 100644 index 00000000..c0729ca0 --- /dev/null +++ b/pjmedia/src/pjmedia-videodev/errno.c @@ -0,0 +1,112 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjmedia-videodev/errno.h> +#include <pj/string.h> +#include <pj/unicode.h> + +/* PJMEDIA-videodev's own error codes/messages + * MUST KEEP THIS ARRAY SORTED!! + * Message must be limited to 64 chars! + */ + +#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0) + +static const struct +{ + int code; + const char *msg; +} err_str[] = +{ + PJ_BUILD_ERR( PJMEDIA_EVID_ERR, "Unspecified video device error" ), + PJ_BUILD_ERR( PJMEDIA_EVID_SYSERR, "Unknown error from video driver" ), + PJ_BUILD_ERR( PJMEDIA_EVID_INIT, "video subsystem not initialized" ), + PJ_BUILD_ERR( PJMEDIA_EVID_INVDEV, "Invalid video device" ), + PJ_BUILD_ERR( PJMEDIA_EVID_NODEV, "Found no video devices" ), + PJ_BUILD_ERR( PJMEDIA_EVID_NODEFDEV, "Unable to find default video device" ), + PJ_BUILD_ERR( PJMEDIA_EVID_NOTREADY, "video device not ready" ), + PJ_BUILD_ERR( PJMEDIA_EVID_INVCAP, "Invalid or unsupported video capability" ), + PJ_BUILD_ERR( PJMEDIA_EVID_INVOP, "Invalid or unsupported video device operation" ), + PJ_BUILD_ERR( PJMEDIA_EVID_BADFORMAT, "Bad or invalid video device format" ), + PJ_BUILD_ERR( PJMEDIA_EVID_SAMPFORMAT, "Invalid video device sample format"), + PJ_BUILD_ERR( PJMEDIA_EVID_BADLATENCY, "Bad video latency setting") + +}; + +#endif /* PJ_HAS_ERROR_STRING */ + + + +/* + * pjmedia_videodev_strerror() + */ +PJ_DEF(pj_str_t) pjmedia_videodev_strerror(pj_status_t statcode, + char *buf, pj_size_t bufsize ) +{ + pj_str_t errstr; + +#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0) + + /* videodev error */ + if (statcode >= PJMEDIA_VIDEODEV_ERRNO_START && + statcode < PJMEDIA_VIDEODEV_ERRNO_END) + { + /* Find the error in the table. + * Use binary search! + */ + int first = 0; + int n = PJ_ARRAY_SIZE(err_str); + + while (n > 0) { + int half = n/2; + int mid = first + half; + + if (err_str[mid].code < statcode) { + first = mid+1; + n -= (half+1); + } else if (err_str[mid].code > statcode) { + n = half; + } else { + first = mid; + break; + } + } + + + if (PJ_ARRAY_SIZE(err_str) && err_str[first].code == statcode) { + pj_str_t msg; + + msg.ptr = (char*)err_str[first].msg; + msg.slen = pj_ansi_strlen(err_str[first].msg); + + errstr.ptr = buf; + pj_strncpy_with_null(&errstr, &msg, bufsize); + return errstr; + + } + } +#endif /* PJ_HAS_ERROR_STRING */ + + /* Error not found. */ + errstr.ptr = buf; + errstr.slen = pj_ansi_snprintf(buf, bufsize, + "Unknown pjmedia-videodev error %d", + statcode); + + return errstr; +} diff --git a/pjmedia/src/pjmedia-videodev/ffmpeg_dev.c b/pjmedia/src/pjmedia-videodev/ffmpeg_dev.c new file mode 100644 index 00000000..aa02c87b --- /dev/null +++ b/pjmedia/src/pjmedia-videodev/ffmpeg_dev.c @@ -0,0 +1,514 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-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 + */ + +/* Video device with ffmpeg backend, currently only capture devices are + * implemented. + * + * Issues: + * - no device enumeration (ffmpeg limitation), so this uses "host API" enum + * instead + * - need stricter filter on "host API" enum, currently audio capture devs are + * still listed. + * - no format enumeration, currently hardcoded to PJMEDIA_FORMAT_RGB24 only + * - tested on Vista only (vfw backend) with virtual cam + * - vfw backend produce bottom up pictures + * - using VS IDE, this cannot run under debugger! + */ + +#include <pjmedia-videodev/videodev_imp.h> +#include <pj/assert.h> +#include <pj/log.h> +#include <pj/os.h> +#include <pj/unicode.h> + +#if PJMEDIA_VIDEO_DEV_HAS_FFMPEG + +#define THIS_FILE "ffmpeg.c" + +#include "../pjmedia/ffmpeg_util.h" +#include <libavdevice/avdevice.h> +#include <libavformat/avformat.h> + +#define MAX_DEV_CNT 8 + +typedef struct ffmpeg_dev_info +{ + pjmedia_vid_dev_info base; + AVInputFormat *host_api; + const char *def_devname; +} ffmpeg_dev_info; + + +typedef struct ffmpeg_factory +{ + pjmedia_vid_dev_factory base; + pj_pool_factory *pf; + pj_pool_t *pool; + pj_pool_t *dev_pool; + unsigned dev_count; + ffmpeg_dev_info dev_info[MAX_DEV_CNT]; +} ffmpeg_factory; + + +typedef struct ffmpeg_stream +{ + pjmedia_vid_dev_stream base; + ffmpeg_factory *factory; + pj_pool_t *pool; + pjmedia_vid_dev_param param; + AVFormatContext *ff_fmt_ctx; +} ffmpeg_stream; + + +/* Prototypes */ +static pj_status_t ffmpeg_factory_init(pjmedia_vid_dev_factory *f); +static pj_status_t ffmpeg_factory_destroy(pjmedia_vid_dev_factory *f); +static pj_status_t ffmpeg_factory_refresh(pjmedia_vid_dev_factory *f); +static unsigned ffmpeg_factory_get_dev_count(pjmedia_vid_dev_factory *f); +static pj_status_t ffmpeg_factory_get_dev_info(pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_info *info); +static pj_status_t ffmpeg_factory_default_param(pj_pool_t *pool, + pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_param *param); +static pj_status_t ffmpeg_factory_create_stream( + pjmedia_vid_dev_factory *f, + pjmedia_vid_dev_param *param, + const pjmedia_vid_dev_cb *cb, + void *user_data, + pjmedia_vid_dev_stream **p_vid_strm); + +static pj_status_t ffmpeg_stream_get_param(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_param *param); +static pj_status_t ffmpeg_stream_get_cap(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_cap cap, + void *value); +static pj_status_t ffmpeg_stream_set_cap(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_cap cap, + const void *value); +static pj_status_t ffmpeg_stream_start(pjmedia_vid_dev_stream *strm); +static pj_status_t ffmpeg_stream_get_frame(pjmedia_vid_dev_stream *s, + pjmedia_frame *frame); +static pj_status_t ffmpeg_stream_stop(pjmedia_vid_dev_stream *strm); +static pj_status_t ffmpeg_stream_destroy(pjmedia_vid_dev_stream *strm); + +/* Operations */ +static pjmedia_vid_dev_factory_op factory_op = +{ + &ffmpeg_factory_init, + &ffmpeg_factory_destroy, + &ffmpeg_factory_get_dev_count, + &ffmpeg_factory_get_dev_info, + &ffmpeg_factory_default_param, + &ffmpeg_factory_create_stream, + &ffmpeg_factory_refresh +}; + +static pjmedia_vid_dev_stream_op stream_op = +{ + &ffmpeg_stream_get_param, + &ffmpeg_stream_get_cap, + &ffmpeg_stream_set_cap, + &ffmpeg_stream_start, + &ffmpeg_stream_get_frame, + NULL, + &ffmpeg_stream_stop, + &ffmpeg_stream_destroy +}; + + +static void print_ffmpeg_err(int err) +{ + char errbuf[512]; + if (av_strerror(err, errbuf, sizeof(errbuf)) >= 0) + PJ_LOG(1, (THIS_FILE, "ffmpeg err %d: %s", err, errbuf)); + +} + +static void print_ffmpeg_log(void* ptr, int level, const char* fmt, va_list vl) +{ + PJ_UNUSED_ARG(ptr); + PJ_UNUSED_ARG(level); + vfprintf(stdout, fmt, vl); +} + + +static pj_status_t ffmpeg_capture_open(AVFormatContext **ctx, + AVInputFormat *ifmt, + const char *dev_name, + const pjmedia_vid_dev_param *param) +{ + AVFormatParameters fp; + pjmedia_video_format_detail *vfd; + int err; + + PJ_ASSERT_RETURN(ctx && ifmt && dev_name && param, PJ_EINVAL); + PJ_ASSERT_RETURN(param->fmt.detail_type == PJMEDIA_FORMAT_DETAIL_VIDEO, + PJ_EINVAL); + + vfd = pjmedia_format_get_video_format_detail(¶m->fmt, PJ_TRUE); + + /* Init ffmpeg format context */ + *ctx = avformat_alloc_context(); + + /* Init ffmpeg format param */ + pj_bzero(&fp, sizeof(fp)); + fp.prealloced_context = 1; + fp.width = vfd->size.w; + fp.height = vfd->size.h; + fp.pix_fmt = PIX_FMT_BGR24; + fp.time_base.num = vfd->fps.denum; + fp.time_base.den = vfd->fps.num; + + /* Open capture stream */ + err = av_open_input_stream(ctx, NULL, dev_name, ifmt, &fp); + if (err < 0) { + *ctx = NULL; /* ffmpeg freed its states on failure, do we must too */ + print_ffmpeg_err(err); + return PJ_EUNKNOWN; + } + + return PJ_SUCCESS; +} + +static void ffmpeg_capture_close(AVFormatContext *ctx) +{ + if (ctx) + av_close_input_stream(ctx); +} + + +/**************************************************************************** + * Factory operations + */ +/* + * Init ffmpeg_ video driver. + */ +pjmedia_vid_dev_factory* pjmedia_ffmpeg_factory(pj_pool_factory *pf) +{ + ffmpeg_factory *f; + pj_pool_t *pool; + + pool = pj_pool_create(pf, "ffmpeg_cap_dev", 1000, 1000, NULL); + f = PJ_POOL_ZALLOC_T(pool, ffmpeg_factory); + + f->pool = pool; + f->pf = pf; + f->base.op = &factory_op; + + avdevice_register_all(); + + return &f->base; +} + + +/* API: init factory */ +static pj_status_t ffmpeg_factory_init(pjmedia_vid_dev_factory *f) +{ + return ffmpeg_factory_refresh(f); +} + +/* API: destroy factory */ +static pj_status_t ffmpeg_factory_destroy(pjmedia_vid_dev_factory *f) +{ + ffmpeg_factory *ff = (ffmpeg_factory*)f; + pj_pool_t *pool = ff->pool; + + ff->dev_count = 0; + ff->pool = NULL; + if (ff->dev_pool) + pj_pool_release(ff->dev_pool); + if (pool) + pj_pool_release(pool); + + return PJ_SUCCESS; +} + +/* API: refresh the list of devices */ +static pj_status_t ffmpeg_factory_refresh(pjmedia_vid_dev_factory *f) +{ + ffmpeg_factory *ff = (ffmpeg_factory*)f; + AVInputFormat *p; + ffmpeg_dev_info *info; + + av_log_set_callback(&print_ffmpeg_log); + av_log_set_level(AV_LOG_DEBUG); + + if (ff->dev_pool) { + pj_pool_release(ff->dev_pool); + ff->dev_pool = NULL; + } + + /* TODO: this should enumerate devices, now it enumerates host APIs */ + ff->dev_count = 0; + ff->dev_pool = pj_pool_create(ff->pf, "ffmpeg_cap_dev", 500, 500, NULL); + + p = av_iformat_next(NULL); + while (p) { + if (p->flags & AVFMT_NOFILE) { + unsigned i; + + info = &ff->dev_info[ff->dev_count++]; + pj_bzero(info, sizeof(*info)); + pj_ansi_strncpy(info->base.name, "default", + sizeof(info->base.name)); + pj_ansi_snprintf(info->base.driver, sizeof(info->base.driver), + "%s (ffmpeg)", p->name); + info->base.dir = PJMEDIA_DIR_CAPTURE; + info->base.has_callback = PJ_FALSE; + + info->host_api = p; + +#if defined(PJ_WIN32) && PJ_WIN32!=0 + info->def_devname = "0"; +#elif defined(PJ_LINUX) && PJ_LINUX!=0 + info->def_devname = "/dev/video0"; +#endif + + /* Set supported formats, currently hardcoded to RGB24 only */ + info->base.caps = PJMEDIA_VID_DEV_CAP_FORMAT; + info->base.fmt_cnt = 1; + for (i = 0; i < info->base.fmt_cnt; ++i) { + pjmedia_format *fmt = &info->base.fmt[i]; + + fmt->id = PJMEDIA_FORMAT_RGB24; + fmt->type = PJMEDIA_TYPE_VIDEO; + fmt->detail_type = PJMEDIA_FORMAT_DETAIL_NONE; + } + } + p = av_iformat_next(p); + } + + return PJ_SUCCESS; +} + +/* API: get number of devices */ +static unsigned ffmpeg_factory_get_dev_count(pjmedia_vid_dev_factory *f) +{ + ffmpeg_factory *ff = (ffmpeg_factory*)f; + return ff->dev_count; +} + +/* API: get device info */ +static pj_status_t ffmpeg_factory_get_dev_info(pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_info *info) +{ + ffmpeg_factory *ff = (ffmpeg_factory*)f; + + PJ_ASSERT_RETURN(index < ff->dev_count, PJMEDIA_EVID_INVDEV); + + pj_memcpy(info, &ff->dev_info[index].base, sizeof(*info)); + + return PJ_SUCCESS; +} + +/* API: create default device parameter */ +static pj_status_t ffmpeg_factory_default_param(pj_pool_t *pool, + pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_param *param) +{ + ffmpeg_factory *ff = (ffmpeg_factory*)f; + ffmpeg_dev_info *info; + + PJ_ASSERT_RETURN(index < ff->dev_count, PJMEDIA_EVID_INVDEV); + + PJ_UNUSED_ARG(pool); + + info = &ff->dev_info[index]; + + pj_bzero(param, sizeof(*param)); + param->dir = PJMEDIA_DIR_CAPTURE; + param->cap_id = index; + param->rend_id = PJMEDIA_VID_INVALID_DEV; + param->clock_rate = 0; + + /* Set the device capabilities here */ + param->flags = PJMEDIA_VID_DEV_CAP_FORMAT; + param->clock_rate = 90000; + pjmedia_format_init_video(¶m->fmt, 0, 320, 240, 25, 1); + param->fmt.id = info->base.fmt[0].id; + + return PJ_SUCCESS; +} + + + +/* API: create stream */ +static pj_status_t ffmpeg_factory_create_stream( + pjmedia_vid_dev_factory *f, + pjmedia_vid_dev_param *param, + const pjmedia_vid_dev_cb *cb, + void *user_data, + pjmedia_vid_dev_stream **p_vid_strm) +{ + ffmpeg_factory *ff = (ffmpeg_factory*)f; + pj_pool_t *pool; + ffmpeg_stream *strm; + + PJ_ASSERT_RETURN(f && param && p_vid_strm, PJ_EINVAL); + PJ_ASSERT_RETURN(param->dir == PJMEDIA_DIR_CAPTURE, PJ_EINVAL); + PJ_ASSERT_RETURN((unsigned)param->cap_id < ff->dev_count, PJ_EINVAL); + PJ_ASSERT_RETURN(param->fmt.detail_type == PJMEDIA_FORMAT_DETAIL_VIDEO, + PJ_EINVAL); + + PJ_UNUSED_ARG(cb); + PJ_UNUSED_ARG(user_data); + + /* Create and Initialize stream descriptor */ + pool = pj_pool_create(ff->pf, "ffmpeg-dev", 1000, 1000, NULL); + PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); + + strm = PJ_POOL_ZALLOC_T(pool, struct ffmpeg_stream); + strm->factory = (ffmpeg_factory*)f; + strm->pool = pool; + pj_memcpy(&strm->param, param, sizeof(*param)); + pjmedia_event_publisher_init(&strm->base.epub); + + /* Done */ + strm->base.op = &stream_op; + *p_vid_strm = &strm->base; + + return PJ_SUCCESS; +} + +/* API: Get stream info. */ +static pj_status_t ffmpeg_stream_get_param(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_param *pi) +{ + ffmpeg_stream *strm = (ffmpeg_stream*)s; + + PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL); + + pj_memcpy(pi, &strm->param, sizeof(*pi)); + + return PJ_SUCCESS; +} + +/* API: get capability */ +static pj_status_t ffmpeg_stream_get_cap(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_cap cap, + void *pval) +{ + ffmpeg_stream *strm = (ffmpeg_stream*)s; + + PJ_UNUSED_ARG(strm); + PJ_UNUSED_ARG(cap); + PJ_UNUSED_ARG(pval); + + return PJMEDIA_EVID_INVCAP; +} + +/* API: set capability */ +static pj_status_t ffmpeg_stream_set_cap(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_cap cap, + const void *pval) +{ + ffmpeg_stream *strm = (ffmpeg_stream*)s; + + PJ_UNUSED_ARG(strm); + PJ_UNUSED_ARG(cap); + PJ_UNUSED_ARG(pval); + + return PJMEDIA_EVID_INVCAP; +} + + +/* API: Start stream. */ +static pj_status_t ffmpeg_stream_start(pjmedia_vid_dev_stream *s) +{ + ffmpeg_stream *strm = (ffmpeg_stream*)s; + ffmpeg_dev_info *info; + pj_status_t status; + + info = &strm->factory->dev_info[strm->param.cap_id]; + + PJ_LOG(4, (THIS_FILE, "Starting ffmpeg capture stream")); + + status = ffmpeg_capture_open(&strm->ff_fmt_ctx, info->host_api, + info->def_devname, &strm->param); + if (status != PJ_SUCCESS) { + /* must set ffmpeg states to NULL on any failure */ + strm->ff_fmt_ctx = NULL; + } + + return status; +} + + +/* API: Get frame from stream */ +static pj_status_t ffmpeg_stream_get_frame(pjmedia_vid_dev_stream *s, + pjmedia_frame *frame) +{ + ffmpeg_stream *strm = (ffmpeg_stream*)s; + AVPacket p; + int err; + + err = av_read_frame(strm->ff_fmt_ctx, &p); + if (err < 0) { + print_ffmpeg_err(err); + return PJ_EUNKNOWN; + } + + pj_bzero(frame, sizeof(*frame)); + frame->type = PJMEDIA_FRAME_TYPE_VIDEO; + frame->buf = p.data; + frame->size = p.size; + + return PJ_SUCCESS; +} + + +/* API: Stop stream. */ +static pj_status_t ffmpeg_stream_stop(pjmedia_vid_dev_stream *s) +{ + ffmpeg_stream *strm = (ffmpeg_stream*)s; + + PJ_LOG(4, (THIS_FILE, "Stopping ffmpeg capture stream")); + + ffmpeg_capture_close(strm->ff_fmt_ctx); + strm->ff_fmt_ctx = NULL; + + return PJ_SUCCESS; +} + + +/* API: Destroy stream. */ +static pj_status_t ffmpeg_stream_destroy(pjmedia_vid_dev_stream *s) +{ + ffmpeg_stream *strm = (ffmpeg_stream*)s; + + PJ_ASSERT_RETURN(strm != NULL, PJ_EINVAL); + + ffmpeg_stream_stop(s); + + pj_pool_release(strm->pool); + + return PJ_SUCCESS; +} + +#ifdef _MSC_VER +# pragma comment( lib, "avdevice.lib") +# pragma comment( lib, "avformat.lib") +# pragma comment( lib, "avutil.lib") +#endif + +#endif /* PJMEDIA_VIDEO_DEV_HAS_FFMPEG */ diff --git a/pjmedia/src/pjmedia-videodev/ios_dev.m b/pjmedia/src/pjmedia-videodev/ios_dev.m new file mode 100644 index 00000000..cafa57ad --- /dev/null +++ b/pjmedia/src/pjmedia-videodev/ios_dev.m @@ -0,0 +1,685 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2010 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjmedia-videodev/videodev_imp.h> +#include <pj/assert.h> +#include <pj/log.h> +#include <pj/os.h> + +#if PJMEDIA_VIDEO_DEV_HAS_IOS +#include "Availability.h" +#ifdef __IPHONE_4_0 + +#import <UIKit/UIKit.h> +#import <AVFoundation/AVFoundation.h> + +#define THIS_FILE "ios_dev.c" +#define DEFAULT_CLOCK_RATE 9000 +#define DEFAULT_WIDTH 480 +#define DEFAULT_HEIGHT 360 +#define DEFAULT_FPS 15 + +typedef struct ios_fmt_info +{ + pjmedia_format_id pjmedia_format; + UInt32 ios_format; +} ios_fmt_info; + +static ios_fmt_info ios_fmts[] = +{ + {PJMEDIA_FORMAT_BGRA, kCVPixelFormatType_32BGRA} , +}; + +/* qt device info */ +struct ios_dev_info +{ + pjmedia_vid_dev_info info; +}; + +/* qt factory */ +struct ios_factory +{ + pjmedia_vid_dev_factory base; + pj_pool_t *pool; + pj_pool_factory *pf; + + unsigned dev_count; + struct ios_dev_info *dev_info; +}; + +@interface VOutDelegate: NSObject + <AVCaptureVideoDataOutputSampleBufferDelegate> +{ +@public + struct ios_stream *stream; +} +@end + +/* Video stream. */ +struct ios_stream +{ + pjmedia_vid_dev_stream base; /**< Base stream */ + pjmedia_vid_dev_param param; /**< Settings */ + pj_pool_t *pool; /**< Memory pool */ + + pjmedia_vid_dev_cb vid_cb; /**< Stream callback */ + void *user_data; /**< Application data */ + + pjmedia_rect_size size; + pj_uint8_t bpp; + unsigned bytes_per_row; + unsigned frame_size; + + AVCaptureSession *cap_session; + AVCaptureDeviceInput *dev_input; + AVCaptureVideoDataOutput *video_output; + VOutDelegate *vout_delegate; + + UIImageView *imgView; + void *buf; + + pj_timestamp frame_ts; + unsigned ts_inc; +}; + + +/* Prototypes */ +static pj_status_t ios_factory_init(pjmedia_vid_dev_factory *f); +static pj_status_t ios_factory_destroy(pjmedia_vid_dev_factory *f); +static pj_status_t ios_factory_refresh(pjmedia_vid_dev_factory *f); +static unsigned ios_factory_get_dev_count(pjmedia_vid_dev_factory *f); +static pj_status_t ios_factory_get_dev_info(pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_info *info); +static pj_status_t ios_factory_default_param(pj_pool_t *pool, + pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_param *param); +static pj_status_t ios_factory_create_stream( + pjmedia_vid_dev_factory *f, + pjmedia_vid_dev_param *param, + const pjmedia_vid_dev_cb *cb, + void *user_data, + pjmedia_vid_dev_stream **p_vid_strm); + +static pj_status_t ios_stream_get_param(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_param *param); +static pj_status_t ios_stream_get_cap(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_cap cap, + void *value); +static pj_status_t ios_stream_set_cap(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_cap cap, + const void *value); +static pj_status_t ios_stream_start(pjmedia_vid_dev_stream *strm); +static pj_status_t ios_stream_put_frame(pjmedia_vid_dev_stream *strm, + const pjmedia_frame *frame); +static pj_status_t ios_stream_stop(pjmedia_vid_dev_stream *strm); +static pj_status_t ios_stream_destroy(pjmedia_vid_dev_stream *strm); + +/* Operations */ +static pjmedia_vid_dev_factory_op factory_op = +{ + &ios_factory_init, + &ios_factory_destroy, + &ios_factory_get_dev_count, + &ios_factory_get_dev_info, + &ios_factory_default_param, + &ios_factory_create_stream, + &ios_factory_refresh +}; + +static pjmedia_vid_dev_stream_op stream_op = +{ + &ios_stream_get_param, + &ios_stream_get_cap, + &ios_stream_set_cap, + &ios_stream_start, + NULL, + &ios_stream_put_frame, + &ios_stream_stop, + &ios_stream_destroy +}; + + +/**************************************************************************** + * Factory operations + */ +/* + * Init ios_ video driver. + */ +pjmedia_vid_dev_factory* pjmedia_ios_factory(pj_pool_factory *pf) +{ + struct ios_factory *f; + pj_pool_t *pool; + + pool = pj_pool_create(pf, "ios video", 512, 512, NULL); + f = PJ_POOL_ZALLOC_T(pool, struct ios_factory); + f->pf = pf; + f->pool = pool; + f->base.op = &factory_op; + + return &f->base; +} + + +/* API: init factory */ +static pj_status_t ios_factory_init(pjmedia_vid_dev_factory *f) +{ + struct ios_factory *qf = (struct ios_factory*)f; + struct ios_dev_info *qdi; + unsigned i, l; + + /* Initialize input and output devices here */ + qf->dev_info = (struct ios_dev_info*) + pj_pool_calloc(qf->pool, 2, + sizeof(struct ios_dev_info)); + + qf->dev_count = 0; + qdi = &qf->dev_info[qf->dev_count++]; + pj_bzero(qdi, sizeof(*qdi)); + strcpy(qdi->info.name, "iOS UIView"); + strcpy(qdi->info.driver, "iOS"); + qdi->info.dir = PJMEDIA_DIR_RENDER; + qdi->info.has_callback = PJ_FALSE; + qdi->info.caps = PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW; + + if (NSClassFromString(@"AVCaptureSession")) { + qdi = &qf->dev_info[qf->dev_count++]; + pj_bzero(qdi, sizeof(*qdi)); + strcpy(qdi->info.name, "iOS AVCapture"); + strcpy(qdi->info.driver, "iOS"); + qdi->info.dir = PJMEDIA_DIR_CAPTURE; + qdi->info.has_callback = PJ_TRUE; + } + + for (i = 0; i < qf->dev_count; i++) { + qdi = &qf->dev_info[i]; + qdi->info.fmt_cnt = PJ_ARRAY_SIZE(ios_fmts); + qdi->info.caps |= PJMEDIA_VID_DEV_CAP_FORMAT; + + for (l = 0; l < PJ_ARRAY_SIZE(ios_fmts); l++) { + pjmedia_format *fmt = &qdi->info.fmt[l]; + pjmedia_format_init_video(fmt, + ios_fmts[l].pjmedia_format, + DEFAULT_WIDTH, + DEFAULT_HEIGHT, + DEFAULT_FPS, 1); + } + } + + PJ_LOG(4, (THIS_FILE, "iOS video initialized with %d devices", + qf->dev_count)); + + return PJ_SUCCESS; +} + +/* API: destroy factory */ +static pj_status_t ios_factory_destroy(pjmedia_vid_dev_factory *f) +{ + struct ios_factory *qf = (struct ios_factory*)f; + pj_pool_t *pool = qf->pool; + + qf->pool = NULL; + pj_pool_release(pool); + + return PJ_SUCCESS; +} + +/* API: refresh the list of devices */ +static pj_status_t ios_factory_refresh(pjmedia_vid_dev_factory *f) +{ + PJ_UNUSED_ARG(f); + return PJ_SUCCESS; +} + +/* API: get number of devices */ +static unsigned ios_factory_get_dev_count(pjmedia_vid_dev_factory *f) +{ + struct ios_factory *qf = (struct ios_factory*)f; + return qf->dev_count; +} + +/* API: get device info */ +static pj_status_t ios_factory_get_dev_info(pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_info *info) +{ + struct ios_factory *qf = (struct ios_factory*)f; + + PJ_ASSERT_RETURN(index < qf->dev_count, PJMEDIA_EVID_INVDEV); + + pj_memcpy(info, &qf->dev_info[index].info, sizeof(*info)); + + return PJ_SUCCESS; +} + +/* API: create default device parameter */ +static pj_status_t ios_factory_default_param(pj_pool_t *pool, + pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_param *param) +{ + struct ios_factory *qf = (struct ios_factory*)f; + struct ios_dev_info *di = &qf->dev_info[index]; + + PJ_ASSERT_RETURN(index < qf->dev_count, PJMEDIA_EVID_INVDEV); + + PJ_UNUSED_ARG(pool); + + pj_bzero(param, sizeof(*param)); + if (di->info.dir & PJMEDIA_DIR_CAPTURE) { + param->dir = PJMEDIA_DIR_CAPTURE; + param->cap_id = index; + param->rend_id = PJMEDIA_VID_INVALID_DEV; + } else if (di->info.dir & PJMEDIA_DIR_RENDER) { + param->dir = PJMEDIA_DIR_RENDER; + param->rend_id = index; + param->cap_id = PJMEDIA_VID_INVALID_DEV; + } else { + return PJMEDIA_EVID_INVDEV; + } + + param->flags = PJMEDIA_VID_DEV_CAP_FORMAT; + param->clock_rate = DEFAULT_CLOCK_RATE; + pj_memcpy(¶m->fmt, &di->info.fmt[0], sizeof(param->fmt)); + + return PJ_SUCCESS; +} + +@implementation VOutDelegate +- (void)update_image +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + /* Create a device-dependent RGB color space */ + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + + /* Create a bitmap graphics context with the sample buffer data */ + CGContextRef context = + CGBitmapContextCreate(stream->buf, stream->size.w, stream->size.h, 8, + stream->bytes_per_row, colorSpace, + kCGBitmapByteOrder32Little | + kCGImageAlphaPremultipliedFirst); + + /** + * Create a Quartz image from the pixel data in the bitmap graphics + * context + */ + CGImageRef quartzImage = CGBitmapContextCreateImage(context); + + /* Free up the context and color space */ + CGContextRelease(context); + CGColorSpaceRelease(colorSpace); + + /* Create an image object from the Quartz image */ + UIImage *image = [UIImage imageWithCGImage:quartzImage scale:1.0 + orientation:UIImageOrientationRight]; + + /* Release the Quartz image */ + CGImageRelease(quartzImage); + + [stream->imgView performSelectorOnMainThread:@selector(setImage:) + withObject:image waitUntilDone:NO]; + + [pool release]; +} + +- (void)captureOutput:(AVCaptureOutput *)captureOutput + didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer + fromConnection:(AVCaptureConnection *)connection +{ + pjmedia_frame frame; + CVImageBufferRef imageBuffer; + + if (!sampleBuffer) + return; + + /* Get a CMSampleBuffer's Core Video image buffer for the media data */ + imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); + + /* Lock the base address of the pixel buffer */ + CVPixelBufferLockBaseAddress(imageBuffer, 0); + + frame.type = PJMEDIA_TYPE_VIDEO; + frame.buf = CVPixelBufferGetBaseAddress(imageBuffer); + frame.size = stream->frame_size; + frame.bit_info = 0; + frame.timestamp.u64 = stream->frame_ts.u64; + + if (stream->vid_cb.capture_cb) + (*stream->vid_cb.capture_cb)(&stream->base, stream->user_data, &frame); + + stream->frame_ts.u64 += stream->ts_inc; + + /* Unlock the pixel buffer */ + CVPixelBufferUnlockBaseAddress(imageBuffer,0); +} +@end + +static ios_fmt_info* get_ios_format_info(pjmedia_format_id id) +{ + unsigned i; + + for (i = 0; i < PJ_ARRAY_SIZE(ios_fmts); i++) { + if (ios_fmts[i].pjmedia_format == id) + return &ios_fmts[i]; + } + + return NULL; +} + +/* API: create stream */ +static pj_status_t ios_factory_create_stream( + pjmedia_vid_dev_factory *f, + pjmedia_vid_dev_param *param, + const pjmedia_vid_dev_cb *cb, + void *user_data, + pjmedia_vid_dev_stream **p_vid_strm) +{ + struct ios_factory *qf = (struct ios_factory*)f; + pj_pool_t *pool; + struct ios_stream *strm; + const pjmedia_video_format_detail *vfd; + const pjmedia_video_format_info *vfi; + pj_status_t status = PJ_SUCCESS; + ios_fmt_info *ifi = get_ios_format_info(param->fmt.id); + NSError *error; + + PJ_ASSERT_RETURN(f && param && p_vid_strm, PJ_EINVAL); + PJ_ASSERT_RETURN(param->fmt.type == PJMEDIA_TYPE_VIDEO && + param->fmt.detail_type == PJMEDIA_FORMAT_DETAIL_VIDEO && + (param->dir == PJMEDIA_DIR_CAPTURE || + param->dir == PJMEDIA_DIR_RENDER), + PJ_EINVAL); + + if (!(ifi = get_ios_format_info(param->fmt.id))) + return PJMEDIA_EVID_BADFORMAT; + + vfi = pjmedia_get_video_format_info(NULL, param->fmt.id); + if (!vfi) + return PJMEDIA_EVID_BADFORMAT; + + /* Create and Initialize stream descriptor */ + pool = pj_pool_create(qf->pf, "ios-dev", 4000, 4000, NULL); + PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); + + strm = PJ_POOL_ZALLOC_T(pool, struct ios_stream); + pj_memcpy(&strm->param, param, sizeof(*param)); + strm->pool = pool; + pj_memcpy(&strm->vid_cb, cb, sizeof(*cb)); + strm->user_data = user_data; + pjmedia_event_publisher_init(&strm->base.epub, PJMEDIA_SIG_VID_DEV_IOS); + + vfd = pjmedia_format_get_video_format_detail(&strm->param.fmt, PJ_TRUE); + pj_memcpy(&strm->size, &vfd->size, sizeof(vfd->size)); + strm->bpp = vfi->bpp; + strm->bytes_per_row = strm->size.w * strm->bpp / 8; + strm->frame_size = strm->bytes_per_row * strm->size.h; + strm->ts_inc = PJMEDIA_SPF2(param->clock_rate, &vfd->fps, 1); + + if (param->dir & PJMEDIA_DIR_CAPTURE) { + /* Create capture stream here */ + strm->cap_session = [[AVCaptureSession alloc] init]; + if (!strm->cap_session) { + status = PJ_ENOMEM; + goto on_error; + } + strm->cap_session.sessionPreset = AVCaptureSessionPresetMedium; + + /* Open video device */ + AVCaptureDevice *videoDevice = + [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; + if (!videoDevice) { + status = PJMEDIA_EVID_SYSERR; + goto on_error; + } + + /* Add the video device to the session as a device input */ + strm->dev_input = [AVCaptureDeviceInput + deviceInputWithDevice:videoDevice + error: &error]; + if (!strm->dev_input) { + status = PJMEDIA_EVID_SYSERR; + goto on_error; + } + [strm->cap_session addInput:strm->dev_input]; + + strm->video_output = [[[AVCaptureVideoDataOutput alloc] init] + autorelease]; + if (!strm->video_output) { + status = PJMEDIA_EVID_SYSERR; + goto on_error; + } + [strm->cap_session addOutput:strm->video_output]; + + /* Configure the video output */ + strm->vout_delegate = [VOutDelegate alloc]; + strm->vout_delegate->stream = strm; + dispatch_queue_t queue = dispatch_queue_create("myQueue", NULL); + [strm->video_output setSampleBufferDelegate:strm->vout_delegate + queue:queue]; + dispatch_release(queue); + + strm->video_output.videoSettings = + [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:ifi->ios_format], + kCVPixelBufferPixelFormatTypeKey, + [NSNumber numberWithInt: vfd->size.w], + kCVPixelBufferWidthKey, + [NSNumber numberWithInt: vfd->size.h], + kCVPixelBufferHeightKey, nil]; + strm->video_output.minFrameDuration = CMTimeMake(vfd->fps.denum, + vfd->fps.num); + } else if (param->dir & PJMEDIA_DIR_RENDER) { + /* Create renderer stream here */ + /* Get the main window */ + UIWindow *window = [[UIApplication sharedApplication] keyWindow]; + + if (param->flags & PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW && param->window) + window = (UIWindow *)param->window; + + pj_assert(window); + strm->imgView = [[UIImageView alloc] initWithFrame:[window bounds]]; + if (!strm->imgView) { + status = PJ_ENOMEM; + goto on_error; + } + [window addSubview:strm->imgView]; + + if (!strm->vout_delegate) { + strm->vout_delegate = [VOutDelegate alloc]; + strm->vout_delegate->stream = strm; + } + + strm->buf = pj_pool_alloc(pool, strm->frame_size); + } + + /* Apply the remaining settings */ + /* + if (param->flags & PJMEDIA_VID_DEV_CAP_INPUT_SCALE) { + ios_stream_set_cap(&strm->base, + PJMEDIA_VID_DEV_CAP_INPUT_SCALE, + ¶m->fmt); + } + */ + /* Done */ + strm->base.op = &stream_op; + *p_vid_strm = &strm->base; + + return PJ_SUCCESS; + +on_error: + ios_stream_destroy((pjmedia_vid_dev_stream *)strm); + + return status; +} + +/* API: Get stream info. */ +static pj_status_t ios_stream_get_param(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_param *pi) +{ + struct ios_stream *strm = (struct ios_stream*)s; + + PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL); + + pj_memcpy(pi, &strm->param, sizeof(*pi)); + +/* if (ios_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_INPUT_SCALE, + &pi->fmt.info_size) == PJ_SUCCESS) + { + pi->flags |= PJMEDIA_VID_DEV_CAP_INPUT_SCALE; + } +*/ + return PJ_SUCCESS; +} + +/* API: get capability */ +static pj_status_t ios_stream_get_cap(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_cap cap, + void *pval) +{ + struct ios_stream *strm = (struct ios_stream*)s; + + PJ_UNUSED_ARG(strm); + + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + if (cap==PJMEDIA_VID_DEV_CAP_INPUT_SCALE) + { + return PJMEDIA_EVID_INVCAP; +// return PJ_SUCCESS; + } else { + return PJMEDIA_EVID_INVCAP; + } +} + +/* API: set capability */ +static pj_status_t ios_stream_set_cap(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_cap cap, + const void *pval) +{ + struct ios_stream *strm = (struct ios_stream*)s; + + PJ_UNUSED_ARG(strm); + + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + if (cap==PJMEDIA_VID_DEV_CAP_INPUT_SCALE) + { + return PJ_SUCCESS; + } + + return PJMEDIA_EVID_INVCAP; +} + +/* API: Start stream. */ +static pj_status_t ios_stream_start(pjmedia_vid_dev_stream *strm) +{ + struct ios_stream *stream = (struct ios_stream*)strm; + + PJ_UNUSED_ARG(stream); + + PJ_LOG(4, (THIS_FILE, "Starting qt video stream")); + + if (stream->cap_session) { + [stream->cap_session startRunning]; + + if (![stream->cap_session isRunning]) + return PJ_EUNKNOWN; + } + + return PJ_SUCCESS; +} + + +/* API: Put frame from stream */ +static pj_status_t ios_stream_put_frame(pjmedia_vid_dev_stream *strm, + const pjmedia_frame *frame) +{ + struct ios_stream *stream = (struct ios_stream*)strm; + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + pj_assert(stream->frame_size >= frame->size); + pj_memcpy(stream->buf, frame->buf, frame->size); + /* Perform video display in a background thread */ +// [stream->vout_delegate update_image]; + [NSThread detachNewThreadSelector:@selector(update_image) + toTarget:stream->vout_delegate withObject:nil]; + + [pool release]; + + return PJ_SUCCESS; +} + +/* API: Stop stream. */ +static pj_status_t ios_stream_stop(pjmedia_vid_dev_stream *strm) +{ + struct ios_stream *stream = (struct ios_stream*)strm; + + PJ_UNUSED_ARG(stream); + + PJ_LOG(4, (THIS_FILE, "Stopping qt video stream")); + + if (stream->cap_session && [stream->cap_session isRunning]) + [stream->cap_session stopRunning]; + + return PJ_SUCCESS; +} + + +/* API: Destroy stream. */ +static pj_status_t ios_stream_destroy(pjmedia_vid_dev_stream *strm) +{ + struct ios_stream *stream = (struct ios_stream*)strm; + + PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); + + ios_stream_stop(strm); + + if (stream->imgView) { + [stream->imgView removeFromSuperview]; + [stream->imgView release]; + stream->imgView = NULL; + } + + if (stream->cap_session) { + [stream->cap_session release]; + stream->cap_session = NULL; + } +/* if (stream->dev_input) { + [stream->dev_input release]; + stream->dev_input = NULL; + } +*/ + if (stream->vout_delegate) { + [stream->vout_delegate release]; + stream->vout_delegate = NULL; + } +/* if (stream->video_output) { + [stream->video_output release]; + stream->video_output = NULL; + } +*/ + + pj_pool_release(stream->pool); + + return PJ_SUCCESS; +} + +#endif +#endif /* PJMEDIA_VIDEO_DEV_HAS_IOS */ diff --git a/pjmedia/src/pjmedia-videodev/qt_dev.m b/pjmedia/src/pjmedia-videodev/qt_dev.m new file mode 100644 index 00000000..80546a5c --- /dev/null +++ b/pjmedia/src/pjmedia-videodev/qt_dev.m @@ -0,0 +1,636 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjmedia-videodev/videodev_imp.h> +#include <pj/assert.h> +#include <pj/log.h> +#include <pj/os.h> + +#if PJMEDIA_VIDEO_DEV_HAS_QT + +#include <Foundation/NSAutoreleasePool.h> +#include <QTKit/QTKit.h> + +#define THIS_FILE "qt_dev.c" +#define DEFAULT_CLOCK_RATE 9000 +#define DEFAULT_WIDTH 640 +#define DEFAULT_HEIGHT 480 +#define DEFAULT_FPS 15 + +#define kCVPixelFormatType_422YpCbCr8_yuvs 'yuvs' + +typedef struct qt_fmt_info +{ + pjmedia_format_id pjmedia_format; + unsigned qt_format; +} qt_fmt_info; + +static qt_fmt_info qt_fmts[] = +{ + {PJMEDIA_FORMAT_YUY2, kCVPixelFormatType_422YpCbCr8_yuvs}, + {PJMEDIA_FORMAT_UYVY, kCVPixelFormatType_422YpCbCr8}, +}; + +/* qt device info */ +struct qt_dev_info +{ + pjmedia_vid_dev_info info; + char dev_id[192]; +}; + +/* qt factory */ +struct qt_factory +{ + pjmedia_vid_dev_factory base; + pj_pool_t *pool; + pj_pool_t *dev_pool; + pj_pool_factory *pf; + + unsigned dev_count; + struct qt_dev_info *dev_info; +}; + +@interface VOutDelegate: NSObject +{ +@public + struct qt_stream *stream; +} +@end + +/* Video stream. */ +struct qt_stream +{ + pjmedia_vid_dev_stream base; /**< Base stream */ + pjmedia_vid_dev_param param; /**< Settings */ + pj_pool_t *pool; /**< Memory pool. */ + + pj_timestamp cap_frame_ts; /**< Captured frame tstamp */ + unsigned cap_ts_inc; /**< Increment */ + + pjmedia_vid_dev_cb vid_cb; /**< Stream callback. */ + void *user_data; /**< Application data. */ + + pj_bool_t cap_thread_exited; + pj_bool_t cap_thread_initialized; + pj_thread_desc cap_thread_desc; + pj_thread_t *cap_thread; + + NSAutoreleasePool *apool; + QTCaptureSession *cap_session; + QTCaptureDeviceInput *dev_input; + QTCaptureDecompressedVideoOutput *video_output; + VOutDelegate *vout_delegate; +}; + + +/* Prototypes */ +static pj_status_t qt_factory_init(pjmedia_vid_dev_factory *f); +static pj_status_t qt_factory_destroy(pjmedia_vid_dev_factory *f); +static pj_status_t qt_factory_refresh(pjmedia_vid_dev_factory *f); +static unsigned qt_factory_get_dev_count(pjmedia_vid_dev_factory *f); +static pj_status_t qt_factory_get_dev_info(pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_info *info); +static pj_status_t qt_factory_default_param(pj_pool_t *pool, + pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_param *param); +static pj_status_t qt_factory_create_stream( + pjmedia_vid_dev_factory *f, + pjmedia_vid_dev_param *param, + const pjmedia_vid_dev_cb *cb, + void *user_data, + pjmedia_vid_dev_stream **p_vid_strm); + +static pj_status_t qt_stream_get_param(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_param *param); +static pj_status_t qt_stream_get_cap(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_cap cap, + void *value); +static pj_status_t qt_stream_set_cap(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_cap cap, + const void *value); +static pj_status_t qt_stream_start(pjmedia_vid_dev_stream *strm); +static pj_status_t qt_stream_stop(pjmedia_vid_dev_stream *strm); +static pj_status_t qt_stream_destroy(pjmedia_vid_dev_stream *strm); + +/* Operations */ +static pjmedia_vid_dev_factory_op factory_op = +{ + &qt_factory_init, + &qt_factory_destroy, + &qt_factory_get_dev_count, + &qt_factory_get_dev_info, + &qt_factory_default_param, + &qt_factory_create_stream, + &qt_factory_refresh +}; + +static pjmedia_vid_dev_stream_op stream_op = +{ + &qt_stream_get_param, + &qt_stream_get_cap, + &qt_stream_set_cap, + &qt_stream_start, + NULL, + NULL, + &qt_stream_stop, + &qt_stream_destroy +}; + + +/**************************************************************************** + * Factory operations + */ +/* + * Init qt_ video driver. + */ +pjmedia_vid_dev_factory* pjmedia_qt_factory(pj_pool_factory *pf) +{ + struct qt_factory *f; + pj_pool_t *pool; + + pool = pj_pool_create(pf, "qt video", 4000, 4000, NULL); + f = PJ_POOL_ZALLOC_T(pool, struct qt_factory); + f->pf = pf; + f->pool = pool; + f->base.op = &factory_op; + + return &f->base; +} + + +/* API: init factory */ +static pj_status_t qt_factory_init(pjmedia_vid_dev_factory *f) +{ + return qt_factory_refresh(f); +} + +/* API: destroy factory */ +static pj_status_t qt_factory_destroy(pjmedia_vid_dev_factory *f) +{ + struct qt_factory *qf = (struct qt_factory*)f; + pj_pool_t *pool = qf->pool; + + if (qf->dev_pool) + pj_pool_release(qf->dev_pool); + qf->pool = NULL; + if (pool) + pj_pool_release(pool); + + return PJ_SUCCESS; +} + +/* API: refresh the list of devices */ +static pj_status_t qt_factory_refresh(pjmedia_vid_dev_factory *f) +{ + struct qt_factory *qf = (struct qt_factory*)f; + struct qt_dev_info *qdi; + unsigned i, dev_count = 0; + NSAutoreleasePool *apool = [[NSAutoreleasePool alloc]init]; + NSArray *dev_array; + + if (qf->dev_pool) { + pj_pool_release(qf->dev_pool); + qf->dev_pool = NULL; + } + + dev_array = [QTCaptureDevice inputDevices]; + for (i = 0; i < [dev_array count]; i++) { + QTCaptureDevice *dev = [dev_array objectAtIndex:i]; + if ([dev hasMediaType:QTMediaTypeVideo] || + [dev hasMediaType:QTMediaTypeMuxed]) + { + dev_count++; + } + } + + /* Initialize input and output devices here */ + qf->dev_count = 0; + qf->dev_pool = pj_pool_create(qf->pf, "qt video", 500, 500, NULL); + + qf->dev_info = (struct qt_dev_info*) + pj_pool_calloc(qf->dev_pool, dev_count, + sizeof(struct qt_dev_info)); + for (i = 0; i < [dev_array count]; i++) { + QTCaptureDevice *dev = [dev_array objectAtIndex:i]; + if ([dev hasMediaType:QTMediaTypeVideo] || + [dev hasMediaType:QTMediaTypeMuxed]) + { + unsigned k; + + qdi = &qf->dev_info[qf->dev_count++]; + pj_bzero(qdi, sizeof(*qdi)); + [[dev localizedDisplayName] getCString:qdi->info.name + maxLength:sizeof(qdi->info.name) + encoding: + [NSString defaultCStringEncoding]]; + [[dev uniqueID] getCString:qdi->dev_id + maxLength:sizeof(qdi->dev_id) + encoding:[NSString defaultCStringEncoding]]; + strcpy(qdi->info.driver, "QT"); + qdi->info.dir = PJMEDIA_DIR_CAPTURE; + qdi->info.has_callback = PJ_TRUE; + + qdi->info.fmt_cnt = 0; + qdi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT; + for (k = 0; k < [[dev formatDescriptions] count]; k++) { + unsigned l; + QTFormatDescription *desc = [[dev formatDescriptions] + objectAtIndex:k]; + for (l = 0; l < PJ_ARRAY_SIZE(qt_fmts); l++) { + if ([desc formatType] == qt_fmts[l].qt_format) { + pjmedia_format *fmt = + &qdi->info.fmt[qdi->info.fmt_cnt++]; + pjmedia_format_init_video(fmt, + qt_fmts[l].pjmedia_format, + DEFAULT_WIDTH, + DEFAULT_HEIGHT, + DEFAULT_FPS, 1); + break; + } + } + } + + PJ_LOG(4, (THIS_FILE, " dev_id %d: %s", i, qdi->info.name)); + } + } + + [apool release]; + + PJ_LOG(4, (THIS_FILE, "qt video has %d devices", + qf->dev_count)); + + return PJ_SUCCESS; +} + +/* API: get number of devices */ +static unsigned qt_factory_get_dev_count(pjmedia_vid_dev_factory *f) +{ + struct qt_factory *qf = (struct qt_factory*)f; + return qf->dev_count; +} + +/* API: get device info */ +static pj_status_t qt_factory_get_dev_info(pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_info *info) +{ + struct qt_factory *qf = (struct qt_factory*)f; + + PJ_ASSERT_RETURN(index < qf->dev_count, PJMEDIA_EVID_INVDEV); + + pj_memcpy(info, &qf->dev_info[index].info, sizeof(*info)); + + return PJ_SUCCESS; +} + +/* API: create default device parameter */ +static pj_status_t qt_factory_default_param(pj_pool_t *pool, + pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_param *param) +{ + struct qt_factory *qf = (struct qt_factory*)f; + struct qt_dev_info *di = &qf->dev_info[index]; + + PJ_ASSERT_RETURN(index < qf->dev_count, PJMEDIA_EVID_INVDEV); + + PJ_UNUSED_ARG(pool); + + pj_bzero(param, sizeof(*param)); + param->dir = PJMEDIA_DIR_CAPTURE; + param->cap_id = index; + param->rend_id = PJMEDIA_VID_INVALID_DEV; + param->flags = PJMEDIA_VID_DEV_CAP_FORMAT; + param->clock_rate = DEFAULT_CLOCK_RATE; + pj_memcpy(¶m->fmt, &di->info.fmt[0], sizeof(param->fmt)); + + return PJ_SUCCESS; +} + +@implementation VOutDelegate +- (void)captureOutput:(QTCaptureOutput *)captureOutput + didOutputVideoFrame:(CVImageBufferRef)videoFrame + withSampleBuffer:(QTSampleBuffer *)sampleBuffer + fromConnection:(QTCaptureConnection *)connection +{ + unsigned size = [sampleBuffer lengthForAllSamples]; + pjmedia_frame frame; + + if (stream->cap_thread_initialized == 0 || !pj_thread_is_registered()) + { + pj_thread_register("qt_cap", stream->cap_thread_desc, + &stream->cap_thread); + stream->cap_thread_initialized = 1; + PJ_LOG(5,(THIS_FILE, "Capture thread started")); + } + + if (!videoFrame) + return; + + frame.type = PJMEDIA_TYPE_VIDEO; + frame.buf = [sampleBuffer bytesForAllSamples]; + frame.size = size; + frame.bit_info = 0; + frame.timestamp.u64 = stream->cap_frame_ts.u64; + + if (stream->vid_cb.capture_cb) + (*stream->vid_cb.capture_cb)(&stream->base, stream->user_data, + &frame); + + stream->cap_frame_ts.u64 += stream->cap_ts_inc; +} +@end + +static qt_fmt_info* get_qt_format_info(pjmedia_format_id id) +{ + unsigned i; + + for (i = 0; i < PJ_ARRAY_SIZE(qt_fmts); i++) { + if (qt_fmts[i].pjmedia_format == id) + return &qt_fmts[i]; + } + + return NULL; +} + +/* API: create stream */ +static pj_status_t qt_factory_create_stream( + pjmedia_vid_dev_factory *f, + pjmedia_vid_dev_param *param, + const pjmedia_vid_dev_cb *cb, + void *user_data, + pjmedia_vid_dev_stream **p_vid_strm) +{ + struct qt_factory *qf = (struct qt_factory*)f; + pj_pool_t *pool; + struct qt_stream *strm; + const pjmedia_video_format_info *vfi; + pj_status_t status = PJ_SUCCESS; + BOOL success = NO; + NSError *error; + + PJ_ASSERT_RETURN(f && param && p_vid_strm, PJ_EINVAL); + PJ_ASSERT_RETURN(param->fmt.type == PJMEDIA_TYPE_VIDEO && + param->fmt.detail_type == PJMEDIA_FORMAT_DETAIL_VIDEO && + param->dir == PJMEDIA_DIR_CAPTURE, + PJ_EINVAL); + + vfi = pjmedia_get_video_format_info(NULL, param->fmt.id); + if (!vfi) + return PJMEDIA_EVID_BADFORMAT; + + /* Create and Initialize stream descriptor */ + pool = pj_pool_create(qf->pf, "qt-dev", 4000, 4000, NULL); + PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); + + strm = PJ_POOL_ZALLOC_T(pool, struct qt_stream); + pj_memcpy(&strm->param, param, sizeof(*param)); + strm->pool = pool; + pj_memcpy(&strm->vid_cb, cb, sizeof(*cb)); + strm->user_data = user_data; + strm->apool = [[NSAutoreleasePool alloc]init]; + pjmedia_event_publisher_init(&strm->base.epub, PJMEDIA_SIG_VID_DEV_COLORBAR); + + /* Create capture stream here */ + if (param->dir & PJMEDIA_DIR_CAPTURE) { + const pjmedia_video_format_detail *vfd; + qt_fmt_info *qfi = get_qt_format_info(param->fmt.id); + + if (!qfi) { + status = PJMEDIA_EVID_BADFORMAT; + goto on_error; + } + + strm->cap_session = [[QTCaptureSession alloc] init]; + if (!strm->cap_session) { + status = PJ_ENOMEM; + goto on_error; + } + + /* Open video device */ + QTCaptureDevice *videoDevice = + [QTCaptureDevice deviceWithUniqueID: + [NSString stringWithCString: + qf->dev_info[param->cap_id].dev_id + encoding: + [NSString defaultCStringEncoding]]]; + if (!videoDevice || ![videoDevice open:&error]) { + status = PJMEDIA_EVID_SYSERR; + goto on_error; + } + + /* Add the video device to the session as a device input */ + strm->dev_input = [[QTCaptureDeviceInput alloc] + initWithDevice:videoDevice]; + success = [strm->cap_session addInput:strm->dev_input error:&error]; + if (!success) { + status = PJMEDIA_EVID_SYSERR; + goto on_error; + } + + strm->video_output = [[QTCaptureDecompressedVideoOutput alloc] init]; + success = [strm->cap_session addOutput:strm->video_output + error:&error]; + if (!success) { + status = PJMEDIA_EVID_SYSERR; + goto on_error; + } + + vfd = pjmedia_format_get_video_format_detail(&strm->param.fmt, + PJ_TRUE); + [strm->video_output setPixelBufferAttributes: + [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt: + qfi->qt_format], + kCVPixelBufferPixelFormatTypeKey, + [NSNumber numberWithInt: + vfd->size.w], + kCVPixelBufferWidthKey, + [NSNumber numberWithInt: + vfd->size.h], + kCVPixelBufferHeightKey, nil]]; + + pj_assert(vfd->fps.num); + strm->cap_ts_inc = PJMEDIA_SPF2(strm->param.clock_rate, &vfd->fps, 1); + + if ([strm->video_output + respondsToSelector:@selector(setMinimumVideoFrameInterval)]) + { + [strm->video_output setMinimumVideoFrameInterval: + (1.0f * vfd->fps.denum / + (double)vfd->fps.num)]; + } + + strm->vout_delegate = [[VOutDelegate alloc]init]; + strm->vout_delegate->stream = strm; + [strm->video_output setDelegate:strm->vout_delegate]; + } + + /* Apply the remaining settings */ + /* + if (param->flags & PJMEDIA_VID_DEV_CAP_INPUT_SCALE) { + qt_stream_set_cap(&strm->base, + PJMEDIA_VID_DEV_CAP_INPUT_SCALE, + ¶m->fmt); + } + */ + /* Done */ + strm->base.op = &stream_op; + *p_vid_strm = &strm->base; + + return PJ_SUCCESS; + +on_error: + qt_stream_destroy((pjmedia_vid_dev_stream *)strm); + + return status; +} + +/* API: Get stream info. */ +static pj_status_t qt_stream_get_param(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_param *pi) +{ + struct qt_stream *strm = (struct qt_stream*)s; + + PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL); + + pj_memcpy(pi, &strm->param, sizeof(*pi)); + +/* if (qt_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_INPUT_SCALE, + &pi->fmt.info_size) == PJ_SUCCESS) + { + pi->flags |= PJMEDIA_VID_DEV_CAP_INPUT_SCALE; + } +*/ + return PJ_SUCCESS; +} + +/* API: get capability */ +static pj_status_t qt_stream_get_cap(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_cap cap, + void *pval) +{ + struct qt_stream *strm = (struct qt_stream*)s; + + PJ_UNUSED_ARG(strm); + + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + if (cap==PJMEDIA_VID_DEV_CAP_INPUT_SCALE) + { + return PJMEDIA_EVID_INVCAP; +// return PJ_SUCCESS; + } else { + return PJMEDIA_EVID_INVCAP; + } +} + +/* API: set capability */ +static pj_status_t qt_stream_set_cap(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_cap cap, + const void *pval) +{ + struct qt_stream *strm = (struct qt_stream*)s; + + PJ_UNUSED_ARG(strm); + + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + if (cap==PJMEDIA_VID_DEV_CAP_INPUT_SCALE) + { + return PJ_SUCCESS; + } + + return PJMEDIA_EVID_INVCAP; +} + +/* API: Start stream. */ +static pj_status_t qt_stream_start(pjmedia_vid_dev_stream *strm) +{ + struct qt_stream *stream = (struct qt_stream*)strm; + + PJ_UNUSED_ARG(stream); + + PJ_LOG(4, (THIS_FILE, "Starting qt video stream")); + + if (stream->cap_session) { + [stream->cap_session startRunning]; + + if (![stream->cap_session isRunning]) + return PJ_EUNKNOWN; + + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false); + } + + return PJ_SUCCESS; +} + +/* API: Stop stream. */ +static pj_status_t qt_stream_stop(pjmedia_vid_dev_stream *strm) +{ + struct qt_stream *stream = (struct qt_stream*)strm; + + PJ_UNUSED_ARG(stream); + + PJ_LOG(4, (THIS_FILE, "Stopping qt video stream")); + + if (stream->cap_session && [stream->cap_session isRunning]) + [stream->cap_session stopRunning]; + + return PJ_SUCCESS; +} + + +/* API: Destroy stream. */ +static pj_status_t qt_stream_destroy(pjmedia_vid_dev_stream *strm) +{ + struct qt_stream *stream = (struct qt_stream*)strm; + + PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); + + qt_stream_stop(strm); + + if (stream->dev_input && [[stream->dev_input device] isOpen]) + [[stream->dev_input device] close]; + + if (stream->cap_session) { + [stream->cap_session release]; + stream->cap_session = NULL; + } + if (stream->dev_input) { + [stream->dev_input release]; + stream->dev_input = NULL; + } + if (stream->vout_delegate) { + [stream->vout_delegate release]; + stream->vout_delegate = NULL; + } + if (stream->video_output) { + [stream->video_output release]; + stream->video_output = NULL; + } + +// [stream->apool release]; + pj_pool_release(stream->pool); + + return PJ_SUCCESS; +} + +#endif /* PJMEDIA_VIDEO_DEV_HAS_QT */ diff --git a/pjmedia/src/pjmedia-videodev/sdl_dev.c b/pjmedia/src/pjmedia-videodev/sdl_dev.c new file mode 100644 index 00000000..115b362e --- /dev/null +++ b/pjmedia/src/pjmedia-videodev/sdl_dev.c @@ -0,0 +1,1291 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2010 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjmedia/converter.h> +#include <pjmedia-videodev/videodev_imp.h> +#include <pj/assert.h> +#include <pj/log.h> +#include <pj/os.h> + +#if PJMEDIA_VIDEO_DEV_HAS_SDL + +#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 +# include <Foundation/Foundation.h> +#endif + +#include <SDL.h> +#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL +# include "SDL_opengl.h" +# define OPENGL_DEV_IDX 1 +#else +# define OPENGL_DEV_IDX -999 +#endif + +#define THIS_FILE "sdl_dev.c" +#define DEFAULT_CLOCK_RATE 90000 +#define DEFAULT_WIDTH 640 +#define DEFAULT_HEIGHT 480 +#define DEFAULT_FPS 25 + +#if !(SDL_VERSION_ATLEAST(1,3,0)) +# define SDL_PIXELFORMAT_RGBA8888 0 +# define SDL_PIXELFORMAT_RGB24 0 +# define SDL_PIXELFORMAT_BGRA8888 0 +# define SDL_PIXELFORMAT_ABGR8888 0 +# define SDL_PIXELFORMAT_BGR24 0 +# define SDL_PIXELFORMAT_ARGB8888 0 +# define SDL_PIXELFORMAT_RGB24 0 +#endif + +typedef struct sdl_fmt_info +{ + pjmedia_format_id fmt_id; + Uint32 sdl_format; + Uint32 Rmask; + Uint32 Gmask; + Uint32 Bmask; + Uint32 Amask; +} sdl_fmt_info; + +static sdl_fmt_info sdl_fmts[] = +{ +#if PJ_IS_BIG_ENDIAN + {PJMEDIA_FORMAT_RGBA, (Uint32)SDL_PIXELFORMAT_RGBA8888, + 0xFF000000, 0xFF0000, 0xFF00, 0xFF} , + {PJMEDIA_FORMAT_RGB24, (Uint32)SDL_PIXELFORMAT_RGB24, + 0xFF0000, 0xFF00, 0xFF, 0} , + {PJMEDIA_FORMAT_BGRA, (Uint32)SDL_PIXELFORMAT_BGRA8888, + 0xFF00, 0xFF0000, 0xFF000000, 0xFF} , +#else + {PJMEDIA_FORMAT_RGBA, (Uint32)SDL_PIXELFORMAT_ABGR8888, + 0xFF, 0xFF00, 0xFF0000, 0xFF000000} , + {PJMEDIA_FORMAT_RGB24, (Uint32)SDL_PIXELFORMAT_BGR24, + 0xFF, 0xFF00, 0xFF0000, 0} , + {PJMEDIA_FORMAT_BGRA, (Uint32)SDL_PIXELFORMAT_ARGB8888, + 0xFF0000, 0xFF00, 0xFF, 0xFF000000} , +#endif + + {PJMEDIA_FORMAT_DIB , (Uint32)SDL_PIXELFORMAT_RGB24, + 0xFF0000, 0xFF00, 0xFF, 0} , + + {PJMEDIA_FORMAT_YUY2, SDL_YUY2_OVERLAY, 0, 0, 0, 0} , + {PJMEDIA_FORMAT_UYVY, SDL_UYVY_OVERLAY, 0, 0, 0, 0} , + {PJMEDIA_FORMAT_YVYU, SDL_YVYU_OVERLAY, 0, 0, 0, 0} , + {PJMEDIA_FORMAT_I420, SDL_IYUV_OVERLAY, 0, 0, 0, 0} , + {PJMEDIA_FORMAT_YV12, SDL_YV12_OVERLAY, 0, 0, 0, 0} , + {PJMEDIA_FORMAT_I420JPEG, SDL_IYUV_OVERLAY, 0, 0, 0, 0} , + {PJMEDIA_FORMAT_I422JPEG, SDL_YV12_OVERLAY, 0, 0, 0, 0} , +}; + +#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 +@interface SDLDelegate: NSObject +{ + @public + struct sdl_stream *strm; +} + +- (void)sdl_init; +- (void)sdl_quit; +- (void)detect_new_fmt; +- (int)sdl_create; +- (void)sdl_destroy; +- (int)handle_event; +- (pj_status_t)put_frame; +@end +#endif + +/* sdl_ device info */ +struct sdl_dev_info +{ + pjmedia_vid_dev_info info; +}; + +/* sdl_ factory */ +struct sdl_factory +{ + pjmedia_vid_dev_factory base; + pj_pool_t *pool; + pj_pool_factory *pf; + + unsigned dev_count; + struct sdl_dev_info *dev_info; +#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 + NSAutoreleasePool *apool; + SDLDelegate *delegate; +#endif +}; + +/* Video stream. */ +struct sdl_stream +{ + pjmedia_vid_dev_stream base; /**< Base stream */ + pjmedia_vid_dev_param param; /**< Settings */ + pj_pool_t *pool; /**< Memory pool. */ + + pjmedia_vid_dev_cb vid_cb; /**< Stream callback. */ + void *user_data; /**< Application data. */ + + pj_thread_t *sdl_thread; /**< SDL thread. */ + pj_bool_t is_quitting; + pj_bool_t is_running; + pj_bool_t render_exited; + pj_status_t status; + pjmedia_format *new_fmt; + pjmedia_rect_size *new_disp_size; + pj_timestamp last_ts; + +#if SDL_VERSION_ATLEAST(1,3,0) + SDL_Window *window; /**< Display window. */ + SDL_Renderer *renderer; /**< Display renderer. */ + SDL_Texture *scr_tex; /**< Screen texture. */ + int pitch; /**< Pitch value. */ +#endif + SDL_Rect rect; /**< Frame rectangle. */ + SDL_Rect dstrect; /**< Display rectangle. */ + SDL_Surface *screen; /**< Display screen. */ + SDL_Surface *surf; /**< RGB surface. */ + SDL_Overlay *overlay; /**< YUV overlay. */ +#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL +#if SDL_VERSION_ATLEAST(1,3,0) + SDL_GLContext *gl_context; +#endif + GLuint texture; + void *tex_buf; + pj_size_t tex_buf_size; +#endif +#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 + NSAutoreleasePool *apool; + SDLDelegate *delegate; + const pjmedia_frame *frame; +#endif + + /* For frame conversion */ + pjmedia_converter *conv; + pjmedia_conversion_param conv_param; + pjmedia_frame conv_buf; + + pjmedia_video_apply_fmt_param vafp; +}; + + +/* Prototypes */ +static pj_status_t sdl_factory_init(pjmedia_vid_dev_factory *f); +static pj_status_t sdl_factory_destroy(pjmedia_vid_dev_factory *f); +static pj_status_t sdl_factory_refresh(pjmedia_vid_dev_factory *f); +static unsigned sdl_factory_get_dev_count(pjmedia_vid_dev_factory *f); +static pj_status_t sdl_factory_get_dev_info(pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_info *info); +static pj_status_t sdl_factory_default_param(pj_pool_t *pool, + pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_param *param); +static pj_status_t sdl_factory_create_stream( + pjmedia_vid_dev_factory *f, + pjmedia_vid_dev_param *param, + const pjmedia_vid_dev_cb *cb, + void *user_data, + pjmedia_vid_dev_stream **p_vid_strm); + +static pj_status_t sdl_stream_get_param(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_param *param); +static pj_status_t sdl_stream_get_cap(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_cap cap, + void *value); +static pj_status_t sdl_stream_set_cap(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_cap cap, + const void *value); +static pj_status_t sdl_stream_put_frame(pjmedia_vid_dev_stream *strm, + const pjmedia_frame *frame); +static pj_status_t sdl_stream_start(pjmedia_vid_dev_stream *strm); +static pj_status_t sdl_stream_stop(pjmedia_vid_dev_stream *strm); +static pj_status_t sdl_stream_destroy(pjmedia_vid_dev_stream *strm); + +#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL +static void draw_gl(struct sdl_stream *stream, void *tex_buf); +#endif + +/* Operations */ +static pjmedia_vid_dev_factory_op factory_op = +{ + &sdl_factory_init, + &sdl_factory_destroy, + &sdl_factory_get_dev_count, + &sdl_factory_get_dev_info, + &sdl_factory_default_param, + &sdl_factory_create_stream, + &sdl_factory_refresh +}; + +static pjmedia_vid_dev_stream_op stream_op = +{ + &sdl_stream_get_param, + &sdl_stream_get_cap, + &sdl_stream_set_cap, + &sdl_stream_start, + NULL, + &sdl_stream_put_frame, + &sdl_stream_stop, + &sdl_stream_destroy +}; + + +/**************************************************************************** + * Factory operations + */ +/* + * Init sdl_ video driver. + */ +pjmedia_vid_dev_factory* pjmedia_sdl_factory(pj_pool_factory *pf) +{ + struct sdl_factory *f; + pj_pool_t *pool; + + pool = pj_pool_create(pf, "sdl video", 1000, 1000, NULL); + f = PJ_POOL_ZALLOC_T(pool, struct sdl_factory); + f->pf = pf; + f->pool = pool; + f->base.op = &factory_op; + + return &f->base; +} + + +/* API: init factory */ +static pj_status_t sdl_factory_init(pjmedia_vid_dev_factory *f) +{ + struct sdl_factory *sf = (struct sdl_factory*)f; + struct sdl_dev_info *ddi; + unsigned i, j; + +#if SDL_VERSION_ATLEAST(1,3,0) +#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 + sf->apool = [[NSAutoreleasePool alloc] init]; + sf->delegate = [[SDLDelegate alloc] init]; + [sf->delegate performSelectorOnMainThread:@selector(sdl_init) + withObject:nil waitUntilDone:YES]; +#else + /* Initialize the SDL library */ + if (SDL_Init(SDL_INIT_VIDEO)) + return PJMEDIA_EVID_INIT; +#endif +#endif + + sf->dev_count = 1; +#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL + sf->dev_count++; +#endif + sf->dev_info = (struct sdl_dev_info*) + pj_pool_calloc(sf->pool, sf->dev_count, + sizeof(struct sdl_dev_info)); + + ddi = &sf->dev_info[0]; + pj_bzero(ddi, sizeof(*ddi)); + strncpy(ddi->info.name, "SDL renderer", sizeof(ddi->info.name)); + ddi->info.name[sizeof(ddi->info.name)-1] = '\0'; + ddi->info.fmt_cnt = PJ_ARRAY_SIZE(sdl_fmts); + +#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL + ddi = &sf->dev_info[OPENGL_DEV_IDX]; + pj_bzero(ddi, sizeof(*ddi)); + strncpy(ddi->info.name, "SDL openGL renderer", sizeof(ddi->info.name)); + ddi->info.name[sizeof(ddi->info.name)-1] = '\0'; + ddi->info.fmt_cnt = 1; +#endif + + for (i = 0; i < sf->dev_count; i++) { + ddi = &sf->dev_info[i]; + strncpy(ddi->info.driver, "SDL", sizeof(ddi->info.driver)); + ddi->info.driver[sizeof(ddi->info.driver)-1] = '\0'; + ddi->info.dir = PJMEDIA_DIR_RENDER; + ddi->info.has_callback = PJ_FALSE; + ddi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT | + PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE; +#if SDL_VERSION_ATLEAST(1,3,0) + ddi->info.caps |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW; +#endif + + for (j = 0; j < ddi->info.fmt_cnt; j++) { + pjmedia_format *fmt = &ddi->info.fmt[j]; + pjmedia_format_init_video(fmt, sdl_fmts[j].fmt_id, + DEFAULT_WIDTH, DEFAULT_HEIGHT, + DEFAULT_FPS, 1); + } + } + + PJ_LOG(4, (THIS_FILE, "SDL initialized")); + + return PJ_SUCCESS; +} + +/* API: destroy factory */ +static pj_status_t sdl_factory_destroy(pjmedia_vid_dev_factory *f) +{ + struct sdl_factory *sf = (struct sdl_factory*)f; + pj_pool_t *pool = sf->pool; + + sf->pool = NULL; + pj_pool_release(pool); + +#if SDL_VERSION_ATLEAST(1,3,0) +#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 + [sf->delegate performSelectorOnMainThread:@selector(sdl_quit) + withObject:nil waitUntilDone:YES]; + [sf->delegate release]; + [sf->apool release]; +#else + SDL_Quit(); +#endif +#endif + + return PJ_SUCCESS; +} + +/* API: refresh the list of devices */ +static pj_status_t sdl_factory_refresh(pjmedia_vid_dev_factory *f) +{ + PJ_UNUSED_ARG(f); + return PJ_SUCCESS; +} + +/* API: get number of devices */ +static unsigned sdl_factory_get_dev_count(pjmedia_vid_dev_factory *f) +{ + struct sdl_factory *sf = (struct sdl_factory*)f; + return sf->dev_count; +} + +/* API: get device info */ +static pj_status_t sdl_factory_get_dev_info(pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_info *info) +{ + struct sdl_factory *sf = (struct sdl_factory*)f; + + PJ_ASSERT_RETURN(index < sf->dev_count, PJMEDIA_EVID_INVDEV); + + pj_memcpy(info, &sf->dev_info[index].info, sizeof(*info)); + + return PJ_SUCCESS; +} + +/* API: create default device parameter */ +static pj_status_t sdl_factory_default_param(pj_pool_t *pool, + pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_param *param) +{ + struct sdl_factory *sf = (struct sdl_factory*)f; + struct sdl_dev_info *di = &sf->dev_info[index]; + + PJ_ASSERT_RETURN(index < sf->dev_count, PJMEDIA_EVID_INVDEV); + + PJ_UNUSED_ARG(pool); + + pj_bzero(param, sizeof(*param)); + param->dir = PJMEDIA_DIR_RENDER; + param->rend_id = index; + param->cap_id = PJMEDIA_VID_INVALID_DEV; + + /* Set the device capabilities here */ + param->flags = PJMEDIA_VID_DEV_CAP_FORMAT; + param->fmt.type = PJMEDIA_TYPE_VIDEO; + param->clock_rate = DEFAULT_CLOCK_RATE; + pj_memcpy(¶m->fmt, &di->info.fmt[0], sizeof(param->fmt)); + + return PJ_SUCCESS; +} + +static sdl_fmt_info* get_sdl_format_info(pjmedia_format_id id) +{ + unsigned i; + + for (i = 0; i < sizeof(sdl_fmts)/sizeof(sdl_fmts[0]); i++) { + if (sdl_fmts[i].fmt_id == id) + return &sdl_fmts[i]; + } + + return NULL; +} + +static void destroy_sdl(struct sdl_stream *strm, pj_bool_t destroy_win) +{ + PJ_UNUSED_ARG(destroy_win); + + if (strm->surf) { + SDL_FreeSurface(strm->surf); + strm->surf = NULL; + } + if (strm->overlay) { + SDL_FreeYUVOverlay(strm->overlay); + strm->overlay = NULL; + } +#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL + if (strm->texture) { + glDeleteTextures(1, &strm->texture); + strm->texture = 0; + } +#endif +#if SDL_VERSION_ATLEAST(1,3,0) +#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL + if (strm->gl_context) { + SDL_GL_DeleteContext(strm->gl_context); + strm->gl_context = NULL; + } +#endif + if (strm->scr_tex) { + SDL_DestroyTexture(strm->scr_tex); + strm->scr_tex = NULL; + } + if (strm->renderer) { + SDL_DestroyRenderer(strm->renderer); + strm->renderer = NULL; + } +#ifndef __IPHONEOS__ + if (destroy_win) { + if (strm->window && + !(strm->param.flags & PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW)) + { + SDL_DestroyWindow(strm->window); + } + strm->window = NULL; + } +#endif +#endif +} + +static pj_status_t init_sdl(struct sdl_stream *strm, pjmedia_format *fmt) +{ + sdl_fmt_info *sdl_info = get_sdl_format_info(fmt->id); + const pjmedia_video_format_info *vfi; + pjmedia_video_format_detail *vfd; + + vfi = pjmedia_get_video_format_info(pjmedia_video_format_mgr_instance(), + fmt->id); + if (!vfi || !sdl_info) + return PJMEDIA_EVID_BADFORMAT; + + strm->vafp.size = fmt->det.vid.size; + strm->vafp.buffer = NULL; + if (vfi->apply_fmt(vfi, &strm->vafp) != PJ_SUCCESS) + return PJMEDIA_EVID_BADFORMAT; + + vfd = pjmedia_format_get_video_format_detail(fmt, PJ_TRUE); + strm->rect.x = strm->rect.y = 0; + strm->rect.w = (Uint16)vfd->size.w; + strm->rect.h = (Uint16)vfd->size.h; + if (strm->param.disp_size.w == 0) + strm->param.disp_size.w = strm->rect.w; + if (strm->param.disp_size.h == 0) + strm->param.disp_size.h = strm->rect.h; + strm->dstrect.x = strm->dstrect.y = 0; + strm->dstrect.w = (Uint16)strm->param.disp_size.w; + strm->dstrect.h = (Uint16)strm->param.disp_size.h; + + destroy_sdl(strm, PJ_FALSE); + +#if SDL_VERSION_ATLEAST(1,3,0) + if (!strm->window) { + Uint32 flags = SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE; + + if (strm->param.rend_id == OPENGL_DEV_IDX) + flags |= SDL_WINDOW_OPENGL; + + if (strm->param.flags & PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW) { + /* Use the window supplied by the application. */ + strm->window = SDL_CreateWindowFrom(strm->param.window); + } else { + /* Create the window where we will draw. */ + strm->window = SDL_CreateWindow("pjmedia-SDL video", + SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, + strm->param.disp_size.w, + strm->param.disp_size.h, + flags); + } + if (!strm->window) + return PJMEDIA_EVID_SYSERR; + } + + SDL_SetWindowSize(strm->window, strm->param.disp_size.w, + strm->param.disp_size.h); + + /** + * We must call SDL_CreateRenderer in order for draw calls to + * affect this window. + */ + strm->renderer = SDL_CreateRenderer(strm->window, -1, 0); + if (!strm->renderer) + return PJMEDIA_EVID_SYSERR; + +#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL + if (strm->param.rend_id == OPENGL_DEV_IDX) { + strm->gl_context = SDL_GL_CreateContext(strm->window); + if (!strm->gl_context) + return PJMEDIA_EVID_SYSERR; + SDL_GL_MakeCurrent(strm->window, strm->gl_context); + } +#endif + + strm->screen = SDL_GetWindowSurface(strm->window); + +#else + /* Initialize the display */ + strm->screen = SDL_SetVideoMode(strm->param.disp_size.w, + strm->param.disp_size.h, 0, ( +#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL + strm->param.rend_id == OPENGL_DEV_IDX? + SDL_OPENGL | SDL_RESIZABLE: +#endif + SDL_RESIZABLE | SDL_SWSURFACE)); + if (strm->screen == NULL) + return PJMEDIA_EVID_SYSERR; + + SDL_WM_SetCaption("pjmedia-SDL video", NULL); + +#endif + +#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL + if (strm->param.rend_id == OPENGL_DEV_IDX) { + /* Init some OpenGL settings */ + glDisable(GL_DEPTH_TEST); + glDisable(GL_CULL_FACE); + glEnable(GL_TEXTURE_2D); + + /* Init the viewport */ + glViewport(0, 0, strm->param.disp_size.w, strm->param.disp_size.h); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + glOrtho(0.0, (GLdouble)strm->param.disp_size.w, + (GLdouble)strm->param.disp_size.h, 0.0, 0.0, 1.0); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + /* Create a texture */ + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); + glGenTextures(1, &strm->texture); + + if (!strm->texture) + return PJMEDIA_EVID_SYSERR; + +#if defined(PJ_WIN32) && PJ_WIN32 != 0 + /** + * On Win32 platform, the OpenGL drawing must be in the same + * thread that calls SDL_SetVideoMode(), hence we need a buffer + * for the frame from sdl_stream_put_frame() + */ + if (strm->vafp.framebytes > strm->tex_buf_size) { + strm->tex_buf_size = strm->vafp.framebytes; + strm->tex_buf = pj_pool_alloc(strm->pool, strm->vafp.framebytes); + } +#endif + } else +#endif +#if SDL_VERSION_ATLEAST(1,3,0) + { + strm->scr_tex = SDL_CreateTexture(strm->renderer, sdl_info->sdl_format, + SDL_TEXTUREACCESS_STREAMING, + strm->rect.w, strm->rect.h); + if (strm->scr_tex == NULL) + return PJMEDIA_EVID_SYSERR; + + strm->pitch = strm->rect.w * SDL_BYTESPERPIXEL(sdl_info->sdl_format); + } +#else + if (vfi->color_model == PJMEDIA_COLOR_MODEL_RGB) { + strm->surf = SDL_CreateRGBSurface(SDL_SWSURFACE, + strm->rect.w, strm->rect.h, + vfi->bpp, + sdl_info->Rmask, + sdl_info->Gmask, + sdl_info->Bmask, + sdl_info->Amask); + if (strm->surf == NULL) + return PJMEDIA_EVID_SYSERR; + } else if (vfi->color_model == PJMEDIA_COLOR_MODEL_YUV) { + strm->overlay = SDL_CreateYUVOverlay(strm->rect.w, strm->rect.h, + sdl_info->sdl_format, + strm->screen); + if (strm->overlay == NULL) + return PJMEDIA_EVID_SYSERR; + } +#endif + + return PJ_SUCCESS; +} + +static void detect_fmt_change(struct sdl_stream *strm) +{ + if (strm->new_fmt || strm->new_disp_size) { + /* Stop the stream */ + sdl_stream_stop((pjmedia_vid_dev_stream *)strm); + + if (strm->new_disp_size) + pj_memcpy(&strm->param.disp_size, strm->new_disp_size, + sizeof(strm->param.disp_size)); + + /* Re-initialize SDL */ + strm->status = init_sdl(strm, (strm->new_fmt? strm->new_fmt : + &strm->param.fmt)); + + if (strm->status == PJ_SUCCESS) { + if (strm->new_fmt) + pjmedia_format_copy(&strm->param.fmt, strm->new_fmt); + /* Restart the stream */ + sdl_stream_start((pjmedia_vid_dev_stream *)strm); + } + strm->new_fmt = NULL; + strm->new_disp_size = NULL; + } +} + +#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 +@implementation SDLDelegate +- (void)sdl_init +{ + if (SDL_Init(SDL_INIT_VIDEO)) { + PJ_LOG(4, (THIS_FILE, "Cannot initialize SDL")); + } +} + +- (void)sdl_quit +{ + SDL_Quit(); +} + +- (void)detect_new_fmt +{ + detect_fmt_change(strm); +} + +- (int)sdl_create +{ +#else +static int sdlthread(void * data) +{ + struct sdl_stream *strm = (struct sdl_stream*)data; + pj_bool_t notify_wnd_closed_event = PJ_FALSE; + pj_status_t saved_stream_status; +#endif + +#if !(SDL_VERSION_ATLEAST(1,3,0)) + if (SDL_Init(SDL_INIT_VIDEO)) { + strm->status = PJMEDIA_EVID_INIT; + goto on_return; + } +#endif + +#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL + if (strm->param.rend_id == OPENGL_DEV_IDX) { + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER,1); + } +#endif + + strm->status = init_sdl(strm, &strm->param.fmt); + if (strm->status != PJ_SUCCESS) + goto on_return; + +#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 +on_return: + if (strm->status != PJ_SUCCESS) { + destroy_sdl(strm, PJ_TRUE); +#if !(SDL_VERSION_ATLEAST(1,3,0)) + SDL_Quit(); +#endif + strm->screen = NULL; + } + + return strm->status; +} + +- (void)sdl_destroy +{ + destroy_sdl(strm, PJ_TRUE); +} + +- (int)handle_event +{ + const pjmedia_video_format_info *vfi; + pjmedia_video_format_detail *vfd; + pj_bool_t notify_wnd_closed_event = PJ_FALSE; + pj_status_t saved_stream_status; + + vfi = pjmedia_get_video_format_info(pjmedia_video_format_mgr_instance(), + strm->param.fmt.id); + vfd = pjmedia_format_get_video_format_detail(&strm->param.fmt, PJ_TRUE); +#else + while(!strm->is_quitting) +#endif + { + SDL_Event sevent; + pjmedia_event pevent; + +#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL +#if defined(PJ_WIN32) && PJ_WIN32 != 0 + if (strm->param.rend_id == OPENGL_DEV_IDX) { + draw_gl(strm, strm->tex_buf); + } +#endif +#endif + + detect_fmt_change(strm); + + /** + * The event polling must be placed in the same thread that + * call SDL_SetVideoMode(). Please consult the official doc of + * SDL_PumpEvents(). + */ + while (SDL_PollEvent(&sevent)) { + pjmedia_event_init(&pevent, PJMEDIA_EVENT_NONE, &strm->last_ts, + &strm->base.epub); + + switch(sevent.type) { + case SDL_MOUSEBUTTONDOWN: + pevent.type = PJMEDIA_EVENT_MOUSE_BTN_DOWN; + break; +#if SDL_VERSION_ATLEAST(1,3,0) + case SDL_WINDOWEVENT: + switch (sevent.window.event) { + case SDL_WINDOWEVENT_RESIZED: + pevent.type = PJMEDIA_EVENT_WND_RESIZED; + pevent.data.wnd_resized.new_size.w = + sevent.window.data1; + pevent.data.wnd_resized.new_size.h = + sevent.window.data2; + break; + } + break; +#else + case SDL_VIDEORESIZE: + pevent.type = PJMEDIA_EVENT_WND_RESIZED; + pevent.data.wnd_resized.new_size.w = sevent.resize.w; + pevent.data.wnd_resized.new_size.h = sevent.resize.h; + break; + case SDL_QUIT: + pevent.type = PJMEDIA_EVENT_WND_CLOSING; + break; +#endif + } + + if (pevent.type != PJMEDIA_EVENT_NONE) { + pj_status_t status; + + status = pjmedia_event_publish(&strm->base.epub, &pevent); + + switch (pevent.type) { + case PJMEDIA_EVENT_WND_RESIZED: + strm->new_disp_size = &pevent.data.wnd_resized.new_size; + detect_fmt_change(strm); + break; + + case PJMEDIA_EVENT_WND_CLOSING: + if (pevent.data.wnd_closing.cancel) { + /* Cancel the closing operation */ + break; + } + + /* Proceed to cleanup SDL. App must still call + * pjmedia_dev_stream_destroy() when getting WND_CLOSED + * event + */ + strm->is_quitting = PJ_TRUE; + notify_wnd_closed_event = PJ_TRUE; + sdl_stream_stop(&strm->base); + goto on_return; + + default: + /* Just to prevent gcc warning about unused enums */ + break; + } + } + } + } + +#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 + return 0; +#endif +on_return: + destroy_sdl(strm, PJ_TRUE); +#if !(SDL_VERSION_ATLEAST(1,3,0)) + SDL_Quit(); +#endif + strm->screen = NULL; + saved_stream_status = strm->status; + + if (notify_wnd_closed_event) { + pjmedia_event pevent; + + pjmedia_event_init(&pevent, PJMEDIA_EVENT_WND_CLOSED, &strm->last_ts, + &strm->base.epub); + pjmedia_event_publish(&strm->base.epub, &pevent); + } + + /* + * Note: don't access the stream after this point, it might have + * been destroyed + */ + + return saved_stream_status; +} + +#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL +static void draw_gl(struct sdl_stream *stream, void *tex_buf) +{ + if (stream->texture) { + glBindTexture(GL_TEXTURE_2D, stream->texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, + stream->rect.w, stream->rect.h, 0, + GL_RGBA, GL_UNSIGNED_BYTE, tex_buf); + glBegin(GL_TRIANGLE_STRIP); + glTexCoord2f(0, 0); glVertex2i(0, 0); + glTexCoord2f(1, 0); glVertex2i(stream->param.disp_size.w, 0); + glTexCoord2f(0, 1); glVertex2i(0, stream->param.disp_size.h); + glTexCoord2f(1, 1); + glVertex2i(stream->param.disp_size.w, stream->param.disp_size.h); + glEnd(); +#if SDL_VERSION_ATLEAST(1,3,0) + SDL_GL_SwapWindow(stream->window); +#else + SDL_GL_SwapBuffers(); +#endif + } +} +#endif + +#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 +- (pj_status_t)put_frame +{ + const pjmedia_frame *frame = strm->frame; +#else +/* API: Put frame from stream */ +static pj_status_t sdl_stream_put_frame(pjmedia_vid_dev_stream *strm, + const pjmedia_frame *frame) +{ +#endif + struct sdl_stream *stream = (struct sdl_stream*)strm; + pj_status_t status = PJ_SUCCESS; + + stream->last_ts.u64 = frame->timestamp.u64; + + if (!stream->is_running) { + stream->render_exited = PJ_TRUE; + goto on_return; + } + + if (frame->size==0 || frame->buf==NULL || + frame->size < stream->vafp.framebytes) + goto on_return; + + if (stream->surf) { + if (SDL_MUSTLOCK(stream->surf)) { + if (SDL_LockSurface(stream->surf) < 0) { + PJ_LOG(3, (THIS_FILE, "Unable to lock SDL surface")); + status = PJMEDIA_EVID_NOTREADY; + goto on_return; + } + } + + pj_memcpy(stream->surf->pixels, frame->buf, + stream->vafp.framebytes); + + if (SDL_MUSTLOCK(stream->surf)) { + SDL_UnlockSurface(stream->surf); + } + SDL_BlitSurface(stream->surf, NULL, stream->screen, &stream->dstrect); +#if SDL_VERSION_ATLEAST(1,3,0) + SDL_UpdateWindowSurface(stream->window); +#else + SDL_UpdateRect(stream->screen, 0, 0, 0, 0); +#endif + } else if (stream->overlay) { + int i, sz, offset; + + if (SDL_LockYUVOverlay(stream->overlay) < 0) { + PJ_LOG(3, (THIS_FILE, "Unable to lock SDL overlay")); + status = PJMEDIA_EVID_NOTREADY; + goto on_return; + } + + for (i = 0, offset = 0; i < stream->overlay->planes; i++) { + sz = stream->vafp.plane_bytes[i]; + pj_memcpy(stream->overlay->pixels[i], + (char *)frame->buf + offset, sz); + offset += sz; + } + + SDL_UnlockYUVOverlay(stream->overlay); + SDL_DisplayYUVOverlay(stream->overlay, &stream->dstrect); + } +#if SDL_VERSION_ATLEAST(1,3,0) + else if (stream->scr_tex) { + SDL_UpdateTexture(stream->scr_tex, NULL, frame->buf, stream->pitch); + SDL_RenderClear(stream->renderer); + SDL_RenderCopy(stream->renderer, stream->scr_tex, NULL, NULL); + SDL_RenderPresent(stream->renderer); + } +#endif +#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL + else if (stream->param.rend_id == OPENGL_DEV_IDX) { +#if defined(PJ_WIN32) && PJ_WIN32 != 0 + pj_memcpy(stream->tex_buf, frame->buf, stream->vafp.framebytes); +#else + draw_gl(stream, frame->buf); +#endif + } +#endif + +on_return: + return status; +} +#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 +@end + +static pj_status_t sdl_stream_put_frame(pjmedia_vid_dev_stream *strm, + const pjmedia_frame *frame) +{ + struct sdl_stream *stream = (struct sdl_stream*)strm; + stream->frame = frame; + [stream->delegate performSelectorOnMainThread:@selector(put_frame) + withObject:nil waitUntilDone:YES]; + + return PJ_SUCCESS; +} + +static int sdlthread(void * data) +{ + struct sdl_stream *strm = (struct sdl_stream*)data; + + while(!strm->is_quitting) { + [strm->delegate performSelectorOnMainThread:@selector(handle_event) + withObject:nil waitUntilDone:YES]; + } + + return 0; +} + +#endif + +/* API: create stream */ +static pj_status_t sdl_factory_create_stream( + pjmedia_vid_dev_factory *f, + pjmedia_vid_dev_param *param, + const pjmedia_vid_dev_cb *cb, + void *user_data, + pjmedia_vid_dev_stream **p_vid_strm) +{ + struct sdl_factory *sf = (struct sdl_factory*)f; + pj_pool_t *pool; + struct sdl_stream *strm; + pj_status_t status; + + PJ_ASSERT_RETURN(param->dir == PJMEDIA_DIR_RENDER, PJ_EINVAL); + + /* Create and Initialize stream descriptor */ + pool = pj_pool_create(sf->pf, "sdl-dev", 1000, 1000, NULL); + PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); + + strm = PJ_POOL_ZALLOC_T(pool, struct sdl_stream); + pj_memcpy(&strm->param, param, sizeof(*param)); + strm->pool = pool; + pj_memcpy(&strm->vid_cb, cb, sizeof(*cb)); + strm->user_data = user_data; + pjmedia_event_publisher_init(&strm->base.epub, PJMEDIA_SIG_VID_DEV_SDL); + + /* Create render stream here */ + if (param->dir & PJMEDIA_DIR_RENDER) { + strm->status = PJ_SUCCESS; +#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 + strm->apool = [[NSAutoreleasePool alloc] init]; + strm->delegate = [[SDLDelegate alloc]init]; + strm->delegate->strm = strm; + /* On Darwin OS, we need to call SDL functions in the main thread */ + [strm->delegate performSelectorOnMainThread:@selector(sdl_create) + withObject:nil waitUntilDone:YES]; + if ((status = strm->status) != PJ_SUCCESS) { + goto on_error; + } +#endif + status = pj_thread_create(pool, "sdl_thread", sdlthread, + strm, 0, 0, &strm->sdl_thread); + + if (status != PJ_SUCCESS) { + goto on_error; + } + + while(strm->status == PJ_SUCCESS && !strm->surf && !strm->overlay +#if SDL_VERSION_ATLEAST(1,3,0) + && !strm->scr_tex +#endif +#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL + && !strm->texture +#endif + ) + { + pj_thread_sleep(10); + } + if ((status = strm->status) != PJ_SUCCESS) { + goto on_error; + } + } + + /* Apply the remaining settings */ + if (param->flags & PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION) { + sdl_stream_set_cap(&strm->base, + PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION, + ¶m->window_pos); + } + if (param->flags & PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE) { + sdl_stream_set_cap(&strm->base, + PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE, + ¶m->window_hide); + } + + /* Done */ + strm->base.op = &stream_op; + *p_vid_strm = &strm->base; + + return PJ_SUCCESS; + +on_error: + sdl_stream_destroy(&strm->base); + return status; +} + +/* API: Get stream info. */ +static pj_status_t sdl_stream_get_param(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_param *pi) +{ + struct sdl_stream *strm = (struct sdl_stream*)s; + + PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL); + + pj_memcpy(pi, &strm->param, sizeof(*pi)); + + if (sdl_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW, + &pi->window) == PJ_SUCCESS) + { + pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW; + } + if (sdl_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION, + &pi->window_pos) == PJ_SUCCESS) + { + pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION; + } + if (sdl_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE, + &pi->disp_size) == PJ_SUCCESS) + { + pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE; + } + if (sdl_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE, + &pi->window_hide) == PJ_SUCCESS) + { + pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE; + } + + return PJ_SUCCESS; +} + +/* API: get capability */ +static pj_status_t sdl_stream_get_cap(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_cap cap, + void *pval) +{ + struct sdl_stream *strm = (struct sdl_stream*)s; + + PJ_UNUSED_ARG(strm); + + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + +#if SDL_VERSION_ATLEAST(1,3,0) + if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW) + { + *((void **)pval) = strm->window; + return PJ_SUCCESS; + } else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION) { + SDL_GetWindowPosition(strm->window, &((pjmedia_coord *)pval)->x, + &((pjmedia_coord *)pval)->y); + return PJ_SUCCESS; + } else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE) { + SDL_GetWindowSize(strm->window, (int *)&((pjmedia_rect_size *)pval)->w, + (int *)&((pjmedia_rect_size *)pval)->h); + return PJ_SUCCESS; + } else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE) { + Uint32 flag = SDL_GetWindowFlags(strm->window); + *((pj_bool_t *)pval) = (flag | SDL_WINDOW_HIDDEN)? PJ_TRUE: PJ_FALSE; + return PJ_SUCCESS; + } +#endif + + return PJMEDIA_EVID_INVCAP; +} + +/* API: set capability */ +static pj_status_t sdl_stream_set_cap(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_cap cap, + const void *pval) +{ + struct sdl_stream *strm = (struct sdl_stream*)s; + + PJ_UNUSED_ARG(strm); + + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + +#if SDL_VERSION_ATLEAST(1,3,0) + if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION) { + SDL_SetWindowPosition(strm->window, ((pjmedia_coord *)pval)->x, + ((pjmedia_coord *)pval)->y); + return PJ_SUCCESS; + } else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE) { + if (*(pj_bool_t *)pval) + SDL_HideWindow(strm->window); + else + SDL_ShowWindow(strm->window); + return PJ_SUCCESS; + } else +#endif + if (cap == PJMEDIA_VID_DEV_CAP_FORMAT) { + strm->new_fmt = (pjmedia_format *)pval; +#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 + [strm->delegate performSelectorOnMainThread:@selector(detect_new_fmt) + withObject:nil waitUntilDone:YES]; +#endif + while (strm->new_fmt) + pj_thread_sleep(10); + + if (strm->status != PJ_SUCCESS) { + pj_status_t status = strm->status; + + /** + * Failed to change the output format. Try to revert + * to its original format. + */ + strm->new_fmt = &strm->param.fmt; +#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 + [strm->delegate performSelectorOnMainThread: + @selector(detect_new_fmt) + withObject:nil waitUntilDone:YES]; +#endif + while (strm->new_fmt) + pj_thread_sleep(10); + + if (strm->status != PJ_SUCCESS) { + /** + * This means that we failed to revert to our + * original state! + */ + status = PJMEDIA_EVID_ERR; + } + + strm->status = status; + } + + return strm->status; + } else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE) { + strm->new_disp_size = (pjmedia_rect_size *)pval; +#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 + [strm->delegate performSelectorOnMainThread: + @selector(detect_new_fmt) + withObject:nil waitUntilDone:YES]; +#endif + while (strm->new_disp_size) + pj_thread_sleep(10); + + return strm->status; + } + + return PJMEDIA_EVID_INVCAP; +} + +/* API: Start stream. */ +static pj_status_t sdl_stream_start(pjmedia_vid_dev_stream *strm) +{ + struct sdl_stream *stream = (struct sdl_stream*)strm; + + PJ_LOG(4, (THIS_FILE, "Starting sdl video stream")); + + stream->is_running = PJ_TRUE; + stream->render_exited = PJ_FALSE; + + return PJ_SUCCESS; +} + +/* API: Stop stream. */ +static pj_status_t sdl_stream_stop(pjmedia_vid_dev_stream *strm) +{ + struct sdl_stream *stream = (struct sdl_stream*)strm; + unsigned i; + + PJ_LOG(4, (THIS_FILE, "Stopping sdl video stream")); + + /* Wait for renderer put_frame() to finish */ + stream->is_running = PJ_FALSE; +#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 + if (![NSThread isMainThread]) +#endif + for (i=0; !stream->render_exited && i<50; ++i) + pj_thread_sleep(10); + + return PJ_SUCCESS; +} + + +/* API: Destroy stream. */ +static pj_status_t sdl_stream_destroy(pjmedia_vid_dev_stream *strm) +{ + struct sdl_stream *stream = (struct sdl_stream*)strm; + + PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); + + sdl_stream_stop(strm); + + if (!stream->is_quitting) { + stream->is_quitting = PJ_TRUE; + if (stream->sdl_thread) + pj_thread_join(stream->sdl_thread); + } + +#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 + if (stream->delegate) { + [stream->delegate performSelectorOnMainThread:@selector(sdl_destroy) + withObject:nil waitUntilDone:YES]; + [stream->delegate release]; + stream->delegate = NULL; + } + if (stream->apool) { + [stream->apool release]; + stream->apool = NULL; + } +#endif + pj_pool_release(stream->pool); + + + return PJ_SUCCESS; +} + +#ifdef _MSC_VER +# pragma comment( lib, "sdl.lib") +# if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL +# pragma comment(lib, "OpenGL32.lib") +# endif +#endif + +#endif /* PJMEDIA_VIDEO_DEV_HAS_SDL */ diff --git a/pjmedia/src/pjmedia-videodev/sdl_dev_m.m b/pjmedia/src/pjmedia-videodev/sdl_dev_m.m new file mode 100644 index 00000000..566bf31e --- /dev/null +++ b/pjmedia/src/pjmedia-videodev/sdl_dev_m.m @@ -0,0 +1,20 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "sdl_dev.c" diff --git a/pjmedia/src/pjmedia-videodev/v4l2_dev.c b/pjmedia/src/pjmedia-videodev/v4l2_dev.c new file mode 100644 index 00000000..545c9975 --- /dev/null +++ b/pjmedia/src/pjmedia-videodev/v4l2_dev.c @@ -0,0 +1,820 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2010 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjmedia-videodev/videodev_imp.h> +#include <pjmedia/errno.h> +#include <pj/assert.h> +#include <pj/errno.h> +#include <pj/file_access.h> +#include <pj/log.h> +#include <pj/os.h> +#include <pj/rand.h> + +#if PJMEDIA_VIDEO_DEV_HAS_V4L2 + +#include <linux/videodev2.h> +#include <libv4l2.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/mman.h> + +#define THIS_FILE "v4l2_dev.c" +#define DRIVER_NAME "v4l2" +#define V4L2_MAX_DEVS 4 +#define DEFAULT_WIDTH 640 +#define DEFAULT_HEIGHT 480 +#define DEFAULT_FPS 25 +#define DEFAULT_CLOCK_RATE 90000 +#define INVALID_FD -1 +#define BUFFER_CNT 2 +#define MAX_IOCTL_RETRY 20 + + +/* mapping between pjmedia_fmt_id and v4l2 pixel format */ +typedef struct vid4lin_fmt_map +{ + pj_uint32_t pjmedia_fmt_id; + pj_uint32_t v4l2_fmt_id; +} vid4lin_fmt_map; + +/* I/O type being used */ +enum vid4lin_io_type +{ + IO_TYPE_NONE, + IO_TYPE_READ, + IO_TYPE_MMAP, + IO_TYPE_MMAP_USER +}; + +/* descriptor for each mmap-ed buffer */ +typedef struct vid4lin_buffer +{ + void *start; + size_t length; +} vid4lin_buffer; + +/* v4l2 device info */ +typedef struct vid4lin_dev_info +{ + pjmedia_vid_dev_info info; + char dev_name[32]; + struct v4l2_capability v4l2_cap; +} vid4lin_dev_info; + +/* v4l2 factory */ +typedef struct vid4lin_factory +{ + pjmedia_vid_dev_factory base; + pj_pool_t *pool; + pj_pool_t *dev_pool; + pj_pool_factory *pf; + + unsigned dev_count; + vid4lin_dev_info *dev_info; +} vid4lin_factory; + +/* Video stream. */ +typedef struct vid4lin_stream +{ + pjmedia_vid_dev_stream base; /**< Base stream */ + pjmedia_vid_dev_param param; /**< Settings */ + pj_pool_t *pool; /**< Memory pool. */ + + int fd; /**< Video fd. */ + char name[64]; /**< Name for log */ + enum vid4lin_io_type io_type; /**< I/O method. */ + unsigned buf_cnt; /**< MMap buf cnt. */ + vid4lin_buffer *buffers; /**< MMap buffers. */ + pj_time_val start_time; /**< Time when started */ + + pjmedia_vid_dev_cb vid_cb; /**< Stream callback */ + void *user_data; /**< Application data */ +} vid4lin_stream; + +/* Use this to convert between pjmedia_format_id and V4L2 fourcc */ +static vid4lin_fmt_map v4l2_fmt_maps[] = +{ + { PJMEDIA_FORMAT_RGB24, V4L2_PIX_FMT_BGR24 }, + { PJMEDIA_FORMAT_RGBA, V4L2_PIX_FMT_BGR32 }, + { PJMEDIA_FORMAT_RGB32, V4L2_PIX_FMT_BGR32 }, + { PJMEDIA_FORMAT_AYUV, V4L2_PIX_FMT_YUV32 }, + { PJMEDIA_FORMAT_YUY2, V4L2_PIX_FMT_YUYV }, + { PJMEDIA_FORMAT_UYVY, V4L2_PIX_FMT_UYVY } +}; + +/* Prototypes */ +static pj_status_t vid4lin_factory_init(pjmedia_vid_dev_factory *f); +static pj_status_t vid4lin_factory_destroy(pjmedia_vid_dev_factory *f); +static pj_status_t vid4lin_factory_refresh(pjmedia_vid_dev_factory *f); +static unsigned vid4lin_factory_get_dev_count(pjmedia_vid_dev_factory *f); +static pj_status_t vid4lin_factory_get_dev_info(pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_info *info); +static pj_status_t vid4lin_factory_default_param(pj_pool_t *pool, + pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_param *param); +static pj_status_t vid4lin_factory_create_stream(pjmedia_vid_dev_factory *f, + pjmedia_vid_dev_param *prm, + const pjmedia_vid_dev_cb *cb, + void *user_data, + pjmedia_vid_dev_stream **p); + +static pj_status_t vid4lin_stream_get_param(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_param *param); +static pj_status_t vid4lin_stream_get_cap(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_cap cap, + void *value); +static pj_status_t vid4lin_stream_set_cap(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_cap cap, + const void *value); +static pj_status_t vid4lin_stream_get_frame(pjmedia_vid_dev_stream *strm, + pjmedia_frame *frame); +static pj_status_t vid4lin_stream_start(pjmedia_vid_dev_stream *strm); +static pj_status_t vid4lin_stream_stop(pjmedia_vid_dev_stream *strm); +static pj_status_t vid4lin_stream_destroy(pjmedia_vid_dev_stream *strm); + +/* Operations */ +static pjmedia_vid_dev_factory_op factory_op = +{ + &vid4lin_factory_init, + &vid4lin_factory_destroy, + &vid4lin_factory_get_dev_count, + &vid4lin_factory_get_dev_info, + &vid4lin_factory_default_param, + &vid4lin_factory_create_stream, + &vid4lin_factory_refresh +}; + +static pjmedia_vid_dev_stream_op stream_op = +{ + &vid4lin_stream_get_param, + &vid4lin_stream_get_cap, + &vid4lin_stream_set_cap, + &vid4lin_stream_start, + &vid4lin_stream_get_frame, + NULL, + &vid4lin_stream_stop, + &vid4lin_stream_destroy +}; + + +/**************************************************************************** + * Factory operations + */ +/* + * Factory creation function. + */ +pjmedia_vid_dev_factory* pjmedia_v4l2_factory(pj_pool_factory *pf) +{ + vid4lin_factory *f; + pj_pool_t *pool; + + pool = pj_pool_create(pf, DRIVER_NAME, 512, 512, NULL); + f = PJ_POOL_ZALLOC_T(pool, vid4lin_factory); + f->pf = pf; + f->pool = pool; + f->base.op = &factory_op; + + return &f->base; +} + +/* util: ioctl that tries harder. */ +static pj_status_t xioctl(int fh, int request, void *arg) +{ + enum { RETRY = MAX_IOCTL_RETRY }; + int r, c=0; + + do { + r = v4l2_ioctl(fh, request, arg); + } while (r==-1 && c++<RETRY && ((errno==EINTR) || (errno==EAGAIN))); + + return (r == -1) ? pj_get_os_error() : PJ_SUCCESS; +} + +/* Scan V4L2 devices */ +static pj_status_t v4l2_scan_devs(vid4lin_factory *f) +{ + vid4lin_dev_info vdi[V4L2_MAX_DEVS]; + char dev_name[32]; + unsigned i, old_count; + pj_status_t status; + + if (f->dev_pool) { + pj_pool_release(f->dev_pool); + f->dev_pool = NULL; + } + + pj_bzero(vdi, sizeof(vdi)); + old_count = f->dev_count; + f->dev_count = 0; + f->dev_pool = pj_pool_create(f->pf, DRIVER_NAME, 500, 500, NULL); + + for (i=0; i<V4L2_MAX_DEVS && f->dev_count < V4L2_MAX_DEVS; ++i) { + int fd; + vid4lin_dev_info *pdi; + pj_pool_t *pool = f->dev_pool; + pj_uint32_t fmt_cap[8]; + int j, fmt_cnt=0; + + pdi = &vdi[f->dev_count]; + + snprintf(dev_name, sizeof(dev_name), "/dev/video%d", i); + if (!pj_file_exists(dev_name)) + continue; + + fd = v4l2_open(dev_name, O_RDWR, 0); + if (fd == -1) + continue; + + status = xioctl(fd, VIDIOC_QUERYCAP, &pdi->v4l2_cap); + if (status != PJ_SUCCESS) { + PJ_PERROR(4,(THIS_FILE, status, "Error querying %s", dev_name)); + v4l2_close(fd); + continue; + } + + if ((pdi->v4l2_cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0) { + v4l2_close(fd); + continue; + } + + PJ_LOG(5,(THIS_FILE, "Found capture device %s", pdi->v4l2_cap.card)); + PJ_LOG(5,(THIS_FILE, " Enumerating formats:")); + for (j=0; fmt_cnt<PJ_ARRAY_SIZE(fmt_cap); ++j) { + struct v4l2_fmtdesc fdesc; + unsigned k; + + pj_bzero(&fdesc, sizeof(fdesc)); + fdesc.index = j; + fdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + status = xioctl(fd, VIDIOC_ENUM_FMT, &fdesc); + if (status != PJ_SUCCESS) + break; + + for (k=0; k<PJ_ARRAY_SIZE(v4l2_fmt_maps); ++k) { + if (v4l2_fmt_maps[k].v4l2_fmt_id == fdesc.pixelformat) { + fmt_cap[fmt_cnt++] = v4l2_fmt_maps[k].pjmedia_fmt_id; + PJ_LOG(5,(THIS_FILE, " Supported: %s", + fdesc.description)); + break; + } + } + if (k==PJ_ARRAY_SIZE(v4l2_fmt_maps)) { + PJ_LOG(5,(THIS_FILE, " Unsupported: %s", fdesc.description)); + } + } + + v4l2_close(fd); + + if (fmt_cnt==0) { + PJ_LOG(5,(THIS_FILE, " Found no common format")); + continue; + } + + strncpy(pdi->dev_name, dev_name, sizeof(pdi->dev_name)); + pdi->dev_name[sizeof(pdi->dev_name)-1] = '\0'; + strncpy(pdi->info.name, (char*)pdi->v4l2_cap.card, + sizeof(pdi->info.name)); + pdi->info.name[sizeof(pdi->info.name)-1] = '\0'; + strncpy(pdi->info.driver, DRIVER_NAME, sizeof(pdi->info.driver)); + pdi->info.driver[sizeof(pdi->info.driver)-1] = '\0'; + pdi->info.dir = PJMEDIA_DIR_CAPTURE; + pdi->info.has_callback = PJ_FALSE; + pdi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT; + + pdi->info.fmt_cnt = fmt_cnt; + for (j=0; j<fmt_cnt; ++j) { + pjmedia_format_init_video(&pdi->info.fmt[j], + fmt_cap[j], + DEFAULT_WIDTH, + DEFAULT_HEIGHT, + DEFAULT_FPS, 1); + } + if (j < fmt_cnt) + continue; + + f->dev_count++; + } + + if (f->dev_count == 0) + return PJ_SUCCESS; + + if (f->dev_count > old_count || f->dev_info == NULL) { + f->dev_info = (vid4lin_dev_info*) + pj_pool_calloc(f->dev_pool, + f->dev_count, + sizeof(vid4lin_dev_info)); + } + pj_memcpy(f->dev_info, vdi, f->dev_count * sizeof(vid4lin_dev_info)); + + return PJ_SUCCESS; +} + + +/* API: init factory */ +static pj_status_t vid4lin_factory_init(pjmedia_vid_dev_factory *f) +{ + return vid4lin_factory_refresh(f); +} + +/* API: destroy factory */ +static pj_status_t vid4lin_factory_destroy(pjmedia_vid_dev_factory *f) +{ + vid4lin_factory *cf = (vid4lin_factory*)f; + pj_pool_t *pool = cf->pool; + + if (cf->dev_pool) + pj_pool_release(cf->dev_pool); + if (cf->pool) { + cf->pool = NULL; + pj_pool_release(pool); + } + + return PJ_SUCCESS; +} + +/* API: refresh the list of devices */ +static pj_status_t vid4lin_factory_refresh(pjmedia_vid_dev_factory *f) +{ + vid4lin_factory *cf = (vid4lin_factory*)f; + pj_status_t status; + + status = v4l2_scan_devs(cf); + if (status != PJ_SUCCESS) + return status; + + PJ_LOG(4, (THIS_FILE, "Video4Linux2 has %d devices", + cf->dev_count)); + + return PJ_SUCCESS; +} + +/* API: get number of devices */ +static unsigned vid4lin_factory_get_dev_count(pjmedia_vid_dev_factory *f) +{ + vid4lin_factory *cf = (vid4lin_factory*)f; + return cf->dev_count; +} + +/* API: get device info */ +static pj_status_t vid4lin_factory_get_dev_info(pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_info *info) +{ + vid4lin_factory *cf = (vid4lin_factory*)f; + + PJ_ASSERT_RETURN(index < cf->dev_count, PJMEDIA_EVID_INVDEV); + + pj_memcpy(info, &cf->dev_info[index].info, sizeof(*info)); + + return PJ_SUCCESS; +} + +/* API: create default device parameter */ +static pj_status_t vid4lin_factory_default_param(pj_pool_t *pool, + pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_param *param) +{ + vid4lin_factory *cf = (vid4lin_factory*)f; + + PJ_ASSERT_RETURN(index < cf->dev_count, PJMEDIA_EVID_INVDEV); + + pj_bzero(param, sizeof(*param)); + param->dir = PJMEDIA_DIR_CAPTURE; + param->cap_id = index; + param->rend_id = PJMEDIA_VID_INVALID_DEV; + param->flags = PJMEDIA_VID_DEV_CAP_FORMAT; + param->clock_rate = DEFAULT_CLOCK_RATE; + pjmedia_format_copy(¶m->fmt, &cf->dev_info[index].info.fmt[0]); + + return PJ_SUCCESS; +} + +static vid4lin_fmt_map* get_v4l2_format_info(pjmedia_format_id id) +{ + unsigned i; + + for (i = 0; i < PJ_ARRAY_SIZE(v4l2_fmt_maps); i++) { + if (v4l2_fmt_maps[i].pjmedia_fmt_id == id) + return &v4l2_fmt_maps[i]; + } + + return NULL; +} + +/* util: setup format */ +static pj_status_t vid4lin_stream_init_fmt(vid4lin_stream *stream, + const pjmedia_vid_dev_param *param, + pj_uint32_t pix_fmt) +{ + pjmedia_video_format_detail *vfd; + struct v4l2_format v4l2_fmt; + pj_status_t status; + + vfd = pjmedia_format_get_video_format_detail(¶m->fmt, PJ_TRUE); + if (vfd == NULL) + return PJMEDIA_EVID_BADFORMAT; + + pj_bzero(&v4l2_fmt, sizeof(v4l2_fmt)); + v4l2_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + v4l2_fmt.fmt.pix.width = vfd->size.w; + v4l2_fmt.fmt.pix.height = vfd->size.h; + v4l2_fmt.fmt.pix.pixelformat = pix_fmt; + v4l2_fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; + status = xioctl(stream->fd, VIDIOC_S_FMT, &v4l2_fmt); + if (status != PJ_SUCCESS) + return status; + + if (v4l2_fmt.fmt.pix.pixelformat != pix_fmt) { + status = PJMEDIA_EVID_BADFORMAT; + return status; + } + + if ((v4l2_fmt.fmt.pix.width != vfd->size.w) || + (v4l2_fmt.fmt.pix.height != vfd->size.h)) + { + /* Size has changed */ + vfd->size.w = v4l2_fmt.fmt.pix.width; + vfd->size.h = v4l2_fmt.fmt.pix.height; + } + + return PJ_SUCCESS; +} + +/* Util: initiate v4l2 streaming via mmap */ +static pj_status_t vid4lin_stream_init_streaming(vid4lin_stream *stream) +{ + struct v4l2_requestbuffers req; + unsigned i; + pj_status_t status; + + pj_bzero(&req, sizeof(req)); + req.count = BUFFER_CNT; + req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + req.memory = V4L2_MEMORY_MMAP; + status = xioctl(stream->fd, VIDIOC_REQBUFS, &req); + if (status != PJ_SUCCESS) + return status; + + stream->buffers = pj_pool_calloc(stream->pool, req.count, + sizeof(*stream->buffers)); + stream->buf_cnt = 0; + + for (i = 0; i < req.count; ++i) { + struct v4l2_buffer buf; + + pj_bzero(&buf, sizeof(buf)); + + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = i; + + status = xioctl(stream->fd, VIDIOC_QUERYBUF, &buf); + if (status != PJ_SUCCESS) + goto on_error; + + stream->buffers[i].length = buf.length; + stream->buffers[i].start = v4l2_mmap(NULL, buf.length, + PROT_READ | PROT_WRITE, + MAP_SHARED, stream->fd, + buf.m.offset); + + if (MAP_FAILED == stream->buffers[i].start) { + status = pj_get_os_error(); + goto on_error; + } + + stream->buf_cnt++; + } + + PJ_LOG(5,(THIS_FILE, " mmap streaming initialized")); + + stream->io_type = IO_TYPE_MMAP; + return PJ_SUCCESS; + +on_error: + return status; +} + +/* init streaming with user pointer */ +static pj_status_t vid4lin_stream_init_streaming_user(vid4lin_stream *stream) +{ + return PJ_ENOTSUP; +} + +/* init streaming with read() */ +static pj_status_t vid4lin_stream_init_read_write(vid4lin_stream *stream) +{ + return PJ_ENOTSUP; +} + +/* API: create stream */ +static pj_status_t vid4lin_factory_create_stream(pjmedia_vid_dev_factory *f, + pjmedia_vid_dev_param *param, + const pjmedia_vid_dev_cb *cb, + void *user_data, + pjmedia_vid_dev_stream **p_vid_strm) +{ + vid4lin_factory *cf = (vid4lin_factory*)f; + pj_pool_t *pool; + vid4lin_stream *stream; + vid4lin_dev_info *vdi; + const vid4lin_fmt_map *fmt_map; + const pjmedia_video_format_info *fmt_info; + pjmedia_video_format_detail *vfd; + pj_status_t status = PJ_SUCCESS; + + + PJ_ASSERT_RETURN(f && param && p_vid_strm, PJ_EINVAL); + PJ_ASSERT_RETURN(param->fmt.type == PJMEDIA_TYPE_VIDEO && + param->fmt.detail_type == PJMEDIA_FORMAT_DETAIL_VIDEO && + param->dir == PJMEDIA_DIR_CAPTURE, + PJ_EINVAL); + PJ_ASSERT_RETURN(param->cap_id >= 0 && param->cap_id < cf->dev_count, + PJMEDIA_EVID_INVDEV); + + fmt_info = pjmedia_get_video_format_info(NULL, param->fmt.id); + if (!fmt_info || (fmt_map=get_v4l2_format_info(param->fmt.id))==NULL) + return PJMEDIA_EVID_BADFORMAT; + + vdi = &cf->dev_info[param->cap_id]; + vfd = pjmedia_format_get_video_format_detail(¶m->fmt, PJ_TRUE); + + /* Create and Initialize stream descriptor */ + pool = pj_pool_create(cf->pf, vdi->info.name, 512, 512, NULL); + PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); + + stream = PJ_POOL_ZALLOC_T(pool, vid4lin_stream); + pj_memcpy(&stream->param, param, sizeof(*param)); + stream->pool = pool; + pj_memcpy(&stream->vid_cb, cb, sizeof(*cb)); + strncpy(stream->name, vdi->info.name, sizeof(stream->name)); + stream->name[sizeof(stream->name)-1] = '\0'; + stream->user_data = user_data; + stream->fd = INVALID_FD; + pjmedia_event_publisher_init(&stream->base.epub, PJMEDIA_SIG_VID_DEV_V4L2); + + stream->fd = v4l2_open(vdi->dev_name, O_RDWR | O_NONBLOCK, 0); + if (stream->fd < 0) + goto on_error; + + status = vid4lin_stream_init_fmt(stream, param, fmt_map->v4l2_fmt_id); + if (status != PJ_SUCCESS) + goto on_error; + + if (vdi->v4l2_cap.capabilities & V4L2_CAP_STREAMING) + status = vid4lin_stream_init_streaming(stream); + + if (status!=PJ_SUCCESS && vdi->v4l2_cap.capabilities & V4L2_CAP_STREAMING) + status = vid4lin_stream_init_streaming_user(stream); + + if (status!=PJ_SUCCESS && vdi->v4l2_cap.capabilities & V4L2_CAP_READWRITE) + status = vid4lin_stream_init_read_write(stream); + + if (status != PJ_SUCCESS) { + PJ_LOG(1,(THIS_FILE, "Error: unable to initiate I/O on %s", + stream->name)); + goto on_error; + } + + /* Done */ + stream->base.op = &stream_op; + *p_vid_strm = &stream->base; + + return PJ_SUCCESS; + +on_error: + if (status == PJ_SUCCESS) + status = PJ_RETURN_OS_ERROR(errno); + + vid4lin_stream_destroy(&stream->base); + return status; +} + +/* API: Get stream info. */ +static pj_status_t vid4lin_stream_get_param(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_param *pi) +{ + vid4lin_stream *strm = (vid4lin_stream*)s; + + PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL); + + pj_memcpy(pi, &strm->param, sizeof(*pi)); + + return PJ_SUCCESS; +} + +/* API: get capability */ +static pj_status_t vid4lin_stream_get_cap(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_cap cap, + void *pval) +{ + vid4lin_stream *strm = (vid4lin_stream*)s; + + PJ_UNUSED_ARG(strm); + + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + if (cap==PJMEDIA_VID_DEV_CAP_INPUT_SCALE) + { + return PJMEDIA_EVID_INVCAP; +// return PJ_SUCCESS; + } else { + return PJMEDIA_EVID_INVCAP; + } +} + +/* API: set capability */ +static pj_status_t vid4lin_stream_set_cap(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_cap cap, + const void *pval) +{ + vid4lin_stream *strm = (vid4lin_stream*)s; + + + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + /* + if (cap==PJMEDIA_VID_DEV_CAP_INPUT_SCALE) + { + return PJ_SUCCESS; + } + */ + PJ_UNUSED_ARG(strm); + PJ_UNUSED_ARG(cap); + PJ_UNUSED_ARG(pval); + + return PJMEDIA_EVID_INVCAP; +} + +/* get frame from mmap */ +static pj_status_t vid4lin_stream_get_frame_mmap(vid4lin_stream *stream, + pjmedia_frame *frame) +{ + struct v4l2_buffer buf; + pj_time_val time; + pj_status_t status = PJ_SUCCESS; + + pj_bzero(&buf, sizeof(buf)); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + status = xioctl(stream->fd, VIDIOC_DQBUF, &buf); + if (status != PJ_SUCCESS) + return status; + + if (frame->size < buf.bytesused) { + /* supplied buffer is too small */ + pj_assert(!"frame buffer is too small for v4l2"); + status = PJ_ETOOSMALL; + goto on_return; + } + + time.sec = buf.timestamp.tv_sec; + time.msec = buf.timestamp.tv_usec / 1000; + PJ_TIME_VAL_SUB(time, stream->start_time); + + frame->type = PJMEDIA_FRAME_TYPE_VIDEO; + frame->size = buf.bytesused; + frame->timestamp.u64 = PJ_UINT64(1) * PJ_TIME_VAL_MSEC(time) * + stream->param.clock_rate / PJ_UINT64(1000); + pj_memcpy(frame->buf, stream->buffers[buf.index].start, buf.bytesused); + +on_return: + pj_bzero(&buf, sizeof(buf)); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + xioctl(stream->fd, VIDIOC_QBUF, &buf); + + return status; +} + +/* API: Get frame from stream */ +static pj_status_t vid4lin_stream_get_frame(pjmedia_vid_dev_stream *strm, + pjmedia_frame *frame) +{ + vid4lin_stream *stream = (vid4lin_stream*)strm; + + if (stream->io_type == IO_TYPE_MMAP) + return vid4lin_stream_get_frame_mmap(stream, frame); + else { + pj_assert(!"Unsupported i/o type"); + return PJ_EINVALIDOP; + } +} + +/* API: Start stream. */ +static pj_status_t vid4lin_stream_start(pjmedia_vid_dev_stream *strm) +{ + vid4lin_stream *stream = (vid4lin_stream*)strm; + struct v4l2_buffer buf; + enum v4l2_buf_type type; + unsigned i; + pj_status_t status; + + PJ_ASSERT_RETURN(stream->fd != -1, PJ_EINVALIDOP); + + PJ_LOG(4, (THIS_FILE, "Starting v4l2 video stream %s", stream->name)); + + pj_gettimeofday(&stream->start_time); + + for (i = 0; i < stream->buf_cnt; ++i) { + pj_bzero(&buf, sizeof(buf)); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + buf.index = i; + status = xioctl(stream->fd, VIDIOC_QBUF, &buf); + if (status != PJ_SUCCESS) + goto on_error; + } + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + status = xioctl(stream->fd, VIDIOC_STREAMON, &type); + if (status != PJ_SUCCESS) + goto on_error; + + return PJ_SUCCESS; + +on_error: + if (i > 0) { + /* Dequeue already enqueued buffers. Can we do this while streaming + * is not started? + */ + unsigned n = i; + for (i=0; i<n; ++i) { + pj_bzero(&buf, sizeof(buf)); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + xioctl(stream->fd, VIDIOC_DQBUF, &buf); + } + } + return status; +} + +/* API: Stop stream. */ +static pj_status_t vid4lin_stream_stop(pjmedia_vid_dev_stream *strm) +{ + vid4lin_stream *stream = (vid4lin_stream*)strm; + enum v4l2_buf_type type; + pj_status_t status; + + if (stream->fd < 0) + return PJ_SUCCESS; + + PJ_LOG(4, (THIS_FILE, "Stopping v4l2 video stream %s", stream->name)); + + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + status = xioctl(stream->fd, VIDIOC_STREAMOFF, &type); + if (status != PJ_SUCCESS) + return status; + + return PJ_SUCCESS; +} + + +/* API: Destroy stream. */ +static pj_status_t vid4lin_stream_destroy(pjmedia_vid_dev_stream *strm) +{ + vid4lin_stream *stream = (vid4lin_stream*)strm; + unsigned i; + + PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); + + vid4lin_stream_stop(strm); + + PJ_LOG(4, (THIS_FILE, "Destroying v4l2 video stream %s", stream->name)); + + for (i=0; i<stream->buf_cnt; ++i) { + if (stream->buffers[i].start != MAP_FAILED) { + v4l2_munmap(stream->buffers[i].start, stream->buffers[i].length); + stream->buffers[i].start = MAP_FAILED; + } + } + + if (stream->fd >= 0) { + v4l2_close(stream->fd); + stream->fd = -1; + } + pj_pool_release(stream->pool); + + return PJ_SUCCESS; +} + +#endif /* PJMEDIA_VIDEO_DEV_HAS_V4L2 */ diff --git a/pjmedia/src/pjmedia-videodev/videodev.c b/pjmedia/src/pjmedia-videodev/videodev.c new file mode 100644 index 00000000..833608e8 --- /dev/null +++ b/pjmedia/src/pjmedia-videodev/videodev.c @@ -0,0 +1,806 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2010 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjmedia-videodev/videodev_imp.h> +#include <pj/assert.h> +#include <pj/errno.h> +#include <pj/log.h> +#include <pj/pool.h> +#include <pj/string.h> + +#define THIS_FILE "videodev.c" + +#define DEFINE_CAP(name, info) {name, info} + +/* Capability names */ +static struct cap_info +{ + const char *name; + const char *info; +} cap_infos[] = +{ + DEFINE_CAP("format", "Video format"), + DEFINE_CAP("scale", "Input dimension"), + DEFINE_CAP("window", "Renderer window"), + DEFINE_CAP("resize", "Renderer resize"), + DEFINE_CAP("position", "Renderer position"), + DEFINE_CAP("hide", "Renderer hide"), +}; + + +/* + * The device index seen by application and driver is different. + * + * At application level, device index is index to global list of device. + * At driver level, device index is index to device list on that particular + * factory only. + */ +#define MAKE_DEV_ID(f_id, index) (((f_id & 0xFFFF) << 16) | (index & 0xFFFF)) +#define GET_INDEX(dev_id) ((dev_id) & 0xFFFF) +#define GET_FID(dev_id) ((dev_id) >> 16) +#define DEFAULT_DEV_ID 0 + + +/* extern functions to create factories */ +#if PJMEDIA_VIDEO_DEV_HAS_NULL_VIDEO +pjmedia_vid_dev_factory* pjmedia_null_video_factory(pj_pool_factory *pf); +#endif + +#if PJMEDIA_VIDEO_DEV_HAS_DSHOW +pjmedia_vid_dev_factory* pjmedia_dshow_factory(pj_pool_factory *pf); +#endif + +#if PJMEDIA_VIDEO_DEV_HAS_CBAR_SRC +pjmedia_vid_dev_factory* pjmedia_cbar_factory(pj_pool_factory *pf); +#endif + +#if PJMEDIA_VIDEO_DEV_HAS_SDL +pjmedia_vid_dev_factory* pjmedia_sdl_factory(pj_pool_factory *pf); +#endif + +#if PJMEDIA_VIDEO_DEV_HAS_FFMPEG +pjmedia_vid_dev_factory* pjmedia_ffmpeg_factory(pj_pool_factory *pf); +#endif + +#if PJMEDIA_VIDEO_DEV_HAS_V4L2 +pjmedia_vid_dev_factory* pjmedia_v4l2_factory(pj_pool_factory *pf); +#endif + +#if PJMEDIA_VIDEO_DEV_HAS_QT +pjmedia_vid_dev_factory* pjmedia_qt_factory(pj_pool_factory *pf); +#endif + +#if PJMEDIA_VIDEO_DEV_HAS_IOS +pjmedia_vid_dev_factory* pjmedia_ios_factory(pj_pool_factory *pf); +#endif + +#define MAX_DRIVERS 16 +#define MAX_DEVS 64 + + +/* driver structure */ +struct driver +{ + /* Creation function */ + pjmedia_vid_dev_factory_create_func_ptr create; + /* Factory instance */ + pjmedia_vid_dev_factory *f; + char name[32]; /* Driver name */ + unsigned dev_cnt; /* Number of devices */ + unsigned start_idx; /* Start index in global list */ + int cap_dev_idx; /* Default capture device. */ + int rend_dev_idx; /* Default render device */ +}; + +/* The video device subsystem */ +static struct vid_subsys +{ + unsigned init_count; /* How many times init() is called */ + pj_pool_factory *pf; /* The pool factory. */ + + unsigned drv_cnt; /* Number of drivers. */ + struct driver drv[MAX_DRIVERS]; /* Array of drivers. */ + + unsigned dev_cnt; /* Total number of devices. */ + pj_uint32_t dev_list[MAX_DEVS];/* Array of device IDs. */ + +} vid_subsys; + +/* API: get capability name/info */ +PJ_DEF(const char*) pjmedia_vid_dev_cap_name(pjmedia_vid_dev_cap cap, + const char **p_desc) +{ + const char *desc; + unsigned i; + + if (p_desc==NULL) p_desc = &desc; + + for (i=0; i<PJ_ARRAY_SIZE(cap_infos); ++i) { + if ((1 << i)==cap) + break; + } + + if (i==PJ_ARRAY_SIZE(cap_infos)) { + *p_desc = "??"; + return "??"; + } + + *p_desc = cap_infos[i].info; + return cap_infos[i].name; +} + +static pj_status_t get_cap_pointer(const pjmedia_vid_dev_param *param, + pjmedia_vid_dev_cap cap, + void **ptr, + unsigned *size) +{ +#define FIELD_INFO(name) *ptr = (void*)¶m->name; \ + *size = sizeof(param->name) + + switch (cap) { + case PJMEDIA_VID_DEV_CAP_FORMAT: + FIELD_INFO(fmt); + break; + case PJMEDIA_VID_DEV_CAP_INPUT_SCALE: + FIELD_INFO(disp_size); + break; + case PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW: + FIELD_INFO(window); + break; + case PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE: + FIELD_INFO(disp_size); + break; + case PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION: + FIELD_INFO(window_pos); + break; + case PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE: + FIELD_INFO(window_hide); + break; + default: + return PJMEDIA_EVID_INVCAP; + } + +#undef FIELD_INFO + + return PJ_SUCCESS; +} + +/* API: set cap value to param */ +PJ_DEF(pj_status_t) +pjmedia_vid_dev_param_set_cap( pjmedia_vid_dev_param *param, + pjmedia_vid_dev_cap cap, + const void *pval) +{ + void *cap_ptr; + unsigned cap_size; + pj_status_t status; + + status = get_cap_pointer(param, cap, &cap_ptr, &cap_size); + if (status != PJ_SUCCESS) + return status; + + pj_memcpy(cap_ptr, pval, cap_size); + param->flags |= cap; + + return PJ_SUCCESS; +} + +/* API: get cap value from param */ +PJ_DEF(pj_status_t) +pjmedia_vid_dev_param_get_cap( const pjmedia_vid_dev_param *param, + pjmedia_vid_dev_cap cap, + void *pval) +{ + void *cap_ptr; + unsigned cap_size; + pj_status_t status; + + status = get_cap_pointer(param, cap, &cap_ptr, &cap_size); + if (status != PJ_SUCCESS) + return status; + + if ((param->flags & cap) == 0) { + pj_bzero(cap_ptr, cap_size); + return PJMEDIA_EVID_INVCAP; + } + + pj_memcpy(pval, cap_ptr, cap_size); + return PJ_SUCCESS; +} + +/* Internal: init driver */ +static pj_status_t init_driver(unsigned drv_idx, pj_bool_t refresh) +{ + struct driver *drv = &vid_subsys.drv[drv_idx]; + pjmedia_vid_dev_factory *f; + unsigned i, dev_cnt; + pj_status_t status; + + if (!refresh) { + /* Create the factory */ + f = (*drv->create)(vid_subsys.pf); + if (!f) + return PJ_EUNKNOWN; + + /* Call factory->init() */ + status = f->op->init(f); + if (status != PJ_SUCCESS) { + f->op->destroy(f); + return status; + } + } else { + f = drv->f; + } + + /* Get number of devices */ + dev_cnt = f->op->get_dev_count(f); + if (dev_cnt + vid_subsys.dev_cnt > MAX_DEVS) { + PJ_LOG(4,(THIS_FILE, "%d device(s) cannot be registered because" + " there are too many devices", + vid_subsys.dev_cnt + dev_cnt - MAX_DEVS)); + dev_cnt = MAX_DEVS - vid_subsys.dev_cnt; + } + + /* enabling this will cause pjsua-lib initialization to fail when there + * is no video device installed in the system, even when pjsua has been + * run with --null-video + * + if (dev_cnt == 0) { + f->op->destroy(f); + return PJMEDIA_EVID_NODEV; + } + */ + + /* Fill in default devices */ + drv->rend_dev_idx = drv->cap_dev_idx = -1; + for (i=0; i<dev_cnt; ++i) { + pjmedia_vid_dev_info info; + + status = f->op->get_dev_info(f, i, &info); + if (status != PJ_SUCCESS) { + f->op->destroy(f); + return status; + } + + if (drv->name[0]=='\0') { + /* Set driver name */ + pj_ansi_strncpy(drv->name, info.driver, sizeof(drv->name)); + drv->name[sizeof(drv->name)-1] = '\0'; + } + + if (drv->rend_dev_idx < 0 && (info.dir & PJMEDIA_DIR_RENDER)) { + /* Set default render device */ + drv->rend_dev_idx = i; + } + if (drv->cap_dev_idx < 0 && (info.dir & PJMEDIA_DIR_CAPTURE)) { + /* Set default capture device */ + drv->cap_dev_idx = i; + } + + if (drv->rend_dev_idx >= 0 && drv->cap_dev_idx >= 0) { + /* Done. */ + break; + } + } + + /* Register the factory */ + drv->f = f; + drv->f->sys.drv_idx = drv_idx; + drv->start_idx = vid_subsys.dev_cnt; + drv->dev_cnt = dev_cnt; + + /* Register devices to global list */ + for (i=0; i<dev_cnt; ++i) { + vid_subsys.dev_list[vid_subsys.dev_cnt++] = MAKE_DEV_ID(drv_idx, i); + } + + return PJ_SUCCESS; +} + +/* Internal: deinit driver */ +static void deinit_driver(unsigned drv_idx) +{ + struct driver *drv = &vid_subsys.drv[drv_idx]; + + if (drv->f) { + drv->f->op->destroy(drv->f); + drv->f = NULL; + } + + drv->dev_cnt = 0; + drv->rend_dev_idx = drv->cap_dev_idx = -1; +} + +/* API: Initialize the video device subsystem. */ +PJ_DEF(pj_status_t) pjmedia_vid_dev_subsys_init(pj_pool_factory *pf) +{ + unsigned i; + pj_status_t status = PJ_SUCCESS; + + /* Allow init() to be called multiple times as long as there is matching + * number of shutdown(). + */ + if (vid_subsys.init_count++ != 0) { + return PJ_SUCCESS; + } + + /* Register error subsystem */ + pj_register_strerror(PJMEDIA_VIDEODEV_ERRNO_START, + PJ_ERRNO_SPACE_SIZE, + &pjmedia_videodev_strerror); + + /* Init */ + vid_subsys.pf = pf; + vid_subsys.drv_cnt = 0; + vid_subsys.dev_cnt = 0; + + /* Register creation functions */ +#if PJMEDIA_VIDEO_DEV_HAS_V4L2 + vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_v4l2_factory; +#endif +#if PJMEDIA_VIDEO_DEV_HAS_QT + vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_qt_factory; +#endif +#if PJMEDIA_VIDEO_DEV_HAS_IOS + vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_ios_factory; +#endif +#if PJMEDIA_VIDEO_DEV_HAS_DSHOW + vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_dshow_factory; +#endif +#if PJMEDIA_VIDEO_DEV_HAS_FFMPEG + vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_ffmpeg_factory; +#endif +#if PJMEDIA_VIDEO_DEV_HAS_CBAR_SRC + vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_cbar_factory; +#endif +#if PJMEDIA_VIDEO_DEV_HAS_SDL + vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_sdl_factory; +#endif + + /* Initialize each factory and build the device ID list */ + for (i=0; i<vid_subsys.drv_cnt; ++i) { + status = init_driver(i, PJ_FALSE); + if (status != PJ_SUCCESS) { + deinit_driver(i); + continue; + } + } + + return vid_subsys.dev_cnt ? PJ_SUCCESS : status; +} + +/* API: register a video device factory to the video device subsystem. */ +PJ_DEF(pj_status_t) +pjmedia_vid_register_factory(pjmedia_vid_dev_factory_create_func_ptr adf) +{ + pj_status_t status; + + if (vid_subsys.init_count == 0) + return PJMEDIA_EVID_INIT; + + vid_subsys.drv[vid_subsys.drv_cnt].create = adf; + status = init_driver(vid_subsys.drv_cnt, PJ_FALSE); + if (status == PJ_SUCCESS) { + vid_subsys.drv_cnt++; + } else { + deinit_driver(vid_subsys.drv_cnt); + } + + return status; +} + +/* API: unregister a video device factory from the video device subsystem. */ +PJ_DEF(pj_status_t) +pjmedia_vid_unregister_factory(pjmedia_vid_dev_factory_create_func_ptr adf) +{ + unsigned i, j; + + if (vid_subsys.init_count == 0) + return PJMEDIA_EVID_INIT; + + for (i=0; i<vid_subsys.drv_cnt; ++i) { + struct driver *drv = &vid_subsys.drv[i]; + + if (drv->create == adf) { + for (j = drv->start_idx; j < drv->start_idx + drv->dev_cnt; j++) + { + vid_subsys.dev_list[j] = (pj_uint32_t)PJMEDIA_VID_INVALID_DEV; + } + + deinit_driver(i); + pj_bzero(drv, sizeof(*drv)); + return PJ_SUCCESS; + } + } + + return PJMEDIA_EVID_ERR; +} + +/* API: get the pool factory registered to the video device subsystem. */ +PJ_DEF(pj_pool_factory*) pjmedia_vid_dev_subsys_get_pool_factory(void) +{ + return vid_subsys.pf; +} + +/* API: Shutdown the video device subsystem. */ +PJ_DEF(pj_status_t) pjmedia_vid_dev_subsys_shutdown(void) +{ + unsigned i; + + /* Allow shutdown() to be called multiple times as long as there is + * matching number of init(). + */ + if (vid_subsys.init_count == 0) { + return PJ_SUCCESS; + } + --vid_subsys.init_count; + + if (vid_subsys.init_count == 0) { + for (i=0; i<vid_subsys.drv_cnt; ++i) { + deinit_driver(i); + } + + vid_subsys.pf = NULL; + } + return PJ_SUCCESS; +} + +/* API: Refresh the list of video devices installed in the system. */ +PJ_DEF(pj_status_t) pjmedia_vid_dev_refresh(void) +{ + unsigned i; + + vid_subsys.dev_cnt = 0; + for (i=0; i<vid_subsys.drv_cnt; ++i) { + struct driver *drv = &vid_subsys.drv[i]; + + if (drv->f && drv->f->op->refresh) { + pj_status_t status = drv->f->op->refresh(drv->f); + if (status != PJ_SUCCESS) { + PJ_PERROR(4, (THIS_FILE, status, "Unable to refresh device " + "list for %s", drv->name)); + } + } + init_driver(i, PJ_TRUE); + } + return PJ_SUCCESS; +} + +/* API: Get the number of video devices installed in the system. */ +PJ_DEF(unsigned) pjmedia_vid_dev_count(void) +{ + return vid_subsys.dev_cnt; +} + +/* Internal: convert local index to global device index */ +static pj_status_t make_global_index(unsigned drv_idx, + pjmedia_vid_dev_index *id) +{ + if (*id < 0) { + return PJ_SUCCESS; + } + + /* Check that factory still exists */ + PJ_ASSERT_RETURN(vid_subsys.drv[drv_idx].f, PJ_EBUG); + + /* Check that device index is valid */ + PJ_ASSERT_RETURN(*id>=0 && *id<(int)vid_subsys.drv[drv_idx].dev_cnt, + PJ_EBUG); + + *id += vid_subsys.drv[drv_idx].start_idx; + return PJ_SUCCESS; +} + +/* Internal: lookup device id */ +static pj_status_t lookup_dev(pjmedia_vid_dev_index id, + pjmedia_vid_dev_factory **p_f, + unsigned *p_local_index) +{ + int f_id, index; + + if (id < 0) { + unsigned i; + + if (id <= PJMEDIA_VID_INVALID_DEV) + return PJMEDIA_EVID_INVDEV; + + for (i=0; i<vid_subsys.drv_cnt; ++i) { + struct driver *drv = &vid_subsys.drv[i]; + if (id==PJMEDIA_VID_DEFAULT_CAPTURE_DEV && + drv->cap_dev_idx >= 0) + { + id = drv->cap_dev_idx; + make_global_index(i, &id); + break; + } else if (id==PJMEDIA_VID_DEFAULT_RENDER_DEV && + drv->rend_dev_idx >= 0) + { + id = drv->rend_dev_idx; + make_global_index(i, &id); + break; + } + } + + if (id < 0) { + return PJMEDIA_EVID_NODEFDEV; + } + } + + f_id = GET_FID(vid_subsys.dev_list[id]); + index = GET_INDEX(vid_subsys.dev_list[id]); + + if (f_id < 0 || f_id >= (int)vid_subsys.drv_cnt) + return PJMEDIA_EVID_INVDEV; + + if (index < 0 || index >= (int)vid_subsys.drv[f_id].dev_cnt) + return PJMEDIA_EVID_INVDEV; + + *p_f = vid_subsys.drv[f_id].f; + *p_local_index = (unsigned)index; + + return PJ_SUCCESS; + +} + +/* API: Get device information. */ +PJ_DEF(pj_status_t) pjmedia_vid_dev_get_info(pjmedia_vid_dev_index id, + pjmedia_vid_dev_info *info) +{ + pjmedia_vid_dev_factory *f; + unsigned index; + pj_status_t status; + + PJ_ASSERT_RETURN(info, PJ_EINVAL); + PJ_ASSERT_RETURN(vid_subsys.pf, PJMEDIA_EVID_INIT); + + if (id <= PJMEDIA_VID_INVALID_DEV) + return PJMEDIA_EVID_INVDEV; + + status = lookup_dev(id, &f, &index); + if (status != PJ_SUCCESS) + return status; + + status = f->op->get_dev_info(f, index, info); + + /* Make sure device ID is the real ID (not PJMEDIA_VID_DEFAULT_*_DEV) */ + info->id = index; + make_global_index(f->sys.drv_idx, &info->id); + + return status; +} + +/* API: find device */ +PJ_DEF(pj_status_t) pjmedia_vid_dev_lookup( const char *drv_name, + const char *dev_name, + pjmedia_vid_dev_index *id) +{ + pjmedia_vid_dev_factory *f = NULL; + unsigned drv_idx, dev_idx; + + PJ_ASSERT_RETURN(drv_name && dev_name && id, PJ_EINVAL); + PJ_ASSERT_RETURN(vid_subsys.pf, PJMEDIA_EVID_INIT); + + for (drv_idx=0; drv_idx<vid_subsys.drv_cnt; ++drv_idx) { + if (!pj_ansi_stricmp(drv_name, vid_subsys.drv[drv_idx].name)) + { + f = vid_subsys.drv[drv_idx].f; + break; + } + } + + if (!f) + return PJ_ENOTFOUND; + + for (dev_idx=0; dev_idx<vid_subsys.drv[drv_idx].dev_cnt; ++dev_idx) + { + pjmedia_vid_dev_info info; + pj_status_t status; + + status = f->op->get_dev_info(f, dev_idx, &info); + if (status != PJ_SUCCESS) + return status; + + if (!pj_ansi_stricmp(dev_name, info.name)) + break; + } + + if (dev_idx==vid_subsys.drv[drv_idx].dev_cnt) + return PJ_ENOTFOUND; + + *id = dev_idx; + make_global_index(drv_idx, id); + + return PJ_SUCCESS; +} + +/* API: Initialize the video device parameters with default values for the + * specified device. + */ +PJ_DEF(pj_status_t) pjmedia_vid_dev_default_param(pj_pool_t *pool, + pjmedia_vid_dev_index id, + pjmedia_vid_dev_param *param) +{ + pjmedia_vid_dev_factory *f; + unsigned index; + pj_status_t status; + + PJ_ASSERT_RETURN(param, PJ_EINVAL); + PJ_ASSERT_RETURN(vid_subsys.pf, PJMEDIA_EVID_INIT); + + if (id <= PJMEDIA_VID_INVALID_DEV) + return PJMEDIA_EVID_INVDEV; + + status = lookup_dev(id, &f, &index); + if (status != PJ_SUCCESS) + return status; + + status = f->op->default_param(pool, f, index, param); + if (status != PJ_SUCCESS) + return status; + + /* Normalize device IDs */ + make_global_index(f->sys.drv_idx, ¶m->cap_id); + make_global_index(f->sys.drv_idx, ¶m->rend_id); + + return PJ_SUCCESS; +} + +/* API: Open video stream object using the specified parameters. */ +PJ_DEF(pj_status_t) pjmedia_vid_dev_stream_create( + pjmedia_vid_dev_param *prm, + const pjmedia_vid_dev_cb *cb, + void *user_data, + pjmedia_vid_dev_stream **p_vid_strm) +{ + pjmedia_vid_dev_factory *cap_f=NULL, *rend_f=NULL, *f=NULL; + pj_status_t status; + + PJ_ASSERT_RETURN(prm && prm->dir && p_vid_strm, PJ_EINVAL); + PJ_ASSERT_RETURN(vid_subsys.pf, PJMEDIA_EVID_INIT); + PJ_ASSERT_RETURN(prm->dir==PJMEDIA_DIR_CAPTURE || + prm->dir==PJMEDIA_DIR_RENDER || + prm->dir==PJMEDIA_DIR_CAPTURE_RENDER, + PJ_EINVAL); + + /* Normalize cap_id */ + if (prm->dir & PJMEDIA_DIR_CAPTURE) { + unsigned index; + + if (prm->cap_id < 0) + prm->cap_id = PJMEDIA_VID_DEFAULT_CAPTURE_DEV; + + status = lookup_dev(prm->cap_id, &cap_f, &index); + if (status != PJ_SUCCESS) + return status; + + prm->cap_id = index; + f = cap_f; + } + + /* Normalize rend_id */ + if (prm->dir & PJMEDIA_DIR_RENDER) { + unsigned index; + + if (prm->rend_id < 0) + prm->rend_id = PJMEDIA_VID_DEFAULT_RENDER_DEV; + + status = lookup_dev(prm->rend_id, &rend_f, &index); + if (status != PJ_SUCCESS) + return status; + + prm->rend_id = index; + f = rend_f; + } + + PJ_ASSERT_RETURN(f != NULL, PJ_EBUG); + + /* For now, cap_id and rend_id must belong to the same factory */ + PJ_ASSERT_RETURN((prm->dir != PJMEDIA_DIR_CAPTURE_RENDER) || + (cap_f == rend_f), + PJMEDIA_EVID_INVDEV); + + /* Create the stream */ + status = f->op->create_stream(f, prm, cb, + user_data, p_vid_strm); + if (status != PJ_SUCCESS) + return status; + + /* Assign factory id to the stream */ + (*p_vid_strm)->sys.drv_idx = f->sys.drv_idx; + return PJ_SUCCESS; +} + +/* API: Get the running parameters for the specified video stream. */ +PJ_DEF(pj_status_t) pjmedia_vid_dev_stream_get_param( + pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_param *param) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(strm && param, PJ_EINVAL); + PJ_ASSERT_RETURN(vid_subsys.pf, PJMEDIA_EVID_INIT); + + status = strm->op->get_param(strm, param); + if (status != PJ_SUCCESS) + return status; + + /* Normalize device id's */ + make_global_index(strm->sys.drv_idx, ¶m->cap_id); + make_global_index(strm->sys.drv_idx, ¶m->rend_id); + + return PJ_SUCCESS; +} + +/* API: Get the value of a specific capability of the video stream. */ +PJ_DEF(pj_status_t) pjmedia_vid_dev_stream_get_cap( + pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_cap cap, + void *value) +{ + return strm->op->get_cap(strm, cap, value); +} + +/* API: Set the value of a specific capability of the video stream. */ +PJ_DEF(pj_status_t) pjmedia_vid_dev_stream_set_cap( + pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_cap cap, + const void *value) +{ + return strm->op->set_cap(strm, cap, value); +} + +PJ_DEF(pjmedia_event_publisher*) +pjmedia_vid_dev_stream_get_event_publisher(pjmedia_vid_dev_stream *strm) +{ + return &strm->epub; +} + +/* API: Start the stream. */ +PJ_DEF(pj_status_t) pjmedia_vid_dev_stream_start(pjmedia_vid_dev_stream *strm) +{ + return strm->op->start(strm); +} + +PJ_DEF(pj_status_t) pjmedia_vid_dev_stream_get_frame( + pjmedia_vid_dev_stream *strm, + pjmedia_frame *frame) +{ + pj_assert(strm->op->get_frame); + return strm->op->get_frame(strm, frame); +} + +PJ_DEF(pj_status_t) pjmedia_vid_dev_stream_put_frame( + pjmedia_vid_dev_stream *strm, + const pjmedia_frame *frame) +{ + pj_assert(strm->op->put_frame); + return strm->op->put_frame(strm, frame); +} + +/* API: Stop the stream. */ +PJ_DEF(pj_status_t) pjmedia_vid_dev_stream_stop(pjmedia_vid_dev_stream *strm) +{ + return strm->op->stop(strm); +} + +/* API: Destroy the stream. */ +PJ_DEF(pj_status_t) pjmedia_vid_dev_stream_destroy( + pjmedia_vid_dev_stream *strm) +{ + return strm->op->destroy(strm); +} diff --git a/pjmedia/src/pjmedia/avi_player.c b/pjmedia/src/pjmedia/avi_player.c new file mode 100644 index 00000000..ce8d914a --- /dev/null +++ b/pjmedia/src/pjmedia/avi_player.c @@ -0,0 +1,711 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-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 + */ + +/** + * Default file player/writer buffer size. + */ +#include <pjmedia/avi_stream.h> +#include <pjmedia/avi.h> +#include <pjmedia/errno.h> +#include <pjmedia/wave.h> +#include <pj/assert.h> +#include <pj/file_access.h> +#include <pj/file_io.h> +#include <pj/log.h> +#include <pj/pool.h> +#include <pj/string.h> + + +#define THIS_FILE "avi_player.c" + +#define AVIF_MUSTUSEINDEX 0x00000020 +#define AVIF_ISINTERLEAVED 0x00000100 +#define AVISF_DISABLED 0x00000001 +#define AVISF_VIDEO_PALCHANGES 0x00010000 + +#define AVI_EOF 0xFFEEFFEE + +#define COMPARE_TAG(doc_tag, tag) (doc_tag == *((pj_uint32_t *)avi_tags[tag])) + +#define SIGNATURE PJMEDIA_SIG_PORT_VID_AVI_PLAYER + +#if 0 +# define TRACE_(x) PJ_LOG(4,x) +#else +# define TRACE_(x) +#endif + +#if defined(PJ_IS_BIG_ENDIAN) && PJ_IS_BIG_ENDIAN!=0 + static void data_to_host(void *data, pj_uint8_t bits, unsigned count) + { + unsigned i; + pj_int32_t *data32 = (pj_int32_t *)data; + pj_int16_t *data16 = (pj_int16_t *)data; + count /= (bits == 32? 4 : 2); + for (i=0; i<count; ++i) { + if (bits == 32) + data32[i] = pj_swap32(data32[i]); + else + data16[i] = pj_swap16(data16[i]); + } + } + static void data_to_host2(void *data, pj_uint8_t nsizes, + pj_uint8_t *sizes) + { + unsigned i; + pj_int8_t *datap = (pj_int8_t *)data; + for (i = 0; i < nsizes; i++) { + data_to_host(datap, 32, sizes[i]); + datap += sizes[i++]; + if (i >= nsizes) + break; + data_to_host(datap, 16, sizes[i]); + datap += sizes[i]; + } + } +#else +# define data_to_host(data, bits, count) +# define data_to_host2(data, nsizes, sizes) +#endif + +struct pjmedia_avi_streams +{ + unsigned num_streams; + pjmedia_port **streams; +}; + +struct avi_reader_port +{ + pjmedia_port base; + unsigned stream_id; + unsigned options; + pj_uint16_t bits_per_sample; + pj_bool_t eof; + pj_off_t fsize; + pj_off_t start_data; + pj_uint8_t pad; + pj_oshandle_t fd; + pj_ssize_t size_left; + + pj_status_t (*cb)(pjmedia_port*, void*); +}; + + +static pj_status_t avi_get_frame(pjmedia_port *this_port, + pjmedia_frame *frame); +static pj_status_t avi_on_destroy(pjmedia_port *this_port); + +static struct avi_reader_port *create_avi_port(pj_pool_t *pool) +{ + const pj_str_t name = pj_str("file"); + struct avi_reader_port *port; + + port = PJ_POOL_ZALLOC_T(pool, struct avi_reader_port); + if (!port) + return NULL; + + /* Put in default values. + * These will be overriden once the file is read. + */ + pjmedia_port_info_init(&port->base.info, &name, SIGNATURE, + 8000, 1, 16, 80); + + port->fd = (pj_oshandle_t)-1; + port->base.get_frame = &avi_get_frame; + port->base.on_destroy = &avi_on_destroy; + + return port; +} + +#define file_read(fd, data, size) file_read2(fd, data, size, 32) +#define file_read2(fd, data, size, bits) file_read3(fd, data, size, bits, NULL) + +static pj_status_t file_read3(pj_oshandle_t fd, void *data, pj_ssize_t size, + pj_uint16_t bits, pj_ssize_t *psz_read) +{ + pj_ssize_t size_read = size, size_to_read = size; + pj_status_t status = pj_file_read(fd, data, &size_read); + if (status != PJ_SUCCESS) + return status; + + /* Normalize AVI header fields values from little-endian to host + * byte order. + */ + if (bits > 0) + data_to_host(data, bits, size_read); + + if (size_read != size_to_read) { + if (psz_read) + *psz_read = size_read; + return AVI_EOF; + } + + return status; +} + +/* + * Create AVI player port. + */ +PJ_DEF(pj_status_t) +pjmedia_avi_player_create_streams(pj_pool_t *pool, + const char *filename, + unsigned options, + pjmedia_avi_streams **p_streams) +{ + pjmedia_avi_hdr avi_hdr; + struct avi_reader_port *fport[PJMEDIA_AVI_MAX_NUM_STREAMS]; + pj_off_t pos; + unsigned i, nstr = 0; + pj_status_t status = PJ_SUCCESS; + + /* Check arguments. */ + PJ_ASSERT_RETURN(pool && filename && p_streams, PJ_EINVAL); + + /* Check the file really exists. */ + if (!pj_file_exists(filename)) { + return PJ_ENOTFOUND; + } + + /* Create fport instance. */ + fport[0] = create_avi_port(pool); + if (!fport[0]) { + return PJ_ENOMEM; + } + + /* Get the file size. */ + fport[0]->fsize = pj_file_size(filename); + + /* Size must be more than AVI header size */ + if (fport[0]->fsize <= sizeof(riff_hdr_t) + sizeof(avih_hdr_t) + + sizeof(strl_hdr_t)) + { + return PJMEDIA_EINVALIMEDIATYPE; + } + + /* Open file. */ + status = pj_file_open(pool, filename, PJ_O_RDONLY, &fport[0]->fd); + if (status != PJ_SUCCESS) + return status; + + /* Read the RIFF + AVIH header. */ + status = file_read(fport[0]->fd, &avi_hdr, + sizeof(riff_hdr_t) + sizeof(avih_hdr_t)); + if (status != PJ_SUCCESS) + goto on_error; + + /* Validate AVI file. */ + if (!COMPARE_TAG(avi_hdr.riff_hdr.riff, PJMEDIA_AVI_RIFF_TAG) || + !COMPARE_TAG(avi_hdr.riff_hdr.avi, PJMEDIA_AVI_AVI_TAG) || + !COMPARE_TAG(avi_hdr.avih_hdr.list_tag, PJMEDIA_AVI_LIST_TAG) || + !COMPARE_TAG(avi_hdr.avih_hdr.hdrl_tag, PJMEDIA_AVI_HDRL_TAG) || + !COMPARE_TAG(avi_hdr.avih_hdr.avih, PJMEDIA_AVI_AVIH_TAG)) + { + status = PJMEDIA_EINVALIMEDIATYPE; + goto on_error; + } + + PJ_LOG(5, (THIS_FILE, "The AVI file has %d streams.", + avi_hdr.avih_hdr.num_streams)); + + /* Unsupported AVI format. */ + if (avi_hdr.avih_hdr.num_streams > PJMEDIA_AVI_MAX_NUM_STREAMS) { + status = PJMEDIA_EAVIUNSUPP; + goto on_error; + } + + /** + * TODO: Possibly unsupported AVI format. + * If you encounter this warning, verify whether the avi player + * is working properly. + */ + if (avi_hdr.avih_hdr.flags & AVIF_MUSTUSEINDEX || + avi_hdr.avih_hdr.pad > 1) + { + PJ_LOG(3, (THIS_FILE, "Warning!!! Possibly unsupported AVI format: " + "flags:%d, pad:%d", avi_hdr.avih_hdr.flags, + avi_hdr.avih_hdr.pad)); + } + + /* Read the headers of each stream. */ + for (i = 0; i < avi_hdr.avih_hdr.num_streams; i++) { + pj_size_t elem = 0; + pj_ssize_t size_to_read; + + /* Read strl header */ + status = file_read(fport[0]->fd, &avi_hdr.strl_hdr[i], + sizeof(strl_hdr_t)); + if (status != PJ_SUCCESS) + goto on_error; + + elem = COMPARE_TAG(avi_hdr.strl_hdr[i].data_type, + PJMEDIA_AVI_VIDS_TAG) ? + sizeof(strf_video_hdr_t) : + COMPARE_TAG(avi_hdr.strl_hdr[i].data_type, + PJMEDIA_AVI_AUDS_TAG) ? + sizeof(strf_audio_hdr_t) : 0; + + /* Read strf header */ + status = file_read2(fport[0]->fd, &avi_hdr.strf_hdr[i], + elem, 0); + if (status != PJ_SUCCESS) + goto on_error; + + /* Normalize the endian */ + if (elem == sizeof(strf_video_hdr_t)) + data_to_host2(&avi_hdr.strf_hdr[i], + sizeof(strf_video_hdr_sizes)/ + sizeof(strf_video_hdr_sizes[0]), + strf_video_hdr_sizes); + else if (elem == sizeof(strf_audio_hdr_t)) + data_to_host2(&avi_hdr.strf_hdr[i], + sizeof(strf_audio_hdr_sizes)/ + sizeof(strf_audio_hdr_sizes[0]), + strf_audio_hdr_sizes); + + /* Skip the remainder of the header */ + size_to_read = avi_hdr.strl_hdr[i].list_sz - (sizeof(strl_hdr_t) - + 8) - elem; + status = pj_file_setpos(fport[0]->fd, size_to_read, PJ_SEEK_CUR); + if (status != PJ_SUCCESS) { + goto on_error; + } + } + + /* Finish reading the AVIH header */ + status = pj_file_setpos(fport[0]->fd, avi_hdr.avih_hdr.list_sz + + sizeof(riff_hdr_t) + 8, PJ_SEEK_SET); + if (status != PJ_SUCCESS) { + goto on_error; + } + + /* Skip any JUNK or LIST INFO until we get MOVI tag */ + do { + pjmedia_avi_subchunk ch; + int read = 0; + + status = file_read(fport[0]->fd, &ch, sizeof(pjmedia_avi_subchunk)); + if (status != PJ_SUCCESS) { + goto on_error; + } + + if (COMPARE_TAG(ch.id, PJMEDIA_AVI_LIST_TAG)) + { + read = 4; + status = file_read(fport[0]->fd, &ch, read); + if (COMPARE_TAG(ch.id, PJMEDIA_AVI_MOVI_TAG)) + break; + } + + status = pj_file_setpos(fport[0]->fd, ch.len-read, PJ_SEEK_CUR); + if (status != PJ_SUCCESS) { + goto on_error; + } + } while(1); + + status = pj_file_getpos(fport[0]->fd, &pos); + if (status != PJ_SUCCESS) + goto on_error; + + for (i = 0, nstr = 0; i < avi_hdr.avih_hdr.num_streams; i++) { + /* Skip non-audio, non-video, or disabled streams) */ + if ((!COMPARE_TAG(avi_hdr.strl_hdr[i].data_type, + PJMEDIA_AVI_VIDS_TAG) && + !COMPARE_TAG(avi_hdr.strl_hdr[i].data_type, + PJMEDIA_AVI_AUDS_TAG)) || + avi_hdr.strl_hdr[i].flags & AVISF_DISABLED) + { + continue; + } + + if (COMPARE_TAG(avi_hdr.strl_hdr[i].data_type, + PJMEDIA_AVI_VIDS_TAG)) + { + /* Check supported video formats here */ + if (avi_hdr.strl_hdr[i].flags & AVISF_VIDEO_PALCHANGES || + (avi_hdr.strl_hdr[i].codec != PJMEDIA_FORMAT_MJPEG && + avi_hdr.strl_hdr[i].codec != PJMEDIA_FORMAT_XVID && + avi_hdr.strl_hdr[i].codec != PJMEDIA_FORMAT_UYVY && + avi_hdr.strl_hdr[i].codec != PJMEDIA_FORMAT_YUY2 && + avi_hdr.strl_hdr[i].codec != PJMEDIA_FORMAT_IYUV && + avi_hdr.strl_hdr[i].codec != PJMEDIA_FORMAT_I420 && + avi_hdr.strl_hdr[i].codec != PJMEDIA_FORMAT_DIB && + avi_hdr.strl_hdr[i].codec != PJMEDIA_FORMAT_RGB24 && + avi_hdr.strl_hdr[i].codec != PJMEDIA_FORMAT_RGB32)) + { + PJ_LOG(4, (THIS_FILE, "Unsupported video stream")); + continue; + } + } else { + /* Check supported audio formats here */ + if ((avi_hdr.strl_hdr[i].codec != PJMEDIA_FORMAT_PCM && + avi_hdr.strl_hdr[i].codec != PJMEDIA_FORMAT_ALAW && + avi_hdr.strl_hdr[i].codec != PJMEDIA_FORMAT_ULAW && + avi_hdr.strl_hdr[i].codec != PJMEDIA_WAVE_FMT_TAG_PCM) || + avi_hdr.strf_hdr[i].strf_audio_hdr.bits_per_sample != 16) + { + PJ_LOG(4, (THIS_FILE, "Unsupported audio stream")); + continue; + } + } + + if (nstr == 0) { + fport[0]->stream_id = i; + nstr++; + continue; + } + + /* Create fport instance. */ + fport[nstr] = create_avi_port(pool); + if (!fport[nstr]) { + status = PJ_ENOMEM; + goto on_error; + } + + fport[nstr]->stream_id = i; + + /* Open file. */ + status = pj_file_open(pool, filename, PJ_O_RDONLY, &fport[nstr]->fd); + if (status != PJ_SUCCESS) + goto on_error; + + /* Set the file position */ + status = pj_file_setpos(fport[nstr]->fd, pos, PJ_SEEK_SET); + if (status != PJ_SUCCESS) { + goto on_error; + } + + nstr++; + } + + if (nstr == 0) { + status = PJMEDIA_EAVIUNSUPP; + goto on_error; + } + + for (i = 0; i < nstr; i++) { + strl_hdr_t *strl_hdr = &avi_hdr.strl_hdr[fport[i]->stream_id]; + + /* Initialize */ + fport[i]->options = options; + fport[i]->fsize = fport[0]->fsize; + /* Current file position now points to start of data */ + fport[i]->start_data = pos; + + if (COMPARE_TAG(strl_hdr->data_type, PJMEDIA_AVI_VIDS_TAG)) { + strf_video_hdr_t *strf_hdr = + &avi_hdr.strf_hdr[fport[i]->stream_id].strf_video_hdr; + const pjmedia_video_format_info *vfi; + + vfi = pjmedia_get_video_format_info( + pjmedia_video_format_mgr_instance(), + strl_hdr->codec); + + fport[i]->bits_per_sample = (vfi ? vfi->bpp : 0); + pjmedia_format_init_video(&fport[i]->base.info.fmt, + strl_hdr->codec, + strf_hdr->biWidth, + strf_hdr->biHeight, + strl_hdr->rate, + strl_hdr->scale); + + } else { + strf_audio_hdr_t *strf_hdr = + &avi_hdr.strf_hdr[fport[i]->stream_id].strf_audio_hdr; + + fport[i]->bits_per_sample = strf_hdr->bits_per_sample; + pjmedia_format_init_audio(&fport[i]->base.info.fmt, + strl_hdr->codec, + strf_hdr->sample_rate, + strf_hdr->nchannels, + strf_hdr->bits_per_sample, + 20000, + strf_hdr->bytes_per_sec, + strf_hdr->bytes_per_sec); + } + + pj_strdup2(pool, &fport[i]->base.info.name, filename); + } + + /* Done. */ + *p_streams = pj_pool_alloc(pool, sizeof(pjmedia_avi_streams)); + (*p_streams)->num_streams = nstr; + (*p_streams)->streams = pj_pool_calloc(pool, (*p_streams)->num_streams, + sizeof(pjmedia_port *)); + for (i = 0; i < nstr; i++) + (*p_streams)->streams[i] = &fport[i]->base; + + PJ_LOG(4,(THIS_FILE, + "AVI file player '%.*s' created with " + "%d media ports", + (int)fport[0]->base.info.name.slen, + fport[0]->base.info.name.ptr, + (*p_streams)->num_streams)); + + return PJ_SUCCESS; + +on_error: + fport[0]->base.on_destroy(&fport[0]->base); + for (i = 1; i < nstr; i++) + fport[i]->base.on_destroy(&fport[i]->base); + if (status == AVI_EOF) + return PJMEDIA_EINVALIMEDIATYPE; + return status; +} + +PJ_DEF(unsigned) +pjmedia_avi_streams_get_num_streams(pjmedia_avi_streams *streams) +{ + pj_assert(streams); + return streams->num_streams; +} + +PJ_DEF(pjmedia_avi_stream *) +pjmedia_avi_streams_get_stream(pjmedia_avi_streams *streams, + unsigned idx) +{ + pj_assert(streams); + return (idx >=0 && idx < streams->num_streams ? + streams->streams[idx] : NULL); +} + +PJ_DEF(pjmedia_avi_stream *) +pjmedia_avi_streams_get_stream_by_media(pjmedia_avi_streams *streams, + unsigned start_idx, + pjmedia_type media_type) +{ + unsigned i; + + pj_assert(streams); + for (i = start_idx; i < streams->num_streams; i++) + if (streams->streams[i]->info.fmt.type == media_type) + return streams->streams[i]; + return NULL; +} + + +/* + * Get the data length, in bytes. + */ +PJ_DEF(pj_ssize_t) pjmedia_avi_stream_get_len(pjmedia_avi_stream *stream) +{ + struct avi_reader_port *fport; + + /* Sanity check */ + PJ_ASSERT_RETURN(stream, -PJ_EINVAL); + + /* Check that this is really a player port */ + PJ_ASSERT_RETURN(stream->info.signature == SIGNATURE, -PJ_EINVALIDOP); + + fport = (struct avi_reader_port*) stream; + + return (pj_ssize_t)(fport->fsize - fport->start_data); +} + + +/* + * Register a callback to be called when the file reading has reached the + * end of file. + */ +PJ_DEF(pj_status_t) +pjmedia_avi_stream_set_eof_cb( pjmedia_avi_stream *stream, + void *user_data, + pj_status_t (*cb)(pjmedia_avi_stream *stream, + void *usr_data)) +{ + struct avi_reader_port *fport; + + /* Sanity check */ + PJ_ASSERT_RETURN(stream, -PJ_EINVAL); + + /* Check that this is really a player port */ + PJ_ASSERT_RETURN(stream->info.signature == SIGNATURE, -PJ_EINVALIDOP); + + fport = (struct avi_reader_port*) stream; + + fport->base.port_data.pdata = user_data; + fport->cb = cb; + + return PJ_SUCCESS; +} + + +/* + * Get frame from file. + */ +static pj_status_t avi_get_frame(pjmedia_port *this_port, + pjmedia_frame *frame) +{ + struct avi_reader_port *fport = (struct avi_reader_port*)this_port; + pj_status_t status; + pj_ssize_t size_read = 0, size_to_read = 0; + + pj_assert(fport->base.info.signature == SIGNATURE); + + /* We encountered end of file */ + if (fport->eof) { + pj_status_t status = PJ_SUCCESS; + + PJ_LOG(5,(THIS_FILE, "File port %.*s EOF", + (int)fport->base.info.name.slen, + fport->base.info.name.ptr)); + + /* Call callback, if any */ + if (fport->cb) + status = (*fport->cb)(this_port, fport->base.port_data.pdata); + + /* If callback returns non PJ_SUCCESS or 'no loop' is specified, + * return immediately (and don't try to access player port since + * it might have been destroyed by the callback). + */ + if ((status != PJ_SUCCESS) || + (fport->options & PJMEDIA_AVI_FILE_NO_LOOP)) + { + frame->type = PJMEDIA_FRAME_TYPE_NONE; + frame->size = 0; + return PJ_EEOF; + } + + /* Rewind file */ + PJ_LOG(5,(THIS_FILE, "File port %.*s rewinding..", + (int)fport->base.info.name.slen, + fport->base.info.name.ptr)); + fport->eof = PJ_FALSE; + pj_file_setpos(fport->fd, fport->start_data, PJ_SEEK_SET); + } + + /* Fill frame buffer. */ + size_to_read = frame->size; + do { + pjmedia_avi_subchunk ch = {0, 0}; + char *cid; + unsigned stream_id; + + /* We need to read data from the file past the chunk boundary */ + if (fport->size_left > 0 && fport->size_left < size_to_read) { + status = file_read3(fport->fd, frame->buf, fport->size_left, + fport->bits_per_sample, &size_read); + if (status != PJ_SUCCESS) + goto on_error2; + size_to_read -= fport->size_left; + fport->size_left = 0; + } + + /* Read new chunk data */ + if (fport->size_left == 0) { + pj_off_t pos; + pj_file_getpos(fport->fd, &pos); + + /* Data is padded to the nearest WORD boundary */ + if (fport->pad) { + status = pj_file_setpos(fport->fd, fport->pad, PJ_SEEK_CUR); + fport->pad = 0; + } + + status = file_read(fport->fd, &ch, sizeof(pjmedia_avi_subchunk)); + if (status != PJ_SUCCESS) { + size_read = 0; + goto on_error2; + } + + cid = (char *)&ch.id; + if (cid[0] >= '0' && cid[0] <= '9' && + cid[1] >= '0' && cid[1] <= '9') + { + stream_id = (cid[0] - '0') * 10 + (cid[1] - '0'); + } else + stream_id = 100; + fport->pad = (pj_uint8_t)ch.len & 1; + + TRACE_((THIS_FILE, "Reading movi data at pos %u (%x), id: %.*s, " + "length: %u", (unsigned long)pos, + (unsigned long)pos, 4, cid, ch.len)); + + /* We are only interested in data with our stream id */ + if (stream_id != fport->stream_id) { + if (COMPARE_TAG(ch.id, PJMEDIA_AVI_LIST_TAG)) + PJ_LOG(5, (THIS_FILE, "Unsupported LIST tag found in " + "the movi data.")); + else if (COMPARE_TAG(ch.id, PJMEDIA_AVI_RIFF_TAG)) { + PJ_LOG(3, (THIS_FILE, "Unsupported format: multiple " + "AVIs in a single file.")); + status = AVI_EOF; + goto on_error2; + } + + status = pj_file_setpos(fport->fd, ch.len, + PJ_SEEK_CUR); + continue; + } + fport->size_left = ch.len; + } + + frame->type = (fport->base.info.fmt.type == PJMEDIA_TYPE_VIDEO ? + PJMEDIA_FRAME_TYPE_VIDEO : PJMEDIA_FRAME_TYPE_AUDIO); + frame->timestamp.u64 = 0; + if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO) { + if (size_to_read > fport->size_left) + size_to_read = fport->size_left; + status = file_read3(fport->fd, (char *)frame->buf + frame->size - + size_to_read, size_to_read, + fport->bits_per_sample, &size_read); + if (status != PJ_SUCCESS) + goto on_error2; + fport->size_left -= size_to_read; + } else { + pj_assert(frame->size >= ch.len); + status = file_read3(fport->fd, frame->buf, ch.len, + 0, &size_read); + if (status != PJ_SUCCESS) + goto on_error2; + frame->size = ch.len; + fport->size_left = 0; + } + + break; + + } while(1); + + return PJ_SUCCESS; + +on_error2: + if (status == AVI_EOF) { + size_to_read -= size_read; + pj_bzero((char *)frame->buf + frame->size - size_to_read, + size_to_read); + fport->eof = PJ_TRUE; + + return PJ_SUCCESS; + } + + return status; +} + +/* + * Destroy port. + */ +static pj_status_t avi_on_destroy(pjmedia_port *this_port) +{ + struct avi_reader_port *fport = (struct avi_reader_port*) this_port; + + pj_assert(this_port->info.signature == SIGNATURE); + + if (fport->fd != (pj_oshandle_t) -1) + pj_file_close(fport->fd); + return PJ_SUCCESS; +} diff --git a/pjmedia/src/pjmedia/bidirectional.c b/pjmedia/src/pjmedia/bidirectional.c index eb33a061..a106d2dd 100644 --- a/pjmedia/src/pjmedia/bidirectional.c +++ b/pjmedia/src/pjmedia/bidirectional.c @@ -22,7 +22,7 @@ #define THIS_FILE "bidirectional.c" -#define SIGNATURE PJMEDIA_PORT_SIGNATURE('B', 'D', 'I', 'R') +#define SIGNATURE PJMEDIA_SIG_PORT_BIDIR struct bidir_port { @@ -33,7 +33,7 @@ struct bidir_port static pj_status_t put_frame(pjmedia_port *this_port, - const pjmedia_frame *frame) + pjmedia_frame *frame) { struct bidir_port *p = (struct bidir_port*)this_port; return pjmedia_port_put_frame(p->put_port, frame); @@ -54,14 +54,16 @@ PJ_DEF(pj_status_t) pjmedia_bidirectional_port_create( pj_pool_t *pool, pjmedia_port **p_port ) { struct bidir_port *port; + const pjmedia_audio_format_detail *gafd; port = PJ_POOL_ZALLOC_T(pool, struct bidir_port); + gafd = pjmedia_format_get_audio_format_detail(&get_port->info.fmt, 1); pjmedia_port_info_init(&port->base.info, &get_port->info.name, SIGNATURE, - get_port->info.clock_rate, - get_port->info.channel_count, - get_port->info.bits_per_sample, - get_port->info.samples_per_frame); + gafd->clock_rate, + gafd->channel_count, + gafd->bits_per_sample, + PJMEDIA_AFD_SPF(gafd)); port->get_port = get_port; port->put_port = put_port; diff --git a/pjmedia/src/pjmedia/clock_thread.c b/pjmedia/src/pjmedia/clock_thread.c index 245db00c..5875450f 100644 --- a/pjmedia/src/pjmedia/clock_thread.c +++ b/pjmedia/src/pjmedia/clock_thread.c @@ -23,6 +23,84 @@ #include <pj/lock.h> #include <pj/os.h> #include <pj/pool.h> +#include <pj/string.h> +#include <pj/compat/high_precision.h> + +/* API: Init clock source */ +PJ_DEF(pj_status_t) pjmedia_clock_src_init( pjmedia_clock_src *clocksrc, + pjmedia_type media_type, + unsigned clock_rate, + unsigned ptime_usec ) +{ + PJ_ASSERT_RETURN(clocksrc, PJ_EINVAL); + + clocksrc->media_type = media_type; + clocksrc->clock_rate = clock_rate; + clocksrc->ptime_usec = ptime_usec; + pj_set_timestamp32(&clocksrc->timestamp, 0, 0); + pj_get_timestamp(&clocksrc->last_update); + + return PJ_SUCCESS; +} + +/* API: Update clock source */ +PJ_DECL(pj_status_t) pjmedia_clock_src_update( pjmedia_clock_src *clocksrc, + const pj_timestamp *timestamp ) +{ + PJ_ASSERT_RETURN(clocksrc, PJ_EINVAL); + + if (timestamp) + pj_memcpy(&clocksrc->timestamp, timestamp, sizeof(pj_timestamp)); + pj_get_timestamp(&clocksrc->last_update); + + return PJ_SUCCESS; +} + +/* API: Get clock source's current timestamp */ +PJ_DEF(pj_status_t) +pjmedia_clock_src_get_current_timestamp( const pjmedia_clock_src *clocksrc, + pj_timestamp *timestamp) +{ + pj_timestamp now; + unsigned elapsed_ms; + + PJ_ASSERT_RETURN(clocksrc && timestamp, PJ_EINVAL); + + pj_get_timestamp(&now); + elapsed_ms = pj_elapsed_msec(&clocksrc->last_update, &now); + pj_memcpy(timestamp, &clocksrc->timestamp, sizeof(pj_timestamp)); + pj_add_timestamp32(timestamp, elapsed_ms * clocksrc->clock_rate / 1000); + + return PJ_SUCCESS; +} + +/* API: Get clock source's time (in ms) */ +PJ_DEF(pj_uint32_t) +pjmedia_clock_src_get_time_msec( const pjmedia_clock_src *clocksrc ) +{ + pj_timestamp ts; + + pjmedia_clock_src_get_current_timestamp(clocksrc, &ts); + +#if PJ_HAS_INT64 + if (ts.u64 > 0x3FFFFFFFFFFFFFUL) + return (pj_uint32_t)(ts.u64 / clocksrc->clock_rate * 1000); + else + return (pj_uint32_t)(ts.u64 * 1000 / clocksrc->clock_rate); +#elif PJ_HAS_FLOATING_POINT + return (pj_uint32_t)((1.0 * ts.u32.hi * 0xFFFFFFFFUL + ts.u32.lo) + * 1000.0 / clocksrc->clock_rate); +#else + if (ts.u32.lo > 0x3FFFFFUL) + return (pj_uint32_t)(0xFFFFFFFFUL / clocksrc->clock_rate * ts.u32.hi + * 1000UL + ts.u32.lo / clocksrc->clock_rate * + 1000UL); + else + return (pj_uint32_t)(0xFFFFFFFFUL / clocksrc->clock_rate * ts.u32.hi + * 1000UL + ts.u32.lo * 1000UL / + clocksrc->clock_rate); +#endif +} /* @@ -50,6 +128,7 @@ struct pjmedia_clock static int clock_thread(void *arg); #define MAX_JUMP_MSEC 500 +#define USEC_IN_SEC (pj_uint64_t)1000000 /* * Create media clock. @@ -63,25 +142,42 @@ PJ_DEF(pj_status_t) pjmedia_clock_create( pj_pool_t *pool, void *user_data, pjmedia_clock **p_clock) { + pjmedia_clock_param param; + + param.usec_interval = (unsigned)(samples_per_frame * USEC_IN_SEC / + channel_count / clock_rate); + param.clock_rate = clock_rate; + return pjmedia_clock_create2(pool, ¶m, options, cb, + user_data, p_clock); +} + +PJ_DEF(pj_status_t) pjmedia_clock_create2(pj_pool_t *pool, + const pjmedia_clock_param *param, + unsigned options, + pjmedia_clock_callback *cb, + void *user_data, + pjmedia_clock **p_clock) +{ pjmedia_clock *clock; pj_status_t status; - PJ_ASSERT_RETURN(pool && clock_rate && samples_per_frame && p_clock, - PJ_EINVAL); + PJ_ASSERT_RETURN(pool && param->usec_interval && param->clock_rate && + p_clock, PJ_EINVAL); clock = PJ_POOL_ALLOC_T(pool, pjmedia_clock); - status = pj_get_timestamp_freq(&clock->freq); if (status != PJ_SUCCESS) return status; - clock->interval.u64 = samples_per_frame * clock->freq.u64 / - channel_count / clock_rate; + clock->interval.u64 = param->usec_interval * clock->freq.u64 / + USEC_IN_SEC; clock->next_tick.u64 = 0; clock->timestamp.u64 = 0; clock->max_jump = MAX_JUMP_MSEC * clock->freq.u64 / 1000; - clock->timestamp_inc = samples_per_frame / channel_count; + clock->timestamp_inc = (unsigned)(param->usec_interval * + param->clock_rate / + USEC_IN_SEC); clock->options = options; clock->cb = cb; clock->user_data = user_data; @@ -149,6 +245,22 @@ PJ_DEF(pj_status_t) pjmedia_clock_stop(pjmedia_clock *clock) } +/* + * Update the clock. + */ +PJ_DEF(pj_status_t) pjmedia_clock_modify(pjmedia_clock *clock, + const pjmedia_clock_param *param) +{ + clock->interval.u64 = param->usec_interval * clock->freq.u64 / + USEC_IN_SEC; + clock->timestamp_inc = (unsigned)(param->usec_interval * + param->clock_rate / + USEC_IN_SEC); + + return PJ_SUCCESS; +} + + /* Calculate next tick */ PJ_INLINE(void) clock_calc_next_tick(pjmedia_clock *clock, pj_timestamp *now) diff --git a/pjmedia/src/pjmedia/codec.c b/pjmedia/src/pjmedia/codec.c index 647b0b5d..111ad0c7 100644 --- a/pjmedia/src/pjmedia/codec.c +++ b/pjmedia/src/pjmedia/codec.c @@ -72,10 +72,19 @@ PJ_DEF(pj_status_t) pjmedia_codec_mgr_init (pjmedia_codec_mgr *mgr, */ PJ_DEF(pj_status_t) pjmedia_codec_mgr_destroy (pjmedia_codec_mgr *mgr) { + pjmedia_codec_factory *factory; unsigned i; PJ_ASSERT_RETURN(mgr, PJ_EINVAL); + /* Destroy all factories in the list */ + factory = mgr->factory_list.next; + while (factory != &mgr->factory_list) { + pjmedia_codec_factory *next = factory->next; + (*factory->op->destroy)(); + factory = next; + } + /* Cleanup all pools of all codec default params */ for (i=0; i<mgr->codec_cnt; ++i) { if (mgr->codec_desc[i].param) { @@ -111,6 +120,13 @@ PJ_DEF(pj_status_t) pjmedia_codec_mgr_register_factory( pjmedia_codec_mgr *mgr, PJ_ASSERT_RETURN(mgr && factory, PJ_EINVAL); + /* Since 2.0 we require codec factory to implement "destroy" op. Please + * see: https://trac.pjsip.org/repos/ticket/1294 + * + * Really! Please do see it. + */ + PJ_ASSERT_RETURN(factory->op->destroy != NULL, PJ_ENOTSUP); + /* Enum codecs */ count = PJ_ARRAY_SIZE(info); status = factory->op->enum_info(factory, &count, info); diff --git a/pjmedia/src/pjmedia/conf_switch.c b/pjmedia/src/pjmedia/conf_switch.c index d7df076e..4299a872 100644 --- a/pjmedia/src/pjmedia/conf_switch.c +++ b/pjmedia/src/pjmedia/conf_switch.c @@ -23,10 +23,10 @@ #include <pjmedia/port.h> #include <pjmedia/silencedet.h> #include <pjmedia/sound_port.h> -#include <pjmedia/stream.h> #include <pj/array.h> #include <pj/assert.h> #include <pj/log.h> +#include <pj/math.h> #include <pj/pool.h> #include <pj/string.h> @@ -80,6 +80,7 @@ struct conf_port /* Shortcut for port info. */ pjmedia_port_info *info; + unsigned samples_per_frame; /* Calculated signal levels: */ unsigned tx_level; /**< Last tx level to this port. */ @@ -123,7 +124,7 @@ struct pjmedia_conf /* Prototypes */ static pj_status_t put_frame(pjmedia_port *this_port, - const pjmedia_frame *frame); + pjmedia_frame *frame); static pj_status_t get_frame(pjmedia_port *this_port, pjmedia_frame *frame); static pj_status_t destroy_port(pjmedia_port *this_port); @@ -166,6 +167,7 @@ static pj_status_t create_conf_port( pj_pool_t *pool, /* Save some port's infos, for convenience. */ conf_port->port = port; conf_port->info = &port->info; + conf_port->samples_per_frame= PJMEDIA_PINFO_SAMPLES_PER_FRAME(&port->info); /* Init pjmedia_frame structure in the TX buffer. */ f = (pjmedia_frame*)conf_port->tx_buf; @@ -504,6 +506,7 @@ PJ_DEF(pj_status_t) pjmedia_conf_connect_port( pjmedia_conf *conf, { struct conf_port *src_port, *dst_port; pj_bool_t start_sound = PJ_FALSE; + pjmedia_audio_format_detail *src_afd, *dst_afd; unsigned i; /* Check arguments */ @@ -523,31 +526,32 @@ PJ_DEF(pj_status_t) pjmedia_conf_connect_port( pjmedia_conf *conf, return PJ_EINVAL; } + src_afd = pjmedia_format_get_audio_format_detail(&src_port->info->fmt, 1); + dst_afd = pjmedia_format_get_audio_format_detail(&dst_port->info->fmt, 1); + /* Format must match. */ - if (src_port->info->format.id != dst_port->info->format.id || - src_port->info->format.bitrate != dst_port->info->format.bitrate) + if (src_port->info->fmt.id != dst_port->info->fmt.id || + src_afd->avg_bps != dst_afd->avg_bps) { pj_mutex_unlock(conf->mutex); return PJMEDIA_ENOTCOMPATIBLE; } /* Clock rate must match. */ - if (src_port->info->clock_rate != dst_port->info->clock_rate) { + if (src_afd->clock_rate != dst_afd->clock_rate) { pj_mutex_unlock(conf->mutex); return PJMEDIA_ENCCLOCKRATE; } /* Channel count must match. */ - if (src_port->info->channel_count != dst_port->info->channel_count) { + if (src_afd->channel_count != dst_afd->channel_count) { pj_mutex_unlock(conf->mutex); return PJMEDIA_ENCCLOCKRATE; } /* Source and sink ptime must be equal or a multiplication factor. */ - if ((src_port->info->samples_per_frame % - dst_port->info->samples_per_frame != 0) && - (dst_port->info->samples_per_frame % - src_port->info->samples_per_frame != 0)) + if ((src_afd->frame_time_usec % dst_afd->frame_time_usec != 0) && + (dst_afd->frame_time_usec % src_afd->frame_time_usec != 0)) { pj_mutex_unlock(conf->mutex); return PJMEDIA_ENCSAMPLESPFRAME; @@ -829,6 +833,7 @@ PJ_DEF(pj_status_t) pjmedia_conf_get_port_info( pjmedia_conf *conf, pjmedia_conf_port_info *info) { struct conf_port *conf_port; + const pjmedia_audio_format_detail *afd; /* Check arguments */ PJ_ASSERT_RETURN(conf && slot<conf->max_ports, PJ_EINVAL); @@ -843,6 +848,8 @@ PJ_DEF(pj_status_t) pjmedia_conf_get_port_info( pjmedia_conf *conf, return PJ_EINVAL; } + afd = pjmedia_format_get_audio_format_detail(&conf_port->info->fmt, 1); + pj_bzero(info, sizeof(pjmedia_conf_port_info)); info->slot = slot; @@ -852,11 +859,11 @@ PJ_DEF(pj_status_t) pjmedia_conf_get_port_info( pjmedia_conf *conf, info->listener_cnt = conf_port->listener_cnt; info->listener_slots = conf_port->listener_slots; info->transmitter_cnt = conf_port->transmitter_cnt; - info->clock_rate = conf_port->info->clock_rate; - info->channel_count = conf_port->info->channel_count; - info->samples_per_frame = conf_port->info->samples_per_frame; - info->bits_per_sample = conf_port->info->bits_per_sample; - info->format = conf_port->port->info.format; + info->clock_rate = afd->clock_rate; + info->channel_count = afd->channel_count; + info->samples_per_frame = conf_port->samples_per_frame; + info->bits_per_sample = afd->bits_per_sample; + info->format = conf_port->port->info.fmt; info->tx_adj_level = conf_port->tx_adj_level - NORMAL_LEVEL; info->rx_adj_level = conf_port->rx_adj_level - NORMAL_LEVEL; @@ -960,7 +967,7 @@ PJ_DEF(pj_status_t) pjmedia_conf_adjust_rx_level( pjmedia_conf *conf, } /* Level adjustment is applicable only for ports that work with raw PCM. */ - PJ_ASSERT_RETURN(conf_port->info->format.id == PJMEDIA_FORMAT_L16, + PJ_ASSERT_RETURN(conf_port->info->fmt.id == PJMEDIA_FORMAT_L16, PJ_EIGNORED); /* Set normalized adjustment level. */ @@ -1002,7 +1009,7 @@ PJ_DEF(pj_status_t) pjmedia_conf_adjust_tx_level( pjmedia_conf *conf, } /* Level adjustment is applicable only for ports that work with raw PCM. */ - PJ_ASSERT_RETURN(conf_port->info->format.id == PJMEDIA_FORMAT_L16, + PJ_ASSERT_RETURN(conf_port->info->fmt.id == PJMEDIA_FORMAT_L16, PJ_EIGNORED); /* Set normalized adjustment level. */ @@ -1046,7 +1053,7 @@ static pj_status_t write_frame(struct conf_port *cport_dst, * i.e: samples count in TX buffer equal to listener's * samples per frame. */ - if (f_dst->samples_cnt >= cport_dst->info->samples_per_frame) + if (f_dst->samples_cnt >= cport_dst->samples_per_frame) { if (cport_dst->slot) { pjmedia_port_put_frame(cport_dst->port, @@ -1058,8 +1065,8 @@ static pj_status_t write_frame(struct conf_port *cport_dst, } /* Update TX timestamp. */ - pj_add_timestamp32(&cport_dst->ts_tx, - cport_dst->info->samples_per_frame); + pj_add_timestamp32(&cport_dst->ts_tx, + cport_dst->samples_per_frame); } } @@ -1075,7 +1082,7 @@ static pj_status_t write_frame(struct conf_port *cport_dst, /* Copy frame to listener's TX buffer. */ nsamples_to_copy = f_end - f_start; - nsamples_req = cport_dst->info->samples_per_frame - + nsamples_req = cport_dst->samples_per_frame - (frm_dst->size>>1); if (nsamples_to_copy > nsamples_req) nsamples_to_copy = nsamples_req; @@ -1112,7 +1119,7 @@ static pj_status_t write_frame(struct conf_port *cport_dst, * i.e: samples count in TX buffer equal to listener's * samples per frame. */ - if ((frm_dst->size >> 1) == cport_dst->info->samples_per_frame) + if ((frm_dst->size >> 1) == cport_dst->samples_per_frame) { if (cport_dst->slot) { pjmedia_port_put_frame(cport_dst->port, frm_dst); @@ -1123,7 +1130,7 @@ static pj_status_t write_frame(struct conf_port *cport_dst, /* Update TX timestamp. */ pj_add_timestamp32(&cport_dst->ts_tx, - cport_dst->info->samples_per_frame); + cport_dst->samples_per_frame); } } @@ -1131,18 +1138,18 @@ static pj_status_t write_frame(struct conf_port *cport_dst, /* Check port format. */ if (cport_dst->port && - cport_dst->port->info.format.id == PJMEDIA_FORMAT_L16) + cport_dst->port->info.fmt.id == PJMEDIA_FORMAT_L16) { /* When there is already some samples in listener's TX buffer, * pad the buffer with "zero samples". */ if (frm_dst->size != 0) { pjmedia_zero_samples((pj_int16_t*)frm_dst->buf, - cport_dst->info->samples_per_frame - + cport_dst->samples_per_frame - (frm_dst->size>>1)); frm_dst->type = PJMEDIA_FRAME_TYPE_AUDIO; - frm_dst->size = cport_dst->info->samples_per_frame << 1; + frm_dst->size = cport_dst->samples_per_frame << 1; if (cport_dst->slot) { pjmedia_port_put_frame(cport_dst->port, frm_dst); @@ -1152,7 +1159,7 @@ static pj_status_t write_frame(struct conf_port *cport_dst, /* Update TX timestamp. */ pj_add_timestamp32(&cport_dst->ts_tx, - cport_dst->info->samples_per_frame); + cport_dst->samples_per_frame); } } else { pjmedia_frame_ext *f_dst = (pjmedia_frame_ext*)frm_dst; @@ -1160,7 +1167,7 @@ static pj_status_t write_frame(struct conf_port *cport_dst, if (f_dst->samples_cnt != 0) { frm_dst->type = PJMEDIA_FRAME_TYPE_EXTENDED; pjmedia_frame_ext_append_subframe(f_dst, NULL, 0, (pj_uint16_t) - (cport_dst->info->samples_per_frame - f_dst->samples_cnt)); + (cport_dst->samples_per_frame - f_dst->samples_cnt)); if (cport_dst->slot) { pjmedia_port_put_frame(cport_dst->port, frm_dst); @@ -1171,7 +1178,7 @@ static pj_status_t write_frame(struct conf_port *cport_dst, /* Update TX timestamp. */ pj_add_timestamp32(&cport_dst->ts_tx, - cport_dst->info->samples_per_frame); + cport_dst->samples_per_frame); } } @@ -1185,7 +1192,7 @@ static pj_status_t write_frame(struct conf_port *cport_dst, pjmedia_port_put_frame(cport_dst->port, frm_dst); /* Update TX timestamp. */ - pj_add_timestamp32(&cport_dst->ts_tx, cport_dst->info->samples_per_frame); + pj_add_timestamp32(&cport_dst->ts_tx, cport_dst->samples_per_frame); } } @@ -1211,6 +1218,7 @@ static pj_status_t get_frame(pjmedia_port *this_port, */ for (i=1, ci=1; i<conf->max_ports && ci<conf->port_cnt; ++i) { struct conf_port *cport = conf->ports[i]; + unsigned master_samples_per_frame; /* Skip empty port. */ if (!cport) @@ -1219,9 +1227,11 @@ static pj_status_t get_frame(pjmedia_port *this_port, /* Var "ci" is to count how many ports have been visited so far. */ ++ci; + master_samples_per_frame = PJMEDIA_PINFO_SAMPLES_PER_FRAME( + &conf->master_port->info); + /* Update clock of the port. */ - pj_add_timestamp32(&cport->ts_clock, - conf->master_port->info.samples_per_frame); + pj_add_timestamp32(&cport->ts_clock, master_samples_per_frame); /* Skip if we're not allowed to receive from this port or * the port doesn't have listeners. @@ -1230,8 +1240,7 @@ static pj_status_t get_frame(pjmedia_port *this_port, cport->listener_cnt == 0) { cport->rx_level = 0; - pj_add_timestamp32(&cport->ts_rx, - conf->master_port->info.samples_per_frame); + pj_add_timestamp32(&cport->ts_rx, master_samples_per_frame); continue; } @@ -1245,10 +1254,10 @@ static pj_status_t get_frame(pjmedia_port *this_port, unsigned j; pj_int32_t level = 0; - pj_add_timestamp32(&cport->ts_rx, cport->info->samples_per_frame); + pj_add_timestamp32(&cport->ts_rx, cport->samples_per_frame); f->buf = &conf->buf[sizeof(pjmedia_frame)]; - f->size = cport->info->samples_per_frame<<1; + f->size = cport->samples_per_frame<<1; /* Get frame from port. */ status = pjmedia_port_get_frame(cport->port, f); @@ -1302,7 +1311,7 @@ static pj_status_t get_frame(pjmedia_port *this_port, /* Skip if this listener doesn't want to receive audio */ if (listener->tx_setting == PJMEDIA_PORT_DISABLE) { pj_add_timestamp32(&listener->ts_tx, - listener->info->samples_per_frame); + listener->samples_per_frame); listener->tx_level = 0; continue; } @@ -1361,7 +1370,7 @@ static pj_status_t get_frame(pjmedia_port *this_port, tmp_f.size = 0; pjmedia_port_put_frame(cport->port, &tmp_f); - pj_add_timestamp32(&cport->ts_tx, cport->info->samples_per_frame); + pj_add_timestamp32(&cport->ts_tx, cport->samples_per_frame); } } } @@ -1380,7 +1389,7 @@ static pj_status_t get_frame(pjmedia_port *this_port, pjmedia_frame_ext_subframe *sf; unsigned samples_per_subframe; - if (f_src_->samples_cnt < this_cport->info->samples_per_frame) { + if (f_src_->samples_cnt < this_cport->samples_per_frame) { f_dst->base.type = PJMEDIA_FRAME_TYPE_NONE; f_dst->samples_cnt = 0; f_dst->subframe_cnt = 0; @@ -1393,7 +1402,7 @@ static pj_status_t get_frame(pjmedia_port *this_port, samples_per_subframe = f_src_->samples_cnt / f_src_->subframe_cnt; - while (f_dst->samples_cnt < this_cport->info->samples_per_frame) { + while (f_dst->samples_cnt < this_cport->samples_per_frame) { sf = pjmedia_frame_ext_get_subframe(f_src_, i++); pj_assert(sf); pjmedia_frame_ext_append_subframe(f_dst, sf->data, sf->bitlen, @@ -1404,7 +1413,7 @@ static pj_status_t get_frame(pjmedia_port *this_port, pjmedia_frame_ext_pop_subframes(f_src_, i); } else if (f_src->type == PJMEDIA_FRAME_TYPE_AUDIO) { - if ((f_src->size>>1) < this_cport->info->samples_per_frame) { + if ((f_src->size>>1) < this_cport->samples_per_frame) { frame->type = PJMEDIA_FRAME_TYPE_NONE; frame->size = 0; break; @@ -1412,15 +1421,15 @@ static pj_status_t get_frame(pjmedia_port *this_port, pjmedia_copy_samples((pj_int16_t*)frame->buf, (pj_int16_t*)f_src->buf, - this_cport->info->samples_per_frame); - frame->size = this_cport->info->samples_per_frame << 1; + this_cport->samples_per_frame); + frame->size = this_cport->samples_per_frame << 1; /* Shift left TX buffer. */ f_src->size -= frame->size; if (f_src->size) pjmedia_move_samples((pj_int16_t*)f_src->buf, (pj_int16_t*)f_src->buf + - this_cport->info->samples_per_frame, + this_cport->samples_per_frame, f_src->size >> 1); } else { /* PJMEDIA_FRAME_TYPE_NONE */ pjmedia_frame_ext *f_src_ = (pjmedia_frame_ext*)f_src; @@ -1442,7 +1451,7 @@ static pj_status_t get_frame(pjmedia_port *this_port, * Recorder callback. */ static pj_status_t put_frame(pjmedia_port *this_port, - const pjmedia_frame *f) + pjmedia_frame *f) { pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata; struct conf_port *cport; @@ -1460,7 +1469,7 @@ static pj_status_t put_frame(pjmedia_port *this_port, return PJ_SUCCESS; } - pj_add_timestamp32(&cport->ts_rx, cport->info->samples_per_frame); + pj_add_timestamp32(&cport->ts_rx, cport->samples_per_frame); /* Skip if this port is muted/disabled. */ if (cport->rx_setting == PJMEDIA_PORT_DISABLE) { @@ -1526,7 +1535,7 @@ static pj_status_t put_frame(pjmedia_port *this_port, /* Skip if this listener doesn't want to receive audio */ if (listener->tx_setting == PJMEDIA_PORT_DISABLE) { pj_add_timestamp32(&listener->ts_tx, - listener->info->samples_per_frame); + listener->samples_per_frame); listener->tx_level = 0; continue; } @@ -1534,7 +1543,7 @@ static pj_status_t put_frame(pjmedia_port *this_port, /* Skip loopback for now. */ if (listener == cport) { pj_add_timestamp32(&listener->ts_tx, - listener->info->samples_per_frame); + listener->samples_per_frame); listener->tx_level = 0; continue; } diff --git a/pjmedia/src/pjmedia/conference.c b/pjmedia/src/pjmedia/conference.c index 46dd4597..905ae588 100644 --- a/pjmedia/src/pjmedia/conference.c +++ b/pjmedia/src/pjmedia/conference.c @@ -26,7 +26,6 @@ #include <pjmedia/silencedet.h> #include <pjmedia/sound_port.h> #include <pjmedia/stereo.h> -#include <pjmedia/stream.h> #include <pj/array.h> #include <pj/assert.h> #include <pj/log.h> @@ -65,7 +64,7 @@ static FILE *fhnd_rec; #define BYTES_PER_SAMPLE 2 #define SIGNATURE PJMEDIA_CONF_BRIDGE_SIGNATURE -#define SIGNATURE_PORT PJMEDIA_PORT_SIGNATURE('C', 'O', 'N', 'P') +#define SIGNATURE_PORT PJMEDIA_SIG_PORT_CONF_PASV /* Normal level is hardcodec to 128 in all over places */ #define NORMAL_LEVEL 128 #define SLOT_TYPE unsigned @@ -241,7 +240,7 @@ struct pjmedia_conf /* Prototypes */ static pj_status_t put_frame(pjmedia_port *this_port, - const pjmedia_frame *frame); + pjmedia_frame *frame); static pj_status_t get_frame(pjmedia_port *this_port, pjmedia_frame *frame); static pj_status_t get_frame_pasv(pjmedia_port *this_port, @@ -285,10 +284,13 @@ static pj_status_t create_conf_port( pj_pool_t *pool, /* Save some port's infos, for convenience. */ if (port) { + pjmedia_audio_format_detail *afd; + + afd = pjmedia_format_get_audio_format_detail(&port->info.fmt, 1); conf_port->port = port; - conf_port->clock_rate = port->info.clock_rate; - conf_port->samples_per_frame = port->info.samples_per_frame; - conf_port->channel_count = port->info.channel_count; + conf_port->clock_rate = afd->clock_rate; + conf_port->samples_per_frame = PJMEDIA_AFD_SPF(afd); + conf_port->channel_count = afd->channel_count; } else { conf_port->port = NULL; conf_port->clock_rate = conf->clock_rate; @@ -750,8 +752,9 @@ PJ_DEF(pj_status_t) pjmedia_conf_add_port( pjmedia_conf *conf, * - same between port & conference bridge. * - monochannel on port or conference bridge. */ - if (strm_port->info.channel_count != conf->channel_count && - (strm_port->info.channel_count != 1 && conf->channel_count != 1)) + if (PJMEDIA_PIA_CCNT(&strm_port->info) != conf->channel_count && + (PJMEDIA_PIA_CCNT(&strm_port->info) != 1 && + conf->channel_count != 1)) { pj_assert(!"Number of channels mismatch"); return PJMEDIA_ENCCHANNEL; @@ -2050,7 +2053,7 @@ static pj_status_t get_frame_pasv(pjmedia_port *this_port, * Recorder (or passive port) callback. */ static pj_status_t put_frame(pjmedia_port *this_port, - const pjmedia_frame *frame) + pjmedia_frame *frame) { pjmedia_conf *conf = (pjmedia_conf*) this_port->port_data.pdata; struct conf_port *port = conf->ports[this_port->port_data.ldata]; diff --git a/pjmedia/src/pjmedia/converter.c b/pjmedia/src/pjmedia/converter.c new file mode 100644 index 00000000..14496fa0 --- /dev/null +++ b/pjmedia/src/pjmedia/converter.c @@ -0,0 +1,178 @@ +/* $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/converter.h> +#include <pj/assert.h> +#include <pj/errno.h> + +#define THIS_FILE "converter.c" + +struct pjmedia_converter_mgr +{ + pjmedia_converter_factory factory_list; +}; + +static pjmedia_converter_mgr *converter_manager_instance; + +#if PJMEDIA_HAS_LIBSWSCALE && PJMEDIA_HAS_LIBAVUTIL +PJ_DECL(pj_status_t) +pjmedia_libswscale_converter_init(pjmedia_converter_mgr *mgr); +#endif + + +PJ_DEF(pj_status_t) pjmedia_converter_mgr_create(pj_pool_t *pool, + pjmedia_converter_mgr **p_mgr) +{ + pjmedia_converter_mgr *mgr; + pj_status_t status; + + mgr = PJ_POOL_ALLOC_T(pool, pjmedia_converter_mgr); + pj_list_init(&mgr->factory_list); + + if (!converter_manager_instance) + converter_manager_instance = mgr; + +#if PJMEDIA_HAS_LIBSWSCALE && PJMEDIA_HAS_LIBAVUTIL + status = pjmedia_libswscale_converter_init(mgr); + if (status != PJ_SUCCESS) { + PJ_PERROR(4,(THIS_FILE, status, + "Error initializing libswscale converter")); + } +#endif + + if (p_mgr) + *p_mgr = mgr; + + return PJ_SUCCESS; +} + +PJ_DEF(pjmedia_converter_mgr*) pjmedia_converter_mgr_instance(void) +{ + pj_assert(converter_manager_instance != NULL); + return converter_manager_instance; +} + +PJ_DEF(void) pjmedia_converter_mgr_set_instance(pjmedia_converter_mgr *mgr) +{ + converter_manager_instance = mgr; +} + +PJ_DEF(void) pjmedia_converter_mgr_destroy(pjmedia_converter_mgr *mgr) +{ + pjmedia_converter_factory *f; + + if (!mgr) mgr = pjmedia_converter_mgr_instance(); + + PJ_ASSERT_ON_FAIL(mgr != NULL, return); + + f = mgr->factory_list.next; + while (f != &mgr->factory_list) { + pjmedia_converter_factory *next = f->next; + pj_list_erase(f); + (*f->op->destroy_factory)(f); + f = next; + } + + if (converter_manager_instance == mgr) + converter_manager_instance = NULL; +} + +PJ_DEF(pj_status_t) +pjmedia_converter_mgr_register_factory(pjmedia_converter_mgr *mgr, + pjmedia_converter_factory *factory) +{ + pjmedia_converter_factory *pf; + + if (!mgr) mgr = pjmedia_converter_mgr_instance(); + + PJ_ASSERT_RETURN(mgr != NULL, PJ_EINVAL); + + PJ_ASSERT_RETURN(!pj_list_find_node(&mgr->factory_list, factory), + PJ_EEXISTS); + + pf = mgr->factory_list.next; + while (pf != &mgr->factory_list) { + if (pf->priority > factory->priority) + break; + pf = pf->next; + } + pj_list_insert_before(pf, factory); + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) +pjmedia_converter_mgr_unregister_factory(pjmedia_converter_mgr *mgr, + pjmedia_converter_factory *f, + pj_bool_t destroy) +{ + if (!mgr) mgr = pjmedia_converter_mgr_instance(); + + PJ_ASSERT_RETURN(mgr != NULL, PJ_EINVAL); + + PJ_ASSERT_RETURN(pj_list_find_node(&mgr->factory_list, f), PJ_ENOTFOUND); + pj_list_erase(f); + if (destroy) + (*f->op->destroy_factory)(f); + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjmedia_converter_create(pjmedia_converter_mgr *mgr, + pj_pool_t *pool, + pjmedia_conversion_param *param, + pjmedia_converter **p_cv) +{ + pjmedia_converter_factory *f; + pjmedia_converter *cv = NULL; + pj_status_t status = PJ_ENOTFOUND; + + if (!mgr) mgr = pjmedia_converter_mgr_instance(); + + PJ_ASSERT_RETURN(mgr != NULL, PJ_EINVAL); + + *p_cv = NULL; + + f = mgr->factory_list.next; + while (f != &mgr->factory_list) { + status = (*f->op->create_converter)(f, pool, param, &cv); + if (status == PJ_SUCCESS) + break; + f = f->next; + } + + if (status != PJ_SUCCESS) + return status; + + *p_cv = cv; + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjmedia_converter_convert(pjmedia_converter *cv, + pjmedia_frame *src_frame, + pjmedia_frame *dst_frame) +{ + return (*cv->op->convert)(cv, src_frame, dst_frame); +} + +PJ_DEF(void) pjmedia_converter_destroy(pjmedia_converter *cv) +{ + (*cv->op->destroy)(cv); +} + + diff --git a/pjmedia/src/pjmedia/converter_libswscale.c b/pjmedia/src/pjmedia/converter_libswscale.c new file mode 100644 index 00000000..b9e74a22 --- /dev/null +++ b/pjmedia/src/pjmedia/converter_libswscale.c @@ -0,0 +1,200 @@ +/* $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/converter.h> +#include <pj/errno.h> + +#if PJMEDIA_HAS_LIBSWSCALE && PJMEDIA_HAS_LIBAVUTIL + +#include "ffmpeg_util.h" +#include <libswscale/swscale.h> + +static pj_status_t factory_create_converter(pjmedia_converter_factory *cf, + pj_pool_t *pool, + const pjmedia_conversion_param*prm, + pjmedia_converter **p_cv); +static void factory_destroy_factory(pjmedia_converter_factory *cf); +static pj_status_t libswscale_conv_convert(pjmedia_converter *converter, + pjmedia_frame *src_frame, + pjmedia_frame *dst_frame); +static void libswscale_conv_destroy(pjmedia_converter *converter); + + +struct fmt_info +{ + const pjmedia_video_format_info *fmt_info; + pjmedia_video_apply_fmt_param apply_param; +}; + +struct ffmpeg_converter +{ + pjmedia_converter base; + struct SwsContext *sws_ctx; + struct fmt_info src, + dst; +}; + +static pjmedia_converter_factory_op libswscale_factory_op = +{ + &factory_create_converter, + &factory_destroy_factory +}; + +static pjmedia_converter_op liswscale_converter_op = +{ + &libswscale_conv_convert, + &libswscale_conv_destroy +}; + +static pj_status_t factory_create_converter(pjmedia_converter_factory *cf, + pj_pool_t *pool, + const pjmedia_conversion_param *prm, + pjmedia_converter **p_cv) +{ + enum PixelFormat srcFormat, dstFormat; + const pjmedia_video_format_detail *src_detail, *dst_detail; + const pjmedia_video_format_info *src_fmt_info, *dst_fmt_info; + struct SwsContext *sws_ctx; + struct ffmpeg_converter *fcv; + pj_status_t status; + + PJ_UNUSED_ARG(cf); + + /* Only supports video */ + if (prm->src.type != PJMEDIA_TYPE_VIDEO || + prm->dst.type != prm->src.type || + prm->src.detail_type != PJMEDIA_FORMAT_DETAIL_VIDEO || + prm->dst.detail_type != prm->src.detail_type) + { + return PJ_ENOTSUP; + } + + /* lookup source format info */ + src_fmt_info = pjmedia_get_video_format_info( + pjmedia_video_format_mgr_instance(), + prm->src.id); + if (!src_fmt_info) + return PJ_ENOTSUP; + + /* lookup destination format info */ + dst_fmt_info = pjmedia_get_video_format_info( + pjmedia_video_format_mgr_instance(), + prm->dst.id); + if (!dst_fmt_info) + return PJ_ENOTSUP; + + src_detail = pjmedia_format_get_video_format_detail(&prm->src, PJ_TRUE); + dst_detail = pjmedia_format_get_video_format_detail(&prm->dst, PJ_TRUE); + + status = pjmedia_format_id_to_PixelFormat(prm->src.id, &srcFormat); + if (status != PJ_SUCCESS) + return PJ_ENOTSUP; + + status = pjmedia_format_id_to_PixelFormat(prm->dst.id, &dstFormat); + if (status != PJ_SUCCESS) + return PJ_ENOTSUP; + + sws_ctx = sws_getContext(src_detail->size.w, src_detail->size.h, srcFormat, + dst_detail->size.w, dst_detail->size.h, dstFormat, + SWS_BICUBIC, + NULL, NULL, NULL); + if (sws_ctx == NULL) + return PJ_ENOTSUP; + + fcv = PJ_POOL_ZALLOC_T(pool, struct ffmpeg_converter); + fcv->base.op = &liswscale_converter_op; + fcv->sws_ctx = sws_ctx; + fcv->src.apply_param.size = src_detail->size; + fcv->src.fmt_info = src_fmt_info; + fcv->dst.apply_param.size = dst_detail->size; + fcv->dst.fmt_info = dst_fmt_info; + + *p_cv = &fcv->base; + + return PJ_SUCCESS; +} + +static void factory_destroy_factory(pjmedia_converter_factory *cf) +{ + PJ_UNUSED_ARG(cf); +} + +static pj_status_t libswscale_conv_convert(pjmedia_converter *converter, + pjmedia_frame *src_frame, + pjmedia_frame *dst_frame) +{ + struct ffmpeg_converter *fcv = (struct ffmpeg_converter*)converter; + struct fmt_info *src = &fcv->src, + *dst = &fcv->dst; + int h; + + src->apply_param.buffer = src_frame->buf; + (*src->fmt_info->apply_fmt)(src->fmt_info, &src->apply_param); + + dst->apply_param.buffer = dst_frame->buf; + (*dst->fmt_info->apply_fmt)(dst->fmt_info, &dst->apply_param); + + h = sws_scale(fcv->sws_ctx, + src->apply_param.planes, src->apply_param.strides, + 0, src->apply_param.size.h, + dst->apply_param.planes, dst->apply_param.strides); + + return h==(int)dst->apply_param.size.h ? PJ_SUCCESS : PJ_EUNKNOWN; +} + +static void libswscale_conv_destroy(pjmedia_converter *converter) +{ + struct ffmpeg_converter *fcv = (struct ffmpeg_converter*)converter; + if (fcv->sws_ctx) { + struct SwsContext *tmp = fcv->sws_ctx; + fcv->sws_ctx = NULL; + sws_freeContext(tmp); + } +} + +static pjmedia_converter_factory libswscale_factory = +{ + NULL, NULL, /* list */ + "libswscale", /* name */ + PJMEDIA_CONVERTER_PRIORITY_NORMAL+1, /* priority */ + NULL /* op will be init-ed later */ +}; + +PJ_DEF(pj_status_t) +pjmedia_libswscale_converter_init(pjmedia_converter_mgr *mgr) +{ + libswscale_factory.op = &libswscale_factory_op; + return pjmedia_converter_mgr_register_factory(mgr, &libswscale_factory); +} + + +PJ_DEF(pj_status_t) +pjmedia_libswscale_converter_shutdown(pjmedia_converter_mgr *mgr, + pj_pool_t *pool) +{ + PJ_UNUSED_ARG(pool); + return pjmedia_converter_mgr_unregister_factory(mgr, &libswscale_factory, + PJ_TRUE); +} + +#ifdef _MSC_VER +# pragma comment( lib, "avutil.lib") +# pragma comment( lib, "swscale.lib") +#endif + +#endif /* #if PJMEDIA_HAS_LIBSWSCALE && PJMEDIA_HAS_LIBAVUTIL */ diff --git a/pjmedia/src/pjmedia/delaybuf.c b/pjmedia/src/pjmedia/delaybuf.c index 39f0e618..a54ec5de 100644 --- a/pjmedia/src/pjmedia/delaybuf.c +++ b/pjmedia/src/pjmedia/delaybuf.c @@ -21,6 +21,7 @@ #include <pjmedia/delaybuf.h> #include <pjmedia/circbuf.h> #include <pjmedia/errno.h> +#include <pjmedia/frame.h> #include <pjmedia/wsola.h> #include <pj/assert.h> #include <pj/lock.h> diff --git a/pjmedia/src/pjmedia/dummy.c b/pjmedia/src/pjmedia/dummy.c new file mode 100644 index 00000000..805de1cf --- /dev/null +++ b/pjmedia/src/pjmedia/dummy.c @@ -0,0 +1,24 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2010 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <pjmedia/frame.h> +#include <pjmedia/port.h> +#include <pjmedia/types.h> + diff --git a/pjmedia/src/pjmedia/echo_common.c b/pjmedia/src/pjmedia/echo_common.c index 1cb2e311..7316dfe8 100644 --- a/pjmedia/src/pjmedia/echo_common.c +++ b/pjmedia/src/pjmedia/echo_common.c @@ -20,6 +20,7 @@ #include <pjmedia/echo.h> #include <pjmedia/delaybuf.h> +#include <pjmedia/frame.h> #include <pjmedia/errno.h> #include <pj/assert.h> #include <pj/list.h> diff --git a/pjmedia/src/pjmedia/echo_port.c b/pjmedia/src/pjmedia/echo_port.c index c3f689ed..4c098cc4 100644 --- a/pjmedia/src/pjmedia/echo_port.c +++ b/pjmedia/src/pjmedia/echo_port.c @@ -26,7 +26,7 @@ #define THIS_FILE "ec_port.c" -#define SIGNATURE PJMEDIA_PORT_SIGNATURE('E', 'C', 'H', 'O') +#define SIGNATURE PJMEDIA_SIG_PORT_ECHO #define BUF_COUNT 32 struct ec @@ -38,7 +38,7 @@ struct ec static pj_status_t ec_put_frame(pjmedia_port *this_port, - const pjmedia_frame *frame); + pjmedia_frame *frame); static pj_status_t ec_get_frame(pjmedia_port *this_port, pjmedia_frame *frame); static pj_status_t ec_on_destroy(pjmedia_port *this_port); @@ -52,25 +52,29 @@ PJ_DEF(pj_status_t) pjmedia_echo_port_create(pj_pool_t *pool, pjmedia_port **p_port ) { const pj_str_t AEC = { "EC", 2 }; + pjmedia_audio_format_detail *afd; struct ec *ec; pj_status_t status; PJ_ASSERT_RETURN(pool && dn_port && p_port, PJ_EINVAL); - PJ_ASSERT_RETURN(dn_port->info.bits_per_sample==16 && tail_ms, + + afd = pjmedia_format_get_audio_format_detail(&dn_port->info.fmt, PJ_TRUE); + + PJ_ASSERT_RETURN(afd->bits_per_sample==16 && tail_ms, PJ_EINVAL); /* Create the port and the AEC itself */ ec = PJ_POOL_ZALLOC_T(pool, struct ec); pjmedia_port_info_init(&ec->base.info, &AEC, SIGNATURE, - dn_port->info.clock_rate, - dn_port->info.channel_count, - dn_port->info.bits_per_sample, - dn_port->info.samples_per_frame); - - status = pjmedia_echo_create2(pool, dn_port->info.clock_rate, - dn_port->info.channel_count, - dn_port->info.samples_per_frame, + afd->clock_rate, + afd->channel_count, + afd->bits_per_sample, + PJMEDIA_AFD_SPF(afd)); + + status = pjmedia_echo_create2(pool, afd->clock_rate, + afd->channel_count, + PJMEDIA_AFD_SPF(afd), tail_ms, latency_ms, options, &ec->ec); if (status != PJ_SUCCESS) return status; @@ -89,7 +93,7 @@ PJ_DEF(pj_status_t) pjmedia_echo_port_create(pj_pool_t *pool, static pj_status_t ec_put_frame( pjmedia_port *this_port, - const pjmedia_frame *frame) + pjmedia_frame *frame) { struct ec *ec = (struct ec*)this_port; @@ -99,7 +103,7 @@ static pj_status_t ec_put_frame( pjmedia_port *this_port, return pjmedia_port_put_frame(ec->dn_port, frame); } - PJ_ASSERT_RETURN(frame->size == this_port->info.samples_per_frame * 2, + PJ_ASSERT_RETURN(frame->size == PJMEDIA_PIA_AVG_FSZ(&this_port->info), PJ_EINVAL); pjmedia_echo_capture(ec->ec, (pj_int16_t*)frame->buf, 0); @@ -119,7 +123,7 @@ static pj_status_t ec_get_frame( pjmedia_port *this_port, status = pjmedia_port_get_frame(ec->dn_port, frame); if (status!=PJ_SUCCESS || frame->type!=PJMEDIA_FRAME_TYPE_AUDIO) { pjmedia_zero_samples((pj_int16_t*)frame->buf, - this_port->info.samples_per_frame); + PJMEDIA_PIA_SPF(&this_port->info)); } pjmedia_echo_playback(ec->ec, (pj_int16_t*)frame->buf); diff --git a/pjmedia/src/pjmedia/echo_speex.c b/pjmedia/src/pjmedia/echo_speex.c index e666e2db..19428597 100644 --- a/pjmedia/src/pjmedia/echo_speex.c +++ b/pjmedia/src/pjmedia/echo_speex.c @@ -20,6 +20,7 @@ #include <pjmedia/echo.h> #include <pjmedia/errno.h> +#include <pjmedia/frame.h> #include <pj/assert.h> #include <pj/log.h> #include <pj/pool.h> diff --git a/pjmedia/src/pjmedia/echo_suppress.c b/pjmedia/src/pjmedia/echo_suppress.c index a0333cb3..85a6efb4 100644 --- a/pjmedia/src/pjmedia/echo_suppress.c +++ b/pjmedia/src/pjmedia/echo_suppress.c @@ -20,6 +20,7 @@ #include <pjmedia/types.h> #include <pjmedia/alaw_ulaw.h> #include <pjmedia/errno.h> +#include <pjmedia/frame.h> #include <pjmedia/silencedet.h> #include <pj/array.h> #include <pj/assert.h> diff --git a/pjmedia/src/pjmedia/endpoint.c b/pjmedia/src/pjmedia/endpoint.c index 112a0981..75831f1f 100644 --- a/pjmedia/src/pjmedia/endpoint.c +++ b/pjmedia/src/pjmedia/endpoint.c @@ -20,6 +20,7 @@ #include <pjmedia/endpoint.h> #include <pjmedia/errno.h> #include <pjmedia/sdp.h> +#include <pjmedia/vid_codec.h> #include <pjmedia-audiodev/audiodev.h> #include <pj/assert.h> #include <pj/ioqueue.h> @@ -312,87 +313,37 @@ PJ_DEF(pj_pool_t*) pjmedia_endpt_create_pool( pjmedia_endpt *endpt, return pj_pool_create(endpt->pf, name, initial, increment, NULL); } -/** - * Create a SDP session description that describes the endpoint - * capability. - */ -PJ_DEF(pj_status_t) pjmedia_endpt_create_sdp( pjmedia_endpt *endpt, - pj_pool_t *pool, - unsigned stream_cnt, - const pjmedia_sock_info sock_info[], - pjmedia_sdp_session **p_sdp ) +/* Common initialization for both audio and video SDP media line */ +static pj_status_t init_sdp_media(pjmedia_sdp_media *m, + pj_pool_t *pool, + const pj_str_t *media_type, + const pjmedia_sock_info *sock_info) { - pj_time_val tv; - unsigned i; - const pj_sockaddr *addr0; - pjmedia_sdp_session *sdp; - pjmedia_sdp_media *m; + char tmp_addr[PJ_INET6_ADDRSTRLEN]; pjmedia_sdp_attr *attr; + const pj_sockaddr *addr; - /* Sanity check arguments */ - PJ_ASSERT_RETURN(endpt && pool && p_sdp && stream_cnt, PJ_EINVAL); - - /* Check that there are not too many codecs */ - PJ_ASSERT_RETURN(endpt->codec_mgr.codec_cnt <= PJMEDIA_MAX_SDP_FMT, - PJ_ETOOMANY); - - /* Create and initialize basic SDP session */ - sdp = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_session); - - addr0 = &sock_info[0].rtp_addr_name; - - pj_gettimeofday(&tv); - sdp->origin.user = pj_str("-"); - sdp->origin.version = sdp->origin.id = tv.sec + 2208988800UL; - sdp->origin.net_type = STR_IN; - - if (addr0->addr.sa_family == pj_AF_INET()) { - sdp->origin.addr_type = STR_IP4; - pj_strdup2(pool, &sdp->origin.addr, - pj_inet_ntoa(addr0->ipv4.sin_addr)); - } else if (addr0->addr.sa_family == pj_AF_INET6()) { - char tmp_addr[PJ_INET6_ADDRSTRLEN]; - - sdp->origin.addr_type = STR_IP6; - pj_strdup2(pool, &sdp->origin.addr, - pj_sockaddr_print(addr0, tmp_addr, sizeof(tmp_addr), 0)); - - } else { - pj_assert(!"Invalid address family"); - return PJ_EAFNOTSUP; - } - - sdp->name = STR_SDP_NAME; - - /* Since we only support one media stream at present, put the - * SDP connection line in the session level. - */ - sdp->conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn); - sdp->conn->net_type = sdp->origin.net_type; - sdp->conn->addr_type = sdp->origin.addr_type; - sdp->conn->addr = sdp->origin.addr; + pj_strdup(pool, &m->desc.media, media_type); + addr = &sock_info->rtp_addr_name; - /* SDP time and attributes. */ - sdp->time.start = sdp->time.stop = 0; - sdp->attr_count = 0; + /* Validate address family */ + PJ_ASSERT_RETURN(addr->addr.sa_family == pj_AF_INET() || + addr->addr.sa_family == pj_AF_INET6(), + PJ_EAFNOTSUP); - /* Create media stream 0: */ + /* SDP connection line */ + m->conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn); + m->conn->net_type = STR_IN; + m->conn->addr_type = (addr->addr.sa_family==pj_AF_INET())? STR_IP4:STR_IP6; + pj_sockaddr_print(addr, tmp_addr, sizeof(tmp_addr), 0); + pj_strdup2(pool, &m->conn->addr, tmp_addr); - sdp->media_count = 1; - m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media); - sdp->media[0] = m; - - /* Standard media info: */ - pj_strdup(pool, &m->desc.media, &STR_AUDIO); - m->desc.port = pj_sockaddr_get_port(addr0); + /* Port and transport in media description */ + m->desc.port = pj_sockaddr_get_port(addr); m->desc.port_count = 1; pj_strdup (pool, &m->desc.transport, &STR_RTP_AVP); - /* Init media line and attribute list. */ - m->desc.fmt_count = 0; - m->attr_count = 0; - /* Add "rtcp" attribute */ #if defined(PJMEDIA_HAS_RTCP_IN_SDP) && PJMEDIA_HAS_RTCP_IN_SDP!=0 if (sock_info->rtcp_addr_name.addr.sa_family != 0) { @@ -402,13 +353,45 @@ PJ_DEF(pj_status_t) pjmedia_endpt_create_sdp( pjmedia_endpt *endpt, } #endif + /* Add sendrecv attribute. */ + attr = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_attr); + attr->name = STR_SENDRECV; + m->attr[m->attr_count++] = attr; + + return PJ_SUCCESS; +} + +/* Create m=audio SDP media line */ +PJ_DEF(pj_status_t) pjmedia_endpt_create_audio_sdp(pjmedia_endpt *endpt, + pj_pool_t *pool, + const pjmedia_sock_info *si, + unsigned options, + pjmedia_sdp_media **p_m) +{ + const pj_str_t STR_AUDIO = { "audio", 5 }; + pjmedia_sdp_media *m; + pjmedia_sdp_attr *attr; + unsigned i; + pj_status_t status; + + PJ_UNUSED_ARG(options); + + /* Check that there are not too many codecs */ + PJ_ASSERT_RETURN(endpt->codec_mgr.codec_cnt <= PJMEDIA_MAX_SDP_FMT, + PJ_ETOOMANY); + + /* Create and init basic SDP media */ + m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media); + status = init_sdp_media(m, pool, &STR_AUDIO, si); + if (status != PJ_SUCCESS) + return status; + /* Add format, rtpmap, and fmtp (when applicable) for each codec */ for (i=0; i<endpt->codec_mgr.codec_cnt; ++i) { pjmedia_codec_info *codec_info; pjmedia_sdp_rtpmap rtpmap; char tmp_param[3]; - pjmedia_sdp_attr *attr; pjmedia_codec_param codec_param; pj_str_t *fmt; @@ -512,11 +495,6 @@ PJ_DEF(pj_status_t) pjmedia_endpt_create_sdp( pjmedia_endpt *endpt, } } - /* Add sendrecv attribute. */ - attr = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_attr); - attr->name = STR_SENDRECV; - m->attr[m->attr_count++] = attr; - #if defined(PJMEDIA_RTP_PT_TELEPHONE_EVENTS) && \ PJMEDIA_RTP_PT_TELEPHONE_EVENTS != 0 /* @@ -541,11 +519,250 @@ PJ_DEF(pj_status_t) pjmedia_endpt_create_sdp( pjmedia_endpt *endpt, } #endif + *p_m = m; + return PJ_SUCCESS; +} + +/* Create m=video SDP media line */ +PJ_DEF(pj_status_t) pjmedia_endpt_create_video_sdp(pjmedia_endpt *endpt, + pj_pool_t *pool, + const pjmedia_sock_info *si, + unsigned options, + pjmedia_sdp_media **p_m) +{ + const pj_str_t STR_VIDEO = { "video", 5 }; + pjmedia_sdp_media *m; + pjmedia_vid_codec_info codec_info[PJMEDIA_VID_CODEC_MGR_MAX_CODECS]; + unsigned codec_prio[PJMEDIA_VID_CODEC_MGR_MAX_CODECS]; + pjmedia_sdp_attr *attr; + unsigned cnt, i; + pj_status_t status; + + PJ_UNUSED_ARG(options); + + /* Make sure video codec manager is instantiated */ + if (!pjmedia_vid_codec_mgr_instance()) + pjmedia_vid_codec_mgr_create(endpt->pool, NULL); + + /* Create and init basic SDP media */ + m = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_media); + status = init_sdp_media(m, pool, &STR_VIDEO, si); + if (status != PJ_SUCCESS) + return status; + + cnt = PJ_ARRAY_SIZE(codec_info); + status = pjmedia_vid_codec_mgr_enum_codecs(NULL, &cnt, + codec_info, codec_prio); + + /* Check that there are not too many codecs */ + PJ_ASSERT_RETURN(0 <= PJMEDIA_MAX_SDP_FMT, + PJ_ETOOMANY); + + /* Add format, rtpmap, and fmtp (when applicable) for each codec */ + for (i=0; i<cnt; ++i) { + pjmedia_sdp_rtpmap rtpmap; + pjmedia_vid_codec_param codec_param; + pj_str_t *fmt; + + pj_bzero(&rtpmap, sizeof(rtpmap)); + + if (codec_prio[i] == PJMEDIA_CODEC_PRIO_DISABLED) + break; + + if (i > PJMEDIA_MAX_SDP_FMT) { + /* Too many codecs, perhaps it is better to tell application by + * returning appropriate status code. + */ + PJ_PERROR(3,(THIS_FILE, PJ_ETOOMANY, + "Skipping some video codecs")); + break; + } + + /* Must support RTP packetization and bidirectional */ + if (!codec_info[i].has_rtp_pack || + codec_info[i].dir != PJMEDIA_DIR_ENCODING_DECODING) + { + continue; + } + + pjmedia_vid_codec_mgr_get_default_param(NULL, &codec_info[i], + &codec_param); + + fmt = &m->desc.fmt[m->desc.fmt_count++]; + fmt->ptr = (char*) pj_pool_alloc(pool, 8); + fmt->slen = pj_utoa(codec_info[i].pt, fmt->ptr); + rtpmap.pt = *fmt; + + /* Encoding name */ + rtpmap.enc_name = codec_info[i].encoding_name; + + /* Clock rate */ + rtpmap.clock_rate = codec_info[i].clock_rate; + + if (codec_info[i].pt >= 96 || pjmedia_add_rtpmap_for_static_pt) { + pjmedia_sdp_rtpmap_to_attr(pool, &rtpmap, &attr); + m->attr[m->attr_count++] = attr; + } + + /* Add fmtp params */ + if (codec_param.dec_fmtp.cnt > 0) { + enum { MAX_FMTP_STR_LEN = 160 }; + char buf[MAX_FMTP_STR_LEN]; + unsigned buf_len = 0, j; + pjmedia_codec_fmtp *dec_fmtp = &codec_param.dec_fmtp; + + /* Print codec PT */ + buf_len += pj_ansi_snprintf(buf, + MAX_FMTP_STR_LEN - buf_len, + "%d", + codec_info[i].pt); + + for (j = 0; j < dec_fmtp->cnt; ++j) { + unsigned test_len = 2; + + /* Check if buf still available */ + test_len = dec_fmtp->param[j].val.slen + + dec_fmtp->param[j].name.slen; + if (test_len + buf_len >= MAX_FMTP_STR_LEN) + return PJ_ETOOBIG; + + /* Print delimiter */ + buf_len += pj_ansi_snprintf(&buf[buf_len], + MAX_FMTP_STR_LEN - buf_len, + (j == 0?" ":";")); + + /* Print an fmtp param */ + if (dec_fmtp->param[j].name.slen) + buf_len += pj_ansi_snprintf( + &buf[buf_len], + MAX_FMTP_STR_LEN - buf_len, + "%.*s=%.*s", + (int)dec_fmtp->param[j].name.slen, + dec_fmtp->param[j].name.ptr, + (int)dec_fmtp->param[j].val.slen, + dec_fmtp->param[j].val.ptr); + else + buf_len += pj_ansi_snprintf(&buf[buf_len], + MAX_FMTP_STR_LEN - buf_len, + "%.*s", + (int)dec_fmtp->param[j].val.slen, + dec_fmtp->param[j].val.ptr); + } + + attr = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_attr); + + attr->name = pj_str("fmtp"); + attr->value = pj_strdup3(pool, buf); + m->attr[m->attr_count++] = attr; + } + } + + *p_m = m; + return PJ_SUCCESS; +} + + +/** + * Create a "blank" SDP session description. The SDP will contain basic SDP + * fields such as origin, time, and name, but without any media lines. + */ +PJ_DEF(pj_status_t) pjmedia_endpt_create_base_sdp( pjmedia_endpt *endpt, + pj_pool_t *pool, + const pj_str_t *sess_name, + const pj_sockaddr *origin, + pjmedia_sdp_session **p_sdp) +{ + pj_time_val tv; + pjmedia_sdp_session *sdp; + + /* Sanity check arguments */ + PJ_ASSERT_RETURN(endpt && pool && p_sdp, PJ_EINVAL); + + sdp = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_session); + + pj_gettimeofday(&tv); + sdp->origin.user = pj_str("-"); + sdp->origin.version = sdp->origin.id = tv.sec + 2208988800UL; + sdp->origin.net_type = STR_IN; + + if (origin->addr.sa_family == pj_AF_INET()) { + sdp->origin.addr_type = STR_IP4; + pj_strdup2(pool, &sdp->origin.addr, + pj_inet_ntoa(origin->ipv4.sin_addr)); + } else if (origin->addr.sa_family == pj_AF_INET6()) { + char tmp_addr[PJ_INET6_ADDRSTRLEN]; + + sdp->origin.addr_type = STR_IP6; + pj_strdup2(pool, &sdp->origin.addr, + pj_sockaddr_print(origin, tmp_addr, sizeof(tmp_addr), 0)); + + } else { + pj_assert(!"Invalid address family"); + return PJ_EAFNOTSUP; + } + + if (sess_name) + pj_strdup(pool, &sdp->name, sess_name); + else + sdp->name = STR_SDP_NAME; + + /* SDP time and attributes. */ + sdp->time.start = sdp->time.stop = 0; + sdp->attr_count = 0; + /* Done */ *p_sdp = sdp; return PJ_SUCCESS; +} +/** + * Create a SDP session description that describes the endpoint + * capability. + */ +PJ_DEF(pj_status_t) pjmedia_endpt_create_sdp( pjmedia_endpt *endpt, + pj_pool_t *pool, + unsigned stream_cnt, + const pjmedia_sock_info sock_info[], + pjmedia_sdp_session **p_sdp ) +{ + const pj_sockaddr *addr0; + pjmedia_sdp_session *sdp; + pjmedia_sdp_media *m; + unsigned i; + pj_status_t status; + + /* Sanity check arguments */ + PJ_ASSERT_RETURN(endpt && pool && p_sdp && stream_cnt, PJ_EINVAL); + PJ_ASSERT_RETURN(stream_cnt < PJMEDIA_MAX_SDP_MEDIA, PJ_ETOOMANY); + + addr0 = &sock_info[0].rtp_addr_name; + + /* Create and initialize basic SDP session */ + status = pjmedia_endpt_create_base_sdp(endpt, pool, NULL, addr0, &sdp); + if (status != PJ_SUCCESS) + return status; + + /* Audio is first, by convention */ + status = pjmedia_endpt_create_audio_sdp(endpt, pool, + &sock_info[0], 0, &m); + if (status != PJ_SUCCESS) + return status; + sdp->media[sdp->media_count++] = m; + + /* The remaining stream, if any, are videos (by convention as well) */ + for (i=1; i<stream_cnt; ++i) { + status = pjmedia_endpt_create_video_sdp(endpt, pool, + &sock_info[i], 0, &m); + if (status != PJ_SUCCESS) + return status; + sdp->media[sdp->media_count++] = m; + } + + /* Done */ + *p_sdp = sdp; + + return PJ_SUCCESS; } diff --git a/pjmedia/src/pjmedia/errno.c b/pjmedia/src/pjmedia/errno.c index 5c9b2c8b..db66a398 100644 --- a/pjmedia/src/pjmedia/errno.c +++ b/pjmedia/src/pjmedia/errno.c @@ -114,6 +114,7 @@ static const struct PJ_BUILD_ERR( PJMEDIA_EREMOTENODTMF, "Remote does not support DTMF" ), PJ_BUILD_ERR( PJMEDIA_RTP_EINDTMF, "Invalid DTMF digit" ), PJ_BUILD_ERR( PJMEDIA_RTP_EREMNORFC2833,"Remote does not support RFC 2833" ), + PJ_BUILD_ERR( PJMEDIA_EBADFMT, "Bad format"), /* RTP session errors. */ PJ_BUILD_ERR( PJMEDIA_RTP_EINPKT, "Invalid RTP packet" ), @@ -142,6 +143,7 @@ static const struct PJ_BUILD_ERR( PJMEDIA_EWAVEUNSUPP, "Unsupported WAVE file format" ), PJ_BUILD_ERR( PJMEDIA_EWAVETOOSHORT, "WAVE file too short" ), PJ_BUILD_ERR( PJMEDIA_EFRMFILETOOBIG, "Sound frame too large for file buffer"), + PJ_BUILD_ERR( PJMEDIA_EAVIUNSUPP, "Unsupported AVI file"), /* Sound device errors: */ PJ_BUILD_ERR( PJMEDIA_ENOSNDREC, "No suitable sound capture device" ), diff --git a/pjmedia/src/pjmedia/event.c b/pjmedia/src/pjmedia/event.c new file mode 100644 index 00000000..fee0917e --- /dev/null +++ b/pjmedia/src/pjmedia/event.c @@ -0,0 +1,148 @@ +/* $Id$ */ +/* + * Copyright (C) 2011-2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjmedia/event.h> +#include <pjmedia/errno.h> +#include <pj/assert.h> +#include <pj/log.h> +#include <pj/string.h> + +#define THIS_FILE "event.c" + +#if 1 +# define TRACE_(x) PJ_LOG(6,x) +#else +# define TRACE_(x) +#endif + +PJ_DEF(void) pjmedia_event_init( pjmedia_event *event, + pjmedia_event_type type, + const pj_timestamp *ts, + const pjmedia_event_publisher *epub) +{ + pj_bzero(event, sizeof(*event)); + event->type = type; + if (ts) + event->timestamp.u64 = ts->u64; + event->epub = epub; + if (epub) + event->epub_sig = epub->sig; +} + +PJ_DEF(void) pjmedia_event_publisher_init(pjmedia_event_publisher *epub, + pjmedia_obj_sig sig) +{ + pj_bzero(epub, sizeof(*epub)); + pj_list_init(&epub->subscription_list); + epub->sig = sig; +} + +PJ_DEF(void) pjmedia_event_subscription_init( pjmedia_event_subscription *esub, + pjmedia_event_cb *cb, + void *user_data) +{ + pj_bzero(esub, sizeof(*esub)); + esub->cb = cb; + esub->user_data = user_data; +} + +PJ_DEF(pj_bool_t) +pjmedia_event_publisher_has_sub(pjmedia_event_publisher *epub) +{ + PJ_ASSERT_RETURN(epub, PJ_FALSE); + return epub->subscription_list.next && + (!pj_list_empty(&epub->subscription_list)); +} + +PJ_DEF(pj_status_t) pjmedia_event_subscribe( pjmedia_event_publisher *epub, + pjmedia_event_subscription *esub) +{ + PJ_ASSERT_RETURN(epub && esub && esub->cb, PJ_EINVAL); + /* Must not currently subscribe to anything */ + PJ_ASSERT_RETURN(esub->subscribe_to == NULL, PJ_EINVALIDOP); + + pj_list_push_back(&epub->subscription_list, esub); + esub->subscribe_to = epub; + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjmedia_event_unsubscribe(pjmedia_event_subscription *esub) +{ + PJ_ASSERT_RETURN(esub, PJ_EINVAL); + if (esub->subscribe_to) { + PJ_ASSERT_RETURN( + pj_list_find_node(&esub->subscribe_to->subscription_list, + esub)==esub, PJ_ENOTFOUND); + pj_list_erase(esub); + esub->subscribe_to = NULL; + } + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjmedia_event_publish( pjmedia_event_publisher *epub, + pjmedia_event *event) +{ + pjmedia_event_subscription *esub; + char event_name[5]; + char epub_name[5]; + pj_status_t err = PJ_SUCCESS; + + PJ_ASSERT_RETURN(epub && event, PJ_EINVAL); + + TRACE_((THIS_FILE, "Event %s is published by publisher %s", + pjmedia_fourcc_name(event->type, event_name), + pjmedia_fourcc_name(epub->sig, epub_name))); + + esub = epub->subscription_list.next; + if (!esub) + return err; + + while (esub != &epub->subscription_list) { + pjmedia_event_subscription *next; + pj_status_t status; + + /* just in case esub is destroyed in the callback */ + next = esub->next; + + status = (*esub->cb)(esub, event); + if (status != PJ_SUCCESS && err == PJ_SUCCESS) + err = status; + + esub = next; + } + + return err; +} + +static pj_status_t republisher_cb(pjmedia_event_subscription *esub, + pjmedia_event *event) +{ + return pjmedia_event_publish((pjmedia_event_publisher*)esub->user_data, + event); +} + +PJ_DEF(pj_status_t) pjmedia_event_republish(pjmedia_event_publisher *esrc, + pjmedia_event_publisher *epub, + pjmedia_event_subscription *esub) +{ + PJ_ASSERT_RETURN(esrc && epub && esub, PJ_EINVAL); + + pjmedia_event_subscription_init(esub, &republisher_cb, epub); + return pjmedia_event_subscribe(esrc, esub); +} + diff --git a/pjmedia/src/pjmedia/ffmpeg_util.c b/pjmedia/src/pjmedia/ffmpeg_util.c new file mode 100644 index 00000000..55dc48ab --- /dev/null +++ b/pjmedia/src/pjmedia/ffmpeg_util.c @@ -0,0 +1,153 @@ +/* $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/types.h> +#include <pj/errno.h> + +#if PJMEDIA_HAS_LIBAVFORMAT && PJMEDIA_HAS_LIBAVUTIL + +#include "ffmpeg_util.h" +#include <libavformat/avformat.h> + +/* Conversion table between pjmedia_format_id and PixelFormat */ +static const struct ffmpeg_fmt_table_t +{ + pjmedia_format_id id; + enum PixelFormat pf; +} ffmpeg_fmt_table[] = +{ + { PJMEDIA_FORMAT_RGBA, PIX_FMT_RGBA}, + { PJMEDIA_FORMAT_RGB24,PIX_FMT_RGB24}, + { PJMEDIA_FORMAT_BGRA, PIX_FMT_BGRA}, + + { PJMEDIA_FORMAT_AYUV, PIX_FMT_NONE}, + { PJMEDIA_FORMAT_YUY2, PIX_FMT_YUYV422}, + { PJMEDIA_FORMAT_UYVY, PIX_FMT_UYVY422}, + { PJMEDIA_FORMAT_I420, PIX_FMT_YUV420P}, + { PJMEDIA_FORMAT_YV12, PIX_FMT_YUV422P}, + { PJMEDIA_FORMAT_I420JPEG, PIX_FMT_YUVJ420P}, + { PJMEDIA_FORMAT_I422JPEG, PIX_FMT_YUVJ422P}, +}; + +/* Conversion table between pjmedia_format_id and CodecID */ +static const struct ffmpeg_codec_table_t +{ + pjmedia_format_id id; + enum CodecID codec_id; +} ffmpeg_codec_table[] = +{ + {PJMEDIA_FORMAT_H261, CODEC_ID_H261}, + {PJMEDIA_FORMAT_H263, CODEC_ID_H263}, + {PJMEDIA_FORMAT_H263P, CODEC_ID_H263P}, + {PJMEDIA_FORMAT_H264, CODEC_ID_H264}, + {PJMEDIA_FORMAT_MPEG1VIDEO, CODEC_ID_MPEG1VIDEO}, + {PJMEDIA_FORMAT_MPEG2VIDEO, CODEC_ID_MPEG2VIDEO}, + {PJMEDIA_FORMAT_MPEG4, CODEC_ID_MPEG4}, + {PJMEDIA_FORMAT_MJPEG, CODEC_ID_MJPEG}, +#if LIBAVCODEC_VERSION_MAJOR < 53 + {PJMEDIA_FORMAT_XVID, CODEC_ID_XVID}, +#endif +}; + +static int pjmedia_ffmpeg_ref_cnt; + +void pjmedia_ffmpeg_add_ref() +{ + if (pjmedia_ffmpeg_ref_cnt++ == 0) { + av_register_all(); + } +} + +void pjmedia_ffmpeg_dec_ref() +{ + if (pjmedia_ffmpeg_ref_cnt-- == 1) { + /* How to shutdown ffmpeg? */ + } + + if (pjmedia_ffmpeg_ref_cnt < 0) pjmedia_ffmpeg_ref_cnt = 0; +} + +pj_status_t pjmedia_format_id_to_PixelFormat(pjmedia_format_id fmt_id, + enum PixelFormat *pixel_format) +{ + unsigned i; + for (i=0; i<PJ_ARRAY_SIZE(ffmpeg_fmt_table); ++i) { + const struct ffmpeg_fmt_table_t *t = &ffmpeg_fmt_table[i]; + if (t->id==fmt_id && t->pf != PIX_FMT_NONE) { + *pixel_format = t->pf; + return PJ_SUCCESS; + } + } + + *pixel_format = PIX_FMT_NONE; + return PJ_ENOTFOUND; +} + +pj_status_t PixelFormat_to_pjmedia_format_id(enum PixelFormat pf, + pjmedia_format_id *fmt_id) +{ + unsigned i; + for (i=0; i<PJ_ARRAY_SIZE(ffmpeg_fmt_table); ++i) { + const struct ffmpeg_fmt_table_t *t = &ffmpeg_fmt_table[i]; + if (t->pf == pf) { + if (fmt_id) *fmt_id = t->id; + return PJ_SUCCESS; + } + } + + return PJ_ENOTFOUND; +} + +pj_status_t pjmedia_format_id_to_CodecID(pjmedia_format_id fmt_id, + enum CodecID *codec_id) +{ + unsigned i; + for (i=0; i<PJ_ARRAY_SIZE(ffmpeg_codec_table); ++i) { + const struct ffmpeg_codec_table_t *t = &ffmpeg_codec_table[i]; + if (t->id==fmt_id && t->codec_id != PIX_FMT_NONE) { + *codec_id = t->codec_id; + return PJ_SUCCESS; + } + } + + *codec_id = PIX_FMT_NONE; + return PJ_ENOTFOUND; +} + +pj_status_t CodecID_to_pjmedia_format_id(enum CodecID codec_id, + pjmedia_format_id *fmt_id) +{ + unsigned i; + for (i=0; i<PJ_ARRAY_SIZE(ffmpeg_codec_table); ++i) { + const struct ffmpeg_codec_table_t *t = &ffmpeg_codec_table[i]; + if (t->codec_id == codec_id) { + if (fmt_id) *fmt_id = t->id; + return PJ_SUCCESS; + } + } + + return PJ_ENOTFOUND; +} + + +#ifdef _MSC_VER +# pragma comment( lib, "avformat.lib") +# pragma comment( lib, "avutil.lib") +#endif + +#endif /* #if PJMEDIA_HAS_LIBAVFORMAT && PJMEDIA_HAS_LIBAVUTIL */ diff --git a/pjmedia/src/pjmedia/ffmpeg_util.h b/pjmedia/src/pjmedia/ffmpeg_util.h new file mode 100644 index 00000000..70e9aaac --- /dev/null +++ b/pjmedia/src/pjmedia/ffmpeg_util.h @@ -0,0 +1,55 @@ +/* $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 + */ + +/* + * This file contains common utilities that are useful for pjmedia components + * that use ffmpeg. This is not a public API. + */ + +#ifndef __PJMEDIA_FFMPEG_UTIL_H__ +#define __PJMEDIA_FFMPEG_UTIL_H__ + +#include <pjmedia/format.h> + +#ifdef _MSC_VER +# ifndef __cplusplus +# define inline _inline +# endif +# pragma warning(disable:4244) /* possible loss of data */ +#endif + +#include <libavutil/avutil.h> +#include <libavcodec/avcodec.h> + +void pjmedia_ffmpeg_add_ref(); +void pjmedia_ffmpeg_dec_ref(); + +pj_status_t pjmedia_format_id_to_PixelFormat(pjmedia_format_id fmt_id, + enum PixelFormat *pixel_format); + +pj_status_t PixelFormat_to_pjmedia_format_id(enum PixelFormat pf, + pjmedia_format_id *fmt_id); + +pj_status_t pjmedia_format_id_to_CodecID(pjmedia_format_id fmt_id, + enum CodecID *codec_id); + +pj_status_t CodecID_to_pjmedia_format_id(enum CodecID codec_id, + pjmedia_format_id *fmt_id); + +#endif /* __PJMEDIA_FFMPEG_UTIL_H__ */ diff --git a/pjmedia/src/pjmedia/format.c b/pjmedia/src/pjmedia/format.c new file mode 100644 index 00000000..a5612dae --- /dev/null +++ b/pjmedia/src/pjmedia/format.c @@ -0,0 +1,366 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2010 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjmedia/format.h> +#include <pj/assert.h> +#include <pj/errno.h> +#include <pj/pool.h> +#include <pj/string.h> + +static pj_status_t apply_packed_fmt(const pjmedia_video_format_info *fi, + pjmedia_video_apply_fmt_param *aparam); + +static pj_status_t apply_planar_420(const pjmedia_video_format_info *fi, + pjmedia_video_apply_fmt_param *aparam); + +static pj_status_t apply_planar_422(const pjmedia_video_format_info *fi, + pjmedia_video_apply_fmt_param *aparam); + +struct pjmedia_video_format_mgr +{ + unsigned max_info; + unsigned info_cnt; + pjmedia_video_format_info **infos; +}; + +static pjmedia_video_format_mgr *video_format_mgr_instance; +static pjmedia_video_format_info built_in_vid_fmt_info[] = +{ + {PJMEDIA_FORMAT_RGB24, "RGB24", PJMEDIA_COLOR_MODEL_RGB, 24, 1, &apply_packed_fmt}, + {PJMEDIA_FORMAT_RGBA, "RGBA", PJMEDIA_COLOR_MODEL_RGB, 32, 1, &apply_packed_fmt}, + {PJMEDIA_FORMAT_BGRA, "BGRA", PJMEDIA_COLOR_MODEL_RGB, 32, 1, &apply_packed_fmt}, + {PJMEDIA_FORMAT_DIB , "DIB ", PJMEDIA_COLOR_MODEL_RGB, 24, 1, &apply_packed_fmt}, + {PJMEDIA_FORMAT_AYUV, "AYUV", PJMEDIA_COLOR_MODEL_YUV, 32, 1, &apply_packed_fmt}, + {PJMEDIA_FORMAT_YUY2, "YUY2", PJMEDIA_COLOR_MODEL_YUV, 16, 1, &apply_packed_fmt}, + {PJMEDIA_FORMAT_UYVY, "UYVY", PJMEDIA_COLOR_MODEL_YUV, 16, 1, &apply_packed_fmt}, + {PJMEDIA_FORMAT_YVYU, "YVYU", PJMEDIA_COLOR_MODEL_YUV, 16, 1, &apply_packed_fmt}, + {PJMEDIA_FORMAT_I420, "I420", PJMEDIA_COLOR_MODEL_YUV, 12, 3, &apply_planar_420}, + {PJMEDIA_FORMAT_YV12, "YV12", PJMEDIA_COLOR_MODEL_YUV, 16, 3, &apply_planar_422}, + {PJMEDIA_FORMAT_I420JPEG, "I420JPG", PJMEDIA_COLOR_MODEL_YUV, 12, 3, &apply_planar_420}, + {PJMEDIA_FORMAT_I422JPEG, "I422JPG", PJMEDIA_COLOR_MODEL_YUV, 16, 3, &apply_planar_422}, +}; + + +PJ_DEF(void) pjmedia_format_init_audio( pjmedia_format *fmt, + pj_uint32_t fmt_id, + unsigned clock_rate, + unsigned channel_count, + unsigned bits_per_sample, + unsigned frame_time_usec, + pj_uint32_t avg_bps, + pj_uint32_t max_bps) +{ + fmt->id = fmt_id; + fmt->type = PJMEDIA_TYPE_AUDIO; + fmt->detail_type = PJMEDIA_FORMAT_DETAIL_AUDIO; + + fmt->det.aud.clock_rate = clock_rate; + fmt->det.aud.channel_count = channel_count; + fmt->det.aud.bits_per_sample = bits_per_sample; + fmt->det.aud.frame_time_usec = frame_time_usec; + fmt->det.aud.avg_bps = avg_bps; + fmt->det.aud.max_bps = max_bps; +} + +PJ_DEF(void) pjmedia_format_init_video( pjmedia_format *fmt, + pj_uint32_t fmt_id, + unsigned width, + unsigned height, + unsigned fps_num, + unsigned fps_denum) +{ + fmt->id = fmt_id; + fmt->type = PJMEDIA_TYPE_VIDEO; + fmt->detail_type = PJMEDIA_FORMAT_DETAIL_VIDEO; + + fmt->det.vid.size.w = width; + fmt->det.vid.size.h = height; + fmt->det.vid.fps.num = fps_num; + fmt->det.vid.fps.denum = fps_denum; + fmt->det.vid.avg_bps = fmt->det.vid.max_bps = 0; + + if (pjmedia_video_format_mgr_instance()) { + const pjmedia_video_format_info *vfi; + pjmedia_video_apply_fmt_param vafp; + pj_uint32_t bps; + + vfi = pjmedia_get_video_format_info(NULL, fmt->id); + if (vfi) { + pj_bzero(&vafp, sizeof(vafp)); + vafp.size = fmt->det.vid.size; + vfi->apply_fmt(vfi, &vafp); + + bps = vafp.framebytes * fps_num * (pj_size_t)8 / fps_denum; + fmt->det.vid.avg_bps = fmt->det.vid.max_bps = bps; + } + } +} + +PJ_DEF(pjmedia_audio_format_detail*) +pjmedia_format_get_audio_format_detail(const pjmedia_format *fmt, + pj_bool_t assert_valid) +{ + if (fmt->detail_type==PJMEDIA_FORMAT_DETAIL_AUDIO) { + return (pjmedia_audio_format_detail*) &fmt->det.aud; + } else { + pj_assert(!assert_valid || !"Invalid audio format detail"); + return NULL; + } +} + +PJ_DEF(pjmedia_video_format_detail*) +pjmedia_format_get_video_format_detail(const pjmedia_format *fmt, + pj_bool_t assert_valid) +{ + if (fmt->detail_type==PJMEDIA_FORMAT_DETAIL_VIDEO) { + return (pjmedia_video_format_detail*)&fmt->det.vid; + } else { + pj_assert(!assert_valid || !"Invalid video format detail"); + return NULL; + } +} + +PJ_DEF(pjmedia_format*) pjmedia_format_copy(pjmedia_format *dst, + const pjmedia_format *src) +{ + return (pjmedia_format*)pj_memcpy(dst, src, sizeof(*src)); +} + + +static pj_status_t apply_packed_fmt(const pjmedia_video_format_info *fi, + pjmedia_video_apply_fmt_param *aparam) +{ + unsigned i; + pj_size_t stride; + + stride = (pj_size_t)((aparam->size.w*fi->bpp) >> 3); + + /* Calculate memsize */ + aparam->framebytes = stride * aparam->size.h; + + /* Packed formats only use 1 plane */ + aparam->planes[0] = aparam->buffer; + aparam->strides[0] = stride; + aparam->plane_bytes[0] = aparam->framebytes; + + /* Zero unused planes */ + for (i=1; i<PJMEDIA_MAX_VIDEO_PLANES; ++i) { + aparam->strides[i] = 0; + aparam->planes[i] = NULL; + } + + return PJ_SUCCESS; +} + +static pj_status_t apply_planar_420(const pjmedia_video_format_info *fi, + pjmedia_video_apply_fmt_param *aparam) +{ + unsigned i; + pj_size_t Y_bytes; + + PJ_UNUSED_ARG(fi); + + /* Calculate memsize */ + Y_bytes = (pj_size_t)(aparam->size.w * aparam->size.h); + aparam->framebytes = Y_bytes + (Y_bytes>>1); + + /* Planar formats use 3 plane */ + aparam->strides[0] = aparam->size.w; + aparam->strides[1] = aparam->strides[2] = (aparam->size.w>>1); + + aparam->planes[0] = aparam->buffer; + aparam->planes[1] = aparam->planes[0] + Y_bytes; + aparam->planes[2] = aparam->planes[1] + (Y_bytes>>2); + + aparam->plane_bytes[0] = Y_bytes; + aparam->plane_bytes[1] = aparam->plane_bytes[2] = (Y_bytes>>2); + + /* Zero unused planes */ + for (i=3; i<PJMEDIA_MAX_VIDEO_PLANES; ++i) { + aparam->strides[i] = 0; + aparam->planes[i] = NULL; + aparam->plane_bytes[i] = 0; + } + + return PJ_SUCCESS; +} + +static pj_status_t apply_planar_422(const pjmedia_video_format_info *fi, + pjmedia_video_apply_fmt_param *aparam) +{ + unsigned i; + pj_size_t Y_bytes; + + PJ_UNUSED_ARG(fi); + + /* Calculate memsize */ + Y_bytes = (pj_size_t)(aparam->size.w * aparam->size.h); + aparam->framebytes = (Y_bytes << 1); + + /* Planar formats use 3 plane */ + aparam->strides[0] = aparam->size.w; + aparam->strides[1] = aparam->strides[2] = (aparam->size.w>>1); + + aparam->planes[0] = aparam->buffer; + aparam->planes[1] = aparam->planes[0] + Y_bytes; + aparam->planes[2] = aparam->planes[1] + (Y_bytes>>1); + + aparam->plane_bytes[0] = Y_bytes; + aparam->plane_bytes[1] = aparam->plane_bytes[2] = (Y_bytes>>1); + + /* Zero unused planes */ + for (i=3; i<PJMEDIA_MAX_VIDEO_PLANES; ++i) { + aparam->strides[i] = 0; + aparam->planes[i] = NULL; + aparam->plane_bytes[i] = 0; + } + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) +pjmedia_video_format_mgr_create(pj_pool_t *pool, + unsigned max_fmt, + unsigned options, + pjmedia_video_format_mgr **p_mgr) +{ + pjmedia_video_format_mgr *mgr; + unsigned i; + + PJ_ASSERT_RETURN(pool && options==0, PJ_EINVAL); + + PJ_UNUSED_ARG(options); + + mgr = PJ_POOL_ALLOC_T(pool, pjmedia_video_format_mgr); + mgr->max_info = max_fmt; + mgr->info_cnt = 0; + mgr->infos = pj_pool_calloc(pool, max_fmt, sizeof(pjmedia_video_format_info *)); + + if (video_format_mgr_instance == NULL) + video_format_mgr_instance = mgr; + + for (i=0; i<PJ_ARRAY_SIZE(built_in_vid_fmt_info); ++i) { + pjmedia_register_video_format_info(mgr, + &built_in_vid_fmt_info[i]); + } + + if (p_mgr) + *p_mgr = mgr; + + return PJ_SUCCESS; +} + + +PJ_DEF(const pjmedia_video_format_info*) +pjmedia_get_video_format_info(pjmedia_video_format_mgr *mgr, + pj_uint32_t id) +{ + pjmedia_video_format_info **first; + int comp; + unsigned n; + + if (!mgr) + mgr = pjmedia_video_format_mgr_instance(); + + PJ_ASSERT_RETURN(mgr != NULL, NULL); + + /* Binary search for the appropriate format id */ + comp = -1; + first = &mgr->infos[0]; + n = mgr->info_cnt; + for (; n > 0; ) { + unsigned half = n / 2; + pjmedia_video_format_info **mid = first + half; + + if ((*mid)->id < id) { + first = ++mid; + n -= half + 1; + } else if ((*mid)->id==id) { + return *mid; + } else { + n = half; + } + } + + return NULL; +} + + +PJ_DEF(pj_status_t) +pjmedia_register_video_format_info(pjmedia_video_format_mgr *mgr, + pjmedia_video_format_info *info) +{ + unsigned i; + + if (!mgr) + mgr = pjmedia_video_format_mgr_instance(); + + PJ_ASSERT_RETURN(mgr != NULL, PJ_EINVALIDOP); + + if (mgr->info_cnt >= mgr->max_info) + return PJ_ETOOMANY; + + /* Insert to the array, sorted */ + for (i=0; i<mgr->info_cnt; ++i) { + if (mgr->infos[i]->id >= info->id) + break; + } + + if (i < mgr->info_cnt) { + if (mgr->infos[i]->id == info->id) { + /* just overwrite */ + mgr->infos[i] = info; + return PJ_SUCCESS; + } + + pj_memmove(&mgr->infos[i+1], &mgr->infos[i], + (mgr->info_cnt - i) * sizeof(pjmedia_video_format_info*)); + } + + mgr->infos[i] = info; + mgr->info_cnt++; + + return PJ_SUCCESS; +} + +PJ_DEF(pjmedia_video_format_mgr*) pjmedia_video_format_mgr_instance(void) +{ + pj_assert(video_format_mgr_instance != NULL); + return video_format_mgr_instance; +} + +PJ_DEF(void) +pjmedia_video_format_mgr_set_instance(pjmedia_video_format_mgr *mgr) +{ + video_format_mgr_instance = mgr; +} + + +PJ_DEF(void) pjmedia_video_format_mgr_destroy(pjmedia_video_format_mgr *mgr) +{ + if (!mgr) + mgr = pjmedia_video_format_mgr_instance(); + + PJ_ASSERT_ON_FAIL(mgr != NULL, return); + + mgr->info_cnt = 0; + if (video_format_mgr_instance == mgr) + video_format_mgr_instance = NULL; +} + diff --git a/pjmedia/src/pjmedia/g711.c b/pjmedia/src/pjmedia/g711.c index 33f6eac6..1a47c53d 100644 --- a/pjmedia/src/pjmedia/g711.c +++ b/pjmedia/src/pjmedia/g711.c @@ -111,7 +111,8 @@ static pjmedia_codec_factory_op g711_factory_op = &g711_default_attr, &g711_enum_codecs, &g711_alloc_codec, - &g711_dealloc_codec + &g711_dealloc_codec, + &pjmedia_codec_g711_deinit }; /* G711 factory private data */ diff --git a/pjmedia/src/pjmedia/jbuf.c b/pjmedia/src/pjmedia/jbuf.c index b85eea61..9ae84b64 100644 --- a/pjmedia/src/pjmedia/jbuf.c +++ b/pjmedia/src/pjmedia/jbuf.c @@ -68,6 +68,7 @@ typedef struct jb_framelist_t int *frame_type; /**< frame type array */ pj_size_t *content_len; /**< frame length array */ pj_uint32_t *bit_info; /**< frame bit info array */ + pj_uint32_t *ts; /**< timestamp array */ /* States */ unsigned head; /**< index of head, pointed frame @@ -197,7 +198,10 @@ static pj_status_t jb_framelist_init( pj_pool_t *pool, pj_pool_alloc(pool, sizeof(framelist->bit_info[0])* framelist->max_count); - + framelist->ts = (pj_uint32_t*) + pj_pool_alloc(pool, + sizeof(framelist->ts[0])* + framelist->max_count); return jb_framelist_reset(framelist); @@ -258,7 +262,9 @@ static int jb_framelist_origin(const jb_framelist_t *framelist) static pj_bool_t jb_framelist_get(jb_framelist_t *framelist, void *frame, pj_size_t *size, pjmedia_jb_frame_type *p_type, - pj_uint32_t *bit_info) + pj_uint32_t *bit_info, + pj_uint32_t *ts, + int *seq) { if (framelist->size) { pj_bool_t prev_discarded = PJ_FALSE; @@ -294,6 +300,10 @@ static pj_bool_t jb_framelist_get(jb_framelist_t *framelist, if (bit_info) *bit_info = framelist->bit_info[framelist->head]; } + if (ts) + *ts = framelist->ts[framelist->head]; + if (seq) + *seq = framelist->origin; //pj_bzero(framelist->content + // framelist->head * framelist->frame_size, @@ -301,6 +311,7 @@ static pj_bool_t jb_framelist_get(jb_framelist_t *framelist, framelist->frame_type[framelist->head] = PJMEDIA_JB_MISSING_FRAME; framelist->content_len[framelist->head] = 0; framelist->bit_info[framelist->head] = 0; + framelist->ts[framelist->head] = 0; framelist->origin++; framelist->head = (framelist->head + 1) % framelist->max_count; @@ -317,6 +328,53 @@ static pj_bool_t jb_framelist_get(jb_framelist_t *framelist, } +static pj_bool_t jb_framelist_peek(jb_framelist_t *framelist, + unsigned offset, + const void **frame, + pj_size_t *size, + pjmedia_jb_frame_type *type, + pj_uint32_t *bit_info, + pj_uint32_t *ts, + int *seq) +{ + unsigned pos, idx; + + if (offset >= jb_framelist_eff_size(framelist)) + return PJ_FALSE; + + pos = framelist->head; + idx = offset; + + /* Find actual peek position, note there may be discarded frames */ + while (1) { + if (framelist->frame_type[pos] != PJMEDIA_JB_DISCARDED_FRAME) { + if (idx == 0) + break; + else + --idx; + } + pos = (pos + 1) % framelist->max_count; + } + + /* Return the frame pointer */ + if (frame) + *frame = framelist->content + pos*framelist->frame_size; + if (type) + *type = (pjmedia_jb_frame_type) + framelist->frame_type[pos]; + if (size) + *size = framelist->content_len[pos]; + if (bit_info) + *bit_info = framelist->bit_info[pos]; + if (ts) + *ts = framelist->ts[pos]; + if (seq) + *seq = framelist->origin + offset; + + return PJ_TRUE; +} + + /* Remove oldest frames as many as param 'count' */ static unsigned jb_framelist_remove_head(jb_framelist_t *framelist, unsigned count) @@ -385,6 +443,7 @@ static pj_status_t jb_framelist_put_at(jb_framelist_t *framelist, const void *frame, unsigned frame_size, pj_uint32_t bit_info, + pj_uint32_t ts, unsigned frame_type) { int distance; @@ -438,6 +497,7 @@ static pj_status_t jb_framelist_put_at(jb_framelist_t *framelist, framelist->frame_type[pos] = frame_type; framelist->content_len[pos] = frame_size; framelist->bit_info[pos] = bit_info; + framelist->ts[pos] = ts; /* update framelist size */ if (framelist->origin + (int)framelist->size <= index) @@ -747,7 +807,7 @@ PJ_DEF(void) pjmedia_jbuf_put_frame( pjmedia_jbuf *jb, pj_size_t frame_size, int frame_seq) { - pjmedia_jbuf_put_frame2(jb, frame, frame_size, 0, frame_seq, NULL); + pjmedia_jbuf_put_frame3(jb, frame, frame_size, 0, frame_seq, 0, NULL); } PJ_DEF(void) pjmedia_jbuf_put_frame2(pjmedia_jbuf *jb, @@ -757,6 +817,18 @@ PJ_DEF(void) pjmedia_jbuf_put_frame2(pjmedia_jbuf *jb, int frame_seq, pj_bool_t *discarded) { + pjmedia_jbuf_put_frame3(jb, frame, frame_size, bit_info, frame_seq, 0, + discarded); +} + +PJ_DEF(void) pjmedia_jbuf_put_frame3(pjmedia_jbuf *jb, + const void *frame, + pj_size_t frame_size, + pj_uint32_t bit_info, + int frame_seq, + pj_uint32_t ts, + pj_bool_t *discarded) +{ pj_size_t min_frame_size; int new_size, cur_size, frame_type = PJMEDIA_JB_NORMAL_FRAME; pj_status_t status; @@ -824,7 +896,7 @@ PJ_DEF(void) pjmedia_jbuf_put_frame2(pjmedia_jbuf *jb, /* Attempt to store the frame */ min_frame_size = PJ_MIN(frame_size, jb->jb_frame_size); status = jb_framelist_put_at(&jb->jb_framelist, frame_seq, frame, - min_frame_size, bit_info, frame_type); + min_frame_size, bit_info, ts, frame_type); /* Jitter buffer is full, remove some older frames */ while (status == PJ_ETOOMANY) { @@ -847,7 +919,7 @@ PJ_DEF(void) pjmedia_jbuf_put_frame2(pjmedia_jbuf *jb, #endif removed = jb_framelist_remove_head(&jb->jb_framelist, distance); status = jb_framelist_put_at(&jb->jb_framelist, frame_seq, frame, - min_frame_size, bit_info, frame_type); + min_frame_size, bit_info, ts, frame_type); jb->jb_discard += removed; } @@ -879,7 +951,8 @@ PJ_DEF(void) pjmedia_jbuf_get_frame( pjmedia_jbuf *jb, void *frame, char *p_frame_type) { - pjmedia_jbuf_get_frame2(jb, frame, NULL, p_frame_type, NULL); + pjmedia_jbuf_get_frame3(jb, frame, NULL, p_frame_type, NULL, + NULL, NULL); } /* @@ -891,6 +964,21 @@ PJ_DEF(void) pjmedia_jbuf_get_frame2(pjmedia_jbuf *jb, char *p_frame_type, pj_uint32_t *bit_info) { + pjmedia_jbuf_get_frame3(jb, frame, size, p_frame_type, bit_info, + NULL, NULL); +} + +/* + * Get frame from jitter buffer. + */ +PJ_DEF(void) pjmedia_jbuf_get_frame3(pjmedia_jbuf *jb, + void *frame, + pj_size_t *size, + char *p_frame_type, + pj_uint32_t *bit_info, + pj_uint32_t *ts, + int *seq) +{ if (jb->jb_status == JB_STATUS_PREFETCHING) { /* Can't return frame because jitter buffer is filling up @@ -914,7 +1002,7 @@ PJ_DEF(void) pjmedia_jbuf_get_frame2(pjmedia_jbuf *jb, /* Try to retrieve a frame from frame list */ res = jb_framelist_get(&jb->jb_framelist, frame, size, &ftype, - bit_info); + bit_info, ts, seq); if (res) { /* We've successfully retrieved a frame from the frame list, but * the frame could be a blank frame! @@ -982,3 +1070,50 @@ PJ_DEF(pj_status_t) pjmedia_jbuf_get_state( const pjmedia_jbuf *jb, return PJ_SUCCESS; } + +PJ_DEF(void) pjmedia_jbuf_peek_frame( pjmedia_jbuf *jb, + unsigned offset, + const void **frame, + pj_size_t *size, + char *p_frm_type, + pj_uint32_t *bit_info, + pj_uint32_t *ts, + int *seq) +{ + pjmedia_jb_frame_type ftype; + pj_bool_t res; + + res = jb_framelist_peek(&jb->jb_framelist, offset, frame, size, &ftype, + bit_info, ts, seq); + if (!res) + *p_frm_type = PJMEDIA_JB_ZERO_EMPTY_FRAME; + else if (ftype == PJMEDIA_JB_NORMAL_FRAME) + *p_frm_type = PJMEDIA_JB_NORMAL_FRAME; + else + *p_frm_type = PJMEDIA_JB_MISSING_FRAME; +} + + +PJ_DEF(unsigned) pjmedia_jbuf_remove_frame(pjmedia_jbuf *jb, + unsigned frame_cnt) +{ + unsigned count, last_discard_num; + + last_discard_num = jb->jb_framelist.discarded_num; + count = jb_framelist_remove_head(&jb->jb_framelist, frame_cnt); + + /* Remove some more when there were discarded frames included */ + while (jb->jb_framelist.discarded_num < last_discard_num) { + /* Calculate frames count to be removed next */ + frame_cnt = last_discard_num - jb->jb_framelist.discarded_num; + + /* Normalize non-discarded frames count just been removed */ + count -= frame_cnt; + + /* Remove more frames */ + last_discard_num = jb->jb_framelist.discarded_num; + count += jb_framelist_remove_head(&jb->jb_framelist, frame_cnt); + } + + return count; +} diff --git a/pjmedia/src/pjmedia/master_port.c b/pjmedia/src/pjmedia/master_port.c index a59796b7..25a9e995 100644 --- a/pjmedia/src/pjmedia/master_port.c +++ b/pjmedia/src/pjmedia/master_port.c @@ -56,38 +56,41 @@ PJ_DEF(pj_status_t) pjmedia_master_port_create( pj_pool_t *pool, unsigned channel_count; unsigned samples_per_frame; unsigned bytes_per_frame; + pjmedia_audio_format_detail *u_afd, *d_afd; pj_status_t status; /* Sanity check */ PJ_ASSERT_RETURN(pool && u_port && d_port && p_m, PJ_EINVAL); + u_afd = pjmedia_format_get_audio_format_detail(&u_port->info.fmt, PJ_TRUE); + d_afd = pjmedia_format_get_audio_format_detail(&d_port->info.fmt, PJ_TRUE); /* Both ports MUST have equal clock rate */ - PJ_ASSERT_RETURN(u_port->info.clock_rate == d_port->info.clock_rate, + PJ_ASSERT_RETURN(u_afd->clock_rate == d_afd->clock_rate, PJMEDIA_ENCCLOCKRATE); /* Both ports MUST have equal samples per frame */ - PJ_ASSERT_RETURN(u_port->info.samples_per_frame== - d_port->info.samples_per_frame, + PJ_ASSERT_RETURN(PJMEDIA_PIA_SPF(&u_port->info)== + PJMEDIA_PIA_SPF(&d_port->info), PJMEDIA_ENCSAMPLESPFRAME); /* Both ports MUST have equal channel count */ - PJ_ASSERT_RETURN(u_port->info.channel_count == d_port->info.channel_count, + PJ_ASSERT_RETURN(u_afd->channel_count == d_afd->channel_count, PJMEDIA_ENCCHANNEL); /* Get clock_rate and samples_per_frame from one of the port. */ - clock_rate = u_port->info.clock_rate; - samples_per_frame = u_port->info.samples_per_frame; - channel_count = u_port->info.channel_count; + clock_rate = u_afd->clock_rate; + samples_per_frame = PJMEDIA_PIA_SPF(&u_port->info); + channel_count = u_afd->channel_count; /* Get the bytes_per_frame value, to determine the size of the * buffer. We take the larger size of the two ports. */ - bytes_per_frame = u_port->info.bytes_per_frame; - if (d_port->info.bytes_per_frame > bytes_per_frame) - bytes_per_frame = d_port->info.bytes_per_frame; + bytes_per_frame = PJMEDIA_AFD_AVG_FSZ(u_afd); + if (PJMEDIA_AFD_AVG_FSZ(d_afd) > bytes_per_frame) + bytes_per_frame = PJMEDIA_AFD_AVG_FSZ(d_afd); /* Create the master port instance */ @@ -207,13 +210,16 @@ PJ_DEF(pj_status_t) pjmedia_master_port_set_uport(pjmedia_master_port *m, { PJ_ASSERT_RETURN(m && port, PJ_EINVAL); + /* Only supports audio for now */ + PJ_ASSERT_RETURN(port->info.fmt.type==PJMEDIA_TYPE_AUDIO, PJ_ENOTSUP); + /* If we have downstream port, make sure they have matching samples per * frame. */ if (m->d_port) { PJ_ASSERT_RETURN( - port->info.clock_rate/port->info.samples_per_frame== - m->d_port->info.clock_rate/m->d_port->info.samples_per_frame, + PJMEDIA_PIA_PTIME(&port->info) == + PJMEDIA_PIA_PTIME(&m->d_port->info), PJMEDIA_ENCSAMPLESPFRAME ); } @@ -246,13 +252,16 @@ PJ_DEF(pj_status_t) pjmedia_master_port_set_dport(pjmedia_master_port *m, { PJ_ASSERT_RETURN(m && port, PJ_EINVAL); + /* Only supports audio for now */ + PJ_ASSERT_RETURN(port->info.fmt.type==PJMEDIA_TYPE_AUDIO, PJ_ENOTSUP); + /* If we have upstream port, make sure they have matching samples per * frame. */ if (m->u_port) { PJ_ASSERT_RETURN( - port->info.clock_rate/port->info.samples_per_frame== - m->u_port->info.clock_rate/m->u_port->info.samples_per_frame, + PJMEDIA_PIA_PTIME(&port->info) == + PJMEDIA_PIA_PTIME(&m->u_port->info), PJMEDIA_ENCSAMPLESPFRAME ); } diff --git a/pjmedia/src/pjmedia/mem_capture.c b/pjmedia/src/pjmedia/mem_capture.c index fe01cd42..a6f9a99e 100644 --- a/pjmedia/src/pjmedia/mem_capture.c +++ b/pjmedia/src/pjmedia/mem_capture.c @@ -25,7 +25,7 @@ #define THIS_FILE "mem_capture.c" -#define SIGNATURE PJMEDIA_PORT_SIGNATURE('M', 'R', 'e', 'c') +#define SIGNATURE PJMEDIA_SIG_PORT_MEM_CAPTURE #define BYTES_PER_SAMPLE 2 struct mem_rec @@ -46,7 +46,7 @@ struct mem_rec static pj_status_t rec_put_frame(pjmedia_port *this_port, - const pjmedia_frame *frame); + pjmedia_frame *frame); static pj_status_t rec_get_frame(pjmedia_port *this_port, pjmedia_frame *frame); static pj_status_t rec_on_destroy(pjmedia_port *this_port); @@ -140,7 +140,7 @@ PJ_DEF(pj_size_t) pjmedia_mem_capture_get_size(pjmedia_port *port) static pj_status_t rec_put_frame( pjmedia_port *this_port, - const pjmedia_frame *frame) + pjmedia_frame *frame) { struct mem_rec *rec; char *endpos; diff --git a/pjmedia/src/pjmedia/mem_player.c b/pjmedia/src/pjmedia/mem_player.c index c32bcb09..b0b6fa9c 100644 --- a/pjmedia/src/pjmedia/mem_player.c +++ b/pjmedia/src/pjmedia/mem_player.c @@ -25,7 +25,7 @@ #define THIS_FILE "mem_player.c" -#define SIGNATURE PJMEDIA_PORT_SIGNATURE('M', 'P', 'l', 'y') +#define SIGNATURE PJMEDIA_SIG_PORT_MEM_PLAYER #define BYTES_PER_SAMPLE 2 struct mem_player @@ -48,7 +48,7 @@ struct mem_player static pj_status_t mem_put_frame(pjmedia_port *this_port, - const pjmedia_frame *frame); + pjmedia_frame *frame); static pj_status_t mem_get_frame(pjmedia_port *this_port, pjmedia_frame *frame); static pj_status_t mem_on_destroy(pjmedia_port *this_port); @@ -125,7 +125,7 @@ PJ_DEF(pj_status_t) pjmedia_mem_player_set_eof_cb( pjmedia_port *port, static pj_status_t mem_put_frame( pjmedia_port *this_port, - const pjmedia_frame *frame) + pjmedia_frame *frame) { PJ_UNUSED_ARG(this_port); PJ_UNUSED_ARG(frame); @@ -165,7 +165,7 @@ static pj_status_t mem_get_frame( pjmedia_port *this_port, player->eof = PJ_FALSE; } - size_needed = this_port->info.bytes_per_frame; + size_needed = PJMEDIA_PIA_AVG_FSZ(&this_port->info); size_written = 0; endpos = player->buffer + player->buf_size; @@ -200,11 +200,11 @@ static pj_status_t mem_get_frame( pjmedia_port *this_port, } } - frame->size = this_port->info.bytes_per_frame; + frame->size = PJMEDIA_PIA_AVG_FSZ(&this_port->info); frame->timestamp.u64 = player->timestamp.u64; frame->type = PJMEDIA_FRAME_TYPE_AUDIO; - player->timestamp.u64 += this_port->info.samples_per_frame; + player->timestamp.u64 += PJMEDIA_PIA_SPF(&this_port->info); return PJ_SUCCESS; } diff --git a/pjmedia/src/pjmedia/null_port.c b/pjmedia/src/pjmedia/null_port.c index be9ec97a..97b8a8b0 100644 --- a/pjmedia/src/pjmedia/null_port.c +++ b/pjmedia/src/pjmedia/null_port.c @@ -24,12 +24,12 @@ #include <pj/string.h> -#define SIGNATURE PJMEDIA_PORT_SIGNATURE('N', 'U', 'L', 'L') +#define SIGNATURE PJMEDIA_SIG_PORT_NULL static pj_status_t null_get_frame(pjmedia_port *this_port, pjmedia_frame *frame); static pj_status_t null_put_frame(pjmedia_port *this_port, - const pjmedia_frame *frame); + pjmedia_frame *frame); static pj_status_t null_on_destroy(pjmedia_port *this_port); @@ -67,7 +67,7 @@ PJ_DEF(pj_status_t) pjmedia_null_port_create( pj_pool_t *pool, * Put frame to file. */ static pj_status_t null_put_frame(pjmedia_port *this_port, - const pjmedia_frame *frame) + pjmedia_frame *frame) { PJ_UNUSED_ARG(this_port); PJ_UNUSED_ARG(frame); @@ -82,10 +82,10 @@ static pj_status_t null_get_frame(pjmedia_port *this_port, pjmedia_frame *frame) { frame->type = PJMEDIA_FRAME_TYPE_AUDIO; - frame->size = this_port->info.samples_per_frame * 2; - frame->timestamp.u32.lo += this_port->info.samples_per_frame; + frame->size = PJMEDIA_PIA_AVG_FSZ(&this_port->info); + frame->timestamp.u32.lo += PJMEDIA_PIA_SPF(&this_port->info); pjmedia_zero_samples((pj_int16_t*)frame->buf, - this_port->info.samples_per_frame); + PJMEDIA_PIA_SPF(&this_port->info)); return PJ_SUCCESS; } diff --git a/pjmedia/src/pjmedia/port.c b/pjmedia/src/pjmedia/port.c index 1db5c742..e838263a 100644 --- a/pjmedia/src/pjmedia/port.c +++ b/pjmedia/src/pjmedia/port.c @@ -21,6 +21,7 @@ #include <pjmedia/errno.h> #include <pj/assert.h> #include <pj/log.h> +#include <pj/pool.h> #define THIS_FILE "port.c" @@ -37,24 +38,53 @@ PJ_DEF(pj_status_t) pjmedia_port_info_init( pjmedia_port_info *info, unsigned bits_per_sample, unsigned samples_per_frame) { +#define USEC_IN_SEC (pj_uint64_t)1000000 + unsigned frame_time_usec, avg_bps; + pj_bzero(info, sizeof(*info)); + info->signature = signature; + info->dir = PJMEDIA_DIR_ENCODING_DECODING; info->name = *name; + + frame_time_usec = (unsigned)(samples_per_frame * USEC_IN_SEC / + channel_count / clock_rate); + avg_bps = clock_rate * channel_count * bits_per_sample; + + pjmedia_format_init_audio(&info->fmt, PJMEDIA_FORMAT_L16, clock_rate, + channel_count, bits_per_sample, frame_time_usec, + avg_bps, avg_bps); + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjmedia_port_info_init2( pjmedia_port_info *info, + const pj_str_t *name, + unsigned signature, + pjmedia_dir dir, + const pjmedia_format *fmt) +{ + pj_bzero(info, sizeof(*info)); info->signature = signature; - info->type = PJMEDIA_TYPE_AUDIO; - info->has_info = PJ_TRUE; - info->need_info = PJ_FALSE; - info->pt = 0xFF; - info->encoding_name = pj_str("pcm"); - info->clock_rate = clock_rate; - info->channel_count = channel_count; - info->bits_per_sample = bits_per_sample; - info->samples_per_frame = samples_per_frame; - info->bytes_per_frame = samples_per_frame * bits_per_sample / 8; + info->dir = dir; + info->name = *name; + + pjmedia_format_copy(&info->fmt, fmt); return PJ_SUCCESS; } +/** + * Get a clock source from the port. + */ +PJ_DEF(pjmedia_clock_src *) pjmedia_port_get_clock_src( pjmedia_port *port, + pjmedia_dir dir ) +{ + if (port && port->get_clock_src) + return port->get_clock_src(port, dir); + else + return NULL; +} /** * Get a frame from the port (and subsequent downstream ports). @@ -77,7 +107,7 @@ PJ_DEF(pj_status_t) pjmedia_port_get_frame( pjmedia_port *port, * Put a frame to the port (and subsequent downstream ports). */ PJ_DEF(pj_status_t) pjmedia_port_put_frame( pjmedia_port *port, - const pjmedia_frame *frame ) + pjmedia_frame *frame ) { PJ_ASSERT_RETURN(port && frame, PJ_EINVAL); @@ -87,6 +117,19 @@ PJ_DEF(pj_status_t) pjmedia_port_put_frame( pjmedia_port *port, return PJ_EINVALIDOP; } +/* + * Get event publisher + */ +PJ_DEF(pjmedia_event_publisher*) +pjmedia_port_get_event_publisher(pjmedia_port *port) +{ + PJ_ASSERT_RETURN(port, NULL); + + if (port->get_event_pub) + return (*port->get_event_pub)(port); + + return NULL; +} /** * Destroy port (and subsequent downstream ports) diff --git a/pjmedia/src/pjmedia/resample_port.c b/pjmedia/src/pjmedia/resample_port.c index 2f907129..5991877e 100644 --- a/pjmedia/src/pjmedia/resample_port.c +++ b/pjmedia/src/pjmedia/resample_port.c @@ -25,7 +25,7 @@ #define BYTES_PER_SAMPLE 2 -#define SIGNATURE PJMEDIA_PORT_SIGNATURE('R','S','M','P') +#define SIGNATURE PJMEDIA_SIG_PORT_RESAMPLE struct resample_port @@ -42,7 +42,7 @@ struct resample_port static pj_status_t resample_put_frame(pjmedia_port *this_port, - const pjmedia_frame *frame); + pjmedia_frame *frame); static pj_status_t resample_get_frame(pjmedia_port *this_port, pjmedia_frame *frame); static pj_status_t resample_destroy(pjmedia_port *this_port); @@ -57,40 +57,40 @@ PJ_DEF(pj_status_t) pjmedia_resample_port_create( pj_pool_t *pool, { const pj_str_t name = pj_str("resample"); struct resample_port *rport; - unsigned ptime; + pjmedia_audio_format_detail *d_afd, *r_afd; pj_status_t status; /* Validate arguments. */ PJ_ASSERT_RETURN(pool && dn_port && clock_rate && p_port, PJ_EINVAL); /* Only supports 16bit samples per frame */ - PJ_ASSERT_RETURN(dn_port->info.bits_per_sample == 16, PJMEDIA_ENCBITS); + PJ_ASSERT_RETURN(PJMEDIA_PIA_BITS(&dn_port->info) == 16, PJMEDIA_ENCBITS); + + d_afd = pjmedia_format_get_audio_format_detail(&dn_port->info.fmt, 1); - ptime = dn_port->info.samples_per_frame * 1000 / - dn_port->info.clock_rate; - /* Create and initialize port. */ rport = PJ_POOL_ZALLOC_T(pool, struct resample_port); PJ_ASSERT_RETURN(rport != NULL, PJ_ENOMEM); pjmedia_port_info_init(&rport->base.info, &name, SIGNATURE, clock_rate, - dn_port->info.channel_count, BYTES_PER_SAMPLE * 8, - clock_rate * ptime / 1000); + d_afd->channel_count, BYTES_PER_SAMPLE * 8, + clock_rate * d_afd->frame_time_usec / 1000000); rport->dn_port = dn_port; rport->options = opt; + r_afd = pjmedia_format_get_audio_format_detail(&rport->base.info.fmt, 1); /* Create buffers. * We need separate buffer for get_frame() and put_frame() since * both functions may run simultaneously. */ rport->get_buf = (pj_int16_t*) - pj_pool_alloc(pool, dn_port->info.bytes_per_frame); + pj_pool_alloc(pool, PJMEDIA_PIA_AVG_FSZ(&dn_port->info)); PJ_ASSERT_RETURN(rport->get_buf != NULL, PJ_ENOMEM); rport->put_buf = (pj_int16_t*) - pj_pool_alloc(pool, dn_port->info.bytes_per_frame); + pj_pool_alloc(pool, PJMEDIA_PIA_AVG_FSZ(&dn_port->info)); PJ_ASSERT_RETURN(rport->put_buf != NULL, PJ_ENOMEM); @@ -98,10 +98,10 @@ PJ_DEF(pj_status_t) pjmedia_resample_port_create( pj_pool_t *pool, status = pjmedia_resample_create(pool, (opt&PJMEDIA_RESAMPLE_USE_LINEAR)==0, (opt&PJMEDIA_RESAMPLE_USE_SMALL_FILTER)==0, - dn_port->info.channel_count, - dn_port->info.clock_rate, - rport->base.info.clock_rate, - dn_port->info.samples_per_frame, + d_afd->channel_count, + d_afd->clock_rate, + r_afd->clock_rate, + PJMEDIA_PIA_SPF(&dn_port->info), &rport->resample_get); if (status != PJ_SUCCESS) return status; @@ -110,10 +110,10 @@ PJ_DEF(pj_status_t) pjmedia_resample_port_create( pj_pool_t *pool, status = pjmedia_resample_create(pool, (opt&PJMEDIA_RESAMPLE_USE_LINEAR)==0, (opt&PJMEDIA_RESAMPLE_USE_SMALL_FILTER)==0, - dn_port->info.channel_count, - rport->base.info.clock_rate, - dn_port->info.clock_rate, - rport->base.info.samples_per_frame, + d_afd->channel_count, + r_afd->clock_rate, + d_afd->clock_rate, + PJMEDIA_PIA_SPF(&rport->base.info), &rport->resample_put); /* Media port interface */ @@ -131,7 +131,7 @@ PJ_DEF(pj_status_t) pjmedia_resample_port_create( pj_pool_t *pool, static pj_status_t resample_put_frame(pjmedia_port *this_port, - const pjmedia_frame *frame) + pjmedia_frame *frame) { struct resample_port *rport = (struct resample_port*) this_port; pjmedia_frame downstream_frame; @@ -147,7 +147,7 @@ static pj_status_t resample_put_frame(pjmedia_port *this_port, rport->put_buf); downstream_frame.buf = rport->put_buf; - downstream_frame.size = rport->dn_port->info.bytes_per_frame; + downstream_frame.size = PJMEDIA_PIA_AVG_FSZ(&rport->dn_port->info); } else { downstream_frame.buf = frame->buf; downstream_frame.size = frame->size; @@ -175,7 +175,7 @@ static pj_status_t resample_get_frame(pjmedia_port *this_port, } tmp_frame.buf = rport->get_buf; - tmp_frame.size = rport->dn_port->info.bytes_per_frame; + tmp_frame.size = PJMEDIA_PIA_AVG_FSZ(&rport->dn_port->info); tmp_frame.timestamp.u64 = frame->timestamp.u64; tmp_frame.type = PJMEDIA_FRAME_TYPE_AUDIO; @@ -187,8 +187,8 @@ static pj_status_t resample_get_frame(pjmedia_port *this_port, frame->type = tmp_frame.type; frame->timestamp = tmp_frame.timestamp; /* Copy whatever returned as long as the buffer size is enough */ - frame->size = tmp_frame.size < rport->base.info.bytes_per_frame ? - tmp_frame.size : rport->base.info.bytes_per_frame; + frame->size = tmp_frame.size < PJMEDIA_PIA_AVG_FSZ(&rport->base.info) ? + tmp_frame.size : PJMEDIA_PIA_AVG_FSZ(&rport->base.info); if (tmp_frame.size) { pjmedia_copy_samples((pj_int16_t*)frame->buf, (const pj_int16_t*)tmp_frame.buf, @@ -201,7 +201,7 @@ static pj_status_t resample_get_frame(pjmedia_port *this_port, (const pj_int16_t*) tmp_frame.buf, (pj_int16_t*) frame->buf); - frame->size = rport->base.info.bytes_per_frame; + frame->size = PJMEDIA_PIA_AVG_FSZ(&rport->base.info); frame->type = PJMEDIA_FRAME_TYPE_AUDIO; return PJ_SUCCESS; diff --git a/pjmedia/src/pjmedia/sdp_cmp.c b/pjmedia/src/pjmedia/sdp_cmp.c index ce6c9403..b4a23c74 100644 --- a/pjmedia/src/pjmedia/sdp_cmp.c +++ b/pjmedia/src/pjmedia/sdp_cmp.c @@ -295,3 +295,10 @@ PJ_DEF(pj_status_t) pjmedia_sdp_session_cmp( const pjmedia_sdp_session *sd1, } +PJ_DEF(pj_status_t) pjmedia_sdp_conn_cmp(const pjmedia_sdp_conn *conn1, + const pjmedia_sdp_conn *conn2, + unsigned option) +{ + PJ_UNUSED_ARG(option); + return compare_conn(conn1, conn2); +} diff --git a/pjmedia/src/pjmedia/sdp_neg.c b/pjmedia/src/pjmedia/sdp_neg.c index 2e3dde6d..dbba836d 100644 --- a/pjmedia/src/pjmedia/sdp_neg.c +++ b/pjmedia/src/pjmedia/sdp_neg.c @@ -52,7 +52,7 @@ static const char *state_str[] = "STATE_DONE", }; -#define GET_FMTP_IVAL(ival, fmtp, param, default_val) \ +#define GET_FMTP_IVAL_BASE(ival, base, fmtp, param, default_val) \ do { \ pj_str_t s; \ char *p; \ @@ -63,9 +63,39 @@ static const char *state_str[] = } \ pj_strset(&s, p + param.slen, fmtp.fmt_param.slen - \ (p - fmtp.fmt_param.ptr) - param.slen); \ - ival = pj_strtoul(&s); \ + ival = pj_strtoul2(&s, NULL, base); \ } while (0) +#define GET_FMTP_IVAL(ival, fmtp, param, default_val) \ + GET_FMTP_IVAL_BASE(ival, 10, fmtp, param, default_val) + + +/* Definition of customized SDP format negotiation callback */ +struct fmt_match_cb_t +{ + pj_str_t fmt_name; + pjmedia_sdp_neg_fmt_match_cb cb; +}; + +/* Number of registered customized SDP format negotiation callbacks */ +static unsigned fmt_match_cb_cnt; + +/* The registered customized SDP format negotiation callbacks */ +static struct fmt_match_cb_t + fmt_match_cb[PJMEDIA_SDP_NEG_MAX_CUSTOM_FMT_NEG_CB]; + +/* Redefining a very long identifier name, just for convenience */ +#define ALLOW_MODIFY_ANSWER PJMEDIA_SDP_NEG_FMT_MATCH_ALLOW_MODIFY_ANSWER + +static pj_status_t custom_fmt_match( pj_pool_t *pool, + const pj_str_t *fmt_name, + pjmedia_sdp_media *offer, + unsigned o_fmt_idx, + pjmedia_sdp_media *answer, + unsigned a_fmt_idx, + unsigned option); + + /* * Get string representation of negotiator state. */ @@ -231,6 +261,31 @@ PJ_DEF(pj_status_t) pjmedia_sdp_neg_get_neg_local( pjmedia_sdp_neg *neg, return PJ_SUCCESS; } +static pjmedia_sdp_media *sdp_media_clone_deactivate( + pj_pool_t *pool, + const pjmedia_sdp_media *rem_med, + const pjmedia_sdp_media *local_med, + const pjmedia_sdp_session *local_sess) +{ + pjmedia_sdp_media *res; + + res = pjmedia_sdp_media_clone_deactivate(pool, rem_med); + if (!res) + return NULL; + + if (!res->conn && (!local_sess || !local_sess->conn)) { + if (local_med && local_med->conn) + res->conn = pjmedia_sdp_conn_clone(pool, local_med->conn); + else { + res->conn = PJ_POOL_ZALLOC_T(pool, pjmedia_sdp_conn); + res->conn->net_type = pj_str("IN"); + res->conn->addr_type = pj_str("IP4"); + res->conn->addr = pj_str("127.0.0.1"); + } + } + + return res; +} /* * Modify local SDP and wait for remote answer. @@ -311,7 +366,7 @@ PJ_DEF(pj_status_t) pjmedia_sdp_neg_modify_local_offer( pj_pool_t *pool, if (!found) { pjmedia_sdp_media *m; - m = pjmedia_sdp_media_clone_deactivate(pool, om); + m = sdp_media_clone_deactivate(pool, om, om, local); pj_array_insert(new_offer->media, sizeof(new_offer->media[0]), new_offer->media_count++, oi, &m); @@ -876,10 +931,12 @@ static pj_status_t process_m_answer( pj_pool_t *pool, (ar.param.slen==1 && *ar.param.ptr=='1'))) { /* Further check for G7221, negotiate bitrate. */ - if (pj_stricmp2(&or_.enc_name, "G7221") == 0) { + if (pj_stricmp2(&or_.enc_name, "G7221") == 0) + { if (match_g7221(offer, i, answer, j)) break; } else + /* Further check for AMR, negotiate fmtp. */ if (pj_stricmp2(&or_.enc_name, "AMR") == 0 || pj_stricmp2(&or_.enc_name, "AMR-WB") == 0) @@ -887,7 +944,13 @@ static pj_status_t process_m_answer( pj_pool_t *pool, if (match_amr(offer, i, answer, j, PJ_FALSE, NULL)) break; - } else { + } else + + /* Call custom format matching callbacks */ + if (custom_fmt_match(pool, &or_.enc_name, + offer, i, answer, j, 0) == + PJ_SUCCESS) + { /* Match! */ break; } @@ -1016,7 +1079,8 @@ static pj_status_t process_answer(pj_pool_t *pool, pjmedia_sdp_media *am; /* Generate matching-but-disabled-media for the answer */ - am = pjmedia_sdp_media_clone_deactivate(pool, offer->media[omi]); + am = sdp_media_clone_deactivate(pool, offer->media[omi], + offer->media[omi], offer); answer->media[answer->media_count++] = am; ++ami; @@ -1078,7 +1142,7 @@ static pj_status_t match_offer(pj_pool_t *pool, /* If offer has zero port, just clone the offer */ if (offer->desc.port == 0) { - answer = pjmedia_sdp_media_clone_deactivate(pool, offer); + answer = sdp_media_clone_deactivate(pool, offer, preanswer, NULL); *p_answer = answer; return PJ_SUCCESS; } @@ -1181,12 +1245,31 @@ static pj_status_t match_offer(pj_pool_t *pool, { /* Match! */ if (is_codec) { + pjmedia_sdp_media *o, *a; + unsigned o_fmt_idx, a_fmt_idx; + + o = (pjmedia_sdp_media*)offer; + a = (pjmedia_sdp_media*)preanswer; + o_fmt_idx = prefer_remote_codec_order? i:j; + a_fmt_idx = prefer_remote_codec_order? j:i; + + /* Call custom format matching callbacks */ + if (custom_fmt_match(pool, &or_.enc_name, + o, o_fmt_idx, + a, a_fmt_idx, + ALLOW_MODIFY_ANSWER) != + PJ_SUCCESS) + { + continue; + } else + /* Further check for G7221, negotiate bitrate */ if (pj_stricmp2(&or_.enc_name, "G7221") == 0 && !match_g7221(master, i, slave, j)) { continue; - } else + } else + /* Further check for AMR, negotiate fmtp */ if (pj_stricmp2(&or_.enc_name, "AMR")==0 || pj_stricmp2(&or_.enc_name, "AMR-WB")==0) @@ -1200,6 +1283,7 @@ static pj_status_t match_offer(pj_pool_t *pool, PJ_TRUE, &pt_amr_need_adapt)) continue; } + found_matching_codec = 1; } else { found_matching_telephone_event = 1; @@ -1371,7 +1455,7 @@ static pj_status_t create_answer( pj_pool_t *pool, * ignore anything in the media once it sees that the port * number is zero. */ - am = pjmedia_sdp_media_clone_deactivate(pool, om); + am = sdp_media_clone_deactivate(pool, om, om, answer); } else { /* The answer is in am */ pj_assert(am != NULL); @@ -1473,3 +1557,134 @@ PJ_DEF(pj_status_t) pjmedia_sdp_neg_negotiate( pj_pool_t *pool, return status; } + +static pj_status_t custom_fmt_match(pj_pool_t *pool, + const pj_str_t *fmt_name, + pjmedia_sdp_media *offer, + unsigned o_fmt_idx, + pjmedia_sdp_media *answer, + unsigned a_fmt_idx, + unsigned option) +{ + unsigned i; + + for (i = 0; i < fmt_match_cb_cnt; ++i) { + if (pj_stricmp(fmt_name, &fmt_match_cb[i].fmt_name) == 0) { + pj_assert(fmt_match_cb[i].cb); + return (*fmt_match_cb[i].cb)(pool, offer, o_fmt_idx, + answer, a_fmt_idx, + option); + } + } + + /* Not customized format matching found, should be matched */ + return PJ_SUCCESS; +} + +/* Register customized SDP format negotiation callback function. */ +PJ_DECL(pj_status_t) pjmedia_sdp_neg_register_fmt_match_cb( + const pj_str_t *fmt_name, + pjmedia_sdp_neg_fmt_match_cb cb) +{ + struct fmt_match_cb_t *f = NULL; + unsigned i; + + PJ_ASSERT_RETURN(fmt_name, PJ_EINVAL); + + /* Check if the callback for the format name has been registered */ + for (i = 0; i < fmt_match_cb_cnt; ++i) { + if (pj_stricmp(fmt_name, &fmt_match_cb[i].fmt_name) == 0) + break; + } + + /* Unregistration */ + + if (cb == NULL) { + if (i == fmt_match_cb_cnt) + return PJ_ENOTFOUND; + + pj_array_erase(fmt_match_cb, sizeof(fmt_match_cb[0]), + fmt_match_cb_cnt, i); + fmt_match_cb_cnt--; + + return PJ_SUCCESS; + } + + /* Registration */ + + if (i < fmt_match_cb_cnt) { + /* The same format name has been registered before */ + if (cb != fmt_match_cb[i].cb) + return PJ_EEXISTS; + else + return PJ_SUCCESS; + } + + if (fmt_match_cb_cnt >= PJ_ARRAY_SIZE(fmt_match_cb)) + return PJ_ETOOMANY; + + f = &fmt_match_cb[fmt_match_cb_cnt++]; + f->fmt_name = *fmt_name; + f->cb = cb; + + return PJ_SUCCESS; +} + + +/* Match format in the SDP media offer and answer. */ +PJ_DEF(pj_bool_t) pjmedia_sdp_neg_fmt_match( pj_pool_t *pool, + pjmedia_sdp_media *offer, + unsigned o_fmt_idx, + pjmedia_sdp_media *answer, + unsigned a_fmt_idx, + unsigned option) +{ + const pjmedia_sdp_attr *attr; + pjmedia_sdp_rtpmap o_rtpmap, a_rtpmap; + + /* Get the format rtpmap from the offer. */ + attr = pjmedia_sdp_media_find_attr2(offer, "rtpmap", + &offer->desc.fmt[o_fmt_idx]); + if (!attr) { + pj_assert(!"Bug! Offer haven't been validated"); + return PJ_EBUG; + } + pjmedia_sdp_attr_get_rtpmap(attr, &o_rtpmap); + + /* Get the format rtpmap from the answer. */ + attr = pjmedia_sdp_media_find_attr2(answer, "rtpmap", + &answer->desc.fmt[a_fmt_idx]); + if (!attr) { + pj_assert(!"Bug! Answer haven't been validated"); + return PJ_EBUG; + } + pjmedia_sdp_attr_get_rtpmap(attr, &a_rtpmap); + + if (pj_stricmp(&o_rtpmap.enc_name, &a_rtpmap.enc_name) != 0 || + o_rtpmap.clock_rate != a_rtpmap.clock_rate) + { + return PJMEDIA_SDP_EFORMATNOTEQUAL; + } + + /* Further check for G7221, negotiate bitrate. */ + if (pj_stricmp2(&o_rtpmap.enc_name, "G7221") == 0) { + if (match_g7221(offer, o_fmt_idx, answer, a_fmt_idx)) + return PJ_SUCCESS; + else + return PJMEDIA_SDP_EFORMATNOTEQUAL; + } else + /* Further check for AMR, negotiate fmtp. */ + if (pj_stricmp2(&o_rtpmap.enc_name, "AMR") == 0 || + pj_stricmp2(&o_rtpmap.enc_name, "AMR-WB") == 0) + { + if (match_amr(offer, o_fmt_idx, answer, a_fmt_idx, PJ_FALSE, NULL)) + return PJ_SUCCESS; + else + return PJMEDIA_SDP_EFORMATNOTEQUAL; + } + PJ_TODO(replace_hardcoded_fmt_match_in_sdp_neg_with_custom_fmt_match_cb); + + return custom_fmt_match(pool, &o_rtpmap.enc_name, + offer, o_fmt_idx, answer, a_fmt_idx, option); +} + diff --git a/pjmedia/src/pjmedia/session.c b/pjmedia/src/pjmedia/session.c index 34dc19fc..48b42af9 100644 --- a/pjmedia/src/pjmedia/session.c +++ b/pjmedia/src/pjmedia/session.c @@ -140,7 +140,7 @@ static void parse_fmtp( pj_pool_t *pool, } } - +#if 0 // Moved to stream.c /* * Create stream info from SDP media line. */ @@ -604,7 +604,7 @@ PJ_DEF(pj_status_t) pjmedia_stream_info_from_sdp( return PJ_SUCCESS; } - +#endif /* * Initialize session info from SDP session descriptors. diff --git a/pjmedia/src/pjmedia/silencedet.c b/pjmedia/src/pjmedia/silencedet.c index b351c3eb..e0342241 100644 --- a/pjmedia/src/pjmedia/silencedet.c +++ b/pjmedia/src/pjmedia/silencedet.c @@ -23,6 +23,7 @@ #include <pj/assert.h> #include <pj/log.h> #include <pj/pool.h> +#include <pj/string.h> #define THIS_FILE "silencedet.c" diff --git a/pjmedia/src/pjmedia/sound_port.c b/pjmedia/src/pjmedia/sound_port.c index 5924720d..e377a1ed 100644 --- a/pjmedia/src/pjmedia/sound_port.c +++ b/pjmedia/src/pjmedia/sound_port.c @@ -44,6 +44,9 @@ struct pjmedia_snd_port pjmedia_dir dir; pjmedia_port *port; + pjmedia_clock_src cap_clocksrc, + play_clocksrc; + unsigned clock_rate; unsigned channel_count; unsigned samples_per_frame; @@ -70,6 +73,8 @@ static pj_status_t play_cb(void *user_data, pjmedia_frame *frame) const unsigned required_size = frame->size; pj_status_t status; + pjmedia_clock_src_update(&snd_port->play_clocksrc, &frame->timestamp); + port = snd_port->port; if (port == NULL) goto no_frame; @@ -127,6 +132,8 @@ static pj_status_t rec_cb(void *user_data, pjmedia_frame *frame) pjmedia_snd_port *snd_port = (pjmedia_snd_port*) user_data; pjmedia_port *port; + pjmedia_clock_src_update(&snd_port->cap_clocksrc, &frame->timestamp); + port = snd_port->port; if (port == NULL) return PJ_SUCCESS; @@ -138,6 +145,7 @@ static pj_status_t rec_cb(void *user_data, pjmedia_frame *frame) pjmedia_port_put_frame(port, frame); + return PJ_SUCCESS; } @@ -423,6 +431,7 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_create2(pj_pool_t *pool, { pjmedia_snd_port *snd_port; pj_status_t status; + unsigned ptime_usec; PJ_ASSERT_RETURN(pool && prm && p_port, PJ_EINVAL); @@ -439,6 +448,13 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_create2(pj_pool_t *pool, snd_port->bits_per_sample = prm->base.bits_per_sample; pj_memcpy(&snd_port->aud_param, prm, sizeof(snd_port->aud_param)); snd_port->options = prm->options; + + ptime_usec = prm->base.samples_per_frame * 1000 / prm->base.channel_count / + prm->base.clock_rate * 1000; + pjmedia_clock_src_init(&snd_port->cap_clocksrc, PJMEDIA_TYPE_AUDIO, + snd_port->clock_rate, ptime_usec); + pjmedia_clock_src_init(&snd_port->play_clocksrc, PJMEDIA_TYPE_AUDIO, + snd_port->clock_rate, ptime_usec); /* Start sound device immediately. * If there's no port connected, the sound callback will return @@ -639,29 +655,42 @@ PJ_DEF(pj_status_t) pjmedia_snd_port_get_ec_tail( pjmedia_snd_port *snd_port, /* + * Get clock source. + */ +PJ_DEF(pjmedia_clock_src *) +pjmedia_snd_port_get_clock_src( pjmedia_snd_port *snd_port, + pjmedia_dir dir ) +{ + return (dir == PJMEDIA_DIR_CAPTURE? &snd_port->cap_clocksrc: + &snd_port->play_clocksrc); +} + + +/* * Connect a port. */ PJ_DEF(pj_status_t) pjmedia_snd_port_connect( pjmedia_snd_port *snd_port, pjmedia_port *port) { - pjmedia_port_info *pinfo; + pjmedia_audio_format_detail *afd; PJ_ASSERT_RETURN(snd_port && port, PJ_EINVAL); + afd = pjmedia_format_get_audio_format_detail(&port->info.fmt, PJ_TRUE); + /* Check that port has the same configuration as the sound device * port. */ - pinfo = &port->info; - if (pinfo->clock_rate != snd_port->clock_rate) + if (afd->clock_rate != snd_port->clock_rate) return PJMEDIA_ENCCLOCKRATE; - if (pinfo->samples_per_frame != snd_port->samples_per_frame) + if (PJMEDIA_AFD_SPF(afd) != snd_port->samples_per_frame) return PJMEDIA_ENCSAMPLESPFRAME; - if (pinfo->channel_count != snd_port->channel_count) + if (afd->channel_count != snd_port->channel_count) return PJMEDIA_ENCCHANNEL; - if (pinfo->bits_per_sample != snd_port->bits_per_sample) + if (afd->bits_per_sample != snd_port->bits_per_sample) return PJMEDIA_ENCBITS; /* Port is okay. */ diff --git a/pjmedia/src/pjmedia/splitcomb.c b/pjmedia/src/pjmedia/splitcomb.c index 570cda3a..2f4baf11 100644 --- a/pjmedia/src/pjmedia/splitcomb.c +++ b/pjmedia/src/pjmedia/splitcomb.c @@ -25,8 +25,8 @@ #include <pj/pool.h> -#define SIGNATURE PJMEDIA_PORT_SIGNATURE('S', 'p', 'C', 'b') -#define SIGNATURE_PORT PJMEDIA_PORT_SIGNATURE('S', 'p', 'C', 'P') +#define SIGNATURE PJMEDIA_SIG_PORT_SPLIT_COMB +#define SIGNATURE_PORT PJMEDIA_SIG_PORT_SPLIT_COMB_P #define THIS_FILE "splitcomb.c" #define TMP_SAMP_TYPE pj_int16_t @@ -189,13 +189,13 @@ struct reverse_port * Prototypes. */ static pj_status_t put_frame(pjmedia_port *this_port, - const pjmedia_frame *frame); + pjmedia_frame *frame); static pj_status_t get_frame(pjmedia_port *this_port, pjmedia_frame *frame); static pj_status_t on_destroy(pjmedia_port *this_port); static pj_status_t rport_put_frame(pjmedia_port *this_port, - const pjmedia_frame *frame); + pjmedia_frame *frame); static pj_status_t rport_get_frame(pjmedia_port *this_port, pjmedia_frame *frame); static pj_status_t rport_on_destroy(pjmedia_port *this_port); @@ -284,7 +284,7 @@ PJ_DEF(pj_status_t) pjmedia_splitcomb_set_channel( pjmedia_port *splitcomb, PJ_ASSERT_RETURN(sc->base.info.signature == SIGNATURE, PJ_EINVAL); /* Check the channel number */ - PJ_ASSERT_RETURN(ch_num < sc->base.info.channel_count, PJ_EINVAL); + PJ_ASSERT_RETURN(ch_num < PJMEDIA_PIA_CCNT(&sc->base.info), PJ_EINVAL); /* options is unused for now */ PJ_UNUSED_ARG(options); @@ -308,7 +308,8 @@ PJ_DEF(pj_status_t) pjmedia_splitcomb_create_rev_channel( pj_pool_t *pool, const pj_str_t name = pj_str("scomb-rev"); struct splitcomb *sc = (struct splitcomb*) splitcomb; struct reverse_port *rport; - unsigned buf_cnt, ptime; + unsigned buf_cnt; + const pjmedia_audio_format_detail *sc_afd, *p_afd; pjmedia_port *port; pj_status_t status; @@ -319,11 +320,13 @@ PJ_DEF(pj_status_t) pjmedia_splitcomb_create_rev_channel( pj_pool_t *pool, PJ_ASSERT_RETURN(sc->base.info.signature == SIGNATURE, PJ_EINVAL); /* Check the channel number */ - PJ_ASSERT_RETURN(ch_num < sc->base.info.channel_count, PJ_EINVAL); + PJ_ASSERT_RETURN(ch_num < PJMEDIA_PIA_CCNT(&sc->base.info), PJ_EINVAL); /* options is unused for now */ PJ_UNUSED_ARG(options); + sc_afd = pjmedia_format_get_audio_format_detail(&splitcomb->info.fmt, 1); + /* Create the port */ rport = PJ_POOL_ZALLOC_T(pool, struct reverse_port); rport->parent = sc; @@ -332,10 +335,12 @@ PJ_DEF(pj_status_t) pjmedia_splitcomb_create_rev_channel( pj_pool_t *pool, /* Initialize port info... */ port = &rport->base; pjmedia_port_info_init(&port->info, &name, SIGNATURE_PORT, - splitcomb->info.clock_rate, 1, - splitcomb->info.bits_per_sample, - splitcomb->info.samples_per_frame / - splitcomb->info.channel_count); + sc_afd->clock_rate, 1, + sc_afd->bits_per_sample, + PJMEDIA_PIA_SPF(&splitcomb->info) / + sc_afd->channel_count); + + p_afd = pjmedia_format_get_audio_format_detail(&port->info.fmt, 1); /* ... and the callbacks */ port->put_frame = &rport_put_frame; @@ -347,30 +352,27 @@ PJ_DEF(pj_status_t) pjmedia_splitcomb_create_rev_channel( pj_pool_t *pool, if (buf_cnt == 0) buf_cnt = MAX_BUF_CNT; - ptime = port->info.samples_per_frame * 1000 / port->info.clock_rate / - port->info.channel_count; - rport->max_burst = MAX_BURST; rport->max_null_frames = MAX_NULL_FRAMES; /* Create downstream/put buffers */ status = pjmedia_delay_buf_create(pool, "scombdb-dn", - port->info.clock_rate, - port->info.samples_per_frame, - port->info.channel_count, - buf_cnt * ptime, 0, - &rport->buf[DIR_DOWNSTREAM].dbuf); + p_afd->clock_rate, + PJMEDIA_PIA_SPF(&port->info), + p_afd->channel_count, + buf_cnt * p_afd->frame_time_usec / 1000, + 0, &rport->buf[DIR_DOWNSTREAM].dbuf); if (status != PJ_SUCCESS) { return status; } /* Create upstream/get buffers */ status = pjmedia_delay_buf_create(pool, "scombdb-up", - port->info.clock_rate, - port->info.samples_per_frame, - port->info.channel_count, - buf_cnt * ptime, 0, - &rport->buf[DIR_UPSTREAM].dbuf); + p_afd->clock_rate, + PJMEDIA_PIA_SPF(&port->info), + p_afd->channel_count, + buf_cnt * p_afd->frame_time_usec / 1000, + 0, &rport->buf[DIR_UPSTREAM].dbuf); if (status != PJ_SUCCESS) { pjmedia_delay_buf_destroy(rport->buf[DIR_DOWNSTREAM].dbuf); return status; @@ -378,7 +380,8 @@ PJ_DEF(pj_status_t) pjmedia_splitcomb_create_rev_channel( pj_pool_t *pool, /* And temporary upstream/get buffer */ rport->tmp_up_buf = (pj_int16_t*) - pj_pool_alloc(pool, port->info.bytes_per_frame); + pj_pool_alloc(pool, + PJMEDIA_PIA_AVG_FSZ(&port->info)); /* Save port in the splitcomb */ sc->port_desc[ch_num].port = &rport->base; @@ -436,7 +439,7 @@ static void op_update(struct reverse_port *rport, int dir, int op) rport->buf[dir].level += op; if (op == OP_PUT) { - rport->buf[dir].ts.u64 += rport->base.info.samples_per_frame; + rport->buf[dir].ts.u64 += PJMEDIA_PIA_SPF(&rport->base.info); } if (rport->buf[dir].paused) { @@ -484,14 +487,14 @@ static void op_update(struct reverse_port *rport, int dir, int op) * it to the appropriate port. */ static pj_status_t put_frame(pjmedia_port *this_port, - const pjmedia_frame *frame) + pjmedia_frame *frame) { struct splitcomb *sc = (struct splitcomb*) this_port; unsigned ch; /* Handle null frame */ if (frame->type == PJMEDIA_FRAME_TYPE_NONE) { - for (ch=0; ch < this_port->info.channel_count; ++ch) { + for (ch=0; ch < PJMEDIA_PIA_CCNT(&this_port->info); ++ch) { pjmedia_port *port = sc->port_desc[ch].port; if (!port) continue; @@ -533,7 +536,7 @@ static pj_status_t put_frame(pjmedia_port *this_port, /* Generate zero frame. */ pjmedia_zero_samples(sc->put_buf, - port->info.samples_per_frame); + PJMEDIA_PIA_SPF(&this_port->info)); /* Put frame to delay buffer */ pjmedia_delay_buf_put(rport->buf[DIR_DOWNSTREAM].dbuf, @@ -547,13 +550,13 @@ static pj_status_t put_frame(pjmedia_port *this_port, /* Not sure how we would handle partial frame, so better reject * it for now. */ - PJ_ASSERT_RETURN(frame->size == this_port->info.bytes_per_frame, + PJ_ASSERT_RETURN(frame->size == PJMEDIA_PIA_AVG_FSZ(&this_port->info), PJ_EINVAL); /* * Write mono frame into each channels */ - for (ch=0; ch < this_port->info.channel_count; ++ch) { + for (ch=0; ch < PJMEDIA_PIA_CCNT(&this_port->info); ++ch) { pjmedia_port *port = sc->port_desc[ch].port; if (!port) @@ -561,17 +564,17 @@ static pj_status_t put_frame(pjmedia_port *this_port, /* Extract the mono frame to temporary buffer */ extract_mono_frame((const pj_int16_t*)frame->buf, sc->put_buf, ch, - this_port->info.channel_count, + PJMEDIA_PIA_CCNT(&this_port->info), frame->size * 8 / - this_port->info.bits_per_sample / - this_port->info.channel_count); + PJMEDIA_PIA_BITS(&this_port->info) / + PJMEDIA_PIA_CCNT(&this_port->info)); if (!sc->port_desc[ch].reversed) { /* Write to normal port */ pjmedia_frame mono_frame; mono_frame.buf = sc->put_buf; - mono_frame.size = frame->size / this_port->info.channel_count; + mono_frame.size = frame->size / PJMEDIA_PIA_CCNT(&this_port->info); mono_frame.type = frame->type; mono_frame.timestamp.u64 = frame->timestamp.u64; @@ -612,20 +615,20 @@ static pj_status_t get_frame(pjmedia_port *this_port, pj_bool_t has_frame = PJ_FALSE; /* Read frame from each port */ - for (ch=0; ch < this_port->info.channel_count; ++ch) { + for (ch=0; ch < PJMEDIA_PIA_CCNT(&this_port->info); ++ch) { pjmedia_port *port = sc->port_desc[ch].port; pjmedia_frame mono_frame; pj_status_t status; if (!port) { pjmedia_zero_samples(sc->get_buf, - this_port->info.samples_per_frame / - this_port->info.channel_count); + PJMEDIA_PIA_SPF(&this_port->info) / + PJMEDIA_PIA_CCNT(&this_port->info)); } else if (sc->port_desc[ch].reversed == PJ_FALSE) { /* Read from normal port */ mono_frame.buf = sc->get_buf; - mono_frame.size = port->info.bytes_per_frame; + mono_frame.size = PJMEDIA_PIA_AVG_FSZ(&port->info); mono_frame.timestamp.u64 = frame->timestamp.u64; status = pjmedia_port_get_frame(port, &mono_frame); @@ -633,7 +636,7 @@ static pj_status_t get_frame(pjmedia_port *this_port, mono_frame.type != PJMEDIA_FRAME_TYPE_AUDIO) { pjmedia_zero_samples(sc->get_buf, - port->info.samples_per_frame); + PJMEDIA_PIA_SPF(&port->info)); } frame->timestamp.u64 = mono_frame.timestamp.u64; @@ -651,7 +654,7 @@ static pj_status_t get_frame(pjmedia_port *this_port, } else { pjmedia_zero_samples(sc->get_buf, - port->info.samples_per_frame); + PJMEDIA_PIA_SPF(&port->info)); } frame->timestamp.u64 = rport->buf[DIR_UPSTREAM].ts.u64; @@ -660,9 +663,9 @@ static pj_status_t get_frame(pjmedia_port *this_port, /* Combine the mono frame into multichannel frame */ store_mono_frame(sc->get_buf, (pj_int16_t*)frame->buf, ch, - this_port->info.channel_count, - this_port->info.samples_per_frame / - this_port->info.channel_count); + PJMEDIA_PIA_CCNT(&this_port->info), + PJMEDIA_PIA_SPF(&this_port->info) / + PJMEDIA_PIA_CCNT(&this_port->info)); has_frame = PJ_TRUE; } @@ -670,7 +673,7 @@ static pj_status_t get_frame(pjmedia_port *this_port, /* Return NO_FRAME is we don't get any frames from downstream ports */ if (has_frame) { frame->type = PJMEDIA_FRAME_TYPE_AUDIO; - frame->size = this_port->info.bytes_per_frame; + frame->size = PJMEDIA_PIA_AVG_FSZ(&this_port->info); } else frame->type = PJMEDIA_FRAME_TYPE_NONE; @@ -694,11 +697,11 @@ static pj_status_t on_destroy(pjmedia_port *this_port) * will be picked up by get_frame() above. */ static pj_status_t rport_put_frame(pjmedia_port *this_port, - const pjmedia_frame *frame) + pjmedia_frame *frame) { struct reverse_port *rport = (struct reverse_port*) this_port; - pj_assert(frame->size <= rport->base.info.bytes_per_frame); + pj_assert(frame->size <= PJMEDIA_PIA_AVG_FSZ(&rport->base.info)); /* Handle NULL frame */ if (frame->type != PJMEDIA_FRAME_TYPE_AUDIO) { @@ -730,7 +733,7 @@ static pj_status_t rport_put_frame(pjmedia_port *this_port, /* Generate zero frame. */ pjmedia_zero_samples(rport->tmp_up_buf, - this_port->info.samples_per_frame); + PJMEDIA_PIA_SPF(&this_port->info)); /* Put frame to delay buffer */ return pjmedia_delay_buf_put(rport->buf[DIR_UPSTREAM].dbuf, @@ -738,7 +741,7 @@ static pj_status_t rport_put_frame(pjmedia_port *this_port, } /* Not sure how to handle partial frame, so better reject for now */ - PJ_ASSERT_RETURN(frame->size == this_port->info.bytes_per_frame, + PJ_ASSERT_RETURN(frame->size == PJMEDIA_PIA_AVG_FSZ(&this_port->info), PJ_EINVAL); /* Reset NULL frame counter */ @@ -755,7 +758,7 @@ static pj_status_t rport_put_frame(pjmedia_port *this_port, * modifies the frame content. */ pjmedia_copy_samples(rport->tmp_up_buf, (const pj_int16_t*)frame->buf, - this_port->info.samples_per_frame); + PJMEDIA_PIA_SPF(&this_port->info)); /* Put frame to delay buffer */ return pjmedia_delay_buf_put(rport->buf[DIR_UPSTREAM].dbuf, @@ -783,7 +786,7 @@ static pj_status_t rport_get_frame(pjmedia_port *this_port, } /* Get frame from delay buffer */ - frame->size = this_port->info.bytes_per_frame; + frame->size = PJMEDIA_PIA_AVG_FSZ(&this_port->info); frame->type = PJMEDIA_FRAME_TYPE_AUDIO; frame->timestamp.u64 = rport->buf[DIR_DOWNSTREAM].ts.u64; diff --git a/pjmedia/src/pjmedia/stereo_port.c b/pjmedia/src/pjmedia/stereo_port.c index 2aad1690..1b4a49df 100644 --- a/pjmedia/src/pjmedia/stereo_port.c +++ b/pjmedia/src/pjmedia/stereo_port.c @@ -24,7 +24,7 @@ #include <pj/string.h> -#define SIGNATURE PJMEDIA_PORT_SIGNATURE('S','T','R','O') +#define SIGNATURE PJMEDIA_SIG_PORT_STEREO struct stereo_port @@ -39,7 +39,7 @@ struct stereo_port static pj_status_t stereo_put_frame(pjmedia_port *this_port, - const pjmedia_frame *frame); + pjmedia_frame *frame); static pj_status_t stereo_get_frame(pjmedia_port *this_port, pjmedia_frame *frame); static pj_status_t stereo_destroy(pjmedia_port *this_port); @@ -60,24 +60,27 @@ PJ_DEF(pj_status_t) pjmedia_stereo_port_create( pj_pool_t *pool, PJ_ASSERT_RETURN(pool && dn_port && channel_count && p_port, PJ_EINVAL); /* Only supports 16bit samples per frame */ - PJ_ASSERT_RETURN(dn_port->info.bits_per_sample == 16, PJMEDIA_ENCBITS); + PJ_ASSERT_RETURN(PJMEDIA_PIA_BITS(&dn_port->info) == 16, + PJMEDIA_ENCBITS); /* Validate channel counts */ - PJ_ASSERT_RETURN(((dn_port->info.channel_count>1 && channel_count==1) || - (dn_port->info.channel_count==1 && channel_count>1)), + PJ_ASSERT_RETURN(((PJMEDIA_PIA_CCNT(&dn_port->info)>1 && + channel_count==1) || + (PJMEDIA_PIA_CCNT(&dn_port->info)==1 && + channel_count>1)), PJ_EINVAL); /* Create and initialize port. */ sport = PJ_POOL_ZALLOC_T(pool, struct stereo_port); PJ_ASSERT_RETURN(sport != NULL, PJ_ENOMEM); - samples_per_frame = dn_port->info.samples_per_frame * channel_count / - dn_port->info.channel_count; + samples_per_frame = PJMEDIA_PIA_SPF(&dn_port->info) * channel_count / + PJMEDIA_PIA_CCNT(&dn_port->info); pjmedia_port_info_init(&sport->base.info, &name, SIGNATURE, - dn_port->info.clock_rate, + PJMEDIA_PIA_SRATE(&dn_port->info), channel_count, - dn_port->info.bits_per_sample, + PJMEDIA_PIA_BITS(&dn_port->info), samples_per_frame); sport->dn_port = dn_port; @@ -85,12 +88,14 @@ PJ_DEF(pj_status_t) pjmedia_stereo_port_create( pj_pool_t *pool, /* We always need buffer for put_frame */ sport->put_buf = (pj_int16_t*) - pj_pool_alloc(pool, dn_port->info.bytes_per_frame); + pj_pool_alloc(pool, + PJMEDIA_PIA_AVG_FSZ(&dn_port->info)); /* See if we need buffer for get_frame */ - if (dn_port->info.channel_count > channel_count) { + if (PJMEDIA_PIA_CCNT(&dn_port->info) > channel_count) { sport->get_buf = (pj_int16_t*) - pj_pool_alloc(pool, dn_port->info.bytes_per_frame); + pj_pool_alloc(pool, + PJMEDIA_PIA_AVG_FSZ(&dn_port->info)); } /* Media port interface */ @@ -106,9 +111,10 @@ PJ_DEF(pj_status_t) pjmedia_stereo_port_create( pj_pool_t *pool, } static pj_status_t stereo_put_frame(pjmedia_port *this_port, - const pjmedia_frame *frame) + pjmedia_frame *frame) { struct stereo_port *sport = (struct stereo_port*) this_port; + const pjmedia_audio_format_detail *s_afd, *dn_afd; pjmedia_frame tmp_frame; /* Return if we don't have downstream port. */ @@ -116,23 +122,27 @@ static pj_status_t stereo_put_frame(pjmedia_port *this_port, return PJ_SUCCESS; } + s_afd = pjmedia_format_get_audio_format_detail(&this_port->info.fmt, 1); + dn_afd = pjmedia_format_get_audio_format_detail(&sport->dn_port->info.fmt, + 1); + if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO) { tmp_frame.buf = sport->put_buf; - if (sport->dn_port->info.channel_count == 1) { + if (dn_afd->channel_count == 1) { pjmedia_convert_channel_nto1((pj_int16_t*)tmp_frame.buf, (const pj_int16_t*)frame->buf, - sport->base.info.channel_count, - sport->base.info.samples_per_frame, + s_afd->channel_count, + PJMEDIA_AFD_SPF(s_afd), (sport->options & PJMEDIA_STEREO_MIX), 0); } else { pjmedia_convert_channel_1ton((pj_int16_t*)tmp_frame.buf, (const pj_int16_t*)frame->buf, - sport->dn_port->info.channel_count, - sport->base.info.samples_per_frame, + dn_afd->channel_count, + PJMEDIA_AFD_SPF(s_afd), sport->options); } - tmp_frame.size = sport->dn_port->info.bytes_per_frame; + tmp_frame.size = PJMEDIA_AFD_AVG_FSZ(dn_afd); } else { tmp_frame.buf = frame->buf; tmp_frame.size = frame->size; @@ -150,6 +160,7 @@ static pj_status_t stereo_get_frame(pjmedia_port *this_port, pjmedia_frame *frame) { struct stereo_port *sport = (struct stereo_port*) this_port; + const pjmedia_audio_format_detail *s_afd, *dn_afd; pjmedia_frame tmp_frame; pj_status_t status; @@ -159,8 +170,12 @@ static pj_status_t stereo_get_frame(pjmedia_port *this_port, return PJ_SUCCESS; } + s_afd = pjmedia_format_get_audio_format_detail(&this_port->info.fmt, 1); + dn_afd = pjmedia_format_get_audio_format_detail(&sport->dn_port->info.fmt, + 1); + tmp_frame.buf = sport->get_buf? sport->get_buf : frame->buf; - tmp_frame.size = sport->dn_port->info.bytes_per_frame; + tmp_frame.size = PJMEDIA_PIA_AVG_FSZ(&sport->dn_port->info); tmp_frame.timestamp.u64 = frame->timestamp.u64; tmp_frame.type = PJMEDIA_FRAME_TYPE_AUDIO; @@ -177,21 +192,21 @@ static pj_status_t stereo_get_frame(pjmedia_port *this_port, return PJ_SUCCESS; } - if (sport->base.info.channel_count == 1) { + if (s_afd->channel_count == 1) { pjmedia_convert_channel_nto1((pj_int16_t*)frame->buf, (const pj_int16_t*)tmp_frame.buf, - sport->dn_port->info.channel_count, - sport->dn_port->info.samples_per_frame, + dn_afd->channel_count, + PJMEDIA_AFD_SPF(s_afd), (sport->options & PJMEDIA_STEREO_MIX), 0); } else { pjmedia_convert_channel_1ton((pj_int16_t*)frame->buf, (const pj_int16_t*)tmp_frame.buf, - sport->base.info.channel_count, - sport->dn_port->info.samples_per_frame, + s_afd->channel_count, + PJMEDIA_AFD_SPF(dn_afd), sport->options); } - frame->size = sport->base.info.bytes_per_frame; + frame->size = PJMEDIA_AFD_AVG_FSZ(s_afd); frame->type = PJMEDIA_FRAME_TYPE_AUDIO; return PJ_SUCCESS; diff --git a/pjmedia/src/pjmedia/stream.c b/pjmedia/src/pjmedia/stream.c index 42761c33..1fe2863d 100644 --- a/pjmedia/src/pjmedia/stream.c +++ b/pjmedia/src/pjmedia/stream.c @@ -22,6 +22,7 @@ #include <pjmedia/rtp.h> #include <pjmedia/rtcp.h> #include <pjmedia/jbuf.h> +#include <pjmedia/stream_common.h> #include <pj/array.h> #include <pj/assert.h> #include <pj/ctype.h> @@ -64,6 +65,13 @@ # define TRACE_JB_OPENED(s) (s->trace_jb_fd != TRACE_JB_INVALID_FD) #endif +#ifndef PJMEDIA_STREAM_SIZE +# define PJMEDIA_STREAM_SIZE 1000 +#endif + +#ifndef PJMEDIA_STREAM_INC +# define PJMEDIA_STREAM_INC 1000 +#endif /** @@ -77,6 +85,7 @@ struct pjmedia_channel pj_bool_t paused; /**< Paused?. */ unsigned out_pkt_size; /**< Size of output buffer. */ void *out_pkt; /**< Output buffer. */ + unsigned out_pkt_len; /**< Length of data in buffer. */ pjmedia_rtp_session rtp; /**< RTP session. */ }; @@ -98,11 +107,13 @@ struct pjmedia_stream { pjmedia_endpt *endpt; /**< Media endpoint. */ pjmedia_codec_mgr *codec_mgr; /**< Codec manager instance. */ - + pjmedia_stream_info si; /**< Creation parameter. */ pjmedia_port port; /**< Port interface. */ pjmedia_channel *enc; /**< Encoding channel. */ pjmedia_channel *dec; /**< Decoding channel. */ + pj_pool_t *own_pool; /**< Only created if not given */ + pjmedia_dir dir; /**< Stream direction. */ void *user_data; /**< User data. */ pj_str_t cname; /**< SDES CNAME */ @@ -177,8 +188,6 @@ struct pjmedia_stream /**< Normalized ts length per frame received according to 'erroneous' definition */ - pj_uint32_t rtp_rx_last_ts;/**< Last received RTP timestamp - for timestamp checking */ unsigned rtp_rx_last_cnt;/**< Nb of frames in last pkt */ unsigned rtp_rx_check_cnt; /**< Counter of remote timestamp @@ -208,6 +217,8 @@ struct pjmedia_stream pj_oshandle_t trace_jb_fd; /**< Jitter tracing file handle.*/ char *trace_jb_buf; /**< Jitter tracing buffer. */ #endif + + pj_uint32_t rtp_rx_last_ts; /**< Last received RTP timestamp*/ }; @@ -471,7 +482,7 @@ static pj_status_t get_frame( pjmedia_port *port, pjmedia_frame *frame) /* Lock jitter buffer mutex first */ pj_mutex_lock( stream->jb_mutex ); - samples_required = stream->port.info.samples_per_frame; + samples_required = PJMEDIA_PIA_SPF(&stream->port.info); samples_per_frame = stream->codec_param.info.frm_ptime * stream->codec_param.info.clock_rate * stream->codec_param.info.channel_cnt / @@ -504,9 +515,9 @@ static pj_status_t get_frame( pjmedia_port *port, pjmedia_frame *frame) frame_out.buf = p_out_samp + samples_count; frame_out.size = frame->size - samples_count*2; - status = (*stream->codec->op->recover)(stream->codec, - frame_out.size, - &frame_out); + status = pjmedia_codec_recover(stream->codec, + frame_out.size, + &frame_out); ++stream->plc_cnt; @@ -553,9 +564,9 @@ static pj_status_t get_frame( pjmedia_port *port, pjmedia_frame *frame) do { frame_out.buf = p_out_samp + samples_count; frame_out.size = frame->size - samples_count*2; - status = (*stream->codec->op->recover)(stream->codec, - frame_out.size, - &frame_out); + status = pjmedia_codec_recover(stream->codec, + frame_out.size, + &frame_out); if (status != PJ_SUCCESS) break; @@ -608,9 +619,9 @@ static pj_status_t get_frame( pjmedia_port *port, pjmedia_frame *frame) do { frame_out.buf = p_out_samp + samples_count; frame_out.size = frame->size - samples_count*2; - status = (*stream->codec->op->recover)(stream->codec, - frame_out.size, - &frame_out); + status = pjmedia_codec_recover(stream->codec, + frame_out.size, + &frame_out); if (status != PJ_SUCCESS) break; samples_count += samples_per_frame; @@ -659,8 +670,8 @@ static pj_status_t get_frame( pjmedia_port *port, pjmedia_frame *frame) frame_out.buf = p_out_samp + samples_count; frame_out.size = frame->size - samples_count*BYTES_PER_SAMPLE; - status = stream->codec->op->decode( stream->codec, &frame_in, - frame_out.size, &frame_out); + status = pjmedia_codec_decode( stream->codec, &frame_in, + frame_out.size, &frame_out); if (status != 0) { LOGERR_((port->info.name.ptr, "codec decode() error", status)); @@ -725,7 +736,7 @@ static pj_status_t get_frame_ext( pjmedia_port *port, pjmedia_frame *frame) * until we have enough frames according to codec's ptime. */ - samples_required = stream->port.info.samples_per_frame; + samples_required = PJMEDIA_PIA_SPF(&stream->port.info); samples_per_frame = stream->codec_param.info.frm_ptime * stream->codec_param.info.clock_rate * stream->codec_param.info.channel_cnt / @@ -763,8 +774,8 @@ static pj_status_t get_frame_ext( pjmedia_port *port, pjmedia_frame *frame) frame_in.bit_info = bit_info; frame_in.type = PJMEDIA_FRAME_TYPE_AUDIO; - status = stream->codec->op->decode( stream->codec, &frame_in, - 0, frame); + status = pjmedia_codec_decode( stream->codec, &frame_in, + 0, frame); if (status != PJ_SUCCESS) { LOGERR_((port->info.name.ptr, "codec decode() error", status)); @@ -790,8 +801,7 @@ static pj_status_t get_frame_ext( pjmedia_port *port, pjmedia_frame *frame) /* Try to generate frame by invoking PLC (when any) */ status = PJ_SUCCESS; if (stream->codec->op->recover) { - status = (*stream->codec->op->recover)(stream->codec, - 0, frame); + status = pjmedia_codec_recover(stream->codec, 0, frame); } /* No PLC or PLC failed */ @@ -876,7 +886,7 @@ static void create_dtmf_payload(pjmedia_stream *stream, *first = 1; } - digit->duration += stream->port.info.samples_per_frame; + digit->duration += PJMEDIA_PIA_SPF(&stream->port.info); event->event = (pj_uint8_t)digit->event; event->e_vol = 10; @@ -1087,7 +1097,7 @@ static void rebuffer(pjmedia_stream *stream, /* How many samples are needed */ count = stream->codec_param.info.enc_ptime * - stream->port.info.clock_rate / 1000; + PJMEDIA_PIA_SRATE(&stream->port.info) / 1000; /* See if we have enough samples */ if (stream->enc_buf_count >= count) { @@ -1110,7 +1120,7 @@ static void rebuffer(pjmedia_stream *stream, * put_frame_imp() */ static pj_status_t put_frame_imp( pjmedia_port *port, - const pjmedia_frame *frame ) + pjmedia_frame *frame ) { pjmedia_stream *stream = (pjmedia_stream*) port->port_data.pdata; pjmedia_channel *channel = stream->enc; @@ -1151,8 +1161,8 @@ static pj_status_t put_frame_imp( pjmedia_port *port, if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO) ts_len = (frame->size >> 1) / stream->codec_param.info.channel_cnt; else if (frame->type == PJMEDIA_FRAME_TYPE_EXTENDED) - ts_len = stream->port.info.samples_per_frame / - stream->port.info.channel_count; + ts_len = PJMEDIA_PIA_SPF(&stream->port.info) / + PJMEDIA_PIA_CCNT(&stream->port.info); else ts_len = 0; @@ -1219,7 +1229,7 @@ static pj_status_t put_frame_imp( pjmedia_port *port, */ } else if (frame->type == PJMEDIA_FRAME_TYPE_AUDIO && frame->buf == NULL && - stream->port.info.format.id == PJMEDIA_FORMAT_L16 && + stream->port.info.fmt.id == PJMEDIA_FORMAT_L16 && (stream->dir & PJMEDIA_DIR_ENCODING) && stream->codec_param.info.frm_ptime * stream->codec_param.info.channel_cnt * @@ -1237,10 +1247,10 @@ static pj_status_t put_frame_imp( pjmedia_port *port, silence_frame.timestamp.u32.lo = pj_ntohl(stream->enc->rtp.out_hdr.ts); /* Encode! */ - status = stream->codec->op->encode( stream->codec, &silence_frame, - channel->out_pkt_size - - sizeof(pjmedia_rtp_hdr), - &frame_out); + status = pjmedia_codec_encode( stream->codec, &silence_frame, + channel->out_pkt_size - + sizeof(pjmedia_rtp_hdr), + &frame_out); if (status != PJ_SUCCESS) { LOGERR_((stream->port.info.name.ptr, "Codec encode() error", status)); @@ -1261,10 +1271,10 @@ static pj_status_t put_frame_imp( pjmedia_port *port, (frame->type == PJMEDIA_FRAME_TYPE_EXTENDED)) { /* Encode! */ - status = stream->codec->op->encode( stream->codec, frame, - channel->out_pkt_size - - sizeof(pjmedia_rtp_hdr), - &frame_out); + status = pjmedia_codec_encode( stream->codec, frame, + channel->out_pkt_size - + sizeof(pjmedia_rtp_hdr), + &frame_out); if (status != PJ_SUCCESS) { LOGERR_((stream->port.info.name.ptr, "Codec encode() error", status)); @@ -1362,7 +1372,7 @@ static pj_status_t put_frame_imp( pjmedia_port *port, * RTP packet, and transmit to peer. */ static pj_status_t put_frame( pjmedia_port *port, - const pjmedia_frame *frame ) + pjmedia_frame *frame ) { pjmedia_stream *stream = (pjmedia_stream*) port->port_data.pdata; pjmedia_frame tmp_zero_frame; @@ -1410,10 +1420,11 @@ static pj_status_t put_frame( pjmedia_port *port, */ if (stream->vad_enabled != stream->codec_param.setting.vad && (stream->tx_duration - stream->ts_vad_disabled) > - stream->port.info.clock_rate * PJMEDIA_STREAM_VAD_SUSPEND_MSEC / 1000) + PJMEDIA_PIA_SRATE(&stream->port.info) * + PJMEDIA_STREAM_VAD_SUSPEND_MSEC / 1000) { stream->codec_param.setting.vad = stream->vad_enabled; - stream->codec->op->modify(stream->codec, &stream->codec_param); + pjmedia_codec_modify(stream->codec, &stream->codec_param); PJ_LOG(4,(stream->port.info.name.ptr,"VAD re-enabled")); } @@ -1573,7 +1584,6 @@ static void on_rx_rtp( void *data, const void *payload; unsigned payloadlen; pjmedia_rtp_status seq_st; - pj_bool_t check_pt; pj_status_t status; pj_bool_t pkt_discarded = PJ_FALSE; @@ -1603,15 +1613,8 @@ static void on_rx_rtp( void *data, /* Update RTP session (also checks if RTP session can accept * the incoming packet. */ - check_pt = (hdr->pt != stream->rx_event_pt) && PJMEDIA_STREAM_CHECK_RTP_PT; - pjmedia_rtp_session_update2(&channel->rtp, hdr, &seq_st, check_pt); -#if !PJMEDIA_STREAM_CHECK_RTP_PT - if (!check_pt && hdr->pt != channel->rtp.out_pt && - hdr->pt != stream->rx_event_pt) - { - seq_st.status.flag.badpt = 1; - } -#endif + pjmedia_rtp_session_update2(&channel->rtp, hdr, &seq_st, + hdr->pt != stream->rx_event_pt); if (seq_st.status.value) { TRC_ ((stream->port.info.name.ptr, "RTP status: badpt=%d, badssrc=%d, dup=%d, " @@ -1671,7 +1674,6 @@ static void on_rx_rtp( void *data, if (seq_st.status.flag.restart) { status = pjmedia_jbuf_reset(stream->jb); PJ_LOG(4,(stream->port.info.name.ptr, "Jitter buffer reset")); - } else { /* * Packets may contain more than one frames, while the jitter @@ -1688,12 +1690,8 @@ static void on_rx_rtp( void *data, ts.u64 = pj_ntohl(hdr->ts); /* Parse the payload. */ - status = (*stream->codec->op->parse)(stream->codec, - (void*)payload, - payloadlen, - &ts, - &count, - frames); + status = pjmedia_codec_parse(stream->codec, (void*)payload, + payloadlen, &ts, &count, frames); if (status != PJ_SUCCESS) { LOGERR_((stream->port.info.name.ptr, "Codec parse() error", @@ -1721,9 +1719,9 @@ static void on_rx_rtp( void *data, unsigned frm_ts_span; /* Calculate actual frame timestamp span */ - frm_ts_span = stream->port.info.samples_per_frame / + frm_ts_span = PJMEDIA_PIA_SPF(&stream->port.info) / stream->codec_param.setting.frm_per_pkt/ - stream->port.info.channel_count; + PJMEDIA_PIA_CCNT(&stream->port.info); /* Get remote frame timestamp span */ peer_frm_ts_diff = @@ -1895,13 +1893,20 @@ static pj_status_t create_channel( pj_pool_t *pool, /* Allocate buffer for outgoing packet. */ - channel->out_pkt_size = sizeof(pjmedia_rtp_hdr) + - stream->codec_param.info.max_bps * - PJMEDIA_MAX_FRAME_DURATION_MS / - 8 / 1000; - - if (channel->out_pkt_size > PJMEDIA_MAX_MTU) - channel->out_pkt_size = PJMEDIA_MAX_MTU; + if (param->type == PJMEDIA_TYPE_AUDIO) { + channel->out_pkt_size = sizeof(pjmedia_rtp_hdr) + + stream->codec_param.info.max_bps * + PJMEDIA_MAX_FRAME_DURATION_MS / + 8 / 1000; + if (channel->out_pkt_size > PJMEDIA_MAX_MTU - + PJMEDIA_STREAM_RESV_PAYLOAD_LEN) + { + channel->out_pkt_size = PJMEDIA_MAX_MTU - + PJMEDIA_STREAM_RESV_PAYLOAD_LEN; + } + } else { + return PJ_ENOTSUP; + } /* It should big enough to hold (minimally) RTCP SR with an SDES. */ min_out_pkt_size = sizeof(pjmedia_rtcp_sr_pkt) + @@ -1955,16 +1960,27 @@ PJ_DEF(pj_status_t) pjmedia_stream_create( pjmedia_endpt *endpt, pjmedia_stream *stream; pj_str_t name; unsigned jb_init, jb_max, jb_min_pre, jb_max_pre, len; + pjmedia_audio_format_detail *afd; + pj_pool_t *own_pool = NULL; char *p; pj_status_t status; - PJ_ASSERT_RETURN(pool && info && p_stream, PJ_EINVAL); + PJ_ASSERT_RETURN(endpt && info && p_stream, PJ_EINVAL); + if (pool == NULL) { + own_pool = pjmedia_endpt_create_pool( endpt, "strm%p", + PJMEDIA_STREAM_SIZE, + PJMEDIA_STREAM_INC); + PJ_ASSERT_RETURN(own_pool != NULL, PJ_ENOMEM); + pool = own_pool; + } /* Allocate the media stream: */ stream = PJ_POOL_ZALLOC_T(pool, pjmedia_stream); PJ_ASSERT_RETURN(stream != NULL, PJ_ENOMEM); + stream->own_pool = own_pool; + pj_memcpy(&stream->si, info, sizeof(*info)); /* Init stream/port name */ name.ptr = (char*) pj_pool_alloc(pool, M); @@ -1974,15 +1990,17 @@ PJ_DEF(pj_status_t) pjmedia_stream_create( pjmedia_endpt *endpt, * once we have more info about the codec. */ pjmedia_port_info_init(&stream->port.info, &name, - PJMEDIA_PORT_SIGNATURE('S', 'T', 'R', 'M'), + PJMEDIA_SIG_PORT_STREAM, info->fmt.clock_rate, info->fmt.channel_cnt, 16, 80); + afd = pjmedia_format_get_audio_format_detail(&stream->port.info.fmt, 1); /* Init port. */ - pj_strdup(pool, &stream->port.info.encoding_name, &info->fmt.encoding_name); - stream->port.info.clock_rate = info->fmt.clock_rate; - stream->port.info.channel_count = info->fmt.channel_cnt; + //No longer there in 2.0 + //pj_strdup(pool, &stream->port.info.encoding_name, &info->fmt.encoding_name); + afd->clock_rate = info->fmt.clock_rate; + afd->channel_count = info->fmt.channel_cnt; stream->port.port_data.pdata = stream; /* Init stream: */ @@ -2048,38 +2066,38 @@ PJ_DEF(pj_status_t) pjmedia_stream_create( pjmedia_endpt *endpt, stream->codec_param.setting.frm_per_pkt = 1; /* Open the codec. */ - status = stream->codec->op->open(stream->codec, &stream->codec_param); + status = pjmedia_codec_open(stream->codec, &stream->codec_param); if (status != PJ_SUCCESS) goto err_cleanup; /* Set additional info and callbacks. */ - stream->port.info.bits_per_sample = 16; - stream->port.info.samples_per_frame = info->fmt.clock_rate * - stream->codec_param.info.channel_cnt * - stream->codec_param.info.frm_ptime * - stream->codec_param.setting.frm_per_pkt / - 1000; - stream->port.info.format.id = stream->codec_param.info.fmt_id; + afd->bits_per_sample = 16; + afd->frame_time_usec = stream->codec_param.info.frm_ptime * + stream->codec_param.setting.frm_per_pkt * 1000; + stream->port.info.fmt.id = stream->codec_param.info.fmt_id; if (stream->codec_param.info.fmt_id == PJMEDIA_FORMAT_L16) { /* Raw format */ - stream->port.info.bytes_per_frame = stream->port.info.samples_per_frame * - stream->port.info.bits_per_sample / 8; + afd->avg_bps = afd->max_bps = afd->clock_rate * + afd->bits_per_sample / 8; + stream->port.put_frame = &put_frame; stream->port.get_frame = &get_frame; } else { /* Encoded format */ - stream->port.info.bytes_per_frame = stream->codec_param.info.max_bps * - stream->codec_param.info.frm_ptime * - stream->codec_param.setting.frm_per_pkt / - 8 / 1000; - if ((stream->codec_param.info.max_bps * stream->codec_param.info.frm_ptime * - stream->codec_param.setting.frm_per_pkt) % 8000 != 0) + afd->avg_bps = stream->codec_param.info.avg_bps; + afd->max_bps = stream->codec_param.info.max_bps; + + /* Not applicable for 2.0 + if ((stream->codec_param.info.max_bps * + stream->codec_param.info.frm_ptime * + stream->codec_param.setting.frm_per_pkt) % 8000 != 0) { ++stream->port.info.bytes_per_frame; } stream->port.info.format.bitrate = stream->codec_param.info.avg_bps; stream->port.info.format.vad = (stream->codec_param.setting.vad != 0); + */ stream->port.put_frame = &put_frame; stream->port.get_frame = &get_frame_ext; @@ -2096,14 +2114,13 @@ PJ_DEF(pj_status_t) pjmedia_stream_create( pjmedia_endpt *endpt, stream->enc_samples_per_pkt = stream->codec_param.info.enc_ptime * stream->codec_param.info.channel_cnt * - stream->port.info.clock_rate / 1000; + afd->clock_rate / 1000; /* Set buffer size as twice the largest ptime value between * stream's ptime, encoder ptime, or decoder ptime. */ - ptime = stream->port.info.samples_per_frame * 1000 / - stream->port.info.clock_rate; + ptime = afd->frame_time_usec / 1000; if (stream->codec_param.info.enc_ptime > ptime) ptime = stream->codec_param.info.enc_ptime; @@ -2114,12 +2131,12 @@ PJ_DEF(pj_status_t) pjmedia_stream_create( pjmedia_endpt *endpt, ptime <<= 1; /* Allocate buffer */ - stream->enc_buf_size = stream->port.info.clock_rate * ptime / 1000; + stream->enc_buf_size = afd->clock_rate * ptime / 1000; stream->enc_buf = (pj_int16_t*) pj_pool_alloc(pool, stream->enc_buf_size * 2); } else { - stream->enc_samples_per_pkt = stream->port.info.samples_per_frame; + stream->enc_samples_per_pkt = PJMEDIA_AFD_SPF(afd); } @@ -2128,7 +2145,7 @@ PJ_DEF(pj_status_t) pjmedia_stream_create( pjmedia_endpt *endpt, if (PJMEDIA_STREAM_VAD_SUSPEND_MSEC > 0 && stream->vad_enabled) { stream->codec_param.setting.vad = 0; stream->ts_vad_disabled = 0; - stream->codec->op->modify(stream->codec, &stream->codec_param); + pjmedia_codec_modify(stream->codec, &stream->codec_param); PJ_LOG(4,(stream->port.info.name.ptr,"VAD temporarily disabled")); } @@ -2152,7 +2169,7 @@ PJ_DEF(pj_status_t) pjmedia_stream_create( pjmedia_endpt *endpt, stream->rtp_rx_last_cnt = 0; stream->rtp_tx_ts_len_per_pkt = stream->enc_samples_per_pkt / stream->codec_param.info.channel_cnt; - stream->rtp_rx_ts_len_per_frame = stream->port.info.samples_per_frame / + stream->rtp_rx_ts_len_per_frame = PJMEDIA_AFD_SPF(afd) / stream->codec_param.setting.frm_per_pkt / stream->codec_param.info.channel_cnt; @@ -2226,7 +2243,7 @@ PJ_DEF(pj_status_t) pjmedia_stream_create( pjmedia_endpt *endpt, rtcp_setting.ssrc = info->ssrc; rtcp_setting.rtp_ts_base = pj_ntohl(stream->enc->rtp.out_hdr.ts); rtcp_setting.clock_rate = info->fmt.clock_rate; - rtcp_setting.samples_per_frame = stream->port.info.samples_per_frame; + rtcp_setting.samples_per_frame = PJMEDIA_AFD_SPF(afd); #if defined(PJMEDIA_HANDLE_G722_MPEG_BUG) && (PJMEDIA_HANDLE_G722_MPEG_BUG!=0) /* Special case for G.722 */ @@ -2432,7 +2449,7 @@ PJ_DEF(pj_status_t) pjmedia_stream_destroy( pjmedia_stream *stream ) /* Free codec. */ if (stream->codec) { - stream->codec->op->close(stream->codec); + pjmedia_codec_close(stream->codec); pjmedia_codec_mgr_dealloc_codec(stream->codec_mgr, stream->codec); stream->codec = NULL; } @@ -2455,6 +2472,11 @@ PJ_DEF(pj_status_t) pjmedia_stream_destroy( pjmedia_stream *stream ) } #endif + if (stream->own_pool) { + pj_pool_t *pool = stream->own_pool; + stream->own_pool = NULL; + pj_pool_release(pool); + } return PJ_SUCCESS; } @@ -2516,6 +2538,15 @@ PJ_DEF(pj_status_t) pjmedia_stream_start(pjmedia_stream *stream) } +PJ_DEF(pj_status_t) pjmedia_stream_get_info( const pjmedia_stream *stream, + pjmedia_stream_info *info) +{ + PJ_ASSERT_RETURN(stream && info, PJ_EINVAL); + + pj_memcpy(info, &stream->si, sizeof(pjmedia_stream_info)); + return PJ_SUCCESS; +} + /* * Get stream statistics. */ @@ -2760,3 +2791,487 @@ PJ_DEF(pj_status_t) pjmedia_stream_set_dtmf_callback(pjmedia_stream *stream, return PJ_SUCCESS; } + +static const pj_str_t ID_AUDIO = { "audio", 5}; +static const pj_str_t ID_IN = { "IN", 2 }; +static const pj_str_t ID_IP4 = { "IP4", 3}; +static const pj_str_t ID_IP6 = { "IP6", 3}; +static const pj_str_t ID_RTP_AVP = { "RTP/AVP", 7 }; +static const pj_str_t ID_RTP_SAVP = { "RTP/SAVP", 8 }; +//static const pj_str_t ID_SDP_NAME = { "pjmedia", 7 }; +static const pj_str_t ID_RTPMAP = { "rtpmap", 6 }; +static const pj_str_t ID_TELEPHONE_EVENT = { "telephone-event", 15 }; + +static const pj_str_t STR_INACTIVE = { "inactive", 8 }; +static const pj_str_t STR_SENDRECV = { "sendrecv", 8 }; +static const pj_str_t STR_SENDONLY = { "sendonly", 8 }; +static const pj_str_t STR_RECVONLY = { "recvonly", 8 }; + + +/* + * Internal function for collecting codec info and param from the SDP media. + */ +static pj_status_t get_audio_codec_info_param(pjmedia_stream_info *si, + pj_pool_t *pool, + pjmedia_codec_mgr *mgr, + const pjmedia_sdp_media *local_m, + const pjmedia_sdp_media *rem_m) +{ + const pjmedia_sdp_attr *attr; + pjmedia_sdp_rtpmap *rtpmap; + unsigned i, fmti, pt = 0; + pj_status_t status; + + /* Find the first codec which is not telephone-event */ + for ( fmti = 0; fmti < local_m->desc.fmt_count; ++fmti ) { + if ( !pj_isdigit(*local_m->desc.fmt[fmti].ptr) ) + return PJMEDIA_EINVALIDPT; + pt = pj_strtoul(&local_m->desc.fmt[fmti]); + if ( PJMEDIA_RTP_PT_TELEPHONE_EVENTS == 0 || + pt != PJMEDIA_RTP_PT_TELEPHONE_EVENTS ) + break; + } + if ( fmti >= local_m->desc.fmt_count ) + return PJMEDIA_EINVALIDPT; + + /* Get codec info. + * For static payload types, get the info from codec manager. + * For dynamic payload types, MUST get the rtpmap. + */ + if (pt < 96) { + pj_bool_t has_rtpmap; + + rtpmap = NULL; + has_rtpmap = PJ_TRUE; + + attr = pjmedia_sdp_media_find_attr(local_m, &ID_RTPMAP, + &local_m->desc.fmt[fmti]); + if (attr == NULL) { + has_rtpmap = PJ_FALSE; + } + if (attr != NULL) { + status = pjmedia_sdp_attr_to_rtpmap(pool, attr, &rtpmap); + if (status != PJ_SUCCESS) + has_rtpmap = PJ_FALSE; + } + + /* Build codec format info: */ + if (has_rtpmap) { + si->fmt.type = si->type; + si->fmt.pt = pj_strtoul(&local_m->desc.fmt[fmti]); + pj_strdup(pool, &si->fmt.encoding_name, &rtpmap->enc_name); + si->fmt.clock_rate = rtpmap->clock_rate; + +#if defined(PJMEDIA_HANDLE_G722_MPEG_BUG) && (PJMEDIA_HANDLE_G722_MPEG_BUG != 0) + /* The session info should have the actual clock rate, because + * this info is used for calculationg buffer size, etc in stream + */ + if (si->fmt.pt == PJMEDIA_RTP_PT_G722) + si->fmt.clock_rate = 16000; +#endif + + /* For audio codecs, rtpmap parameters denotes the number of + * channels. + */ + if (si->type == PJMEDIA_TYPE_AUDIO && rtpmap->param.slen) { + si->fmt.channel_cnt = (unsigned) pj_strtoul(&rtpmap->param); + } else { + si->fmt.channel_cnt = 1; + } + + } else { + const pjmedia_codec_info *p_info; + + status = pjmedia_codec_mgr_get_codec_info( mgr, pt, &p_info); + if (status != PJ_SUCCESS) + return status; + + pj_memcpy(&si->fmt, p_info, sizeof(pjmedia_codec_info)); + } + + /* For static payload type, pt's are symetric */ + si->tx_pt = pt; + + } else { + + attr = pjmedia_sdp_media_find_attr(local_m, &ID_RTPMAP, + &local_m->desc.fmt[fmti]); + if (attr == NULL) + return PJMEDIA_EMISSINGRTPMAP; + + status = pjmedia_sdp_attr_to_rtpmap(pool, attr, &rtpmap); + if (status != PJ_SUCCESS) + return status; + + /* Build codec format info: */ + + si->fmt.type = si->type; + si->fmt.pt = pj_strtoul(&local_m->desc.fmt[fmti]); + pj_strdup(pool, &si->fmt.encoding_name, &rtpmap->enc_name); + si->fmt.clock_rate = rtpmap->clock_rate; + + /* For audio codecs, rtpmap parameters denotes the number of + * channels. + */ + if (si->type == PJMEDIA_TYPE_AUDIO && rtpmap->param.slen) { + si->fmt.channel_cnt = (unsigned) pj_strtoul(&rtpmap->param); + } else { + si->fmt.channel_cnt = 1; + } + + /* Determine payload type for outgoing channel, by finding + * dynamic payload type in remote SDP that matches the answer. + */ + si->tx_pt = 0xFFFF; + for (i=0; i<rem_m->desc.fmt_count; ++i) { + unsigned rpt; + pjmedia_sdp_attr *r_attr; + pjmedia_sdp_rtpmap r_rtpmap; + + rpt = pj_strtoul(&rem_m->desc.fmt[i]); + if (rpt < 96) + continue; + + r_attr = pjmedia_sdp_media_find_attr(rem_m, &ID_RTPMAP, + &rem_m->desc.fmt[i]); + if (!r_attr) + continue; + + if (pjmedia_sdp_attr_get_rtpmap(r_attr, &r_rtpmap) != PJ_SUCCESS) + continue; + + if (!pj_stricmp(&rtpmap->enc_name, &r_rtpmap.enc_name) && + rtpmap->clock_rate == r_rtpmap.clock_rate) + { + /* Found matched codec. */ + si->tx_pt = rpt; + + break; + } + } + + if (si->tx_pt == 0xFFFF) + return PJMEDIA_EMISSINGRTPMAP; + } + + + /* Now that we have codec info, get the codec param. */ + si->param = PJ_POOL_ALLOC_T(pool, pjmedia_codec_param); + status = pjmedia_codec_mgr_get_default_param(mgr, &si->fmt, + si->param); + + /* Get remote fmtp for our encoder. */ + pjmedia_stream_info_parse_fmtp(pool, rem_m, si->tx_pt, + &si->param->setting.enc_fmtp); + + /* Get local fmtp for our decoder. */ + pjmedia_stream_info_parse_fmtp(pool, local_m, si->fmt.pt, + &si->param->setting.dec_fmtp); + + /* Get the remote ptime for our encoder. */ + attr = pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr, + "ptime", NULL); + if (attr) { + pj_str_t tmp_val = attr->value; + unsigned frm_per_pkt; + + pj_strltrim(&tmp_val); + + /* Round up ptime when the specified is not multiple of frm_ptime */ + frm_per_pkt = (pj_strtoul(&tmp_val) + + si->param->info.frm_ptime/2) / + si->param->info.frm_ptime; + if (frm_per_pkt != 0) { + si->param->setting.frm_per_pkt = (pj_uint8_t)frm_per_pkt; + } + } + + /* Get remote maxptime for our encoder. */ + attr = pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr, + "maxptime", NULL); + if (attr) { + pj_str_t tmp_val = attr->value; + + pj_strltrim(&tmp_val); + si->tx_maxptime = pj_strtoul(&tmp_val); + } + + /* When direction is NONE (it means SDP negotiation has failed) we don't + * need to return a failure here, as returning failure will cause + * the whole SDP to be rejected. See ticket #: + * http:// + * + * Thanks Alain Totouom + */ + if (status != PJ_SUCCESS && si->dir != PJMEDIA_DIR_NONE) + return status; + + + /* Get incomming payload type for telephone-events */ + si->rx_event_pt = -1; + for (i=0; i<local_m->attr_count; ++i) { + pjmedia_sdp_rtpmap r; + + attr = local_m->attr[i]; + if (pj_strcmp(&attr->name, &ID_RTPMAP) != 0) + continue; + if (pjmedia_sdp_attr_get_rtpmap(attr, &r) != PJ_SUCCESS) + continue; + if (pj_strcmp(&r.enc_name, &ID_TELEPHONE_EVENT) == 0) { + si->rx_event_pt = pj_strtoul(&r.pt); + break; + } + } + + /* Get outgoing payload type for telephone-events */ + si->tx_event_pt = -1; + for (i=0; i<rem_m->attr_count; ++i) { + pjmedia_sdp_rtpmap r; + + attr = rem_m->attr[i]; + if (pj_strcmp(&attr->name, &ID_RTPMAP) != 0) + continue; + if (pjmedia_sdp_attr_get_rtpmap(attr, &r) != PJ_SUCCESS) + continue; + if (pj_strcmp(&r.enc_name, &ID_TELEPHONE_EVENT) == 0) { + si->tx_event_pt = pj_strtoul(&r.pt); + break; + } + } + + return PJ_SUCCESS; +} + + + +/* + * Create stream info from SDP media line. + */ +PJ_DEF(pj_status_t) pjmedia_stream_info_from_sdp( + pjmedia_stream_info *si, + pj_pool_t *pool, + pjmedia_endpt *endpt, + const pjmedia_sdp_session *local, + const pjmedia_sdp_session *remote, + unsigned stream_idx) +{ + pjmedia_codec_mgr *mgr; + const pjmedia_sdp_attr *attr; + const pjmedia_sdp_media *local_m; + const pjmedia_sdp_media *rem_m; + const pjmedia_sdp_conn *local_conn; + const pjmedia_sdp_conn *rem_conn; + int rem_af, local_af; + pj_sockaddr local_addr; + pj_status_t status; + + + /* Validate arguments: */ + PJ_ASSERT_RETURN(pool && si && local && remote, PJ_EINVAL); + PJ_ASSERT_RETURN(stream_idx < local->media_count, PJ_EINVAL); + PJ_ASSERT_RETURN(stream_idx < remote->media_count, PJ_EINVAL); + + /* Keep SDP shortcuts */ + local_m = local->media[stream_idx]; + rem_m = remote->media[stream_idx]; + + local_conn = local_m->conn ? local_m->conn : local->conn; + if (local_conn == NULL) + return PJMEDIA_SDP_EMISSINGCONN; + + rem_conn = rem_m->conn ? rem_m->conn : remote->conn; + if (rem_conn == NULL) + return PJMEDIA_SDP_EMISSINGCONN; + + /* Media type must be audio */ + if (pj_stricmp(&local_m->desc.media, &ID_AUDIO) != 0) + return PJMEDIA_EINVALIMEDIATYPE; + + /* Get codec manager. */ + mgr = pjmedia_endpt_get_codec_mgr(endpt); + + /* Reset: */ + + pj_bzero(si, sizeof(*si)); + +#if PJMEDIA_HAS_RTCP_XR && PJMEDIA_STREAM_ENABLE_XR + /* Set default RTCP XR enabled/disabled */ + si->rtcp_xr_enabled = PJ_TRUE; +#endif + + /* Media type: */ + si->type = PJMEDIA_TYPE_AUDIO; + + /* Transport protocol */ + + /* At this point, transport type must be compatible, + * the transport instance will do more validation later. + */ + status = pjmedia_sdp_transport_cmp(&rem_m->desc.transport, + &local_m->desc.transport); + if (status != PJ_SUCCESS) + return PJMEDIA_SDPNEG_EINVANSTP; + + if (pj_stricmp(&local_m->desc.transport, &ID_RTP_AVP) == 0) { + + si->proto = PJMEDIA_TP_PROTO_RTP_AVP; + + } else if (pj_stricmp(&local_m->desc.transport, &ID_RTP_SAVP) == 0) { + + si->proto = PJMEDIA_TP_PROTO_RTP_SAVP; + + } else { + + si->proto = PJMEDIA_TP_PROTO_UNKNOWN; + return PJ_SUCCESS; + } + + + /* Check address family in remote SDP */ + rem_af = pj_AF_UNSPEC(); + if (pj_stricmp(&rem_conn->net_type, &ID_IN)==0) { + if (pj_stricmp(&rem_conn->addr_type, &ID_IP4)==0) { + rem_af = pj_AF_INET(); + } else if (pj_stricmp(&rem_conn->addr_type, &ID_IP6)==0) { + rem_af = pj_AF_INET6(); + } + } + + if (rem_af==pj_AF_UNSPEC()) { + /* Unsupported address family */ + return PJ_EAFNOTSUP; + } + + /* Set remote address: */ + status = pj_sockaddr_init(rem_af, &si->rem_addr, &rem_conn->addr, + rem_m->desc.port); + if (status != PJ_SUCCESS) { + /* Invalid IP address. */ + return PJMEDIA_EINVALIDIP; + } + + /* Check address family of local info */ + local_af = pj_AF_UNSPEC(); + if (pj_stricmp(&local_conn->net_type, &ID_IN)==0) { + if (pj_stricmp(&local_conn->addr_type, &ID_IP4)==0) { + local_af = pj_AF_INET(); + } else if (pj_stricmp(&local_conn->addr_type, &ID_IP6)==0) { + local_af = pj_AF_INET6(); + } + } + + if (local_af==pj_AF_UNSPEC()) { + /* Unsupported address family */ + return PJ_SUCCESS; + } + + /* Set remote address: */ + status = pj_sockaddr_init(local_af, &local_addr, &local_conn->addr, + local_m->desc.port); + if (status != PJ_SUCCESS) { + /* Invalid IP address. */ + return PJMEDIA_EINVALIDIP; + } + + /* Local and remote address family must match */ + if (local_af != rem_af) + return PJ_EAFNOTSUP; + + /* Media direction: */ + + if (local_m->desc.port == 0 || + pj_sockaddr_has_addr(&local_addr)==PJ_FALSE || + pj_sockaddr_has_addr(&si->rem_addr)==PJ_FALSE || + pjmedia_sdp_media_find_attr(local_m, &STR_INACTIVE, NULL)!=NULL) + { + /* Inactive stream. */ + + si->dir = PJMEDIA_DIR_NONE; + + } else if (pjmedia_sdp_media_find_attr(local_m, &STR_SENDONLY, NULL)!=NULL) { + + /* Send only stream. */ + + si->dir = PJMEDIA_DIR_ENCODING; + + } else if (pjmedia_sdp_media_find_attr(local_m, &STR_RECVONLY, NULL)!=NULL) { + + /* Recv only stream. */ + + si->dir = PJMEDIA_DIR_DECODING; + + } else { + + /* Send and receive stream. */ + + si->dir = PJMEDIA_DIR_ENCODING_DECODING; + + } + + /* No need to do anything else if stream is rejected */ + if (local_m->desc.port == 0) { + return PJ_SUCCESS; + } + + /* If "rtcp" attribute is present in the SDP, set the RTCP address + * from that attribute. Otherwise, calculate from RTP address. + */ + attr = pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr, + "rtcp", NULL); + if (attr) { + pjmedia_sdp_rtcp_attr rtcp; + status = pjmedia_sdp_attr_get_rtcp(attr, &rtcp); + if (status == PJ_SUCCESS) { + if (rtcp.addr.slen) { + status = pj_sockaddr_init(rem_af, &si->rem_rtcp, &rtcp.addr, + (pj_uint16_t)rtcp.port); + } else { + pj_sockaddr_init(rem_af, &si->rem_rtcp, NULL, + (pj_uint16_t)rtcp.port); + pj_memcpy(pj_sockaddr_get_addr(&si->rem_rtcp), + pj_sockaddr_get_addr(&si->rem_addr), + pj_sockaddr_get_addr_len(&si->rem_addr)); + } + } + } + + if (!pj_sockaddr_has_addr(&si->rem_rtcp)) { + int rtcp_port; + + pj_memcpy(&si->rem_rtcp, &si->rem_addr, sizeof(pj_sockaddr)); + rtcp_port = pj_sockaddr_get_port(&si->rem_addr) + 1; + pj_sockaddr_set_port(&si->rem_rtcp, (pj_uint16_t)rtcp_port); + } + + + /* Get the payload number for receive channel. */ + /* + Previously we used to rely on fmt[0] being the selected codec, + but some UA sends telephone-event as fmt[0] and this would + cause assert failure below. + + Thanks Chris Hamilton <chamilton .at. cs.dal.ca> for this patch. + + // And codec must be numeric! + if (!pj_isdigit(*local_m->desc.fmt[0].ptr) || + !pj_isdigit(*rem_m->desc.fmt[0].ptr)) + { + return PJMEDIA_EINVALIDPT; + } + + pt = pj_strtoul(&local_m->desc.fmt[0]); + pj_assert(PJMEDIA_RTP_PT_TELEPHONE_EVENTS==0 || + pt != PJMEDIA_RTP_PT_TELEPHONE_EVENTS); + */ + + /* Get codec info and param */ + status = get_audio_codec_info_param(si, pool, mgr, local_m, rem_m); + + /* Leave SSRC to random. */ + si->ssrc = pj_rand(); + + /* Set default jitter buffer parameter. */ + si->jb_init = si->jb_max = si->jb_min_pre = si->jb_max_pre = -1; + + return status; +} diff --git a/pjmedia/src/pjmedia/stream_common.c b/pjmedia/src/pjmedia/stream_common.c new file mode 100644 index 00000000..9970cc78 --- /dev/null +++ b/pjmedia/src/pjmedia/stream_common.c @@ -0,0 +1,111 @@ +/* $Id$ */ +/* + * Copyright (C) 2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjmedia/stream_common.h> +#include <pj/log.h> + +#define THIS_FILE "stream_common.c" + +/* + * Parse fmtp for specified format/payload type. + */ +PJ_DEF(pj_status_t) pjmedia_stream_info_parse_fmtp( pj_pool_t *pool, + const pjmedia_sdp_media *m, + unsigned pt, + pjmedia_codec_fmtp *fmtp) +{ + const pjmedia_sdp_attr *attr; + pjmedia_sdp_fmtp sdp_fmtp; + char *p, *p_end, fmt_buf[8]; + pj_str_t fmt; + pj_status_t status; + + pj_assert(m && fmtp); + + pj_bzero(fmtp, sizeof(pjmedia_codec_fmtp)); + + /* Get "fmtp" attribute for the format */ + pj_ansi_sprintf(fmt_buf, "%d", pt); + fmt = pj_str(fmt_buf); + attr = pjmedia_sdp_media_find_attr2(m, "fmtp", &fmt); + if (attr == NULL) + return PJ_SUCCESS; + + /* Parse "fmtp" attribute */ + status = pjmedia_sdp_attr_get_fmtp(attr, &sdp_fmtp); + if (status != PJ_SUCCESS) + return status; + + /* Prepare parsing */ + p = sdp_fmtp.fmt_param.ptr; + p_end = p + sdp_fmtp.fmt_param.slen; + + /* Parse */ + while (p < p_end) { + char *token, *start, *end; + + if (fmtp->cnt >= PJMEDIA_CODEC_MAX_FMTP_CNT) { + PJ_LOG(4,(THIS_FILE, + "Warning: fmtp parameter count exceeds " + "PJMEDIA_CODEC_MAX_FMTP_CNT")); + return PJ_SUCCESS; + } + + /* Skip whitespaces */ + while (p < p_end && (*p == ' ' || *p == '\t')) ++p; + if (p == p_end) + break; + + /* Get token */ + start = p; + while (p < p_end && *p != ';' && *p != '=') ++p; + end = p - 1; + + /* Right trim */ + while (end >= start && (*end == ' ' || *end == '\t' || + *end == '\r' || *end == '\n' )) + --end; + + /* Forward a char after trimming */ + ++end; + + /* Store token */ + if (end > start) { + if (pool) { + token = (char*)pj_pool_alloc(pool, end - start); + pj_ansi_strncpy(token, start, end - start); + } else { + token = start; + } + if (*p == '=') + /* Got param name */ + pj_strset(&fmtp->param[fmtp->cnt].name, token, end - start); + else + /* Got param value */ + pj_strset(&fmtp->param[fmtp->cnt++].val, token, end - start); + } else if (*p != '=') { + ++fmtp->cnt; + } + + /* Next */ + ++p; + } + + return PJ_SUCCESS; +} + diff --git a/pjmedia/src/pjmedia/tonegen.c b/pjmedia/src/pjmedia/tonegen.c index a59c7fd3..954494bf 100644 --- a/pjmedia/src/pjmedia/tonegen.c +++ b/pjmedia/src/pjmedia/tonegen.c @@ -334,7 +334,7 @@ static void generate_tone(struct gen_state *state, /****************************************************************************/ -#define SIGNATURE PJMEDIA_PORT_SIGNATURE('t', 'n', 'g', 'n') +#define SIGNATURE PJMEDIA_SIG_PORT_TONEGEN #define THIS_FILE "tonegen.c" #if 0 @@ -558,7 +558,7 @@ static pj_status_t tonegen_get_frame(pjmedia_port *port, { struct tonegen *tonegen = (struct tonegen*) port; short *dst, *end; - unsigned clock_rate = tonegen->base.info.clock_rate; + unsigned clock_rate = PJMEDIA_PIA_SRATE(&tonegen->base.info); PJ_ASSERT_RETURN(port->info.signature == SIGNATURE, PJ_EINVAL); @@ -622,7 +622,7 @@ static pj_status_t tonegen_get_frame(pjmedia_port *port, } dst = (short*) frame->buf; - end = dst + port->info.samples_per_frame; + end = dst + PJMEDIA_PIA_SPF(&port->info); while (dst < end) { pjmedia_tone_desc *dig = &tonegen->digits[tonegen->cur_digit]; @@ -636,7 +636,8 @@ static pj_status_t tonegen_get_frame(pjmedia_port *port, if (tonegen->dig_samples == 0 && (tonegen->count!=1 || !(dig->flags & PJMEDIA_TONE_INITIALIZED))) { - init_generate_tone(&tonegen->state, port->info.clock_rate, + init_generate_tone(&tonegen->state, + PJMEDIA_PIA_SRATE(&port->info), dig->freq1, dig->freq2, dig->volume); dig->flags |= PJMEDIA_TONE_INITIALIZED; if (tonegen->cur_digit > 0) { @@ -651,7 +652,8 @@ static pj_status_t tonegen_get_frame(pjmedia_port *port, cnt = on_samp - tonegen->dig_samples; if (cnt > required) cnt = required; - generate_tone(&tonegen->state, port->info.channel_count, + generate_tone(&tonegen->state, + PJMEDIA_PIA_CCNT(&port->info), cnt, dst); dst += cnt; @@ -729,7 +731,7 @@ static pj_status_t tonegen_get_frame(pjmedia_port *port, pjmedia_zero_samples(dst, end-dst); frame->type = PJMEDIA_FRAME_TYPE_AUDIO; - frame->size = port->info.bytes_per_frame; + frame->size = PJMEDIA_PIA_AVG_FSZ(&port->info); TRACE_((THIS_FILE, "tonegen_get_frame(): frame created, level=%u", pjmedia_calc_avg_signal((pj_int16_t*)frame->buf, frame->size/2))); diff --git a/pjmedia/src/pjmedia/transport_ice.c b/pjmedia/src/pjmedia/transport_ice.c index df2a68ed..54ae0324 100644 --- a/pjmedia/src/pjmedia/transport_ice.c +++ b/pjmedia/src/pjmedia/transport_ice.c @@ -206,6 +206,22 @@ PJ_DEF(pj_status_t) pjmedia_ice_create2(pjmedia_endpt *endpt, unsigned options, pjmedia_transport **p_tp) { + return pjmedia_ice_create3(endpt, name, comp_cnt, cfg, cb, + options, NULL, p_tp); +} + +/* + * Create ICE media transport. + */ +PJ_DEF(pj_status_t) pjmedia_ice_create3(pjmedia_endpt *endpt, + const char *name, + unsigned comp_cnt, + const pj_ice_strans_cfg *cfg, + const pjmedia_ice_cb *cb, + unsigned options, + void *user_data, + pjmedia_transport **p_tp) +{ pj_pool_t *pool; pj_ice_strans_cb ice_st_cb; struct transport_ice *tp_ice; @@ -223,6 +239,7 @@ PJ_DEF(pj_status_t) pjmedia_ice_create2(pjmedia_endpt *endpt, pj_ansi_strcpy(tp_ice->base.name, pool->obj_name); tp_ice->base.op = &transport_ice_op; tp_ice->base.type = PJMEDIA_TRANSPORT_TYPE_ICE; + tp_ice->base.user_data = user_data; tp_ice->initial_sdp = PJ_TRUE; tp_ice->oa_role = ROLE_NONE; tp_ice->use_ice = PJ_FALSE; diff --git a/pjmedia/src/pjmedia/types.c b/pjmedia/src/pjmedia/types.c new file mode 100644 index 00000000..2f178f24 --- /dev/null +++ b/pjmedia/src/pjmedia/types.c @@ -0,0 +1,47 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2010 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjmedia/types.h> +#include <pj/assert.h> + +/** + * Utility function to return the string name for a pjmedia_type. + * + * @param t The media type. + * + * @return String. + */ +PJ_DEF(const char*) pjmedia_type_name(pjmedia_type t) +{ + const char *type_names[] = { + "none", + "audio", + "video", + "application", + "unknown" + }; + + pj_assert(t < PJ_ARRAY_SIZE(type_names)); + pj_assert(PJMEDIA_TYPE_UNKNOWN == 4); + + if (t < PJ_ARRAY_SIZE(type_names)) + return type_names[t]; + else + return "??"; +} diff --git a/pjmedia/src/pjmedia/vid_codec.c b/pjmedia/src/pjmedia/vid_codec.c new file mode 100644 index 00000000..8eded7df --- /dev/null +++ b/pjmedia/src/pjmedia/vid_codec.c @@ -0,0 +1,731 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjmedia/vid_codec.h> +#include <pjmedia/errno.h> +#include <pj/array.h> +#include <pj/assert.h> +#include <pj/log.h> +#include <pj/string.h> + +#define THIS_FILE "vid_codec.c" + +static pjmedia_vid_codec_mgr *def_vid_codec_mgr; + + +/* + * Codec manager maintains array of these structs for each supported + * codec. + */ +typedef struct pjmedia_vid_codec_desc +{ + pjmedia_vid_codec_info info; /**< Codec info. */ + pjmedia_codec_id id; /**< Fully qualified name */ + pjmedia_codec_priority prio; /**< Priority. */ + pjmedia_vid_codec_factory *factory; /**< The factory. */ + pjmedia_vid_codec_param *def_param; /**< Default codecs + parameters. */ +} pjmedia_vid_codec_desc; + + +/* The declaration of video codec manager */ +struct pjmedia_vid_codec_mgr +{ + /** Codec manager mutex. */ + pj_mutex_t *mutex; + + /** List of codec factories registered to codec manager. */ + pjmedia_vid_codec_factory factory_list; + + /** Number of supported codecs. */ + unsigned codec_cnt; + + /** Array of codec descriptor. */ + pjmedia_vid_codec_desc codec_desc[PJMEDIA_CODEC_MGR_MAX_CODECS]; + +}; + + + +/* Sort codecs in codec manager based on priorities */ +static void sort_codecs(pjmedia_vid_codec_mgr *mgr); + +/* + * Initialize pjmedia_vid_codec structure with default values. + */ +PJ_DEF(void) pjmedia_vid_codec_reset(pjmedia_vid_codec *codec, + pjmedia_obj_sig sig) +{ + pj_bzero(codec, sizeof(*codec)); + pjmedia_event_publisher_init(&codec->epub, sig); +} + +/* + * Duplicate video codec parameter. + */ +PJ_DEF(pjmedia_vid_codec_param*) pjmedia_vid_codec_param_clone( + pj_pool_t *pool, + const pjmedia_vid_codec_param *src) +{ + pjmedia_vid_codec_param *p; + unsigned i; + + PJ_ASSERT_RETURN(pool && src, NULL); + + p = PJ_POOL_ZALLOC_T(pool, pjmedia_vid_codec_param); + + /* Update codec param */ + pj_memcpy(p, src, sizeof(pjmedia_vid_codec_param)); + for (i = 0; i < src->dec_fmtp.cnt; ++i) { + pj_strdup(pool, &p->dec_fmtp.param[i].name, + &src->dec_fmtp.param[i].name); + pj_strdup(pool, &p->dec_fmtp.param[i].val, + &src->dec_fmtp.param[i].val); + } + for (i = 0; i < src->enc_fmtp.cnt; ++i) { + pj_strdup(pool, &p->enc_fmtp.param[i].name, + &src->enc_fmtp.param[i].name); + pj_strdup(pool, &p->enc_fmtp.param[i].val, + &src->enc_fmtp.param[i].val); + } + + return p; +} + +/* + * Initialize codec manager. + */ +PJ_DEF(pj_status_t) pjmedia_vid_codec_mgr_create( + pj_pool_t *pool, + pjmedia_vid_codec_mgr **p_mgr) +{ + pjmedia_vid_codec_mgr *mgr; + pj_status_t status; + + PJ_ASSERT_RETURN(pool, PJ_EINVAL); + + mgr = PJ_POOL_ZALLOC_T(pool, pjmedia_vid_codec_mgr); + pj_list_init (&mgr->factory_list); + mgr->codec_cnt = 0; + + /* Create mutex */ + status = pj_mutex_create_recursive(pool, "vid-codec-mgr", &mgr->mutex); + if (status != PJ_SUCCESS) + return status; + + if (!def_vid_codec_mgr) + def_vid_codec_mgr = mgr; + + if (p_mgr) + *p_mgr = mgr; + + return PJ_SUCCESS; +} + +/* + * Initialize codec manager. + */ +PJ_DEF(pj_status_t) pjmedia_vid_codec_mgr_destroy (pjmedia_vid_codec_mgr *mgr) +{ + if (!mgr) mgr = def_vid_codec_mgr; + PJ_ASSERT_RETURN(mgr, PJ_EINVAL); + + /* Destroy mutex */ + if (mgr->mutex) + pj_mutex_destroy(mgr->mutex); + + /* Just for safety, set codec manager states to zero */ + pj_bzero(mgr, sizeof(pjmedia_vid_codec_mgr)); + + if (mgr == def_vid_codec_mgr) + def_vid_codec_mgr = NULL; + + return PJ_SUCCESS; +} + + +PJ_DEF(pjmedia_vid_codec_mgr*) pjmedia_vid_codec_mgr_instance(void) +{ + //pj_assert(def_vid_codec_mgr); + return def_vid_codec_mgr; +} + +PJ_DEF(void) pjmedia_vid_codec_mgr_set_instance(pjmedia_vid_codec_mgr* mgr) +{ + def_vid_codec_mgr = mgr; +} + + +/* + * Register a codec factory. + */ +PJ_DEF(pj_status_t) pjmedia_vid_codec_mgr_register_factory( + pjmedia_vid_codec_mgr *mgr, + pjmedia_vid_codec_factory *factory) +{ + pjmedia_vid_codec_info info[PJMEDIA_CODEC_MGR_MAX_CODECS]; + unsigned i, count; + pj_status_t status; + + PJ_ASSERT_RETURN(factory, PJ_EINVAL); + + if (!mgr) mgr = def_vid_codec_mgr; + PJ_ASSERT_RETURN(mgr, PJ_EINVAL); + + /* Enum codecs */ + count = PJ_ARRAY_SIZE(info); + status = factory->op->enum_info(factory, &count, info); + if (status != PJ_SUCCESS) + return status; + + pj_mutex_lock(mgr->mutex); + + /* Check codec count */ + if (count + mgr->codec_cnt > PJ_ARRAY_SIZE(mgr->codec_desc)) { + pj_mutex_unlock(mgr->mutex); + return PJ_ETOOMANY; + } + + + /* Save the codecs */ + for (i=0; i<count; ++i) { + pj_memcpy( &mgr->codec_desc[mgr->codec_cnt+i], + &info[i], sizeof(pjmedia_vid_codec_info)); + mgr->codec_desc[mgr->codec_cnt+i].prio = PJMEDIA_CODEC_PRIO_NORMAL; + mgr->codec_desc[mgr->codec_cnt+i].factory = factory; + pjmedia_vid_codec_info_to_id( &info[i], + mgr->codec_desc[mgr->codec_cnt+i].id, + sizeof(pjmedia_codec_id)); + } + + /* Update count */ + mgr->codec_cnt += count; + + /* Re-sort codec based on priorities */ + sort_codecs(mgr); + + /* Add factory to the list */ + pj_list_push_back(&mgr->factory_list, factory); + + pj_mutex_unlock(mgr->mutex); + + return PJ_SUCCESS; +} + + +/* + * Unregister a codec factory. + */ +PJ_DEF(pj_status_t) pjmedia_vid_codec_mgr_unregister_factory( + pjmedia_vid_codec_mgr *mgr, + pjmedia_vid_codec_factory *factory) +{ + unsigned i; + PJ_ASSERT_RETURN(factory, PJ_EINVAL); + + if (!mgr) mgr = def_vid_codec_mgr; + PJ_ASSERT_RETURN(mgr, PJ_EINVAL); + + pj_mutex_lock(mgr->mutex); + + /* Factory must be registered. */ + if (pj_list_find_node(&mgr->factory_list, factory) != factory) { + pj_mutex_unlock(mgr->mutex); + return PJ_ENOTFOUND; + } + + /* Erase factory from the factory list */ + pj_list_erase(factory); + + + /* Remove all supported codecs from the codec manager that were created + * by the specified factory. + */ + for (i=0; i<mgr->codec_cnt; ) { + + if (mgr->codec_desc[i].factory == factory) { + /* Remove the codec from array of codec descriptions */ + pj_array_erase(mgr->codec_desc, sizeof(mgr->codec_desc[0]), + mgr->codec_cnt, i); + --mgr->codec_cnt; + + } else { + ++i; + } + } + + pj_mutex_unlock(mgr->mutex); + + return PJ_SUCCESS; +} + + +/* + * Enum all codecs. + */ +PJ_DEF(pj_status_t) pjmedia_vid_codec_mgr_enum_codecs( + pjmedia_vid_codec_mgr *mgr, + unsigned *count, + pjmedia_vid_codec_info codecs[], + unsigned *prio) +{ + unsigned i; + + PJ_ASSERT_RETURN(count && codecs, PJ_EINVAL); + + if (!mgr) mgr = def_vid_codec_mgr; + PJ_ASSERT_RETURN(mgr, PJ_EINVAL); + + pj_mutex_lock(mgr->mutex); + + if (*count > mgr->codec_cnt) + *count = mgr->codec_cnt; + + for (i=0; i<*count; ++i) { + pj_memcpy(&codecs[i], + &mgr->codec_desc[i].info, + sizeof(pjmedia_vid_codec_info)); + } + + if (prio) { + for (i=0; i < *count; ++i) + prio[i] = mgr->codec_desc[i].prio; + } + + pj_mutex_unlock(mgr->mutex); + + return PJ_SUCCESS; +} + + +/* + * Get codec info for the specified payload type. + */ +PJ_DEF(pj_status_t) pjmedia_vid_codec_mgr_get_codec_info( + pjmedia_vid_codec_mgr *mgr, + unsigned pt, + const pjmedia_vid_codec_info **p_info) +{ + unsigned i; + + PJ_ASSERT_RETURN(p_info, PJ_EINVAL); + + if (!mgr) mgr = def_vid_codec_mgr; + PJ_ASSERT_RETURN(mgr, PJ_EINVAL); + + pj_mutex_lock(mgr->mutex); + + for (i=0; i<mgr->codec_cnt; ++i) { + if (mgr->codec_desc[i].info.pt == pt) { + *p_info = &mgr->codec_desc[i].info; + + pj_mutex_unlock(mgr->mutex); + return PJ_SUCCESS; + } + } + + pj_mutex_unlock(mgr->mutex); + + return PJMEDIA_CODEC_EUNSUP; +} + + +PJ_DEF(pj_status_t) pjmedia_vid_codec_mgr_get_codec_info2( + pjmedia_vid_codec_mgr *mgr, + pjmedia_format_id fmt_id, + const pjmedia_vid_codec_info **p_info) +{ + unsigned i; + + PJ_ASSERT_RETURN(p_info, PJ_EINVAL); + + if (!mgr) mgr = def_vid_codec_mgr; + PJ_ASSERT_RETURN(mgr, PJ_EINVAL); + + pj_mutex_lock(mgr->mutex); + + for (i=0; i<mgr->codec_cnt; ++i) { + if (mgr->codec_desc[i].info.fmt_id == fmt_id) { + *p_info = &mgr->codec_desc[i].info; + + pj_mutex_unlock(mgr->mutex); + return PJ_SUCCESS; + } + } + + pj_mutex_unlock(mgr->mutex); + + return PJMEDIA_CODEC_EUNSUP; +} + + +/* + * Convert codec info struct into a unique codec identifier. + * A codec identifier looks something like "H263/34". + */ +PJ_DEF(char*) pjmedia_vid_codec_info_to_id( + const pjmedia_vid_codec_info *info, + char *id, unsigned max_len ) +{ + int len; + + PJ_ASSERT_RETURN(info && id && max_len, NULL); + + len = pj_ansi_snprintf(id, max_len, "%.*s/%u", + (int)info->encoding_name.slen, + info->encoding_name.ptr, + info->pt); + + if (len < 1 || len >= (int)max_len) { + id[0] = '\0'; + return NULL; + } + + return id; +} + + +/* + * Find codecs by the unique codec identifier. This function will find + * all codecs that match the codec identifier prefix. For example, if + * "L16" is specified, then it will find "L16/8000/1", "L16/16000/1", + * and so on, up to the maximum count specified in the argument. + */ +PJ_DEF(pj_status_t) pjmedia_vid_codec_mgr_find_codecs_by_id( + pjmedia_vid_codec_mgr *mgr, + const pj_str_t *codec_id, + unsigned *count, + const pjmedia_vid_codec_info *p_info[], + unsigned prio[]) +{ + unsigned i, found = 0; + + PJ_ASSERT_RETURN(codec_id && count && *count, PJ_EINVAL); + + if (!mgr) mgr = def_vid_codec_mgr; + PJ_ASSERT_RETURN(mgr, PJ_EINVAL); + + pj_mutex_lock(mgr->mutex); + + for (i=0; i<mgr->codec_cnt; ++i) { + + if (codec_id->slen == 0 || + pj_strnicmp2(codec_id, mgr->codec_desc[i].id, + codec_id->slen) == 0) + { + + if (p_info) + p_info[found] = &mgr->codec_desc[i].info; + if (prio) + prio[found] = mgr->codec_desc[i].prio; + + ++found; + + if (found >= *count) + break; + } + + } + + pj_mutex_unlock(mgr->mutex); + + *count = found; + + return found ? PJ_SUCCESS : PJ_ENOTFOUND; +} + + +/* Swap two codecs positions in codec manager */ +static void swap_codec(pjmedia_vid_codec_mgr *mgr, unsigned i, unsigned j) +{ + pjmedia_vid_codec_desc tmp; + + pj_memcpy(&tmp, &mgr->codec_desc[i], sizeof(pjmedia_vid_codec_desc)); + + pj_memcpy(&mgr->codec_desc[i], &mgr->codec_desc[j], + sizeof(pjmedia_vid_codec_desc)); + + pj_memcpy(&mgr->codec_desc[j], &tmp, sizeof(pjmedia_vid_codec_desc)); +} + + +/* Sort codecs in codec manager based on priorities */ +static void sort_codecs(pjmedia_vid_codec_mgr *mgr) +{ + unsigned i; + + /* Re-sort */ + for (i=0; i<mgr->codec_cnt; ++i) { + unsigned j, max; + + for (max=i, j=i+1; j<mgr->codec_cnt; ++j) { + if (mgr->codec_desc[j].prio > mgr->codec_desc[max].prio) + max = j; + } + + if (max != i) + swap_codec(mgr, i, max); + } + + /* Change PJMEDIA_CODEC_PRIO_HIGHEST codecs to NEXT_HIGHER */ + for (i=0; i<mgr->codec_cnt; ++i) { + if (mgr->codec_desc[i].prio == PJMEDIA_CODEC_PRIO_HIGHEST) + mgr->codec_desc[i].prio = PJMEDIA_CODEC_PRIO_NEXT_HIGHER; + else + break; + } +} + + +/** + * Set codec priority. The codec priority determines the order of + * the codec in the SDP created by the endpoint. If more than one codecs + * are found with the same codec_id prefix, then the function sets the + * priorities of all those codecs. + */ +PJ_DEF(pj_status_t) pjmedia_vid_codec_mgr_set_codec_priority( + pjmedia_vid_codec_mgr *mgr, + const pj_str_t *codec_id, + pj_uint8_t prio) +{ + unsigned i, found = 0; + + PJ_ASSERT_RETURN(codec_id, PJ_EINVAL); + + if (!mgr) mgr = def_vid_codec_mgr; + PJ_ASSERT_RETURN(mgr, PJ_EINVAL); + + pj_mutex_lock(mgr->mutex); + + /* Update the priorities of affected codecs */ + for (i=0; i<mgr->codec_cnt; ++i) + { + if (codec_id->slen == 0 || + pj_strnicmp2(codec_id, mgr->codec_desc[i].id, + codec_id->slen) == 0) + { + mgr->codec_desc[i].prio = (pjmedia_codec_priority) prio; + ++found; + } + } + + if (!found) { + pj_mutex_unlock(mgr->mutex); + return PJ_ENOTFOUND; + } + + /* Re-sort codecs */ + sort_codecs(mgr); + + pj_mutex_unlock(mgr->mutex); + + return PJ_SUCCESS; +} + + +/* + * Allocate one codec. + */ +PJ_DEF(pj_status_t) pjmedia_vid_codec_mgr_alloc_codec( + pjmedia_vid_codec_mgr *mgr, + const pjmedia_vid_codec_info *info, + pjmedia_vid_codec **p_codec) +{ + pjmedia_vid_codec_factory *factory; + pj_status_t status; + + PJ_ASSERT_RETURN(info && p_codec, PJ_EINVAL); + + if (!mgr) mgr = def_vid_codec_mgr; + PJ_ASSERT_RETURN(mgr, PJ_EINVAL); + + *p_codec = NULL; + + pj_mutex_lock(mgr->mutex); + + factory = mgr->factory_list.next; + while (factory != &mgr->factory_list) { + + if ( (*factory->op->test_alloc)(factory, info) == PJ_SUCCESS ) { + + status = (*factory->op->alloc_codec)(factory, info, p_codec); + if (status == PJ_SUCCESS) { + pj_mutex_unlock(mgr->mutex); + return PJ_SUCCESS; + } + + } + + factory = factory->next; + } + + pj_mutex_unlock(mgr->mutex); + + return PJMEDIA_CODEC_EUNSUP; +} + + +/* + * Get default codec parameter. + */ +PJ_DEF(pj_status_t) pjmedia_vid_codec_mgr_get_default_param( + pjmedia_vid_codec_mgr *mgr, + const pjmedia_vid_codec_info *info, + pjmedia_vid_codec_param *param ) +{ + pjmedia_vid_codec_factory *factory; + pj_status_t status; + pjmedia_codec_id codec_id; + pjmedia_vid_codec_desc *codec_desc = NULL; + unsigned i; + + PJ_ASSERT_RETURN(info && param, PJ_EINVAL); + + if (!mgr) mgr = def_vid_codec_mgr; + PJ_ASSERT_RETURN(mgr, PJ_EINVAL); + + if (!pjmedia_vid_codec_info_to_id(info, (char*)&codec_id, + sizeof(codec_id))) + return PJ_EINVAL; + + pj_mutex_lock(mgr->mutex); + + /* First, lookup default param in codec desc */ + for (i=0; i < mgr->codec_cnt; ++i) { + if (pj_ansi_stricmp(codec_id, mgr->codec_desc[i].id) == 0) { + codec_desc = &mgr->codec_desc[i]; + break; + } + } + + /* If we found the codec and its default param is set, return it */ + if (codec_desc && codec_desc->def_param) { + pj_memcpy(param, codec_desc->def_param, + sizeof(pjmedia_vid_codec_param)); + + pj_mutex_unlock(mgr->mutex); + return PJ_SUCCESS; + } + + /* Otherwise query the default param from codec factory */ + factory = mgr->factory_list.next; + while (factory != &mgr->factory_list) { + + if ( (*factory->op->test_alloc)(factory, info) == PJ_SUCCESS ) { + + status = (*factory->op->default_attr)(factory, info, param); + if (status == PJ_SUCCESS) { + /* Check for invalid max_bps. */ + //if (param->info.max_bps < param->info.avg_bps) + // param->info.max_bps = param->info.avg_bps; + + pj_mutex_unlock(mgr->mutex); + return PJ_SUCCESS; + } + + } + + factory = factory->next; + } + + pj_mutex_unlock(mgr->mutex); + + + return PJMEDIA_CODEC_EUNSUP; +} + + +/* + * Set default codec parameter. + */ +PJ_DEF(pj_status_t) pjmedia_vid_codec_mgr_set_default_param( + pjmedia_vid_codec_mgr *mgr, + pj_pool_t *pool, + const pjmedia_vid_codec_info *info, + const pjmedia_vid_codec_param *param ) +{ + unsigned i; + pjmedia_codec_id codec_id; + pjmedia_vid_codec_desc *codec_desc = NULL; + pjmedia_vid_codec_param *p; + + PJ_ASSERT_RETURN(info, PJ_EINVAL); + + if (!mgr) mgr = def_vid_codec_mgr; + PJ_ASSERT_RETURN(mgr, PJ_EINVAL); + + if (!pjmedia_vid_codec_info_to_id(info, (char*)&codec_id, sizeof(codec_id))) + return PJ_EINVAL; + + pj_mutex_lock(mgr->mutex); + + /* Lookup codec desc */ + for (i=0; i < mgr->codec_cnt; ++i) { + if (pj_ansi_stricmp(codec_id, mgr->codec_desc[i].id) == 0) { + codec_desc = &mgr->codec_desc[i]; + break; + } + } + + /* Codec not found */ + if (!codec_desc) { + pj_mutex_unlock(mgr->mutex); + return PJMEDIA_CODEC_EUNSUP; + } + + /* If codec param is previously set */ + if (codec_desc->def_param) { + codec_desc->def_param = NULL; + } + + /* When param is set to NULL, i.e: setting default codec param to library + * default setting, just return PJ_SUCCESS. + */ + if (NULL == param) { + pj_mutex_unlock(mgr->mutex); + return PJ_SUCCESS; + } + + /* Update codec default param */ + p = pjmedia_vid_codec_param_clone(pool, param); + if (!p) + return PJ_EINVAL; + codec_desc->def_param = p; + + pj_mutex_unlock(mgr->mutex); + + return PJ_SUCCESS; +} + + +/* + * Dealloc codec. + */ +PJ_DEF(pj_status_t) +pjmedia_vid_codec_mgr_dealloc_codec(pjmedia_vid_codec_mgr *mgr, + pjmedia_vid_codec *codec) +{ + PJ_ASSERT_RETURN(codec, PJ_EINVAL); + + if (!mgr) mgr = def_vid_codec_mgr; + PJ_ASSERT_RETURN(mgr, PJ_EINVAL); + + return (*codec->factory->op->dealloc_codec)(codec->factory, codec); +} + diff --git a/pjmedia/src/pjmedia/vid_codec_util.c b/pjmedia/src/pjmedia/vid_codec_util.c new file mode 100644 index 00000000..17e713a3 --- /dev/null +++ b/pjmedia/src/pjmedia/vid_codec_util.c @@ -0,0 +1,619 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjmedia/vid_codec_util.h> +#include <pjmedia/errno.h> +#include <pjmedia/stream_common.h> +#include <pjlib-util/base64.h> +#include <pj/ctype.h> +#include <pj/math.h> + +#define THIS_FILE "vid_codec_util.c" + +/* If this is set to non-zero, H.264 custom negotiation will require + * "profile-level-id" and "packetization-mode" to be exact match to + * get a successful negotiation. Note that flexible answer (updating + * SDP answer to match remote offer) is always active regardless the + * value of this macro. + */ +#define H264_STRICT_SDP_NEGO 0 + + +/* ITU resolution definition */ +struct mpi_resolution_t +{ + pj_str_t name; + pjmedia_rect_size size; +} +mpi_resolutions [] = +{ + {{"CIF",3}, {352,288}}, + {{"QCIF",4}, {176,144}}, + {{"SQCIF",5}, {88,72}}, + {{"CIF4",4}, {704,576}}, + {{"CIF16",5}, {1408,1142}}, +}; + + +/* Parse fmtp value for custom resolution, e.g: "CUSTOM=800,600,2" */ +static pj_status_t parse_custom_res_fmtp(const pj_str_t *fmtp_val, + pjmedia_rect_size *size, + unsigned *mpi) +{ + const char *p, *p_end; + pj_str_t token; + unsigned long val[3] = {0}; + unsigned i = 0; + + p = token.ptr = fmtp_val->ptr; + p_end = p + fmtp_val->slen; + + while (p<=p_end && i<PJ_ARRAY_SIZE(val)) { + if (*p==',' || p==p_end) { + token.slen = (char*)p - token.ptr; + val[i++] = pj_strtoul(&token); + token.ptr = (char*)p+1; + } + ++p; + } + + if (!val[0] || !val[1]) + return PJ_ETOOSMALL; + + if (val[2]<1 || val[2]>32) + return PJ_EINVAL; + + size->w = val[0]; + size->h = val[1]; + *mpi = val[2]; + return PJ_SUCCESS; +} + + +/* H263 fmtp parser */ +PJ_DEF(pj_status_t) pjmedia_vid_codec_parse_h263_fmtp( + const pjmedia_codec_fmtp *fmtp, + pjmedia_vid_codec_h263_fmtp *h263_fmtp) +{ + const pj_str_t CUSTOM = {"CUSTOM", 6}; + unsigned i; + + pj_bzero(h263_fmtp, sizeof(*h263_fmtp)); + + for (i=0; i<fmtp->cnt; ++i) { + unsigned j; + pj_bool_t parsed = PJ_FALSE; + + if (h263_fmtp->mpi_cnt >= PJ_ARRAY_SIZE(h263_fmtp->mpi)) { + pj_assert(!"Too small MPI array in H263 fmtp"); + continue; + } + + /* Standard size MPIs */ + for (j=0; j<PJ_ARRAY_SIZE(mpi_resolutions) && !parsed; ++j) { + if (pj_stricmp(&fmtp->param[i].name, &mpi_resolutions[j].name)==0) + { + unsigned mpi; + + mpi = pj_strtoul(&fmtp->param[i].val); + if (mpi<1 || mpi>32) + return PJMEDIA_SDP_EINFMTP; + + h263_fmtp->mpi[h263_fmtp->mpi_cnt].size = + mpi_resolutions[j].size; + h263_fmtp->mpi[h263_fmtp->mpi_cnt].val = mpi; + ++h263_fmtp->mpi_cnt; + parsed = PJ_TRUE; + } + } + if (parsed) + continue; + + /* Custom size MPIs */ + if (pj_stricmp(&fmtp->param[i].name, &CUSTOM)==0) { + pjmedia_rect_size size; + unsigned mpi; + pj_status_t status; + + status = parse_custom_res_fmtp(&fmtp->param[i].val, &size, &mpi); + if (status != PJ_SUCCESS) + return PJMEDIA_SDP_EINFMTP; + + h263_fmtp->mpi[h263_fmtp->mpi_cnt].size = size; + h263_fmtp->mpi[h263_fmtp->mpi_cnt].val = mpi; + ++h263_fmtp->mpi_cnt; + } + } + + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) pjmedia_vid_codec_h263_apply_fmtp( + pjmedia_vid_codec_param *param) +{ + if (param->dir & PJMEDIA_DIR_ENCODING) { + pjmedia_vid_codec_h263_fmtp fmtp_loc, fmtp_rem; + pjmedia_rect_size size = {0}; + unsigned mpi = 0; + pjmedia_video_format_detail *vfd; + pj_status_t status; + + /* Get local param */ + status = pjmedia_vid_codec_parse_h263_fmtp(¶m->dec_fmtp, + &fmtp_loc); + if (status != PJ_SUCCESS) + return status; + + /* Get remote param */ + status = pjmedia_vid_codec_parse_h263_fmtp(¶m->enc_fmtp, + &fmtp_rem); + if (status != PJ_SUCCESS) + return status; + + /* Negotiate size & MPI setting */ + if (fmtp_rem.mpi_cnt == 0) { + /* Remote doesn't specify MPI setting, send QCIF=1 */ + size.w = 176; + size.h = 144; + mpi = 1; + } else if (fmtp_loc.mpi_cnt == 0) { + /* Local MPI setting not set, just use remote preference. */ + size = fmtp_rem.mpi[0].size; + mpi = fmtp_rem.mpi[0].val; + } else { + /* Both have preferences, let's try to match them */ + unsigned i, j; + pj_bool_t matched = PJ_FALSE; + pj_uint32_t min_diff = 0xFFFFFFFF; + pj_uint32_t loc_sq, rem_sq, diff; + + /* Find the exact size match or the closest size, then choose + * the highest MPI among the match/closest pair. + */ + for (i = 0; i < fmtp_rem.mpi_cnt && !matched; ++i) { + rem_sq = fmtp_rem.mpi[i].size.w * fmtp_rem.mpi[i].size.h; + for (j = 0; j < fmtp_loc.mpi_cnt; ++j) { + /* See if we got exact match */ + if (fmtp_rem.mpi[i].size.w == fmtp_loc.mpi[j].size.w && + fmtp_rem.mpi[i].size.h == fmtp_loc.mpi[j].size.h) + { + size = fmtp_rem.mpi[i].size; + mpi = PJ_MAX(fmtp_rem.mpi[i].val, + fmtp_loc.mpi[j].val); + matched = PJ_TRUE; + break; + } + + /* Otherwise keep looking for the closest match */ + loc_sq = fmtp_loc.mpi[j].size.w * fmtp_loc.mpi[j].size.h; + diff = loc_sq>rem_sq? (loc_sq-rem_sq):(rem_sq-loc_sq); + if (diff < min_diff) { + size = rem_sq<loc_sq? fmtp_rem.mpi[i].size : + fmtp_loc.mpi[j].size; + mpi = PJ_MAX(fmtp_rem.mpi[i].val, + fmtp_loc.mpi[j].val); + } + } + } + } + + /* Apply the negotiation result */ + vfd = pjmedia_format_get_video_format_detail(¶m->enc_fmt, + PJ_TRUE); + vfd->size = size; + vfd->fps.num = 30000; + vfd->fps.denum = 1001 * mpi; + } + + if (param->dir & PJMEDIA_DIR_DECODING) { + /* Here we just want to find the highest resolution and the lowest MPI + * we support and set it as the decoder param. + */ + pjmedia_vid_codec_h263_fmtp fmtp; + pjmedia_video_format_detail *vfd; + pj_status_t status; + + status = pjmedia_vid_codec_parse_h263_fmtp(¶m->dec_fmtp, + &fmtp); + if (status != PJ_SUCCESS) + return status; + + vfd = pjmedia_format_get_video_format_detail(¶m->dec_fmt, + PJ_TRUE); + + if (fmtp.mpi_cnt == 0) { + /* No resolution specified, lets just assume 4CIF=1! */ + vfd->size.w = 704; + vfd->size.h = 576; + vfd->fps.num = 30000; + vfd->fps.denum = 1001; + } else { + unsigned i, max_size = 0, max_size_idx = 0, min_mpi = 32; + + /* Get the largest size and the lowest MPI */ + for (i = 0; i < fmtp.mpi_cnt; ++i) { + if (fmtp.mpi[i].size.w * fmtp.mpi[i].size.h > max_size) { + max_size = fmtp.mpi[i].size.w * fmtp.mpi[i].size.h; + max_size_idx = i; + } + if (fmtp.mpi[i].val < min_mpi) + min_mpi = fmtp.mpi[i].val; + } + + vfd->size = fmtp.mpi[max_size_idx].size; + vfd->fps.num = 30000; + vfd->fps.denum = 1001 * min_mpi; + } + } + + return PJ_SUCCESS; +} + + +/* H264 fmtp parser */ +PJ_DEF(pj_status_t) pjmedia_vid_codec_h264_parse_fmtp( + const pjmedia_codec_fmtp *fmtp, + pjmedia_vid_codec_h264_fmtp *h264_fmtp) +{ + const pj_str_t PROFILE_LEVEL_ID = {"profile-level-id", 16}; + const pj_str_t MAX_MBPS = {"max-mbps", 8}; + const pj_str_t MAX_FS = {"max-fs", 6}; + const pj_str_t MAX_CPB = {"max-cpb", 7}; + const pj_str_t MAX_DPB = {"max-dpb", 7}; + const pj_str_t MAX_BR = {"max-br", 6}; + const pj_str_t PACKETIZATION_MODE = {"packetization-mode", 18}; + const pj_str_t SPROP_PARAMETER_SETS = {"sprop-parameter-sets", 20}; + unsigned i; + + pj_bzero(h264_fmtp, sizeof(*h264_fmtp)); + + for (i=0; i<fmtp->cnt; ++i) { + unsigned tmp; + if (pj_stricmp(&fmtp->param[i].name, &PROFILE_LEVEL_ID)==0) { + pj_str_t endst; + + if (fmtp->param[i].val.slen != 6) + return PJMEDIA_SDP_EINFMTP; + + tmp = pj_strtoul2(&fmtp->param[i].val, &endst, 16); + if (endst.slen) + return PJMEDIA_SDP_EINFMTP; + + h264_fmtp->profile_idc = (pj_uint8_t)((tmp >> 16) & 0xFF); + h264_fmtp->profile_iop = (pj_uint8_t)((tmp >> 8) & 0xFF); + h264_fmtp->level = (pj_uint8_t)(tmp & 0xFF); + } else if (pj_stricmp(&fmtp->param[i].name, &PACKETIZATION_MODE)==0) { + tmp = pj_strtoul(&fmtp->param[i].val); + if (tmp >= 0 && tmp <= 2) + h264_fmtp->packetization_mode = (pj_uint8_t)tmp; + else + return PJMEDIA_SDP_EINFMTP; + } else if (pj_stricmp(&fmtp->param[i].name, &MAX_MBPS)==0) { + tmp = pj_strtoul(&fmtp->param[i].val); + h264_fmtp->max_mbps = tmp; + } else if (pj_stricmp(&fmtp->param[i].name, &MAX_FS)==0) { + tmp = pj_strtoul(&fmtp->param[i].val); + h264_fmtp->max_fs = tmp; + } else if (pj_stricmp(&fmtp->param[i].name, &MAX_CPB)==0) { + tmp = pj_strtoul(&fmtp->param[i].val); + h264_fmtp->max_cpb = tmp; + } else if (pj_stricmp(&fmtp->param[i].name, &MAX_DPB)==0) { + tmp = pj_strtoul(&fmtp->param[i].val); + h264_fmtp->max_dpb = tmp; + } else if (pj_stricmp(&fmtp->param[i].name, &MAX_BR)==0) { + tmp = pj_strtoul(&fmtp->param[i].val); + h264_fmtp->max_br = tmp; + } else if (pj_stricmp(&fmtp->param[i].name, &SPROP_PARAMETER_SETS)==0) + { + pj_str_t sps_st; + + sps_st = fmtp->param[i].val; + while (sps_st.slen) { + pj_str_t tmp_st; + int tmp_len; + const pj_uint8_t start_code[3] = {0, 0, 1}; + char *p; + pj_uint8_t *nal; + pj_status_t status; + + /* Find field separator ',' */ + tmp_st = sps_st; + p = pj_strchr(&sps_st, ','); + if (p) { + tmp_st.slen = p - sps_st.ptr; + sps_st.ptr = p+1; + sps_st.slen -= (tmp_st.slen+1); + } else { + sps_st.slen = 0; + } + + /* Decode field and build NAL unit for this param */ + nal = &h264_fmtp->sprop_param_sets[ + h264_fmtp->sprop_param_sets_len]; + tmp_len = PJ_ARRAY_SIZE(h264_fmtp->sprop_param_sets) - + h264_fmtp->sprop_param_sets_len - + PJ_ARRAY_SIZE(start_code); + status = pj_base64_decode(&tmp_st, + nal + PJ_ARRAY_SIZE(start_code), + &tmp_len); + if (status != PJ_SUCCESS) + return PJMEDIA_SDP_EINFMTP; + + tmp_len += PJ_ARRAY_SIZE(start_code); + pj_memcpy(nal, start_code, PJ_ARRAY_SIZE(start_code)); + h264_fmtp->sprop_param_sets_len += tmp_len; + } + } + } + + /* When profile-level-id is not specified, use default value "42000A" */ + if (h264_fmtp->profile_idc == 0) { + h264_fmtp->profile_idc = 0x42; + h264_fmtp->profile_iop = 0x00; + h264_fmtp->level = 0x0A; + } + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjmedia_vid_codec_h264_match_sdp(pj_pool_t *pool, + pjmedia_sdp_media *offer, + unsigned o_fmt_idx, + pjmedia_sdp_media *answer, + unsigned a_fmt_idx, + unsigned option) +{ + const pj_str_t PROFILE_LEVEL_ID = {"profile-level-id", 16}; + const pj_str_t PACKETIZATION_MODE = {"packetization-mode", 18}; + pjmedia_codec_fmtp o_fmtp_raw, a_fmtp_raw; + pjmedia_vid_codec_h264_fmtp o_fmtp, a_fmtp; + pj_status_t status; + + PJ_UNUSED_ARG(pool); + + /* Parse offer */ + status = pjmedia_stream_info_parse_fmtp( + NULL, offer, + pj_strtoul(&offer->desc.fmt[o_fmt_idx]), + &o_fmtp_raw); + if (status != PJ_SUCCESS) + return status; + + status = pjmedia_vid_codec_h264_parse_fmtp(&o_fmtp_raw, &o_fmtp); + if (status != PJ_SUCCESS) + return status; + + /* Parse answer */ + status = pjmedia_stream_info_parse_fmtp( + NULL, answer, + pj_strtoul(&answer->desc.fmt[a_fmt_idx]), + &a_fmtp_raw); + if (status != PJ_SUCCESS) + return status; + + status = pjmedia_vid_codec_h264_parse_fmtp(&a_fmtp_raw, &a_fmtp); + if (status != PJ_SUCCESS) + return status; + + if (option & PJMEDIA_SDP_NEG_FMT_MATCH_ALLOW_MODIFY_ANSWER) { + unsigned i; + + /* Flexible negotiation, if the answer has higher capability than + * the offer, adjust the answer capability to be match to the offer. + */ + if (a_fmtp.profile_idc >= o_fmtp.profile_idc) + a_fmtp.profile_idc = o_fmtp.profile_idc; + if (a_fmtp.profile_iop != o_fmtp.profile_iop) + a_fmtp.profile_iop = o_fmtp.profile_iop; + if (a_fmtp.level >= o_fmtp.level) + a_fmtp.level = o_fmtp.level; + if (a_fmtp.packetization_mode >= o_fmtp.packetization_mode) + a_fmtp.packetization_mode = o_fmtp.packetization_mode; + + /* Match them now */ +#if H264_STRICT_SDP_NEGO + if (a_fmtp.profile_idc != o_fmtp.profile_idc || + a_fmtp.profile_iop != o_fmtp.profile_iop || + a_fmtp.level != o_fmtp.level || + a_fmtp.packetization_mode != o_fmtp.packetization_mode) + { + return PJMEDIA_SDP_EFORMATNOTEQUAL; + } +#else + if (a_fmtp.profile_idc != o_fmtp.profile_idc) + { + return PJMEDIA_SDP_EFORMATNOTEQUAL; + } +#endif + + /* Update the answer */ + for (i = 0; i < a_fmtp_raw.cnt; ++i) { + if (pj_stricmp(&a_fmtp_raw.param[i].name, &PROFILE_LEVEL_ID) == 0) + { + char *p = a_fmtp_raw.param[i].val.ptr; + pj_val_to_hex_digit(a_fmtp.profile_idc, p); + p += 2; + pj_val_to_hex_digit(a_fmtp.profile_iop, p); + p += 2; + pj_val_to_hex_digit(a_fmtp.level, p); + } + else if (pj_stricmp(&a_fmtp_raw.param[i].name, &PACKETIZATION_MODE) == 0) + { + char *p = a_fmtp_raw.param[i].val.ptr; + *p = '0' + a_fmtp.packetization_mode; + } + } + } else { +#if H264_STRICT_SDP_NEGO + /* Strict negotiation */ + if (a_fmtp.profile_idc != o_fmtp.profile_idc || + a_fmtp.profile_iop != o_fmtp.profile_iop || + a_fmtp.level != o_fmtp.level || + a_fmtp.packetization_mode != o_fmtp.packetization_mode) + { + return PJMEDIA_SDP_EFORMATNOTEQUAL; + } +#else + /* Permissive negotiation */ + if (a_fmtp.profile_idc != o_fmtp.profile_idc) + { + return PJMEDIA_SDP_EFORMATNOTEQUAL; + } +#endif + } + + return PJ_SUCCESS; +} + + +/* Declaration of H.264 level info */ +typedef struct h264_level_info_t +{ + unsigned id; /* Level id. */ + unsigned max_mbps; /* Max macroblocks per second. */ + unsigned max_mb; /* Max macroblocks. */ + unsigned bitrate; /* Max bitrate (kbps). */ + unsigned def_w; /* Default width. */ + unsigned def_h; /* Default height. */ + unsigned def_fps; /* Default fps. */ +} h264_level_info_t; + + +/* Get H.264 level info from specified level ID */ +static pj_status_t get_h264_level_info(unsigned id, h264_level_info_t *level) +{ + unsigned i; + const h264_level_info_t level_info[] = + { + { 10, 1485, 99, 64, 176, 144, 15 }, + { 9, 1485, 99, 128, 176, 144, 15 }, /*< level 1b */ + { 11, 3000, 396, 192, 320, 240, 10 }, + { 12, 6000, 396, 384, 352, 288, 15 }, + { 13, 11880, 396, 768, 352, 288, 15 }, + { 20, 11880, 396, 2000, 352, 288, 30 }, + { 21, 19800, 792, 4000, 352, 288, 30 }, + { 22, 20250, 1620, 4000, 352, 288, 30 }, + { 30, 40500, 1620, 10000, 720, 480, 30 }, + { 31, 108000, 3600, 14000, 1280, 720, 30 }, + { 32, 216000, 5120, 20000, 1280, 720, 30 }, + { 40, 245760, 8192, 20000, 1920, 1080, 30 }, + { 41, 245760, 8192, 50000, 1920, 1080, 30 }, + { 42, 522240, 8704, 50000, 1920, 1080, 30 }, + { 50, 589824, 22080, 135000, 1920, 1080, 30 }, + { 51, 983040, 36864, 240000, 1920, 1080, 30 }, + }; + + for (i = 0; i < PJ_ARRAY_SIZE(level_info); ++i) { + if (level_info[i].id == id) { + *level = level_info[i]; + return PJ_SUCCESS; + } + } + return PJ_ENOTFOUND; +} + + +#define CALC_H264_MB_NUM(size) (((size.w+15)/16)*((size.h+15)/16)) +#define CALC_H264_MBPS(size,fps) CALC_H264_MB_NUM(size)*fps.num/fps.denum + + +PJ_DEF(pj_status_t) pjmedia_vid_codec_h264_apply_fmtp( + pjmedia_vid_codec_param *param) +{ + const unsigned default_fps = 30; + + if (param->dir & PJMEDIA_DIR_ENCODING) { + pjmedia_vid_codec_h264_fmtp fmtp; + pjmedia_video_format_detail *vfd; + h264_level_info_t level_info; + pj_status_t status; + + /* Get remote param */ + status = pjmedia_vid_codec_h264_parse_fmtp(¶m->enc_fmtp, + &fmtp); + if (status != PJ_SUCCESS) + return status; + + status = get_h264_level_info(fmtp.level, &level_info); + if (status != PJ_SUCCESS) + return status; + + /* Size and fps for encoding direction must conform to H.264 level + * specified by remote SDP fmtp. + */ + vfd = pjmedia_format_get_video_format_detail(¶m->enc_fmt, + PJ_TRUE); + if (vfd->size.w && vfd->size.h) { + unsigned mb, mbps; + + if (vfd->fps.num == 0 || vfd->fps.denum == 0) { + vfd->fps.num = default_fps; + vfd->fps.denum = 1; + } + mb = CALC_H264_MB_NUM(vfd->size); + mbps = CALC_H264_MBPS(vfd->size, vfd->fps); + if (mb > level_info.max_mb || mbps > level_info.max_mbps) { + vfd->size.w = level_info.def_w; + vfd->size.h = level_info.def_h; + vfd->fps.num = level_info.def_fps; + vfd->fps.denum = 1; + } + } else { + vfd->size.w = level_info.def_w; + vfd->size.h = level_info.def_h; + vfd->fps.num = level_info.def_fps; + vfd->fps.denum = 1; + } + } + + if (param->dir & PJMEDIA_DIR_DECODING) { + /* Here we just want to find the highest resolution possible from the + * fmtp and set it as the decoder param. + */ + pjmedia_vid_codec_h264_fmtp fmtp; + pjmedia_video_format_detail *vfd; + h264_level_info_t level_info; + pj_status_t status; + + status = pjmedia_vid_codec_h264_parse_fmtp(¶m->dec_fmtp, + &fmtp); + if (status != PJ_SUCCESS) + return status; + + status = get_h264_level_info(fmtp.level, &level_info); + if (status != PJ_SUCCESS) + return status; + + vfd = pjmedia_format_get_video_format_detail(¶m->dec_fmt, + PJ_TRUE); + + if (vfd->size.w * vfd->size.h < level_info.def_w * level_info.def_h) { + vfd->size.w = level_info.def_w; + vfd->size.h = level_info.def_h; + } + + if (vfd->fps.num == 0 || vfd->fps.denum == 0) { + vfd->fps.num = default_fps; + vfd->fps.denum = 1; + } + } + + return PJ_SUCCESS; +} diff --git a/pjmedia/src/pjmedia/vid_port.c b/pjmedia/src/pjmedia/vid_port.c new file mode 100644 index 00000000..e5db8b10 --- /dev/null +++ b/pjmedia/src/pjmedia/vid_port.c @@ -0,0 +1,948 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2010 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjmedia/vid_port.h> +#include <pjmedia/clock.h> +#include <pjmedia/converter.h> +#include <pjmedia/errno.h> +#include <pjmedia/event.h> +#include <pjmedia/vid_codec.h> +#include <pj/log.h> +#include <pj/pool.h> + +#define SIGNATURE PJMEDIA_SIG_VID_PORT +#define THIS_FILE "vid_port.c" + +typedef struct vid_pasv_port vid_pasv_port; + +enum role +{ + ROLE_NONE, + ROLE_ACTIVE, + ROLE_PASSIVE +}; + +struct pjmedia_vid_port +{ + pj_pool_t *pool; + pj_str_t dev_name; + pjmedia_dir dir; +// pjmedia_rect_size cap_size; + pjmedia_vid_dev_stream *strm; + pjmedia_vid_dev_cb strm_cb; + void *strm_cb_data; + enum role role, + stream_role; + vid_pasv_port *pasv_port; + pjmedia_port *client_port; + pj_bool_t destroy_client_port; + + pjmedia_converter *conv; + void *conv_buf; + pj_size_t conv_buf_size; + pjmedia_conversion_param conv_param; + + pjmedia_event_publisher epub; + pjmedia_event_subscription esub_dev; + pjmedia_event_subscription esub_client_port; + + pjmedia_clock *clock; + pjmedia_clock_src clocksrc; + + struct sync_clock_src_t + { + pjmedia_clock_src *sync_clocksrc; + pj_int32_t sync_delta; + unsigned max_sync_ticks; + unsigned nsync_frame; + unsigned nsync_progress; + } sync_clocksrc; + + pjmedia_frame *frm_buf; + pj_size_t frm_buf_size; + pj_mutex_t *frm_mutex; +}; + +struct vid_pasv_port +{ + pjmedia_port base; + pjmedia_vid_port *vp; +}; + +static pj_status_t vidstream_cap_cb(pjmedia_vid_dev_stream *stream, + void *user_data, + pjmedia_frame *frame); +static pj_status_t vidstream_render_cb(pjmedia_vid_dev_stream *stream, + void *user_data, + pjmedia_frame *frame); +static pj_status_t vidstream_event_cb(pjmedia_event_subscription *esub, + pjmedia_event *event); +static pj_status_t client_port_event_cb(pjmedia_event_subscription *esub, + pjmedia_event *event); + +static void enc_clock_cb(const pj_timestamp *ts, void *user_data); +static void dec_clock_cb(const pj_timestamp *ts, void *user_data); + +static pj_status_t vid_pasv_port_put_frame(struct pjmedia_port *this_port, + pjmedia_frame *frame); + +static pj_status_t vid_pasv_port_get_frame(struct pjmedia_port *this_port, + pjmedia_frame *frame); + + +PJ_DEF(void) pjmedia_vid_port_param_default(pjmedia_vid_port_param *prm) +{ + pj_bzero(prm, sizeof(*prm)); + prm->active = PJ_TRUE; +} + +static const char *vid_dir_name(pjmedia_dir dir) +{ + switch (dir) { + case PJMEDIA_DIR_CAPTURE: + return "capture"; + case PJMEDIA_DIR_RENDER: + return "render"; + default: + return "??"; + } +} + +static pj_status_t create_converter(pjmedia_vid_port *vp) +{ + /* Instantiate converter if necessary */ + if (vp->conv_param.src.id != vp->conv_param.dst.id || + vp->conv_param.src.det.vid.size.w != vp->conv_param.dst.det.vid.size.w || + vp->conv_param.src.det.vid.size.h != vp->conv_param.dst.det.vid.size.h) + { + pj_status_t status; + + /* Yes, we need converter */ + const pjmedia_video_format_info *vfi; + pjmedia_video_apply_fmt_param vafp; + + if (vp->conv) { + pjmedia_converter_destroy(vp->conv); + vp->conv = NULL; + } + + status = pjmedia_converter_create(NULL, vp->pool, &vp->conv_param, + &vp->conv); + if (status != PJ_SUCCESS) { + PJ_PERROR(4,(THIS_FILE, status, "Error creating converter")); + return status; + } + + /* Allocate buffer for conversion */ + vfi = pjmedia_get_video_format_info(NULL, vp->conv_param.dst.id); + if (!vfi) + return PJMEDIA_EBADFMT; + + pj_bzero(&vafp, sizeof(vafp)); + vafp.size = vp->conv_param.dst.det.vid.size; + status = vfi->apply_fmt(vfi, &vafp); + if (status != PJ_SUCCESS) + return PJMEDIA_EBADFMT; + + if (vafp.framebytes > vp->conv_buf_size) { + vp->conv_buf = pj_pool_alloc(vp->pool, vafp.framebytes); + vp->conv_buf_size = vafp.framebytes; + } + } + + return PJ_SUCCESS; +} + +PJ_DEF(pj_status_t) pjmedia_vid_port_create( pj_pool_t *pool, + const pjmedia_vid_port_param *prm, + pjmedia_vid_port **p_vid_port) +{ + pjmedia_vid_port *vp; + const pjmedia_video_format_detail *vfd; + char dev_name[64]; + pjmedia_vid_dev_cb vid_cb; + pj_bool_t need_frame_buf = PJ_FALSE; + pj_status_t status; + unsigned ptime_usec; + pjmedia_vid_dev_param vparam; + pjmedia_vid_dev_info di; + unsigned i; + + PJ_ASSERT_RETURN(pool && prm && p_vid_port, PJ_EINVAL); + PJ_ASSERT_RETURN(prm->vidparam.fmt.type == PJMEDIA_TYPE_VIDEO && + prm->vidparam.dir != PJMEDIA_DIR_NONE && + prm->vidparam.dir != PJMEDIA_DIR_CAPTURE_RENDER, + PJ_EINVAL); + + /* Retrieve the video format detail */ + vfd = pjmedia_format_get_video_format_detail(&prm->vidparam.fmt, PJ_TRUE); + if (!vfd) + return PJ_EINVAL; + + PJ_ASSERT_RETURN(vfd->fps.num, PJ_EINVAL); + + /* Allocate videoport */ + vp = PJ_POOL_ZALLOC_T(pool, pjmedia_vid_port); + vp->pool = pj_pool_create(pool->factory, "video port", 500, 500, NULL); + vp->role = prm->active ? ROLE_ACTIVE : ROLE_PASSIVE; + vp->dir = prm->vidparam.dir; +// vp->cap_size = vfd->size; + pjmedia_event_publisher_init(&vp->epub, SIGNATURE); + + vparam = prm->vidparam; + dev_name[0] = '\0'; + + /* Get device info */ + if (vp->dir & PJMEDIA_DIR_CAPTURE) + status = pjmedia_vid_dev_get_info(prm->vidparam.cap_id, &di); + else + status = pjmedia_vid_dev_get_info(prm->vidparam.rend_id, &di); + if (status != PJ_SUCCESS) + return status; + + pj_ansi_snprintf(dev_name, sizeof(dev_name), "%s [%s]", + di.name, di.driver); + + for (i = 0; i < di.fmt_cnt; ++i) { + if (prm->vidparam.fmt.id == di.fmt[i].id) + break; + } + + if (i == di.fmt_cnt) { + /* The device has no no matching format. Pick one from + * the supported formats, and later use converter to + * convert it to the required format. + */ + pj_assert(di.fmt_cnt != 0); + vparam.fmt.id = di.fmt[0].id; + } + + pj_strdup2_with_null(pool, &vp->dev_name, di.name); + vp->stream_role = di.has_callback ? ROLE_ACTIVE : ROLE_PASSIVE; + + PJ_LOG(4,(THIS_FILE, + "Opening device %s for %s: format=%s, size=%dx%d @%d:%d fps", + dev_name, + vid_dir_name(prm->vidparam.dir), + pjmedia_get_video_format_info(NULL, vparam.fmt.id)->name, + vfd->size.w, vfd->size.h, + vfd->fps.num, vfd->fps.denum)); + + ptime_usec = PJMEDIA_PTIME(&vfd->fps); + pjmedia_clock_src_init(&vp->clocksrc, PJMEDIA_TYPE_VIDEO, + prm->vidparam.clock_rate, ptime_usec); + vp->sync_clocksrc.max_sync_ticks = + PJMEDIA_CLOCK_SYNC_MAX_RESYNC_DURATION * + 1000 / vp->clocksrc.ptime_usec; + + /* Create the video stream */ + pj_bzero(&vid_cb, sizeof(vid_cb)); + vid_cb.capture_cb = &vidstream_cap_cb; + vid_cb.render_cb = &vidstream_render_cb; + + status = pjmedia_vid_dev_stream_create(&vparam, &vid_cb, vp, + &vp->strm); + if (status != PJ_SUCCESS) + goto on_error; + + PJ_LOG(4,(THIS_FILE, + "Device %s opened: format=%s, size=%dx%d @%d:%d fps", + dev_name, + pjmedia_get_video_format_info(NULL, vparam.fmt.id)->name, + vparam.fmt.det.vid.size.w, vparam.fmt.det.vid.size.h, + vparam.fmt.det.vid.fps.num, vparam.fmt.det.vid.fps.denum)); + + /* Subscribe to device's events */ + pjmedia_event_subscription_init(&vp->esub_dev, vidstream_event_cb, vp); + pjmedia_event_subscribe( + pjmedia_vid_dev_stream_get_event_publisher(vp->strm), + &vp->esub_dev); + + if (vp->dir & PJMEDIA_DIR_CAPTURE) { + pjmedia_format_copy(&vp->conv_param.src, &vparam.fmt); + pjmedia_format_copy(&vp->conv_param.dst, &prm->vidparam.fmt); + } else { + pjmedia_format_copy(&vp->conv_param.src, &prm->vidparam.fmt); + pjmedia_format_copy(&vp->conv_param.dst, &vparam.fmt); + } + + status = create_converter(vp); + if (status != PJ_SUCCESS) + goto on_error; + + if (vp->role==ROLE_ACTIVE && vp->stream_role==ROLE_PASSIVE) { + pjmedia_clock_param param; + + /* Active role is wanted, but our device is passive, so create + * master clocks to run the media flow. + */ + need_frame_buf = PJ_TRUE; + + param.usec_interval = PJMEDIA_PTIME(&vfd->fps); + param.clock_rate = prm->vidparam.clock_rate; + status = pjmedia_clock_create2(pool, ¶m, + PJMEDIA_CLOCK_NO_HIGHEST_PRIO, + (vp->dir & PJMEDIA_DIR_ENCODING) ? + &enc_clock_cb: &dec_clock_cb, + vp, &vp->clock); + if (status != PJ_SUCCESS) + goto on_error; + + } else if (vp->role==ROLE_PASSIVE) { + vid_pasv_port *pp; + + /* Always need to create media port for passive role */ + vp->pasv_port = pp = PJ_POOL_ZALLOC_T(pool, vid_pasv_port); + pp->vp = vp; + pp->base.get_frame = &vid_pasv_port_get_frame; + pp->base.put_frame = &vid_pasv_port_put_frame; + pjmedia_port_info_init2(&pp->base.info, &vp->dev_name, + PJMEDIA_SIG_VID_PORT, + prm->vidparam.dir, &prm->vidparam.fmt); + + if (vp->stream_role == ROLE_ACTIVE) { + need_frame_buf = PJ_TRUE; + } + } + + if (need_frame_buf) { + const pjmedia_video_format_info *vfi; + pjmedia_video_apply_fmt_param vafp; + + vfi = pjmedia_get_video_format_info(NULL, vparam.fmt.id); + if (!vfi) { + status = PJ_ENOTFOUND; + goto on_error; + } + + pj_bzero(&vafp, sizeof(vafp)); + vafp.size = vparam.fmt.det.vid.size; + status = vfi->apply_fmt(vfi, &vafp); + if (status != PJ_SUCCESS) + goto on_error; + + vp->frm_buf = PJ_POOL_ZALLOC_T(pool, pjmedia_frame); + vp->frm_buf_size = vafp.framebytes; + vp->frm_buf->buf = pj_pool_alloc(pool, vafp.framebytes); + vp->frm_buf->size = vp->frm_buf_size; + vp->frm_buf->type = PJMEDIA_FRAME_TYPE_NONE; + + status = pj_mutex_create_simple(pool, vp->dev_name.ptr, + &vp->frm_mutex); + if (status != PJ_SUCCESS) + goto on_error; + } + + *p_vid_port = vp; + + return PJ_SUCCESS; + +on_error: + pjmedia_vid_port_destroy(vp); + return status; +} + +PJ_DEF(void) pjmedia_vid_port_set_cb(pjmedia_vid_port *vid_port, + const pjmedia_vid_dev_cb *cb, + void *user_data) +{ + pj_assert(vid_port && cb); + pj_memcpy(&vid_port->strm_cb, cb, sizeof(*cb)); + vid_port->strm_cb_data = user_data; +} + +PJ_DEF(pjmedia_event_publisher*) +pjmedia_vid_port_get_event_publisher(pjmedia_vid_port *vid_port) +{ + PJ_ASSERT_RETURN(vid_port, NULL); + return &vid_port->epub; +} + +PJ_DEF(pjmedia_vid_dev_stream*) +pjmedia_vid_port_get_stream(pjmedia_vid_port *vp) +{ + PJ_ASSERT_RETURN(vp, NULL); + return vp->strm; +} + + +PJ_DEF(pjmedia_port*) +pjmedia_vid_port_get_passive_port(pjmedia_vid_port *vp) +{ + PJ_ASSERT_RETURN(vp && vp->role==ROLE_PASSIVE, NULL); + return &vp->pasv_port->base; +} + + +PJ_DEF(pjmedia_clock_src *) +pjmedia_vid_port_get_clock_src( pjmedia_vid_port *vid_port ) +{ + PJ_ASSERT_RETURN(vid_port, NULL); + return &vid_port->clocksrc; +} + +PJ_DECL(pj_status_t) +pjmedia_vid_port_set_clock_src( pjmedia_vid_port *vid_port, + pjmedia_clock_src *clocksrc) +{ + PJ_ASSERT_RETURN(vid_port && clocksrc, PJ_EINVAL); + + vid_port->sync_clocksrc.sync_clocksrc = clocksrc; + vid_port->sync_clocksrc.sync_delta = + pjmedia_clock_src_get_time_msec(&vid_port->clocksrc) - + pjmedia_clock_src_get_time_msec(clocksrc); + + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) pjmedia_vid_port_connect(pjmedia_vid_port *vp, + pjmedia_port *port, + pj_bool_t destroy) +{ + pjmedia_event_publisher *epub; + + PJ_ASSERT_RETURN(vp && vp->role==ROLE_ACTIVE, PJ_EINVAL); + vp->destroy_client_port = destroy; + vp->client_port = port; + + /* Subscribe to client port's events */ + epub = pjmedia_port_get_event_publisher(port); + if (epub) { + pjmedia_event_subscription_init(&vp->esub_client_port, + &client_port_event_cb, + vp); + pjmedia_event_subscribe(epub, &vp->esub_client_port); + } + return PJ_SUCCESS; +} + + +PJ_DEF(pj_status_t) pjmedia_vid_port_disconnect(pjmedia_vid_port *vp) +{ + PJ_ASSERT_RETURN(vp && vp->role==ROLE_ACTIVE, PJ_EINVAL); + + if (vp->client_port) { + pjmedia_event_unsubscribe(&vp->esub_client_port); + vp->client_port = NULL; + } + return PJ_SUCCESS; +} + + +PJ_DEF(pjmedia_port*) +pjmedia_vid_port_get_connected_port(pjmedia_vid_port *vp) +{ + PJ_ASSERT_RETURN(vp && vp->role==ROLE_ACTIVE, NULL); + return vp->client_port; +} + +PJ_DEF(pj_status_t) pjmedia_vid_port_start(pjmedia_vid_port *vp) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(vp, PJ_EINVAL); + + status = pjmedia_vid_dev_stream_start(vp->strm); + if (status != PJ_SUCCESS) + goto on_error; + + if (vp->clock) { + status = pjmedia_clock_start(vp->clock); + if (status != PJ_SUCCESS) + goto on_error; + } + + return PJ_SUCCESS; + +on_error: + pjmedia_vid_port_stop(vp); + return status; +} + +PJ_DEF(pj_status_t) pjmedia_vid_port_stop(pjmedia_vid_port *vp) +{ + pj_status_t status; + + PJ_ASSERT_RETURN(vp, PJ_EINVAL); + + status = pjmedia_vid_dev_stream_stop(vp->strm); + + if (vp->clock) { + status = pjmedia_clock_stop(vp->clock); + } + + return status; +} + +PJ_DEF(void) pjmedia_vid_port_destroy(pjmedia_vid_port *vp) +{ + PJ_ASSERT_ON_FAIL(vp, return); + + PJ_LOG(4,(THIS_FILE, "Closing %s..", vp->dev_name.ptr)); + + if (vp->clock) { + pjmedia_clock_destroy(vp->clock); + vp->clock = NULL; + } + if (vp->strm) { + pjmedia_vid_dev_stream_destroy(vp->strm); + vp->strm = NULL; + } + if (vp->client_port) { + if (vp->destroy_client_port) + pjmedia_port_destroy(vp->client_port); + vp->client_port = NULL; + } + if (vp->frm_mutex) { + pj_mutex_destroy(vp->frm_mutex); + vp->frm_mutex = NULL; + } + if (vp->conv) { + pjmedia_converter_destroy(vp->conv); + vp->conv = NULL; + } + pj_pool_release(vp->pool); +} + +/* +static void save_rgb_frame(int width, int height, const pjmedia_frame *frm) +{ + static int counter; + FILE *pFile; + char szFilename[32]; + const pj_uint8_t *pFrame = (const pj_uint8_t*)frm->buf; + int y; + + if (counter > 10) + return; + + // Open file + sprintf(szFilename, "frame%02d.ppm", counter++); + pFile=fopen(szFilename, "wb"); + if(pFile==NULL) + return; + + // Write header + fprintf(pFile, "P6\n%d %d\n255\n", width, height); + + // Write pixel data + for(y=0; y<height; y++) + fwrite(pFrame+y*width*3, 1, width*3, pFile); + + // Close file + fclose(pFile); +} +*/ + +/* Handle event from vidstream */ +static pj_status_t vidstream_event_cb(pjmedia_event_subscription *esub, + pjmedia_event *event) +{ + pjmedia_vid_port *vp = (pjmedia_vid_port*)esub->user_data; + + /* Just republish the event to our client */ + return pjmedia_event_publish(&vp->epub, event); +} + +static pj_status_t client_port_event_cb(pjmedia_event_subscription *esub, + pjmedia_event *event) +{ + pjmedia_vid_port *vp = (pjmedia_vid_port*)esub->user_data; + + if (event->type == PJMEDIA_EVENT_FMT_CHANGED) { + const pjmedia_video_format_detail *vfd; + pj_status_t status; + + ++event->proc_cnt; + + pjmedia_vid_port_stop(vp); + + /* Retrieve the video format detail */ + vfd = pjmedia_format_get_video_format_detail(&vp->client_port->info.fmt, + PJ_TRUE); + if (!vfd) + return PJMEDIA_EVID_BADFORMAT; + pj_assert(vfd->fps.num); + + /* Change the destination format to the new format */ + pjmedia_format_copy(&vp->conv_param.src, + &vp->client_port->info.fmt); + /* Only copy the size here */ + vp->conv_param.dst.det.vid.size = + vp->client_port->info.fmt.det.vid.size, + + status = create_converter(vp); + if (status != PJ_SUCCESS) { + PJ_PERROR(4,(THIS_FILE, status, "Error recreating converter")); + return status; + } + + status = pjmedia_vid_dev_stream_set_cap(vp->strm, + PJMEDIA_VID_DEV_CAP_FORMAT, + &vp->conv_param.dst); + if (status != PJ_SUCCESS) { + PJ_LOG(3, (THIS_FILE, "failure in changing the format of the " + "video device")); + PJ_LOG(3, (THIS_FILE, "reverting to its original format: %s", + status != PJMEDIA_EVID_ERR ? "success" : + "failure")); + return status; + } + + if (vp->stream_role == ROLE_PASSIVE) { + pjmedia_vid_dev_param vid_param; + pjmedia_clock_param clock_param; + + /** + * Initially, frm_buf was allocated the biggest + * supported size, so we do not need to re-allocate + * the buffer here. + */ + /* Adjust the clock */ + pjmedia_vid_dev_stream_get_param(vp->strm, &vid_param); + clock_param.usec_interval = PJMEDIA_PTIME(&vfd->fps); + clock_param.clock_rate = vid_param.clock_rate; + pjmedia_clock_modify(vp->clock, &clock_param); + } + + pjmedia_vid_port_start(vp); + } + + /* Republish the event */ + return pjmedia_event_publish(&vp->epub, event); +} + +static pj_status_t convert_frame(pjmedia_vid_port *vp, + pjmedia_frame *src_frame, + pjmedia_frame *dst_frame) +{ + pj_status_t status = PJ_SUCCESS; + + if (vp->conv) { + dst_frame->buf = vp->conv_buf; + dst_frame->size = vp->conv_buf_size; + status = pjmedia_converter_convert(vp->conv, + src_frame, dst_frame); + } + + return status; +} + +/* Copy frame to buffer. */ +static void copy_frame_to_buffer(pjmedia_vid_port *vp, + pjmedia_frame *frame) +{ + pj_mutex_lock(vp->frm_mutex); + pjmedia_frame_copy(vp->frm_buf, frame); + pj_mutex_unlock(vp->frm_mutex); +} + +/* Get frame from buffer and convert it if necessary. */ +static pj_status_t get_frame_from_buffer(pjmedia_vid_port *vp, + pjmedia_frame *frame) +{ + pj_status_t status = PJ_SUCCESS; + + pj_mutex_lock(vp->frm_mutex); + if (vp->conv) + status = convert_frame(vp, vp->frm_buf, frame); + else + pjmedia_frame_copy(frame, vp->frm_buf); + pj_mutex_unlock(vp->frm_mutex); + + return status; +} + +static void enc_clock_cb(const pj_timestamp *ts, void *user_data) +{ + /* We are here because user wants us to be active but the stream is + * passive. So get a frame from the stream and push it to user. + */ + pjmedia_vid_port *vp = (pjmedia_vid_port*)user_data; + pj_status_t status; + + pj_assert(vp->role==ROLE_ACTIVE && vp->stream_role==ROLE_PASSIVE); + + PJ_UNUSED_ARG(ts); + + if (!vp->client_port) + return; + + vp->frm_buf->size = vp->frm_buf_size; + status = pjmedia_vid_dev_stream_get_frame(vp->strm, vp->frm_buf); + if (status != PJ_SUCCESS) + return; + + //save_rgb_frame(vp->cap_size.w, vp->cap_size.h, vp->frm_buf); + + vidstream_cap_cb(vp->strm, vp, vp->frm_buf); +} + +static void dec_clock_cb(const pj_timestamp *ts, void *user_data) +{ + /* We are here because user wants us to be active but the stream is + * passive. So get a frame from the stream and push it to user. + */ + pjmedia_vid_port *vp = (pjmedia_vid_port*)user_data; + pj_status_t status; + pjmedia_frame frame; + + pj_assert(vp->role==ROLE_ACTIVE && vp->stream_role==ROLE_PASSIVE); + + PJ_UNUSED_ARG(ts); + + if (!vp->client_port) + return; + + status = vidstream_render_cb(vp->strm, vp, &frame); + if (status != PJ_SUCCESS) + return; + + if (frame.size > 0) + status = pjmedia_vid_dev_stream_put_frame(vp->strm, &frame); +} + +static pj_status_t vidstream_cap_cb(pjmedia_vid_dev_stream *stream, + void *user_data, + pjmedia_frame *frame) +{ + pjmedia_vid_port *vp = (pjmedia_vid_port*)user_data; + + if (vp->role==ROLE_ACTIVE) { + pj_status_t status; + pjmedia_frame frame_; + + status = convert_frame(vp, frame, &frame_); + if (status != PJ_SUCCESS) + return status; + + if (vp->client_port) + status = pjmedia_port_put_frame(vp->client_port, + (vp->conv? &frame_: frame)); + if (status != PJ_SUCCESS) + return status; + } else { + /* We are passive while the stream is active so we just store the + * frame in the buffer. + * The decoding counterpart is located in vid_pasv_port_put_frame() + */ + copy_frame_to_buffer(vp, frame); + } + /* This is tricky since the frame is still in its original unconverted + * format, which may not be what the application expects. + */ + if (vp->strm_cb.capture_cb) + return (*vp->strm_cb.capture_cb)(stream, vp->strm_cb_data, frame); + return PJ_SUCCESS; +} + +static pj_status_t vidstream_render_cb(pjmedia_vid_dev_stream *stream, + void *user_data, + pjmedia_frame *frame) +{ + pjmedia_vid_port *vp = (pjmedia_vid_port*)user_data; + pj_status_t status = PJ_SUCCESS; + + pj_bzero(frame, sizeof(pjmedia_frame)); + if (vp->role==ROLE_ACTIVE) { + unsigned frame_ts = vp->clocksrc.clock_rate / 1000 * + vp->clocksrc.ptime_usec / 1000; + + if (!vp->client_port) + return status; + + if (vp->sync_clocksrc.sync_clocksrc) { + pjmedia_clock_src *src = vp->sync_clocksrc.sync_clocksrc; + pj_int32_t diff; + unsigned nsync_frame; + + /* Synchronization */ + /* Calculate the time difference (in ms) with the sync source */ + diff = pjmedia_clock_src_get_time_msec(&vp->clocksrc) - + pjmedia_clock_src_get_time_msec(src) - + vp->sync_clocksrc.sync_delta; + + /* Check whether sync source made a large jump */ + if (diff < 0 && -diff > PJMEDIA_CLOCK_SYNC_MAX_SYNC_MSEC) { + pjmedia_clock_src_update(&vp->clocksrc, NULL); + vp->sync_clocksrc.sync_delta = + pjmedia_clock_src_get_time_msec(src) - + pjmedia_clock_src_get_time_msec(&vp->clocksrc); + vp->sync_clocksrc.nsync_frame = 0; + return status; + } + + /* Calculate the difference (in frames) with the sync source */ + nsync_frame = abs(diff) * 1000 / vp->clocksrc.ptime_usec; + if (nsync_frame == 0) { + /* Nothing to sync */ + vp->sync_clocksrc.nsync_frame = 0; + } else { + pj_int32_t init_sync_frame = nsync_frame; + + /* Check whether it's a new sync or whether we need to reset + * the sync + */ + if (vp->sync_clocksrc.nsync_frame == 0 || + (vp->sync_clocksrc.nsync_frame > 0 && + nsync_frame > vp->sync_clocksrc.nsync_frame)) + { + vp->sync_clocksrc.nsync_frame = nsync_frame; + vp->sync_clocksrc.nsync_progress = 0; + } else { + init_sync_frame = vp->sync_clocksrc.nsync_frame; + } + + if (diff >= 0) { + unsigned skip_mod; + + /* We are too fast */ + if (vp->sync_clocksrc.max_sync_ticks > 0) { + skip_mod = init_sync_frame / + vp->sync_clocksrc.max_sync_ticks + 2; + } else + skip_mod = init_sync_frame + 2; + + PJ_LOG(5, (THIS_FILE, "synchronization: early by %d ms", + diff)); + /* We'll play a frame every skip_mod-th tick instead of + * a complete pause + */ + if (++vp->sync_clocksrc.nsync_progress % skip_mod > 0) { + pjmedia_clock_src_update(&vp->clocksrc, NULL); + return status; + } + } else { + unsigned i, ndrop = init_sync_frame; + + /* We are too late, drop the frame */ + if (vp->sync_clocksrc.max_sync_ticks > 0) { + ndrop /= vp->sync_clocksrc.max_sync_ticks; + ndrop++; + } + PJ_LOG(5, (THIS_FILE, "synchronization: late, " + "dropping %d frame(s)", ndrop)); + + if (ndrop >= nsync_frame) { + vp->sync_clocksrc.nsync_frame = 0; + ndrop = nsync_frame; + } else + vp->sync_clocksrc.nsync_progress += ndrop; + + for (i = 0; i < ndrop; i++) { + vp->frm_buf->size = vp->frm_buf_size; + status = pjmedia_port_get_frame(vp->client_port, + vp->frm_buf); + if (status != PJ_SUCCESS) { + pjmedia_clock_src_update(&vp->clocksrc, NULL); + return status; + } + + pj_add_timestamp32(&vp->clocksrc.timestamp, + frame_ts); + } + } + } + } + + vp->frm_buf->size = vp->frm_buf_size; + status = pjmedia_port_get_frame(vp->client_port, vp->frm_buf); + if (status != PJ_SUCCESS) { + pjmedia_clock_src_update(&vp->clocksrc, NULL); + return status; + } + pj_add_timestamp32(&vp->clocksrc.timestamp, frame_ts); + pjmedia_clock_src_update(&vp->clocksrc, NULL); + + status = convert_frame(vp, vp->frm_buf, frame); + if (status != PJ_SUCCESS) + return status; + + if (!vp->conv) + pj_memcpy(frame, vp->frm_buf, sizeof(*frame)); + } else { + /* The stream is active while we are passive so we need to get the + * frame from the buffer. + * The encoding counterpart is located in vid_pasv_port_get_frame() + */ + get_frame_from_buffer(vp, frame); + } + if (vp->strm_cb.render_cb) + return (*vp->strm_cb.render_cb)(stream, vp->strm_cb_data, frame); + return PJ_SUCCESS; +} + +static pj_status_t vid_pasv_port_put_frame(struct pjmedia_port *this_port, + pjmedia_frame *frame) +{ + struct vid_pasv_port *vpp = (struct vid_pasv_port*)this_port; + pjmedia_vid_port *vp = vpp->vp; + + if (vp->stream_role==ROLE_PASSIVE) { + /* We are passive and the stream is passive. + * The encoding counterpart is in vid_pasv_port_get_frame(). + */ + pj_status_t status; + pjmedia_frame frame_; + + status = convert_frame(vp, frame, &frame_); + if (status != PJ_SUCCESS) + return status; + + return pjmedia_vid_dev_stream_put_frame(vp->strm, + (vp->conv? &frame_: frame)); + } else { + /* We are passive while the stream is active so we just store the + * frame in the buffer. + * The encoding counterpart is located in vidstream_cap_cb() + */ + copy_frame_to_buffer(vp, frame); + } + + return PJ_SUCCESS; +} + +static pj_status_t vid_pasv_port_get_frame(struct pjmedia_port *this_port, + pjmedia_frame *frame) +{ + struct vid_pasv_port *vpp = (struct vid_pasv_port*)this_port; + pjmedia_vid_port *vp = vpp->vp; + pj_status_t status = PJ_SUCCESS; + + if (vp->stream_role==ROLE_PASSIVE) { + /* We are passive and the stream is passive. + * The decoding counterpart is in vid_pasv_port_put_frame(). + */ + status = pjmedia_vid_dev_stream_get_frame(vp->strm, + (vp->conv? vp->frm_buf: + frame)); + if (status != PJ_SUCCESS) + return status; + + status = convert_frame(vp, vp->frm_buf, frame); + } else { + /* The stream is active while we are passive so we need to get the + * frame from the buffer. + * The decoding counterpart is located in vidstream_rend_cb() + */ + get_frame_from_buffer(vp, frame); + } + + return status; +} diff --git a/pjmedia/src/pjmedia/vid_stream.c b/pjmedia/src/pjmedia/vid_stream.c new file mode 100644 index 00000000..216674b5 --- /dev/null +++ b/pjmedia/src/pjmedia/vid_stream.c @@ -0,0 +1,1940 @@ +/* $Id$ */ +/* + * Copyright (C) 2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjmedia/vid_stream.h> +#include <pjmedia/errno.h> +#include <pjmedia/event.h> +#include <pjmedia/rtp.h> +#include <pjmedia/rtcp.h> +#include <pjmedia/jbuf.h> +#include <pjmedia/sdp_neg.h> +#include <pjmedia/stream_common.h> +#include <pj/array.h> +#include <pj/assert.h> +#include <pj/ctype.h> +#include <pj/compat/socket.h> +#include <pj/errno.h> +#include <pj/ioqueue.h> +#include <pj/log.h> +#include <pj/os.h> +#include <pj/pool.h> +#include <pj/rand.h> +#include <pj/sock_select.h> +#include <pj/string.h> /* memcpy() */ + + +#define THIS_FILE "vid_stream.c" +#define ERRLEVEL 1 +#define LOGERR_(expr) stream_perror expr +#define TRC_(expr) PJ_LOG(5,expr) +#define SIGNATURE PJMEDIA_SIG_PORT_VID_STREAM + +/* Tracing jitter buffer operations in a stream session to a CSV file. + * The trace will contain JB operation timestamp, frame info, RTP info, and + * the JB state right after the operation. + */ +#define TRACE_JB 0 /* Enable/disable trace. */ +#define TRACE_JB_PATH_PREFIX "" /* Optional path/prefix + for the CSV filename. */ +#if TRACE_JB +# include <pj/file_io.h> +# define TRACE_JB_INVALID_FD ((pj_oshandle_t)-1) +# define TRACE_JB_OPENED(s) (s->trace_jb_fd != TRACE_JB_INVALID_FD) +#endif + +#ifndef PJMEDIA_VSTREAM_SIZE +# define PJMEDIA_VSTREAM_SIZE 1000 +#endif + +#ifndef PJMEDIA_VSTREAM_INC +# define PJMEDIA_VSTREAM_INC 1000 +#endif + + +/** + * Media channel. + */ +typedef struct pjmedia_vid_channel +{ + pjmedia_vid_stream *stream; /**< Parent stream. */ + pjmedia_dir dir; /**< Channel direction. */ + pjmedia_port port; /**< Port interface. */ + unsigned pt; /**< Payload type. */ + pj_bool_t paused; /**< Paused?. */ + void *buf; /**< Output buffer. */ + unsigned buf_size; /**< Size of output buffer. */ + unsigned buf_len; /**< Length of data in buffer. */ + pjmedia_rtp_session rtp; /**< RTP session. */ +} pjmedia_vid_channel; + + +/** + * This structure describes media stream. + * A media stream is bidirectional media transmission between two endpoints. + * It consists of two channels, i.e. encoding and decoding channels. + * A media stream corresponds to a single "m=" line in a SDP session + * description. + */ +struct pjmedia_vid_stream +{ + pj_pool_t *own_pool; /**< Internal pool. */ + pjmedia_endpt *endpt; /**< Media endpoint. */ + pjmedia_vid_codec_mgr *codec_mgr; /**< Codec manager. */ + pjmedia_vid_stream_info info; /**< Stream info. */ + + pjmedia_vid_channel *enc; /**< Encoding channel. */ + pjmedia_vid_channel *dec; /**< Decoding channel. */ + + pjmedia_dir dir; /**< Stream direction. */ + void *user_data; /**< User data. */ + pj_str_t name; /**< Stream name */ + pj_str_t cname; /**< SDES CNAME */ + + pjmedia_transport *transport; /**< Stream transport. */ + + pj_mutex_t *jb_mutex; + pjmedia_jbuf *jb; /**< Jitter buffer. */ + char jb_last_frm; /**< Last frame type from jb */ + unsigned jb_last_frm_cnt;/**< Last JB frame type counter*/ + + pjmedia_rtcp_session rtcp; /**< RTCP for incoming RTP. */ + pj_uint32_t rtcp_last_tx; /**< RTCP tx time in timestamp */ + pj_uint32_t rtcp_interval; /**< Interval, in timestamp. */ + pj_bool_t initial_rr; /**< Initial RTCP RR sent */ + + unsigned frame_size; /**< Size of encoded base frame.*/ + unsigned frame_ts_len; /**< Frame length in timestamp. */ + +#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0 + pj_bool_t use_ka; /**< Stream keep-alive with non- + codec-VAD mechanism is + enabled? */ + pj_timestamp last_frm_ts_sent; /**< Timestamp of last sending + packet */ +#endif + +#if TRACE_JB + pj_oshandle_t trace_jb_fd; /**< Jitter tracing file handle.*/ + char *trace_jb_buf; /**< Jitter tracing buffer. */ +#endif + + pjmedia_vid_codec *codec; /**< Codec instance being used. */ + pj_uint32_t last_dec_ts; /**< Last decoded timestamp. */ + int last_dec_seq; /**< Last decoded sequence. */ + + pjmedia_event_subscription esub_codec; /**< To subscribe codec events */ + pjmedia_event_publisher epub; /**< To publish events */ +}; + + +/* + * Print error. + */ +static void stream_perror(const char *sender, const char *title, + pj_status_t status) +{ + char errmsg[PJ_ERR_MSG_SIZE]; + + pj_strerror(status, errmsg, sizeof(errmsg)); + PJ_LOG(4,(sender, "%s: %s [err:%d]", title, errmsg, status)); +} + + +#if TRACE_JB + +PJ_INLINE(int) trace_jb_print_timestamp(char **buf, pj_ssize_t len) +{ + pj_time_val now; + pj_parsed_time ptime; + char *p = *buf; + + if (len < 14) + return -1; + + pj_gettimeofday(&now); + pj_time_decode(&now, &ptime); + p += pj_utoa_pad(ptime.hour, p, 2, '0'); + *p++ = ':'; + p += pj_utoa_pad(ptime.min, p, 2, '0'); + *p++ = ':'; + p += pj_utoa_pad(ptime.sec, p, 2, '0'); + *p++ = '.'; + p += pj_utoa_pad(ptime.msec, p, 3, '0'); + *p++ = ','; + + *buf = p; + + return 0; +} + +PJ_INLINE(int) trace_jb_print_state(pjmedia_vid_stream *stream, + char **buf, pj_ssize_t len) +{ + char *p = *buf; + char *endp = *buf + len; + pjmedia_jb_state state; + + pjmedia_jbuf_get_state(stream->jb, &state); + + len = pj_ansi_snprintf(p, endp-p, "%d, %d, %d", + state.size, state.burst, state.prefetch); + if ((len < 0) || (len >= endp-p)) + return -1; + + p += len; + *buf = p; + return 0; +} + +static void trace_jb_get(pjmedia_vid_stream *stream, pjmedia_jb_frame_type ft, + pj_size_t fsize) +{ + char *p = stream->trace_jb_buf; + char *endp = stream->trace_jb_buf + PJ_LOG_MAX_SIZE; + pj_ssize_t len = 0; + const char* ft_st; + + if (!TRACE_JB_OPENED(stream)) + return; + + /* Print timestamp. */ + if (trace_jb_print_timestamp(&p, endp-p)) + goto on_insuff_buffer; + + /* Print frame type and size */ + switch(ft) { + case PJMEDIA_JB_MISSING_FRAME: + ft_st = "missing"; + break; + case PJMEDIA_JB_NORMAL_FRAME: + ft_st = "normal"; + break; + case PJMEDIA_JB_ZERO_PREFETCH_FRAME: + ft_st = "prefetch"; + break; + case PJMEDIA_JB_ZERO_EMPTY_FRAME: + ft_st = "empty"; + break; + default: + ft_st = "unknown"; + break; + } + + /* Print operation, size, frame count, frame type */ + len = pj_ansi_snprintf(p, endp-p, "GET,%d,1,%s,,,,", fsize, ft_st); + if ((len < 0) || (len >= endp-p)) + goto on_insuff_buffer; + p += len; + + /* Print JB state */ + if (trace_jb_print_state(stream, &p, endp-p)) + goto on_insuff_buffer; + + /* Print end of line */ + if (endp-p < 2) + goto on_insuff_buffer; + *p++ = '\n'; + + /* Write and flush */ + len = p - stream->trace_jb_buf; + pj_file_write(stream->trace_jb_fd, stream->trace_jb_buf, &len); + pj_file_flush(stream->trace_jb_fd); + return; + +on_insuff_buffer: + pj_assert(!"Trace buffer too small, check PJ_LOG_MAX_SIZE!"); +} + +static void trace_jb_put(pjmedia_vid_stream *stream, + const pjmedia_rtp_hdr *hdr, + unsigned payloadlen, unsigned frame_cnt) +{ + char *p = stream->trace_jb_buf; + char *endp = stream->trace_jb_buf + PJ_LOG_MAX_SIZE; + pj_ssize_t len = 0; + + if (!TRACE_JB_OPENED(stream)) + return; + + /* Print timestamp. */ + if (trace_jb_print_timestamp(&p, endp-p)) + goto on_insuff_buffer; + + /* Print operation, size, frame count, RTP info */ + len = pj_ansi_snprintf(p, endp-p, + "PUT,%d,%d,,%d,%d,%d,", + payloadlen, frame_cnt, + pj_ntohs(hdr->seq), pj_ntohl(hdr->ts), hdr->m); + if ((len < 0) || (len >= endp-p)) + goto on_insuff_buffer; + p += len; + + /* Print JB state */ + if (trace_jb_print_state(stream, &p, endp-p)) + goto on_insuff_buffer; + + /* Print end of line */ + if (endp-p < 2) + goto on_insuff_buffer; + *p++ = '\n'; + + /* Write and flush */ + len = p - stream->trace_jb_buf; + pj_file_write(stream->trace_jb_fd, stream->trace_jb_buf, &len); + pj_file_flush(stream->trace_jb_fd); + return; + +on_insuff_buffer: + pj_assert(!"Trace buffer too small, check PJ_LOG_MAX_SIZE!"); +} + +#endif /* TRACE_JB */ + +static void dump_port_info(const pjmedia_vid_channel *chan, + const char *event_name) +{ + const pjmedia_port_info *pi = &chan->port.info; + char fourcc_name[5]; + + PJ_LOG(5, (pi->name.ptr, + " %s format %s: %dx%d %s%s %d/%d(~%d)fps", + (chan->dir==PJMEDIA_DIR_DECODING? "Decoding":"Encoding"), + event_name, + pi->fmt.det.vid.size.w, pi->fmt.det.vid.size.h, + pjmedia_fourcc_name(pi->fmt.id, fourcc_name), + (chan->dir==PJMEDIA_DIR_ENCODING?"->":"<-"), + pi->fmt.det.vid.fps.num, pi->fmt.det.vid.fps.denum, + pi->fmt.det.vid.fps.num/pi->fmt.det.vid.fps.denum)); +} + +/* + * Handle events from stream components. + */ +static pj_status_t stream_event_cb(pjmedia_event_subscription *esub, + pjmedia_event *event) +{ + pjmedia_vid_stream *stream = (pjmedia_vid_stream*)esub->user_data; + + if (esub == &stream->esub_codec) { + /* This is codec event */ + switch (event->type) { + case PJMEDIA_EVENT_FMT_CHANGED: + /* Update param from codec */ + pjmedia_vid_codec_get_param(stream->codec, stream->info.codec_param); + + /* Update decoding channel port info */ + pjmedia_format_copy(&stream->dec->port.info.fmt, + &stream->info.codec_param->dec_fmt); + + /* we process the event */ + ++event->proc_cnt; + + dump_port_info(event->data.fmt_changed.dir==PJMEDIA_DIR_DECODING ? + stream->dec : stream->enc, + "changed"); + break; + default: + break; + } + } + + return pjmedia_event_publish(&stream->epub, event); +} + +static pjmedia_event_publisher *port_get_epub(pjmedia_port *port) +{ + pjmedia_vid_stream *stream = (pjmedia_vid_stream*) port->port_data.pdata; + return &stream->epub; +} + +#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA != 0 +/* + * Send keep-alive packet using non-codec frame. + */ +static void send_keep_alive_packet(pjmedia_vid_stream *stream) +{ +#if PJMEDIA_STREAM_ENABLE_KA == PJMEDIA_STREAM_KA_EMPTY_RTP + + /* Keep-alive packet is empty RTP */ + pj_status_t status; + void *pkt; + int pkt_len; + + TRC_((channel->port.info.name.ptr, + "Sending keep-alive (RTCP and empty RTP)")); + + /* Send RTP */ + status = pjmedia_rtp_encode_rtp( &stream->enc->rtp, + stream->enc->pt, 0, + 1, + 0, + (const void**)&pkt, + &pkt_len); + pj_assert(status == PJ_SUCCESS); + + pj_memcpy(stream->enc->buf, pkt, pkt_len); + pjmedia_transport_send_rtp(stream->transport, stream->enc->buf, + pkt_len); + + /* Send RTCP */ + pjmedia_rtcp_build_rtcp(&stream->rtcp, &pkt, &pkt_len); + pjmedia_transport_send_rtcp(stream->transport, pkt, pkt_len); + +#elif PJMEDIA_STREAM_ENABLE_KA == PJMEDIA_STREAM_KA_USER + + /* Keep-alive packet is defined in PJMEDIA_STREAM_KA_USER_PKT */ + int pkt_len; + const pj_str_t str_ka = PJMEDIA_STREAM_KA_USER_PKT; + + TRC_((channel->port.info.name.ptr, + "Sending keep-alive (custom RTP/RTCP packets)")); + + /* Send to RTP port */ + pj_memcpy(stream->enc->buf, str_ka.ptr, str_ka.slen); + pkt_len = str_ka.slen; + pjmedia_transport_send_rtp(stream->transport, stream->enc->buf, + pkt_len); + + /* Send to RTCP port */ + pjmedia_transport_send_rtcp(stream->transport, stream->enc->buf, + pkt_len); + +#else + + PJ_UNUSED_ARG(stream); + +#endif +} +#endif /* defined(PJMEDIA_STREAM_ENABLE_KA) */ + + +/** + * check_tx_rtcp() + * + * This function is can be called by either put_frame() or get_frame(), + * to transmit periodic RTCP SR/RR report. + */ +static void check_tx_rtcp(pjmedia_vid_stream *stream, pj_uint32_t timestamp) +{ + /* Note that timestamp may represent local or remote timestamp, + * depending on whether this function is called from put_frame() + * or get_frame(). + */ + + + if (stream->rtcp_last_tx == 0) { + + stream->rtcp_last_tx = timestamp; + + } else if (timestamp - stream->rtcp_last_tx >= stream->rtcp_interval) { + + void *rtcp_pkt; + int len; + + pjmedia_rtcp_build_rtcp(&stream->rtcp, &rtcp_pkt, &len); + + pjmedia_transport_send_rtcp(stream->transport, rtcp_pkt, len); + + stream->rtcp_last_tx = timestamp; + } +} + +/* Build RTCP SDES packet */ +static unsigned create_rtcp_sdes(pjmedia_vid_stream *stream, pj_uint8_t *pkt, + unsigned max_len) +{ + pjmedia_rtcp_common hdr; + pj_uint8_t *p = pkt; + + /* SDES header */ + hdr.version = 2; + hdr.p = 0; + hdr.count = 1; + hdr.pt = 202; + hdr.length = 2 + (4+stream->cname.slen+3)/4 - 1; + if (max_len < (hdr.length << 2)) { + pj_assert(!"Not enough buffer for SDES packet"); + return 0; + } + hdr.length = pj_htons((pj_uint16_t)hdr.length); + hdr.ssrc = stream->enc->rtp.out_hdr.ssrc; + pj_memcpy(p, &hdr, sizeof(hdr)); + p += sizeof(hdr); + + /* CNAME item */ + *p++ = 1; + *p++ = (pj_uint8_t)stream->cname.slen; + pj_memcpy(p, stream->cname.ptr, stream->cname.slen); + p += stream->cname.slen; + + /* END */ + *p++ = '\0'; + *p++ = '\0'; + + /* Pad to 32bit */ + while ((p-pkt) % 4) + *p++ = '\0'; + + return (p - pkt); +} + +/* Build RTCP BYE packet */ +static unsigned create_rtcp_bye(pjmedia_vid_stream *stream, pj_uint8_t *pkt, + unsigned max_len) +{ + pjmedia_rtcp_common hdr; + + /* BYE header */ + hdr.version = 2; + hdr.p = 0; + hdr.count = 1; + hdr.pt = 203; + hdr.length = 1; + if (max_len < (hdr.length << 2)) { + pj_assert(!"Not enough buffer for SDES packet"); + return 0; + } + hdr.length = pj_htons((pj_uint16_t)hdr.length); + hdr.ssrc = stream->enc->rtp.out_hdr.ssrc; + pj_memcpy(pkt, &hdr, sizeof(hdr)); + + return sizeof(hdr); +} + + +#if 0 +static void dump_bin(const char *buf, unsigned len) +{ + unsigned i; + + PJ_LOG(3,(THIS_FILE, "begin dump")); + for (i=0; i<len; ++i) { + int j; + char bits[9]; + unsigned val = buf[i] & 0xFF; + + bits[8] = '\0'; + for (j=0; j<8; ++j) { + if (val & (1 << (7-j))) + bits[j] = '1'; + else + bits[j] = '0'; + } + + PJ_LOG(3,(THIS_FILE, "%2d %s [%d]", i, bits, val)); + } + PJ_LOG(3,(THIS_FILE, "end dump")); +} +#endif + + +/* + * This callback is called by stream transport on receipt of packets + * in the RTP socket. + */ +static void on_rx_rtp( void *data, + void *pkt, + pj_ssize_t bytes_read) + +{ + pjmedia_vid_stream *stream = (pjmedia_vid_stream*) data; + pjmedia_vid_channel *channel = stream->dec; + const pjmedia_rtp_hdr *hdr; + const void *payload; + unsigned payloadlen; + pjmedia_rtp_status seq_st; + pj_status_t status; + pj_bool_t pkt_discarded = PJ_FALSE; + + /* Check for errors */ + if (bytes_read < 0) { + LOGERR_((channel->port.info.name.ptr, "RTP recv() error", -bytes_read)); + return; + } + + /* Ignore keep-alive packets */ + if (bytes_read < (pj_ssize_t) sizeof(pjmedia_rtp_hdr)) + return; + + /* Update RTP and RTCP session. */ + status = pjmedia_rtp_decode_rtp(&channel->rtp, pkt, bytes_read, + &hdr, &payload, &payloadlen); + if (status != PJ_SUCCESS) { + LOGERR_((channel->port.info.name.ptr, "RTP decode error", status)); + stream->rtcp.stat.rx.discard++; + return; + } + + /* Ignore the packet if decoder is paused */ + if (channel->paused) + goto on_return; + + /* Update RTP session (also checks if RTP session can accept + * the incoming packet. + */ + pjmedia_rtp_session_update2(&channel->rtp, hdr, &seq_st, PJ_TRUE); + if (seq_st.status.value) { + TRC_ ((channel->port.info.name.ptr, + "RTP status: badpt=%d, badssrc=%d, dup=%d, " + "outorder=%d, probation=%d, restart=%d", + seq_st.status.flag.badpt, + seq_st.status.flag.badssrc, + seq_st.status.flag.dup, + seq_st.status.flag.outorder, + seq_st.status.flag.probation, + seq_st.status.flag.restart)); + + if (seq_st.status.flag.badpt) { + PJ_LOG(4,(channel->port.info.name.ptr, + "Bad RTP pt %d (expecting %d)", + hdr->pt, channel->rtp.out_pt)); + } + + if (seq_st.status.flag.badssrc) { + PJ_LOG(4,(channel->port.info.name.ptr, + "Changed RTP peer SSRC %d (previously %d)", + channel->rtp.peer_ssrc, stream->rtcp.peer_ssrc)); + stream->rtcp.peer_ssrc = channel->rtp.peer_ssrc; + } + + + } + + /* Skip bad RTP packet */ + if (seq_st.status.flag.bad) { + pkt_discarded = PJ_TRUE; + goto on_return; + } + + /* Ignore if payloadlen is zero */ + if (payloadlen == 0) { + pkt_discarded = PJ_TRUE; + goto on_return; + } + + + /* Put "good" packet to jitter buffer, or reset the jitter buffer + * when RTP session is restarted. + */ + pj_mutex_lock( stream->jb_mutex ); + if (seq_st.status.flag.restart) { + status = pjmedia_jbuf_reset(stream->jb); + PJ_LOG(4,(channel->port.info.name.ptr, "Jitter buffer reset")); + } else { + /* Just put the payload into jitter buffer */ + pjmedia_jbuf_put_frame3(stream->jb, payload, payloadlen, 0, + pj_ntohs(hdr->seq), pj_ntohl(hdr->ts), NULL); + +#if TRACE_JB + trace_jb_put(stream, hdr, payloadlen, count); +#endif + + } + pj_mutex_unlock( stream->jb_mutex ); + + + /* Check if now is the time to transmit RTCP SR/RR report. + * We only do this when stream direction is "decoding only", + * because otherwise check_tx_rtcp() will be handled by put_frame() + */ + if (stream->dir == PJMEDIA_DIR_DECODING) { + check_tx_rtcp(stream, pj_ntohl(hdr->ts)); + } + + if (status != 0) { + LOGERR_((channel->port.info.name.ptr, "Jitter buffer put() error", + status)); + pkt_discarded = PJ_TRUE; + goto on_return; + } + +on_return: + /* Update RTCP session */ + if (stream->rtcp.peer_ssrc == 0) + stream->rtcp.peer_ssrc = channel->rtp.peer_ssrc; + + pjmedia_rtcp_rx_rtp2(&stream->rtcp, pj_ntohs(hdr->seq), + pj_ntohl(hdr->ts), payloadlen, pkt_discarded); + + /* Send RTCP RR and SDES after we receive some RTP packets */ + if (stream->rtcp.received >= 10 && !stream->initial_rr) { + void *sr_rr_pkt; + pj_uint8_t *pkt; + int len; + + /* Build RR or SR */ + pjmedia_rtcp_build_rtcp(&stream->rtcp, &sr_rr_pkt, &len); + pkt = (pj_uint8_t*) stream->enc->buf; + pj_memcpy(pkt, sr_rr_pkt, len); + pkt += len; + + /* Append SDES */ + len = create_rtcp_sdes(stream, (pj_uint8_t*)pkt, + stream->enc->buf_size - len); + if (len > 0) { + pkt += len; + len = ((pj_uint8_t*)pkt) - ((pj_uint8_t*)stream->enc->buf); + pjmedia_transport_send_rtcp(stream->transport, + stream->enc->buf, len); + } + + stream->initial_rr = PJ_TRUE; + } +} + + +/* + * This callback is called by stream transport on receipt of packets + * in the RTCP socket. + */ +static void on_rx_rtcp( void *data, + void *pkt, + pj_ssize_t bytes_read) +{ + pjmedia_vid_stream *stream = (pjmedia_vid_stream*) data; + + /* Check for errors */ + if (bytes_read < 0) { + LOGERR_((stream->cname.ptr, "RTCP recv() error", + -bytes_read)); + return; + } + + pjmedia_rtcp_rx_rtcp(&stream->rtcp, pkt, bytes_read); +} + +static pj_status_t put_frame(pjmedia_port *port, + pjmedia_frame *frame) +{ + pjmedia_vid_stream *stream = (pjmedia_vid_stream*) port->port_data.pdata; + pjmedia_vid_channel *channel = stream->enc; + pj_status_t status = 0; + pjmedia_frame frame_out; + unsigned rtp_ts_len; + void *rtphdr; + int rtphdrlen; + unsigned processed = 0; + + +#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA != 0 + /* If the interval since last sending packet is greater than + * PJMEDIA_STREAM_KA_INTERVAL, send keep-alive packet. + */ + if (stream->use_ka) + { + pj_uint32_t dtx_duration; + + dtx_duration = pj_timestamp_diff32(&stream->last_frm_ts_sent, + &frame->timestamp); + if (dtx_duration > + PJMEDIA_STREAM_KA_INTERVAL * channel->port.info.clock_rate) + { + send_keep_alive_packet(stream); + stream->last_frm_ts_sent = frame->timestamp; + } + } +#endif + + /* Don't do anything if stream is paused */ + if (channel->paused) { + return PJ_SUCCESS; + } + + /* Get frame length in timestamp unit */ + rtp_ts_len = stream->frame_ts_len; + + /* Init frame_out buffer. */ + frame_out.buf = ((char*)channel->buf) + sizeof(pjmedia_rtp_hdr); + frame_out.size = 0; + + /* Encode! */ + status = pjmedia_vid_codec_encode(stream->codec, frame, + channel->buf_size - + sizeof(pjmedia_rtp_hdr), + &frame_out); + if (status != PJ_SUCCESS) { + LOGERR_((channel->port.info.name.ptr, + "Codec encode() error", status)); + + /* Update RTP timestamp */ + pjmedia_rtp_encode_rtp(&channel->rtp, channel->pt, 1, 0, + rtp_ts_len, (const void**)&rtphdr, &rtphdrlen); + return status; + } + + + while (processed < frame_out.size) { + pj_uint8_t *payload; + pj_uint8_t *rtp_pkt; + pj_size_t payload_len; + + /* Generate RTP payload */ + status = pjmedia_vid_codec_packetize(stream->codec, + (pj_uint8_t*)frame_out.buf, + frame_out.size, + &processed, + (const pj_uint8_t**)&payload, + &payload_len); + if (status != PJ_SUCCESS) { + LOGERR_((channel->port.info.name.ptr, + "Codec pack() error", status)); + + /* Update RTP timestamp */ + pjmedia_rtp_encode_rtp(&channel->rtp, channel->pt, 1, 0, + rtp_ts_len, (const void**)&rtphdr, + &rtphdrlen); + return status; + } + + /* Encapsulate. */ + status = pjmedia_rtp_encode_rtp( &channel->rtp, + channel->pt, + (processed==frame_out.size?1:0), + payload_len, + rtp_ts_len, + (const void**)&rtphdr, + &rtphdrlen); + + if (status != PJ_SUCCESS) { + LOGERR_((channel->port.info.name.ptr, + "RTP encode_rtp() error", status)); + return status; + } + + /* Next packets use same timestamp */ + rtp_ts_len = 0; + + rtp_pkt = payload - sizeof(pjmedia_rtp_hdr); + + /* Copy RTP header to the beginning of packet */ + pj_memcpy(rtp_pkt, rtphdr, sizeof(pjmedia_rtp_hdr)); + + /* Send the RTP packet to the transport. */ + pjmedia_transport_send_rtp(stream->transport, rtp_pkt, + payload_len + sizeof(pjmedia_rtp_hdr)); + } + + /* Check if now is the time to transmit RTCP SR/RR report. + * We only do this when stream direction is not "decoding only", because + * when it is, check_tx_rtcp() will be handled by get_frame(). + */ + if (stream->dir != PJMEDIA_DIR_DECODING) { + check_tx_rtcp(stream, pj_ntohl(channel->rtp.out_hdr.ts)); + } + + /* Do nothing if we have nothing to transmit */ + if (frame_out.size == 0) { + return PJ_SUCCESS; + } + + /* Update stat */ + pjmedia_rtcp_tx_rtp(&stream->rtcp, frame_out.size); + stream->rtcp.stat.rtp_tx_last_ts = pj_ntohl(stream->enc->rtp.out_hdr.ts); + stream->rtcp.stat.rtp_tx_last_seq = pj_ntohs(stream->enc->rtp.out_hdr.seq); + +#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0 + /* Update timestamp of last sending packet. */ + stream->last_frm_ts_sent = frame->timestamp; +#endif + + return PJ_SUCCESS; +} + +static pj_status_t get_frame(pjmedia_port *port, + pjmedia_frame *frame) +{ + pjmedia_vid_stream *stream = (pjmedia_vid_stream*) port->port_data.pdata; + pjmedia_vid_channel *channel = stream->dec; + pjmedia_frame frame_in; + pj_uint32_t last_ts = 0; + int frm_first_seq = 0, frm_last_seq = 0; + pj_status_t status; + + /* Return no frame is channel is paused */ + if (channel->paused) { + frame->type = PJMEDIA_FRAME_TYPE_NONE; + return PJ_SUCCESS; + } + + /* Repeat get payload from the jitter buffer until all payloads with same + * timestamp are collected (a complete frame unpacketized). + */ + { + pj_bool_t got_frame; + unsigned cnt; + + channel->buf_len = 0; + got_frame = PJ_FALSE; + + /* Lock jitter buffer mutex first */ + pj_mutex_lock( stream->jb_mutex ); + + /* Check if we got a decodable frame */ + for (cnt=0; ; ++cnt) { + char ptype; + pj_uint32_t ts; + int seq; + + /* Peek frame from jitter buffer. */ + pjmedia_jbuf_peek_frame(stream->jb, cnt, NULL, NULL, + &ptype, NULL, &ts, &seq); + if (ptype == PJMEDIA_JB_NORMAL_FRAME) { + if (last_ts == 0) { + last_ts = ts; + frm_first_seq = seq; + } + if (ts != last_ts) { + got_frame = PJ_TRUE; + break; + } + frm_last_seq = seq; + } else if (ptype == PJMEDIA_JB_ZERO_EMPTY_FRAME) { + /* No more packet in the jitter buffer */ + break; + } + } + + if (got_frame) { + unsigned i; + + /* Generate frame bitstream from the payload */ + channel->buf_len = 0; + for (i = 0; i < cnt; ++i) { + const pj_uint8_t *p; + pj_size_t psize; + char ptype; + + /* We use jbuf_peek_frame() as it will returns the pointer of + * the payload (no buffer and memcpy needed), just as we need. + */ + pjmedia_jbuf_peek_frame(stream->jb, i, (const void**)&p, + &psize, &ptype, NULL, NULL, NULL); + + if (ptype != PJMEDIA_JB_NORMAL_FRAME) { + /* Packet lost, must set payload to NULL and keep going */ + p = NULL; + psize = 0; + } + + status = pjmedia_vid_codec_unpacketize( + stream->codec, + p, psize, + (pj_uint8_t*)channel->buf, + channel->buf_size, + &channel->buf_len); + if (status != PJ_SUCCESS) { + LOGERR_((channel->port.info.name.ptr, + "Codec unpack() error", status)); + /* Just ignore this unpack error */ + } + } + + pjmedia_jbuf_remove_frame(stream->jb, cnt); + } + + /* Unlock jitter buffer mutex. */ + pj_mutex_unlock( stream->jb_mutex ); + + if (!got_frame) { + frame->type = PJMEDIA_FRAME_TYPE_NONE; + frame->size = 0; + return PJ_SUCCESS; + } + } + + /* Decode */ + frame_in.buf = channel->buf; + frame_in.size = channel->buf_len; + frame_in.bit_info = 0; + frame_in.type = PJMEDIA_FRAME_TYPE_VIDEO; + frame_in.timestamp.u64 = last_ts; + + status = pjmedia_vid_codec_decode(stream->codec, &frame_in, + frame->size, frame); + if (status != PJ_SUCCESS) { + LOGERR_((port->info.name.ptr, "codec decode() error", + status)); + frame->type = PJMEDIA_FRAME_TYPE_NONE; + frame->size = 0; + } + + /* Learn remote frame rate after successful decoding */ + if (0 && frame->type == PJMEDIA_FRAME_TYPE_VIDEO && frame->size) + { + /* Only check remote frame rate when timestamp is not wrapping and + * sequence is increased by 1. + */ + if (last_ts > stream->last_dec_ts && + frm_first_seq - stream->last_dec_seq == 1) + { + pj_uint32_t ts_diff; + pjmedia_video_format_detail *vfd; + + ts_diff = last_ts - stream->last_dec_ts; + vfd = pjmedia_format_get_video_format_detail( + &channel->port.info.fmt, PJ_TRUE); + if ((int)(stream->info.codec_info.clock_rate / ts_diff) != + vfd->fps.num / vfd->fps.denum) + { + /* Frame rate changed, update decoding port info */ + vfd->fps.num = stream->info.codec_info.clock_rate; + vfd->fps.denum = ts_diff; + + /* Update stream info */ + stream->info.codec_param->dec_fmt.det.vid.fps = vfd->fps; + + PJ_LOG(5, (channel->port.info.name.ptr, + "Frame rate changed to %d/%d(~%d)fps", + vfd->fps.num, vfd->fps.denum, + vfd->fps.num / vfd->fps.denum)); + + /* Publish PJMEDIA_EVENT_FMT_CHANGED event */ + if (pjmedia_event_publisher_has_sub(&stream->epub)) { + pjmedia_event event; + + dump_port_info(stream->dec, "changed"); + + pjmedia_event_init(&event, PJMEDIA_EVENT_FMT_CHANGED, + &frame_in.timestamp, &stream->epub); + event.data.fmt_changed.dir = PJMEDIA_DIR_DECODING; + pj_memcpy(&event.data.fmt_changed.new_fmt, + &stream->info.codec_param->dec_fmt, + sizeof(pjmedia_format)); + pjmedia_event_publish(&stream->epub, &event); + } + } + } + + /* Update last frame seq and timestamp */ + stream->last_dec_seq = frm_last_seq; + stream->last_dec_ts = last_ts; + } + + return PJ_SUCCESS; +} + + +/* + * Create media channel. + */ +static pj_status_t create_channel( pj_pool_t *pool, + pjmedia_vid_stream *stream, + pjmedia_dir dir, + unsigned pt, + const pjmedia_vid_stream_info *info, + pjmedia_vid_channel **p_channel) +{ + enum { M = 32 }; + pjmedia_vid_channel *channel; + pj_status_t status; + unsigned min_out_pkt_size; + pj_str_t name; + const char *type_name; + pjmedia_format *fmt; + char fourcc_name[5]; + pjmedia_port_info *pi; + + pj_assert(info->type == PJMEDIA_TYPE_VIDEO); + pj_assert(dir == PJMEDIA_DIR_DECODING || dir == PJMEDIA_DIR_ENCODING); + + /* Allocate memory for channel descriptor */ + channel = PJ_POOL_ZALLOC_T(pool, pjmedia_vid_channel); + PJ_ASSERT_RETURN(channel != NULL, PJ_ENOMEM); + + /* Init vars */ + if (dir==PJMEDIA_DIR_DECODING) { + type_name = "vstdec"; + fmt = &info->codec_param->dec_fmt; + } else { + type_name = "vstenc"; + fmt = &info->codec_param->enc_fmt; + } + name.ptr = (char*) pj_pool_alloc(pool, M); + name.slen = pj_ansi_snprintf(name.ptr, M, "%s%p", type_name, stream); + pi = &channel->port.info; + + /* Init channel info. */ + channel->stream = stream; + channel->dir = dir; + channel->paused = 1; + channel->pt = pt; + + /* Allocate buffer for outgoing packet. */ + channel->buf_size = sizeof(pjmedia_rtp_hdr) + stream->frame_size; + + /* It should big enough to hold (minimally) RTCP SR with an SDES. */ + min_out_pkt_size = sizeof(pjmedia_rtcp_sr_pkt) + + sizeof(pjmedia_rtcp_common) + + (4 + stream->cname.slen) + + 32; + + if (channel->buf_size < min_out_pkt_size) + channel->buf_size = min_out_pkt_size; + + channel->buf = pj_pool_alloc(pool, channel->buf_size); + PJ_ASSERT_RETURN(channel->buf != NULL, PJ_ENOMEM); + + /* Create RTP and RTCP sessions: */ + if (info->rtp_seq_ts_set == 0) { + status = pjmedia_rtp_session_init(&channel->rtp, pt, info->ssrc); + } else { + pjmedia_rtp_session_setting settings; + + settings.flags = (pj_uint8_t)((info->rtp_seq_ts_set << 2) | 3); + settings.default_pt = pt; + settings.sender_ssrc = info->ssrc; + settings.seq = info->rtp_seq; + settings.ts = info->rtp_ts; + status = pjmedia_rtp_session_init2(&channel->rtp, settings); + } + if (status != PJ_SUCCESS) + return status; + + /* Init port. */ + pjmedia_port_info_init2(pi, &name, SIGNATURE, dir, fmt); + if (dir == PJMEDIA_DIR_DECODING) { + channel->port.get_frame = &get_frame; + } else { + pi->fmt.id = info->codec_param->dec_fmt.id; + channel->port.put_frame = &put_frame; + } + + /* Init port. */ + channel->port.port_data.pdata = stream; + channel->port.get_event_pub = &port_get_epub; + + PJ_LOG(5, (name.ptr, + "%s channel created %dx%d %s%s%.*s %d/%d(~%d)fps", + (dir==PJMEDIA_DIR_ENCODING?"Encoding":"Decoding"), + pi->fmt.det.vid.size.w, pi->fmt.det.vid.size.h, + pjmedia_fourcc_name(pi->fmt.id, fourcc_name), + (dir==PJMEDIA_DIR_ENCODING?"->":"<-"), + info->codec_info.encoding_name.slen, + info->codec_info.encoding_name.ptr, + pi->fmt.det.vid.fps.num, pi->fmt.det.vid.fps.denum, + pi->fmt.det.vid.fps.num/pi->fmt.det.vid.fps.denum)); + + /* Done. */ + *p_channel = channel; + return PJ_SUCCESS; +} + + +/* + * Create stream. + */ +PJ_DEF(pj_status_t) pjmedia_vid_stream_create( + pjmedia_endpt *endpt, + pj_pool_t *pool, + pjmedia_vid_stream_info *info, + pjmedia_transport *tp, + void *user_data, + pjmedia_vid_stream **p_stream) +{ + enum { M = 32 }; + pj_pool_t *own_pool = NULL; + pjmedia_vid_stream *stream; + unsigned jb_init, jb_max, jb_min_pre, jb_max_pre, len; + int frm_ptime, chunks_per_frm; + pjmedia_video_format_detail *vfd_enc; + char *p; + pj_status_t status; + + if (!pool) { + own_pool = pjmedia_endpt_create_pool( endpt, "vstrm%p", + PJMEDIA_VSTREAM_SIZE, + PJMEDIA_VSTREAM_INC); + PJ_ASSERT_RETURN(own_pool != NULL, PJ_ENOMEM); + pool = own_pool; + } + + /* Allocate stream */ + stream = PJ_POOL_ZALLOC_T(pool, pjmedia_vid_stream); + PJ_ASSERT_RETURN(stream != NULL, PJ_ENOMEM); + stream->own_pool = own_pool; + + /* Get codec manager */ + stream->codec_mgr = pjmedia_vid_codec_mgr_instance(); + PJ_ASSERT_RETURN(stream->codec_mgr, PJMEDIA_CODEC_EFAILED); + + /* Init stream/port name */ + stream->name.ptr = (char*) pj_pool_alloc(pool, M); + stream->name.slen = pj_ansi_snprintf(stream->name.ptr, M, + "vstrm%p", stream); + + /* Create and initialize codec: */ + status = pjmedia_vid_codec_mgr_alloc_codec(stream->codec_mgr, + &info->codec_info, + &stream->codec); + if (status != PJ_SUCCESS) + return status; + + + /* Get codec param: */ + if (!info->codec_param) { + pjmedia_vid_codec_param def_param; + + status = pjmedia_vid_codec_mgr_get_default_param(stream->codec_mgr, + &info->codec_info, + &def_param); + if (status != PJ_SUCCESS) + return status; + + info->codec_param = pjmedia_vid_codec_param_clone(pool, &def_param); + pj_assert(info->codec_param); + } + + vfd_enc = pjmedia_format_get_video_format_detail( + &info->codec_param->enc_fmt, PJ_TRUE); + + /* Init stream: */ + stream->endpt = endpt; + stream->dir = info->dir; + stream->user_data = user_data; + stream->rtcp_interval = (PJMEDIA_RTCP_INTERVAL-500 + (pj_rand()%1000)) * + info->codec_info.clock_rate / 1000; + + stream->jb_last_frm = PJMEDIA_JB_NORMAL_FRAME; + +#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0 + stream->use_ka = info->use_ka; +#endif + + /* Build random RTCP CNAME. CNAME has user@host format */ + stream->cname.ptr = p = (char*) pj_pool_alloc(pool, 20); + pj_create_random_string(p, 5); + p += 5; + *p++ = '@'; *p++ = 'p'; *p++ = 'j'; + pj_create_random_string(p, 6); + p += 6; + *p++ = '.'; *p++ = 'o'; *p++ = 'r'; *p++ = 'g'; + stream->cname.slen = p - stream->cname.ptr; + + + /* Create mutex to protect jitter buffer: */ + + status = pj_mutex_create_simple(pool, NULL, &stream->jb_mutex); + if (status != PJ_SUCCESS) + return status; + + /* Init codec param */ + info->codec_param->dir = info->dir; + info->codec_param->enc_mtu = PJMEDIA_MAX_MTU - sizeof(pjmedia_rtp_hdr) - + PJMEDIA_STREAM_RESV_PAYLOAD_LEN; + + /* Init and open the codec. */ + status = pjmedia_vid_codec_init(stream->codec, pool); + if (status != PJ_SUCCESS) + return status; + status = pjmedia_vid_codec_open(stream->codec, info->codec_param); + if (status != PJ_SUCCESS) + return status; + + /* Init event publisher and subscribe to codec events */ + pjmedia_event_publisher_init(&stream->epub, SIGNATURE); + pjmedia_event_subscription_init(&stream->esub_codec, &stream_event_cb, + stream); + pjmedia_event_subscribe(&stream->codec->epub, &stream->esub_codec); + + /* Estimate the maximum frame size */ + stream->frame_size = vfd_enc->size.w * vfd_enc->size.h * 4; + +#if 0 + stream->frame_size = vfd_enc->max_bps/8 * vfd_enc->fps.denum / + vfd_enc->fps.num; + + /* As the maximum frame_size is not represented directly by maximum bps + * (which includes intra and predicted frames), let's increase the + * frame size value for safety. + */ + stream->frame_size <<= 4; +#endif + + /* Validate the frame size */ + if (stream->frame_size == 0 || + stream->frame_size > PJMEDIA_MAX_VIDEO_ENC_FRAME_SIZE) + { + stream->frame_size = PJMEDIA_MAX_VIDEO_ENC_FRAME_SIZE; + } + + /* Get frame length in timestamp unit */ + stream->frame_ts_len = info->codec_info.clock_rate * + vfd_enc->fps.denum / vfd_enc->fps.num; + + /* Create decoder channel */ + status = create_channel( pool, stream, PJMEDIA_DIR_DECODING, + info->rx_pt, info, &stream->dec); + if (status != PJ_SUCCESS) + return status; + + + /* Create encoder channel */ + status = create_channel( pool, stream, PJMEDIA_DIR_ENCODING, + info->tx_pt, info, &stream->enc); + if (status != PJ_SUCCESS) + return status; + + /* Init jitter buffer parameters: */ + frm_ptime = 1000 * vfd_enc->fps.denum / vfd_enc->fps.num; + chunks_per_frm = stream->frame_size / PJMEDIA_MAX_MTU; + + /* JB max count, default 500ms */ + if (info->jb_max >= frm_ptime) + jb_max = info->jb_max * chunks_per_frm / frm_ptime; + else + jb_max = 500 * chunks_per_frm / frm_ptime; + + /* JB min prefetch, default 1 frame */ + if (info->jb_min_pre >= frm_ptime) + jb_min_pre = info->jb_min_pre * chunks_per_frm / frm_ptime; + else + jb_min_pre = 1; + + /* JB max prefetch, default 4/5 JB max count */ + if (info->jb_max_pre >= frm_ptime) + jb_max_pre = info->jb_max_pre * chunks_per_frm / frm_ptime; + else + jb_max_pre = jb_max * 4 / 5; + + /* JB init prefetch, default 0 */ + if (info->jb_init >= frm_ptime) + jb_init = info->jb_init * chunks_per_frm / frm_ptime; + else + jb_init = 0; + + /* Create jitter buffer */ + status = pjmedia_jbuf_create(pool, &stream->dec->port.info.name, + PJMEDIA_MAX_MTU, + 1000 * vfd_enc->fps.denum / vfd_enc->fps.num, + jb_max, &stream->jb); + if (status != PJ_SUCCESS) + return status; + + + /* Set up jitter buffer */ + pjmedia_jbuf_set_adaptive( stream->jb, jb_init, jb_min_pre, jb_max_pre); + //pjmedia_jbuf_enable_discard(stream->jb, PJ_FALSE); + + /* Init RTCP session: */ + { + pjmedia_rtcp_session_setting rtcp_setting; + + pjmedia_rtcp_session_setting_default(&rtcp_setting); + rtcp_setting.name = stream->name.ptr; + rtcp_setting.ssrc = info->ssrc; + rtcp_setting.rtp_ts_base = pj_ntohl(stream->enc->rtp.out_hdr.ts); + rtcp_setting.clock_rate = info->codec_info.clock_rate; + rtcp_setting.samples_per_frame = 1; + + pjmedia_rtcp_init2(&stream->rtcp, &rtcp_setting); + } + + /* Only attach transport when stream is ready. */ + status = pjmedia_transport_attach(tp, stream, &info->rem_addr, + &info->rem_rtcp, + pj_sockaddr_get_len(&info->rem_addr), + &on_rx_rtp, &on_rx_rtcp); + if (status != PJ_SUCCESS) + return status; + + stream->transport = tp; + + /* Send RTCP SDES */ + len = create_rtcp_sdes(stream, (pj_uint8_t*)stream->enc->buf, + stream->enc->buf_size); + if (len != 0) { + pjmedia_transport_send_rtcp(stream->transport, + stream->enc->buf, len); + } + +#if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0 + /* NAT hole punching by sending KA packet via RTP transport. */ + if (stream->use_ka) + send_keep_alive_packet(stream); +#endif + +#if TRACE_JB + { + char trace_name[PJ_MAXPATH]; + pj_ssize_t len; + + pj_ansi_snprintf(trace_name, sizeof(trace_name), + TRACE_JB_PATH_PREFIX "%s.csv", + channel->port.info.name.ptr); + status = pj_file_open(pool, trace_name, PJ_O_RDWR, + &stream->trace_jb_fd); + if (status != PJ_SUCCESS) { + stream->trace_jb_fd = TRACE_JB_INVALID_FD; + PJ_LOG(3,(THIS_FILE, "Failed creating RTP trace file '%s'", + trace_name)); + } else { + stream->trace_jb_buf = (char*)pj_pool_alloc(pool, PJ_LOG_MAX_SIZE); + + /* Print column header */ + len = pj_ansi_snprintf(stream->trace_jb_buf, PJ_LOG_MAX_SIZE, + "Time, Operation, Size, Frame Count, " + "Frame type, RTP Seq, RTP TS, RTP M, " + "JB size, JB burst level, JB prefetch\n"); + pj_file_write(stream->trace_jb_fd, stream->trace_jb_buf, &len); + pj_file_flush(stream->trace_jb_fd); + } + } +#endif + + /* Save the stream info */ + pj_memcpy(&stream->info, info, sizeof(*info)); + stream->info.codec_param = pjmedia_vid_codec_param_clone( + pool, info->codec_param); + + /* Success! */ + *p_stream = stream; + + PJ_LOG(5,(THIS_FILE, "Video stream %s created", stream->name.ptr)); + + return PJ_SUCCESS; +} + + +/* + * Destroy stream. + */ +PJ_DEF(pj_status_t) pjmedia_vid_stream_destroy( pjmedia_vid_stream *stream ) +{ + unsigned len; + PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); + + /* Send RTCP BYE */ + if (stream->enc && stream->transport) { + len = create_rtcp_bye(stream, (pj_uint8_t*)stream->enc->buf, + stream->enc->buf_size); + if (len != 0) { + pjmedia_transport_send_rtcp(stream->transport, + stream->enc->buf, len); + } + } + + /* Detach from transport + * MUST NOT hold stream mutex while detaching from transport, as + * it may cause deadlock. See ticket #460 for the details. + */ + if (stream->transport) { + pjmedia_transport_detach(stream->transport, stream); + stream->transport = NULL; + } + + /* This function may be called when stream is partly initialized. */ + if (stream->jb_mutex) + pj_mutex_lock(stream->jb_mutex); + + + /* Free codec. */ + if (stream->codec) { + pjmedia_vid_codec_close(stream->codec); + pjmedia_vid_codec_mgr_dealloc_codec(stream->codec_mgr, stream->codec); + stream->codec = NULL; + } + + /* Free mutex */ + + if (stream->jb_mutex) { + pj_mutex_destroy(stream->jb_mutex); + stream->jb_mutex = NULL; + } + + /* Destroy jitter buffer */ + if (stream->jb) + pjmedia_jbuf_destroy(stream->jb); + +#if TRACE_JB + if (TRACE_JB_OPENED(stream)) { + pj_file_close(stream->trace_jb_fd); + stream->trace_jb_fd = TRACE_JB_INVALID_FD; + } +#endif + + if (stream->own_pool) { + pj_pool_t *pool = stream->own_pool; + stream->own_pool = NULL; + pj_pool_release(pool); + } + + return PJ_SUCCESS; +} + + +/* + * Get the port interface. + */ +PJ_DEF(pj_status_t) pjmedia_vid_stream_get_port(pjmedia_vid_stream *stream, + pjmedia_dir dir, + pjmedia_port **p_port ) +{ + PJ_ASSERT_RETURN(dir==PJMEDIA_DIR_ENCODING || dir==PJMEDIA_DIR_DECODING, + PJ_EINVAL); + + if (dir == PJMEDIA_DIR_ENCODING) + *p_port = &stream->enc->port; + else + *p_port = &stream->dec->port; + + return PJ_SUCCESS; +} + + +/* + * Get the transport object + */ +PJ_DEF(pjmedia_transport*) pjmedia_vid_stream_get_transport( + pjmedia_vid_stream *st) +{ + return st->transport; +} + + +/* + * Get stream statistics. + */ +PJ_DEF(pj_status_t) pjmedia_vid_stream_get_stat( + const pjmedia_vid_stream *stream, + pjmedia_rtcp_stat *stat) +{ + PJ_ASSERT_RETURN(stream && stat, PJ_EINVAL); + + pj_memcpy(stat, &stream->rtcp.stat, sizeof(pjmedia_rtcp_stat)); + return PJ_SUCCESS; +} + + +/* + * Reset the stream statistics in the middle of a stream session. + */ +PJ_DEF(pj_status_t) pjmedia_vid_stream_reset_stat(pjmedia_vid_stream *stream) +{ + PJ_ASSERT_RETURN(stream, PJ_EINVAL); + + pjmedia_rtcp_init_stat(&stream->rtcp.stat); + + return PJ_SUCCESS; +} + + +/* + * Get jitter buffer state. + */ +PJ_DEF(pj_status_t) pjmedia_vid_stream_get_stat_jbuf( + const pjmedia_vid_stream *stream, + pjmedia_jb_state *state) +{ + PJ_ASSERT_RETURN(stream && state, PJ_EINVAL); + return pjmedia_jbuf_get_state(stream->jb, state); +} + + +/* + * Get the stream info. + */ +PJ_DEF(pj_status_t) pjmedia_vid_stream_get_info( + const pjmedia_vid_stream *stream, + pjmedia_vid_stream_info *info) +{ + PJ_ASSERT_RETURN(stream && info, PJ_EINVAL); + pj_memcpy(info, &stream->info, sizeof(*info)); + return PJ_SUCCESS; +} + + +/* + * Start stream. + */ +PJ_DEF(pj_status_t) pjmedia_vid_stream_start(pjmedia_vid_stream *stream) +{ + + PJ_ASSERT_RETURN(stream && stream->enc && stream->dec, PJ_EINVALIDOP); + + if (stream->enc && (stream->dir & PJMEDIA_DIR_ENCODING)) { + stream->enc->paused = 0; + //pjmedia_snd_stream_start(stream->enc->snd_stream); + PJ_LOG(4,(stream->enc->port.info.name.ptr, "Encoder stream started")); + } else { + PJ_LOG(4,(stream->enc->port.info.name.ptr, "Encoder stream paused")); + } + + if (stream->dec && (stream->dir & PJMEDIA_DIR_DECODING)) { + stream->dec->paused = 0; + //pjmedia_snd_stream_start(stream->dec->snd_stream); + PJ_LOG(4,(stream->dec->port.info.name.ptr, "Decoder stream started")); + } else { + PJ_LOG(4,(stream->dec->port.info.name.ptr, "Decoder stream paused")); + } + + return PJ_SUCCESS; +} + + +/* + * Pause stream. + */ +PJ_DEF(pj_status_t) pjmedia_vid_stream_pause(pjmedia_vid_stream *stream, + pjmedia_dir dir) +{ + PJ_ASSERT_RETURN(stream, PJ_EINVAL); + + if ((dir & PJMEDIA_DIR_ENCODING) && stream->enc) { + stream->enc->paused = 1; + PJ_LOG(4,(stream->enc->port.info.name.ptr, "Encoder stream paused")); + } + + if ((dir & PJMEDIA_DIR_DECODING) && stream->dec) { + stream->dec->paused = 1; + + /* Also reset jitter buffer */ + pj_mutex_lock( stream->jb_mutex ); + pjmedia_jbuf_reset(stream->jb); + pj_mutex_unlock( stream->jb_mutex ); + + PJ_LOG(4,(stream->dec->port.info.name.ptr, "Decoder stream paused")); + } + + return PJ_SUCCESS; +} + + +/* + * Resume stream + */ +PJ_DEF(pj_status_t) pjmedia_vid_stream_resume(pjmedia_vid_stream *stream, + pjmedia_dir dir) +{ + PJ_ASSERT_RETURN(stream, PJ_EINVAL); + + if ((dir & PJMEDIA_DIR_ENCODING) && stream->enc) { + stream->enc->paused = 0; + PJ_LOG(4,(stream->enc->port.info.name.ptr, "Encoder stream resumed")); + } + + if ((dir & PJMEDIA_DIR_DECODING) && stream->dec) { + stream->dec->paused = 0; + PJ_LOG(4,(stream->dec->port.info.name.ptr, "Decoder stream resumed")); + } + + return PJ_SUCCESS; +} + + +static const pj_str_t ID_VIDEO = { "video", 5}; +static const pj_str_t ID_IN = { "IN", 2 }; +static const pj_str_t ID_IP4 = { "IP4", 3}; +static const pj_str_t ID_IP6 = { "IP6", 3}; +static const pj_str_t ID_RTP_AVP = { "RTP/AVP", 7 }; +static const pj_str_t ID_RTP_SAVP = { "RTP/SAVP", 8 }; +//static const pj_str_t ID_SDP_NAME = { "pjmedia", 7 }; +static const pj_str_t ID_RTPMAP = { "rtpmap", 6 }; + +static const pj_str_t STR_INACTIVE = { "inactive", 8 }; +static const pj_str_t STR_SENDRECV = { "sendrecv", 8 }; +static const pj_str_t STR_SENDONLY = { "sendonly", 8 }; +static const pj_str_t STR_RECVONLY = { "recvonly", 8 }; + + +/* + * Internal function for collecting codec info and param from the SDP media. + */ +static pj_status_t get_video_codec_info_param(pjmedia_vid_stream_info *si, + pj_pool_t *pool, + pjmedia_vid_codec_mgr *mgr, + const pjmedia_sdp_media *local_m, + const pjmedia_sdp_media *rem_m) +{ + unsigned pt = 0; + const pjmedia_vid_codec_info *p_info; + pj_status_t status; + + pt = pj_strtoul(&local_m->desc.fmt[0]); + + /* Get codec info. */ + status = pjmedia_vid_codec_mgr_get_codec_info(mgr, pt, &p_info); + if (status != PJ_SUCCESS) + return status; + + si->codec_info = *p_info; + + /* Get payload type for receiving direction */ + si->rx_pt = pt; + + /* Get payload type for transmitting direction */ + if (pt < 96) { + /* For static payload type, pt's are symetric */ + si->tx_pt = pt; + + } else { + unsigned i; + + /* Determine payload type for outgoing channel, by finding + * dynamic payload type in remote SDP that matches the answer. + */ + si->tx_pt = 0xFFFF; + for (i=0; i<rem_m->desc.fmt_count; ++i) { + if (pjmedia_sdp_neg_fmt_match(NULL, + (pjmedia_sdp_media*)local_m, 0, + (pjmedia_sdp_media*)rem_m, i, 0) == + PJ_SUCCESS) + { + /* Found matched codec. */ + si->tx_pt = pj_strtoul(&rem_m->desc.fmt[i]); + break; + } + } + + if (si->tx_pt == 0xFFFF) + return PJMEDIA_EMISSINGRTPMAP; + } + + + /* Now that we have codec info, get the codec param. */ + si->codec_param = PJ_POOL_ALLOC_T(pool, pjmedia_vid_codec_param); + status = pjmedia_vid_codec_mgr_get_default_param(mgr, + &si->codec_info, + si->codec_param); + + /* Get remote fmtp for our encoder. */ + pjmedia_stream_info_parse_fmtp(pool, rem_m, si->tx_pt, + &si->codec_param->enc_fmtp); + + /* Get local fmtp for our decoder. */ + pjmedia_stream_info_parse_fmtp(pool, local_m, si->rx_pt, + &si->codec_param->dec_fmtp); + + /* When direction is NONE (it means SDP negotiation has failed) we don't + * need to return a failure here, as returning failure will cause + * the whole SDP to be rejected. See ticket #: + * http:// + * + * Thanks Alain Totouom + */ + if (status != PJ_SUCCESS && si->dir != PJMEDIA_DIR_NONE) + return status; + + return PJ_SUCCESS; +} + + + +/* + * Create stream info from SDP media line. + */ +PJ_DEF(pj_status_t) pjmedia_vid_stream_info_from_sdp( + pjmedia_vid_stream_info *si, + pj_pool_t *pool, + pjmedia_endpt *endpt, + const pjmedia_sdp_session *local, + const pjmedia_sdp_session *remote, + unsigned stream_idx) +{ + const pjmedia_sdp_attr *attr; + const pjmedia_sdp_media *local_m; + const pjmedia_sdp_media *rem_m; + const pjmedia_sdp_conn *local_conn; + const pjmedia_sdp_conn *rem_conn; + int rem_af, local_af; + pj_sockaddr local_addr; + pj_status_t status; + + PJ_UNUSED_ARG(endpt); + + /* Validate arguments: */ + PJ_ASSERT_RETURN(pool && si && local && remote, PJ_EINVAL); + PJ_ASSERT_RETURN(stream_idx < local->media_count, PJ_EINVAL); + PJ_ASSERT_RETURN(stream_idx < remote->media_count, PJ_EINVAL); + + /* Keep SDP shortcuts */ + local_m = local->media[stream_idx]; + rem_m = remote->media[stream_idx]; + + local_conn = local_m->conn ? local_m->conn : local->conn; + if (local_conn == NULL) + return PJMEDIA_SDP_EMISSINGCONN; + + rem_conn = rem_m->conn ? rem_m->conn : remote->conn; + if (rem_conn == NULL) + return PJMEDIA_SDP_EMISSINGCONN; + + /* Media type must be video */ + if (pj_stricmp(&local_m->desc.media, &ID_VIDEO) != 0) + return PJMEDIA_EINVALIMEDIATYPE; + + + /* Reset: */ + + pj_bzero(si, sizeof(*si)); + + /* Media type: */ + si->type = PJMEDIA_TYPE_VIDEO; + + /* Transport protocol */ + + /* At this point, transport type must be compatible, + * the transport instance will do more validation later. + */ + status = pjmedia_sdp_transport_cmp(&rem_m->desc.transport, + &local_m->desc.transport); + if (status != PJ_SUCCESS) + return PJMEDIA_SDPNEG_EINVANSTP; + + if (pj_stricmp(&local_m->desc.transport, &ID_RTP_AVP) == 0) { + + si->proto = PJMEDIA_TP_PROTO_RTP_AVP; + + } else if (pj_stricmp(&local_m->desc.transport, &ID_RTP_SAVP) == 0) { + + si->proto = PJMEDIA_TP_PROTO_RTP_SAVP; + + } else { + + si->proto = PJMEDIA_TP_PROTO_UNKNOWN; + return PJ_SUCCESS; + } + + + /* Check address family in remote SDP */ + rem_af = pj_AF_UNSPEC(); + if (pj_stricmp(&rem_conn->net_type, &ID_IN)==0) { + if (pj_stricmp(&rem_conn->addr_type, &ID_IP4)==0) { + rem_af = pj_AF_INET(); + } else if (pj_stricmp(&rem_conn->addr_type, &ID_IP6)==0) { + rem_af = pj_AF_INET6(); + } + } + + if (rem_af==pj_AF_UNSPEC()) { + /* Unsupported address family */ + return PJ_EAFNOTSUP; + } + + /* Set remote address: */ + status = pj_sockaddr_init(rem_af, &si->rem_addr, &rem_conn->addr, + rem_m->desc.port); + if (status != PJ_SUCCESS) { + /* Invalid IP address. */ + return PJMEDIA_EINVALIDIP; + } + + /* Check address family of local info */ + local_af = pj_AF_UNSPEC(); + if (pj_stricmp(&local_conn->net_type, &ID_IN)==0) { + if (pj_stricmp(&local_conn->addr_type, &ID_IP4)==0) { + local_af = pj_AF_INET(); + } else if (pj_stricmp(&local_conn->addr_type, &ID_IP6)==0) { + local_af = pj_AF_INET6(); + } + } + + if (local_af==pj_AF_UNSPEC()) { + /* Unsupported address family */ + return PJ_SUCCESS; + } + + /* Set remote address: */ + status = pj_sockaddr_init(local_af, &local_addr, &local_conn->addr, + local_m->desc.port); + if (status != PJ_SUCCESS) { + /* Invalid IP address. */ + return PJMEDIA_EINVALIDIP; + } + + /* Local and remote address family must match */ + if (local_af != rem_af) + return PJ_EAFNOTSUP; + + /* Media direction: */ + + if (local_m->desc.port == 0 || + pj_sockaddr_has_addr(&local_addr)==PJ_FALSE || + pj_sockaddr_has_addr(&si->rem_addr)==PJ_FALSE || + pjmedia_sdp_media_find_attr(local_m, &STR_INACTIVE, NULL)!=NULL) + { + /* Inactive stream. */ + + si->dir = PJMEDIA_DIR_NONE; + + } else if (pjmedia_sdp_media_find_attr(local_m, &STR_SENDONLY, NULL)!=NULL) { + + /* Send only stream. */ + + si->dir = PJMEDIA_DIR_ENCODING; + + } else if (pjmedia_sdp_media_find_attr(local_m, &STR_RECVONLY, NULL)!=NULL) { + + /* Recv only stream. */ + + si->dir = PJMEDIA_DIR_DECODING; + + } else { + + /* Send and receive stream. */ + + si->dir = PJMEDIA_DIR_ENCODING_DECODING; + + } + + /* No need to do anything else if stream is rejected */ + if (local_m->desc.port == 0) { + return PJ_SUCCESS; + } + + /* If "rtcp" attribute is present in the SDP, set the RTCP address + * from that attribute. Otherwise, calculate from RTP address. + */ + attr = pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr, + "rtcp", NULL); + if (attr) { + pjmedia_sdp_rtcp_attr rtcp; + status = pjmedia_sdp_attr_get_rtcp(attr, &rtcp); + if (status == PJ_SUCCESS) { + if (rtcp.addr.slen) { + status = pj_sockaddr_init(rem_af, &si->rem_rtcp, &rtcp.addr, + (pj_uint16_t)rtcp.port); + } else { + pj_sockaddr_init(rem_af, &si->rem_rtcp, NULL, + (pj_uint16_t)rtcp.port); + pj_memcpy(pj_sockaddr_get_addr(&si->rem_rtcp), + pj_sockaddr_get_addr(&si->rem_addr), + pj_sockaddr_get_addr_len(&si->rem_addr)); + } + } + } + + if (!pj_sockaddr_has_addr(&si->rem_rtcp)) { + int rtcp_port; + + pj_memcpy(&si->rem_rtcp, &si->rem_addr, sizeof(pj_sockaddr)); + rtcp_port = pj_sockaddr_get_port(&si->rem_addr) + 1; + pj_sockaddr_set_port(&si->rem_rtcp, (pj_uint16_t)rtcp_port); + } + + /* Get codec info and param */ + status = get_video_codec_info_param(si, pool, NULL, local_m, rem_m); + + /* Leave SSRC to random. */ + si->ssrc = pj_rand(); + + /* Set default jitter buffer parameter. */ + si->jb_init = si->jb_max = si->jb_min_pre = si->jb_max_pre = -1; + + return status; +} + diff --git a/pjmedia/src/pjmedia/vid_tee.c b/pjmedia/src/pjmedia/vid_tee.c new file mode 100644 index 00000000..dd12ec3e --- /dev/null +++ b/pjmedia/src/pjmedia/vid_tee.c @@ -0,0 +1,384 @@ +/* $Id$ */ +/* + * Copyright (C) 2011 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include <pjmedia/vid_tee.h> +#include <pjmedia/converter.h> +#include <pjmedia/errno.h> +#include <pj/array.h> +#include <pj/log.h> +#include <pj/pool.h> + +#define TEE_PORT_NAME "vid_tee" +#define TEE_PORT_SIGN PJMEDIA_SIG_PORT_VID_TEE +#define MAX_DST_PORT_COUNT 20 + + +typedef struct vid_tee_dst_port +{ + pjmedia_port *dst; + unsigned option; +} vid_tee_dst_port; + + +typedef struct vid_tee_port +{ + pjmedia_port base; + pj_pool_t *pool; + pj_pool_factory *pf; + pj_pool_t *buf_pool; + void *buf[2]; + unsigned buf_cnt; + pj_size_t buf_size; + unsigned dst_port_maxcnt; + unsigned dst_port_cnt; + vid_tee_dst_port *dst_ports; + + struct vid_tee_conv_t { + pjmedia_converter *conv; + pj_size_t conv_buf_size; + } *tee_conv; +} vid_tee_port; + + +static pj_status_t tee_put_frame(pjmedia_port *port, pjmedia_frame *frame); +static pj_status_t tee_get_frame(pjmedia_port *port, pjmedia_frame *frame); +static pj_status_t tee_destroy(pjmedia_port *port); + +/* + * Create a video tee port with the specified source media port. + */ +PJ_DEF(pj_status_t) pjmedia_vid_tee_create( pj_pool_t *pool, + const pjmedia_format *fmt, + unsigned max_dst_cnt, + pjmedia_port **p_vid_tee) +{ + vid_tee_port *tee; + pj_str_t name_st; + const pjmedia_video_format_info *vfi; + pjmedia_video_apply_fmt_param vafp; + pj_status_t status; + + PJ_ASSERT_RETURN(pool && fmt && p_vid_tee, PJ_EINVAL); + PJ_ASSERT_RETURN(fmt->type == PJMEDIA_TYPE_VIDEO, PJ_EINVAL); + PJ_ASSERT_RETURN(max_dst_cnt <= MAX_DST_PORT_COUNT, PJ_ETOOMANY); + + /* Allocate video tee structure */ + tee = PJ_POOL_ZALLOC_T(pool, vid_tee_port); + tee->pf = pool->factory; + tee->pool = pj_pool_create(tee->pf, "video tee", 500, 500, NULL); + + /* Initialize video tee structure */ + tee->dst_port_maxcnt = max_dst_cnt; + tee->dst_ports = (vid_tee_dst_port*) + pj_pool_calloc(pool, max_dst_cnt, + sizeof(vid_tee_dst_port)); + tee->tee_conv = (struct vid_tee_conv_t *) + pj_pool_calloc(pool, max_dst_cnt, + sizeof(struct vid_tee_conv_t)); + + /* Initialize video tee buffer, its size is one frame */ + vfi = pjmedia_get_video_format_info(NULL, fmt->id); + if (vfi == NULL) + return PJMEDIA_EBADFMT; + + pj_bzero(&vafp, sizeof(vafp)); + vafp.size = fmt->det.vid.size; + status = vfi->apply_fmt(vfi, &vafp); + if (status != PJ_SUCCESS) + return status; + + tee->buf_size = vafp.framebytes; + + /* Initialize video tee port */ + status = pjmedia_port_info_init2(&tee->base.info, + pj_strset2(&name_st, (char*)TEE_PORT_NAME), + TEE_PORT_SIGN, + PJMEDIA_DIR_ENCODING, + fmt); + if (status != PJ_SUCCESS) + return status; + + tee->base.get_frame = &tee_get_frame; + tee->base.put_frame = &tee_put_frame; + tee->base.on_destroy = &tee_destroy; + + /* Done */ + *p_vid_tee = &tee->base; + + return PJ_SUCCESS; +} + +static void realloc_buf(vid_tee_port *vid_tee, + unsigned buf_cnt, pj_size_t buf_size) +{ + unsigned i; + + if (buf_cnt > vid_tee->buf_cnt) + vid_tee->buf_cnt = buf_cnt; + + if (buf_size > vid_tee->buf_size) { + /* We need a larger buffer here. */ + vid_tee->buf_size = buf_size; + if (vid_tee->buf_pool) { + pj_pool_release(vid_tee->buf_pool); + vid_tee->buf_pool = NULL; + } + vid_tee->buf[0] = vid_tee->buf[1] = NULL; + } + + if (!vid_tee->buf_pool) { + vid_tee->buf_pool = pj_pool_create(vid_tee->pf, "video tee buffer", + 1000, 1000, NULL); + } + + for (i = 0; i < vid_tee->buf_cnt; i++) { + if (!vid_tee->buf[i]) + vid_tee->buf[i] = pj_pool_alloc(vid_tee->buf_pool, + vid_tee->buf_size); + } +} + +/* + * Add a destination media port to the video tee. + */ +PJ_DEF(pj_status_t) pjmedia_vid_tee_add_dst_port(pjmedia_port *vid_tee, + unsigned option, + pjmedia_port *port) +{ + vid_tee_port *tee = (vid_tee_port*)vid_tee; + pjmedia_video_format_detail *vfd; + + PJ_ASSERT_RETURN(vid_tee && vid_tee->info.signature==TEE_PORT_SIGN, + PJ_EINVAL); + + if (tee->dst_port_cnt >= tee->dst_port_maxcnt) + return PJ_ETOOMANY; + + if (vid_tee->info.fmt.id != port->info.fmt.id) + return PJMEDIA_EBADFMT; + + vfd = pjmedia_format_get_video_format_detail(&port->info.fmt, PJ_TRUE); + if (vfd->size.w != vid_tee->info.fmt.det.vid.size.w || + vfd->size.h != vid_tee->info.fmt.det.vid.size.h) + { + return PJMEDIA_EBADFMT; + } + + realloc_buf(tee, (option & PJMEDIA_VID_TEE_DST_DO_IN_PLACE_PROC)? + 1: 0, tee->buf_size); + + pj_bzero(&tee->tee_conv[tee->dst_port_cnt], sizeof(tee->tee_conv[0])); + tee->dst_ports[tee->dst_port_cnt].dst = port; + tee->dst_ports[tee->dst_port_cnt].option = option; + ++tee->dst_port_cnt; + + return PJ_SUCCESS; +} + + +/* + * Add a destination media port to the video tee. Create a converter if + * necessary. + */ +PJ_DEF(pj_status_t) pjmedia_vid_tee_add_dst_port2(pjmedia_port *vid_tee, + unsigned option, + pjmedia_port *port) +{ + vid_tee_port *tee = (vid_tee_port*)vid_tee; + pjmedia_video_format_detail *vfd; + + PJ_ASSERT_RETURN(vid_tee && vid_tee->info.signature==TEE_PORT_SIGN, + PJ_EINVAL); + + if (tee->dst_port_cnt >= tee->dst_port_maxcnt) + return PJ_ETOOMANY; + + pj_bzero(&tee->tee_conv[tee->dst_port_cnt], sizeof(tee->tee_conv[0])); + + /* Check if we need to create a converter. */ + vfd = pjmedia_format_get_video_format_detail(&port->info.fmt, PJ_TRUE); + if (vid_tee->info.fmt.id != port->info.fmt.id || + vfd->size.w != vid_tee->info.fmt.det.vid.size.w || + vfd->size.h != vid_tee->info.fmt.det.vid.size.h) + { + const pjmedia_video_format_info *vfi; + pjmedia_video_apply_fmt_param vafp; + pjmedia_conversion_param conv_param; + pj_status_t status; + + vfi = pjmedia_get_video_format_info(NULL, port->info.fmt.id); + if (vfi == NULL) + return PJMEDIA_EBADFMT; + + pj_bzero(&vafp, sizeof(vafp)); + vafp.size = port->info.fmt.det.vid.size; + status = vfi->apply_fmt(vfi, &vafp); + if (status != PJ_SUCCESS) + return status; + + realloc_buf(tee, (option & PJMEDIA_VID_TEE_DST_DO_IN_PLACE_PROC)? + 2: 1, vafp.framebytes); + + pjmedia_format_copy(&conv_param.src, &vid_tee->info.fmt); + pjmedia_format_copy(&conv_param.dst, &port->info.fmt); + + status = pjmedia_converter_create( + NULL, tee->pool, &conv_param, + &tee->tee_conv[tee->dst_port_cnt].conv); + if (status != PJ_SUCCESS) + return status; + + tee->tee_conv[tee->dst_port_cnt].conv_buf_size = vafp.framebytes; + } else { + realloc_buf(tee, (option & PJMEDIA_VID_TEE_DST_DO_IN_PLACE_PROC)? + 1: 0, tee->buf_size); + } + + tee->dst_ports[tee->dst_port_cnt].dst = port; + tee->dst_ports[tee->dst_port_cnt].option = option; + ++tee->dst_port_cnt; + + return PJ_SUCCESS; +} + + +/* + * Remove a destination media port from the video tee. + */ +PJ_DECL(pj_status_t) pjmedia_vid_tee_remove_dst_port(pjmedia_port *vid_tee, + pjmedia_port *port) +{ + vid_tee_port *tee = (vid_tee_port*)vid_tee; + unsigned i; + + PJ_ASSERT_RETURN(vid_tee && vid_tee->info.signature==TEE_PORT_SIGN, + PJ_EINVAL); + + for (i = 0; i < tee->dst_port_cnt; ++i) { + if (tee->dst_ports[i].dst == port) { + if (tee->tee_conv[i].conv) + pjmedia_converter_destroy(tee->tee_conv[i].conv); + + pj_array_erase(tee->dst_ports, sizeof(tee->dst_ports[0]), + tee->dst_port_cnt, i); + pj_array_erase(tee->tee_conv, sizeof(tee->tee_conv[0]), + tee->dst_port_cnt, i); + --tee->dst_port_cnt; + return PJ_SUCCESS; + } + } + + return PJ_ENOTFOUND; +} + + +static pj_status_t tee_put_frame(pjmedia_port *port, pjmedia_frame *frame) +{ + vid_tee_port *tee = (vid_tee_port*)port; + unsigned i, j; + pj_bool_t done[MAX_DST_PORT_COUNT]; + + pj_bzero(done, sizeof(done)); + + for (i = 0; i < tee->dst_port_cnt; ++i) { + pjmedia_frame frame_ = *frame; + + if (done[i]) + continue; + + if (tee->tee_conv[i].conv) { + pj_status_t status; + + frame_.buf = tee->buf[0]; + frame_.size = tee->tee_conv[i].conv_buf_size; + status = pjmedia_converter_convert(tee->tee_conv[i].conv, + frame, &frame_); + if (status != PJ_SUCCESS) { + PJ_LOG(3, ("", "Failed to convert frame for destination" + "port %d (%.*s)", i, + tee->dst_ports[i].dst->info.name.slen, + tee->dst_ports[i].dst->info.name.ptr)); + continue; + } + } + + /* Find other destination ports which has the same format so + * we don't need to do the same conversion twice. + */ + for (j = i; j < tee->dst_port_cnt; ++j) { + pjmedia_frame framep; + + if (done[j] || + (tee->dst_ports[j].dst->info.fmt.id != + tee->dst_ports[i].dst->info.fmt.id) || + (tee->dst_ports[j].dst->info.fmt.det.vid.size.w != + tee->dst_ports[i].dst->info.fmt.det.vid.size.w) || + (tee->dst_ports[j].dst->info.fmt.det.vid.size.h != + tee->dst_ports[i].dst->info.fmt.det.vid.size.h)) + { + continue; + } + + framep = frame_; + /* For dst_ports that do in-place processing, we need to duplicate + * the data source first. + */ + if (tee->dst_ports[j].option & PJMEDIA_VID_TEE_DST_DO_IN_PLACE_PROC) + { + PJ_ASSERT_RETURN(tee->buf_size <= frame_.size, PJ_ETOOBIG); + framep.buf = tee->buf[tee->buf_cnt-1]; + framep.size = frame_.size; + pj_memcpy(framep.buf, frame_.buf, frame_.size); + } + + /* Deliver the data */ + pjmedia_port_put_frame(tee->dst_ports[j].dst, &framep); + done[j] = PJ_TRUE; + + if (!tee->tee_conv[i].conv) + break; + } + } + + return PJ_SUCCESS; +} + +static pj_status_t tee_get_frame(pjmedia_port *port, pjmedia_frame *frame) +{ + PJ_UNUSED_ARG(port); + PJ_UNUSED_ARG(frame); + + pj_assert(!"Bug! Tee port get_frame() shouldn't be called."); + + return PJ_EBUG; +} + +static pj_status_t tee_destroy(pjmedia_port *port) +{ + vid_tee_port *tee = (vid_tee_port*)port; + + PJ_ASSERT_RETURN(port && port->info.signature==TEE_PORT_SIGN, PJ_EINVAL); + + pj_pool_release(tee->pool); + if (tee->buf_pool) + pj_pool_release(tee->buf_pool); + + pj_bzero(tee, sizeof(*tee)); + + return PJ_SUCCESS; +} diff --git a/pjmedia/src/pjmedia/wav_player.c b/pjmedia/src/pjmedia/wav_player.c index e4ad3b48..3bccd527 100644 --- a/pjmedia/src/pjmedia/wav_player.c +++ b/pjmedia/src/pjmedia/wav_player.c @@ -32,7 +32,7 @@ #define THIS_FILE "wav_player.c" -#define SIGNATURE PJMEDIA_PORT_SIGNATURE('F', 'P', 'l', 'y') +#define SIGNATURE PJMEDIA_SIG_PORT_WAV_PLAYER #define BITS_PER_SAMPLE 16 #if 1 @@ -187,7 +187,10 @@ PJ_DEF(pj_status_t) pjmedia_wav_player_port_create( pj_pool_t *pool, pjmedia_wave_hdr wave_hdr; pj_ssize_t size_to_read, size_read; struct file_reader_port *fport; + pjmedia_audio_format_detail *ad; pj_off_t pos; + pj_str_t name; + unsigned samples_per_frame; pj_status_t status = PJ_SUCCESS; @@ -352,17 +355,15 @@ PJ_DEF(pj_status_t) pjmedia_wav_player_port_create( pj_pool_t *pool, fport->options = options; /* Update port info. */ - fport->base.info.channel_count = wave_hdr.fmt_hdr.nchan; - fport->base.info.clock_rate = wave_hdr.fmt_hdr.sample_rate; - fport->base.info.bits_per_sample = BITS_PER_SAMPLE; - fport->base.info.samples_per_frame = fport->base.info.clock_rate * - wave_hdr.fmt_hdr.nchan * - ptime / 1000; - fport->base.info.bytes_per_frame = - fport->base.info.samples_per_frame * - fport->base.info.bits_per_sample / 8; - - pj_strdup2(pool, &fport->base.info.name, filename); + ad = pjmedia_format_get_audio_format_detail(&fport->base.info.fmt, 1); + pj_strdup2(pool, &name, filename); + samples_per_frame = ptime * wave_hdr.fmt_hdr.sample_rate * + wave_hdr.fmt_hdr.nchan / 1000; + pjmedia_port_info_init(&fport->base.info, &name, SIGNATURE, + wave_hdr.fmt_hdr.sample_rate, + wave_hdr.fmt_hdr.nchan, + BITS_PER_SAMPLE, + samples_per_frame); /* If file is shorter than buffer size, adjust buffer size to file * size. Otherwise EOF callback will be called multiple times when @@ -379,9 +380,7 @@ PJ_DEF(pj_status_t) pjmedia_wav_player_port_create( pj_pool_t *pool, /* samples_per_frame must be smaller than bufsize (because get_frame() * doesn't handle this case). */ - if (fport->base.info.samples_per_frame * fport->bytes_per_sample >= - fport->bufsize) - { + if (samples_per_frame * fport->bytes_per_sample >= fport->bufsize) { pj_file_close(fport->fd); return PJ_EINVAL; } @@ -415,8 +414,8 @@ PJ_DEF(pj_status_t) pjmedia_wav_player_port_create( pj_pool_t *pool, "filesize=%luKB", (int)fport->base.info.name.slen, fport->base.info.name.ptr, - fport->base.info.clock_rate, - fport->base.info.channel_count, + ad->clock_rate, + ad->channel_count, fport->bufsize / 1000, (unsigned long)(fport->fsize / 1000))); diff --git a/pjmedia/src/pjmedia/wav_playlist.c b/pjmedia/src/pjmedia/wav_playlist.c index 22dc08f1..ca5e4c55 100644 --- a/pjmedia/src/pjmedia/wav_playlist.c +++ b/pjmedia/src/pjmedia/wav_playlist.c @@ -32,7 +32,7 @@ #define THIS_FILE "wav_playlist.c" -#define SIGNATURE PJMEDIA_PORT_SIGNATURE('P', 'l', 's', 't') +#define SIGNATURE PJMEDIA_SIG_PORT_WAV_PLAYLIST #define BYTES_PER_SAMPLE 2 @@ -236,6 +236,7 @@ PJ_DEF(pj_status_t) pjmedia_wav_playlist_create(pj_pool_t *pool, pjmedia_port **p_port) { struct playlist_port *fport; + pjmedia_audio_format_detail *afd; pj_off_t pos; pj_status_t status; int index; @@ -280,6 +281,8 @@ PJ_DEF(pj_status_t) pjmedia_wav_playlist_create(pj_pool_t *pool, return PJ_ENOMEM; } + afd = pjmedia_format_get_audio_format_detail(&fport->base.info.fmt, 1); + /* start with the first file. */ fport->current_file = 0; fport->max_file = file_count; @@ -466,15 +469,13 @@ PJ_DEF(pj_status_t) pjmedia_wav_playlist_create(pj_pool_t *pool, * that the WAV file has the same attributes as previous files. */ if (!has_wave_info) { - fport->base.info.channel_count = wavehdr.fmt_hdr.nchan; - fport->base.info.clock_rate = wavehdr.fmt_hdr.sample_rate; - fport->base.info.bits_per_sample = wavehdr.fmt_hdr.bits_per_sample; - fport->base.info.samples_per_frame = fport->base.info.clock_rate * - wavehdr.fmt_hdr.nchan * - ptime / 1000; - fport->base.info.bytes_per_frame = - fport->base.info.samples_per_frame * - fport->base.info.bits_per_sample / 8; + afd->channel_count = wavehdr.fmt_hdr.nchan; + afd->clock_rate = wavehdr.fmt_hdr.sample_rate; + afd->bits_per_sample = wavehdr.fmt_hdr.bits_per_sample; + afd->frame_time_usec = ptime * 1000; + afd->avg_bps = afd->max_bps = afd->clock_rate * + afd->channel_count * + afd->bits_per_sample / 8; has_wave_info = PJ_TRUE; @@ -483,9 +484,9 @@ PJ_DEF(pj_status_t) pjmedia_wav_playlist_create(pj_pool_t *pool, /* Check that this file has the same characteristics as the other * files. */ - if (wavehdr.fmt_hdr.nchan != fport->base.info.channel_count || - wavehdr.fmt_hdr.sample_rate != fport->base.info.clock_rate || - wavehdr.fmt_hdr.bits_per_sample != fport->base.info.bits_per_sample) + if (wavehdr.fmt_hdr.nchan != afd->channel_count || + wavehdr.fmt_hdr.sample_rate != afd->clock_rate || + wavehdr.fmt_hdr.bits_per_sample != afd->bits_per_sample) { /* This file has different characteristics than the other * files. @@ -519,8 +520,8 @@ PJ_DEF(pj_status_t) pjmedia_wav_playlist_create(pj_pool_t *pool, "WAV playlist '%.*s' created: samp.rate=%d, ch=%d, bufsize=%uKB", (int)port_label->slen, port_label->ptr, - fport->base.info.clock_rate, - fport->base.info.channel_count, + afd->clock_rate, + afd->channel_count, fport->bufsize / 1000)); return PJ_SUCCESS; diff --git a/pjmedia/src/pjmedia/wav_writer.c b/pjmedia/src/pjmedia/wav_writer.c index 8eb4a02c..d1636276 100644 --- a/pjmedia/src/pjmedia/wav_writer.c +++ b/pjmedia/src/pjmedia/wav_writer.c @@ -30,7 +30,7 @@ #define THIS_FILE "wav_writer.c" -#define SIGNATURE PJMEDIA_PORT_SIGNATURE('F', 'W', 'R', 'T') +#define SIGNATURE PJMEDIA_SIG_PORT_WAV_WRITER struct file_port @@ -51,7 +51,7 @@ struct file_port }; static pj_status_t file_put_frame(pjmedia_port *this_port, - const pjmedia_frame *frame); + pjmedia_frame *frame); static pj_status_t file_get_frame(pjmedia_port *this_port, pjmedia_frame *frame); static pj_status_t file_on_destroy(pjmedia_port *this_port); @@ -198,7 +198,7 @@ PJ_DEF(pj_status_t) pjmedia_wav_writer_port_create( pj_pool_t *pool, fport->bufsize = buff_size; /* Check that buffer size is greater than bytes per frame */ - pj_assert(fport->bufsize >= fport->base.info.bytes_per_frame); + pj_assert(fport->bufsize >= PJMEDIA_PIA_AVG_FSZ(&fport->base.info)); /* Allocate buffer and set initial write position */ @@ -216,7 +216,7 @@ PJ_DEF(pj_status_t) pjmedia_wav_writer_port_create( pj_pool_t *pool, "File writer '%.*s' created: samp.rate=%d, bufsize=%uKB", (int)fport->base.info.name.slen, fport->base.info.name.ptr, - fport->base.info.clock_rate, + PJMEDIA_PIA_SRATE(&fport->base.info), fport->bufsize / 1000)); @@ -308,7 +308,7 @@ static pj_status_t flush_buffer(struct file_port *fport) * to the file. */ static pj_status_t file_put_frame(pjmedia_port *this_port, - const pjmedia_frame *frame) + pjmedia_frame *frame) { struct file_port *fport = (struct file_port *)this_port; unsigned frame_size; diff --git a/pjmedia/src/test/codec_vectors.c b/pjmedia/src/test/codec_vectors.c index b53850b6..5257b5a7 100644 --- a/pjmedia/src/test/codec_vectors.c +++ b/pjmedia/src/test/codec_vectors.c @@ -73,13 +73,13 @@ static int codec_test_encode(pjmedia_codec_mgr *mgr, codec_param.info.avg_bps = bitrate; codec_param.setting.vad = 0; - status = codec->op->init(codec, pool); + status = pjmedia_codec_init(codec, pool); if (status != PJ_SUCCESS) { rc = -60; goto on_return; } - status = codec->op->open(codec, &codec_param); + status = pjmedia_codec_open(codec, &codec_param); if (status != PJ_SUCCESS) { rc = -70; goto on_return; @@ -117,7 +117,7 @@ static int codec_test_encode(pjmedia_codec_mgr *mgr, break; out_frame.size = samples_per_frame; - status = codec->op->encode(codec, &in_frame, samples_per_frame, + status = pjmedia_codec_encode(codec, &in_frame, samples_per_frame, &out_frame); if (status != PJ_SUCCESS) { rc = -95; @@ -188,7 +188,7 @@ on_return: fclose(fref); if (codec) { - codec->op->close(codec); + pjmedia_codec_close(codec); pjmedia_codec_mgr_dealloc_codec(mgr, codec); } @@ -326,13 +326,13 @@ static int codec_test_decode(pjmedia_codec_mgr *mgr, codec_param.info.avg_bps = bitrate; codec_param.setting.vad = 0; - status = codec->op->init(codec, pool); + status = pjmedia_codec_init(codec, pool); if (status != PJ_SUCCESS) { rc = -60; goto on_return; } - status = codec->op->open(codec, &codec_param); + status = pjmedia_codec_open(codec, &codec_param); if (status != PJ_SUCCESS) { rc = -70; goto on_return; @@ -387,8 +387,8 @@ static int codec_test_decode(pjmedia_codec_mgr *mgr, if (has_frame) { count = 2; - if (codec->op->parse(codec, pkt, encoded_len, &ts, - &count, in_frame) != PJ_SUCCESS) + if (pjmedia_codec_parse(codec, pkt, encoded_len, &ts, + &count, in_frame) != PJ_SUCCESS) { rc = -100; goto on_return; @@ -399,15 +399,15 @@ static int codec_test_decode(pjmedia_codec_mgr *mgr, goto on_return; } - if (codec->op->decode(codec, &in_frame[0], samples_per_frame*2, - &out_frame) != PJ_SUCCESS) + if (pjmedia_codec_decode(codec, &in_frame[0], samples_per_frame*2, + &out_frame) != PJ_SUCCESS) { rc = -120; goto on_return; } } else { - if (codec->op->recover(codec, samples_per_frame*2, - &out_frame) != PJ_SUCCESS) + if (pjmedia_codec_recover(codec, samples_per_frame*2, + &out_frame) != PJ_SUCCESS) { rc = -125; goto on_return; @@ -483,7 +483,7 @@ on_return: fclose(input); if (codec) { - codec->op->close(codec); + pjmedia_codec_close(codec); pjmedia_codec_mgr_dealloc_codec(mgr, codec); } diff --git a/pjmedia/src/test/main.c b/pjmedia/src/test/main.c index b48f7d77..6ea1595c 100644 --- a/pjmedia/src/test/main.c +++ b/pjmedia/src/test/main.c @@ -17,6 +17,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include <pj/os.h> + #include "test.h" @@ -29,8 +31,7 @@ # include "../../../pjlib/include/rtems-network-config.h" #endif - -int main(int argc, char *argv[]) +static int main_func(int argc, char *argv[]) { int rc; char s[10]; @@ -46,4 +47,7 @@ int main(int argc, char *argv[]) return rc; } - +int main(int argc, char *argv[]) +{ + return pj_run_app(&main_func, argc, argv, 0); +} diff --git a/pjmedia/src/test/mips_test.c b/pjmedia/src/test/mips_test.c index d5ece9c7..31a33ed1 100644 --- a/pjmedia/src/test/mips_test.c +++ b/pjmedia/src/test/mips_test.c @@ -684,7 +684,7 @@ struct codec_port static pj_status_t codec_put_frame(struct pjmedia_port *this_port, - const pjmedia_frame *frame) + pjmedia_frame *frame) { struct codec_port *cp = (struct codec_port*)this_port; pjmedia_frame out_frame; @@ -692,8 +692,8 @@ static pj_status_t codec_put_frame(struct pjmedia_port *this_port, out_frame.buf = cp->pkt; out_frame.size = sizeof(cp->pkt); - status = cp->codec->op->encode(cp->codec, frame, sizeof(cp->pkt), - &out_frame); + status = pjmedia_codec_encode(cp->codec, frame, sizeof(cp->pkt), + &out_frame); pj_assert(status == PJ_SUCCESS); if (out_frame.size != 0) { @@ -701,16 +701,16 @@ static pj_status_t codec_put_frame(struct pjmedia_port *this_port, unsigned frame_cnt = PJ_ARRAY_SIZE(parsed_frm); unsigned i; - status = cp->codec->op->parse(cp->codec, out_frame.buf, - out_frame.size, &out_frame.timestamp, - &frame_cnt, parsed_frm); + status = pjmedia_codec_parse(cp->codec, out_frame.buf, + out_frame.size, &out_frame.timestamp, + &frame_cnt, parsed_frm); pj_assert(status == PJ_SUCCESS); for (i=0; i<frame_cnt; ++i) { pcm_frm.buf = cp->pcm; pcm_frm.size = sizeof(cp->pkt); - status = cp->codec->op->decode(cp->codec, &parsed_frm[i], - sizeof(cp->pcm), &pcm_frm); + status = pjmedia_codec_decode(cp->codec, &parsed_frm[i], + sizeof(cp->pcm), &pcm_frm); pj_assert(status == PJ_SUCCESS); } } @@ -722,7 +722,7 @@ static pj_status_t codec_on_destroy(struct pjmedia_port *this_port) { struct codec_port *cp = (struct codec_port*)this_port; - cp->codec->op->close(cp->codec); + pjmedia_codec_close(cp->codec); pjmedia_codec_mgr_dealloc_codec(pjmedia_endpt_get_codec_mgr(cp->endpt), cp->codec); cp->codec_deinit(); @@ -782,11 +782,11 @@ static pjmedia_port* codec_encode_decode( pj_pool_t *pool, if (status != PJ_SUCCESS) return NULL; - status = (*cp->codec->op->init)(cp->codec, pool); + status = pjmedia_codec_init(cp->codec, pool); if (status != PJ_SUCCESS) return NULL; - status = cp->codec->op->open(cp->codec, &codec_param); + status = pjmedia_codec_open(cp->codec, &codec_param); if (status != PJ_SUCCESS) return NULL; @@ -1131,13 +1131,13 @@ static pj_status_t wsola_discard_get_frame(struct pjmedia_port *this_port, pj_status_t status; while (pjmedia_circ_buf_get_len(wp->circbuf) < - wp->base.info.samples_per_frame * (CIRC_BUF_FRAME_CNT-1)) + PJMEDIA_PIA_SPF(&wp->base.info) * (CIRC_BUF_FRAME_CNT-1)) { status = pjmedia_port_get_frame(wp->gen_port, frame); pj_assert(status==PJ_SUCCESS); status = pjmedia_circ_buf_write(wp->circbuf, (short*)frame->buf, - wp->base.info.samples_per_frame); + PJMEDIA_PIA_SPF(&wp->base.info)); pj_assert(status==PJ_SUCCESS); } @@ -1149,7 +1149,7 @@ static pj_status_t wsola_discard_get_frame(struct pjmedia_port *this_port, pjmedia_circ_buf_get_read_regions(wp->circbuf, ®1, ®1_len, ®2, ®2_len); - del_cnt = wp->base.info.samples_per_frame; + del_cnt = PJMEDIA_PIA_SPF(&wp->base.info); status = pjmedia_wsola_discard(wp->wsola, reg1, reg1_len, reg2, reg2_len, &del_cnt); pj_assert(status==PJ_SUCCESS); @@ -2010,7 +2010,7 @@ static pj_status_t delaybuf_get_frame(struct pjmedia_port *this_port, } static pj_status_t delaybuf_put_frame(struct pjmedia_port *this_port, - const pjmedia_frame *frame) + pjmedia_frame *frame) { struct delaybuf_port *dp = (struct delaybuf_port*)this_port; pj_status_t status; @@ -2219,7 +2219,7 @@ static pj_timestamp run_entry(unsigned clock_rate, struct test_entry *e) } /* Port may decide to use different ptime (e.g. iLBC) */ - samples_per_frame = port->info.samples_per_frame; + samples_per_frame = PJMEDIA_PIA_SPF(&port->info); gen_port = create_gen_port(pool, clock_rate, 1, samples_per_frame, 100); diff --git a/pjmedia/src/test/test.c b/pjmedia/src/test/test.c index ca53d732..d243ed8a 100644 --- a/pjmedia/src/test/test.c +++ b/pjmedia/src/test/test.c @@ -47,15 +47,33 @@ int test_main(void) { int rc = 0; pj_caching_pool caching_pool; + pj_pool_t *pool; pj_init(); pj_caching_pool_init(&caching_pool, &pj_pool_factory_default_policy, 0); + pool = pj_pool_create(&caching_pool.factory, "test", 1000, 512, NULL); pj_log_set_decor(PJ_LOG_HAS_NEWLINE); pj_log_set_level(3); mem = &caching_pool.factory; + pjmedia_video_format_mgr_create(pool, 64, 0, NULL); + pjmedia_converter_mgr_create(pool, NULL); + pjmedia_vid_codec_mgr_create(pool, NULL); + +#if HAS_VID_PORT_TEST + DO_TEST(vid_port_test()); +#endif + +#if HAS_VID_DEV_TEST + DO_TEST(vid_dev_test()); +#endif + +#if HAS_VID_CODEC_TEST + DO_TEST(vid_codec_test()); +#endif + #if HAS_SDP_NEG_TEST DO_TEST(sdp_neg_test()); #endif @@ -81,6 +99,11 @@ on_return: PJ_LOG(3,(THIS_FILE,"Looks like everything is okay!")); } + pjmedia_video_format_mgr_destroy(pjmedia_video_format_mgr_instance()); + pjmedia_converter_mgr_destroy(pjmedia_converter_mgr_instance()); + pjmedia_vid_codec_mgr_destroy(pjmedia_vid_codec_mgr_instance()); + + pj_pool_release(pool); pj_caching_pool_destroy(&caching_pool); return rc; diff --git a/pjmedia/src/test/test.h b/pjmedia/src/test/test.h index 74279c38..abd7d0bd 100644 --- a/pjmedia/src/test/test.h +++ b/pjmedia/src/test/test.h @@ -23,6 +23,9 @@ #include <pjmedia.h> #include <pjlib.h> +#define HAS_VID_DEV_TEST 1 +#define HAS_VID_PORT_TEST 0 +#define HAS_VID_CODEC_TEST 1 #define HAS_SDP_NEG_TEST 1 #define HAS_JBUF_TEST 1 #define HAS_MIPS_TEST 1 @@ -35,6 +38,9 @@ int jbuf_main(void); int sdp_neg_test(void); int mips_test(void); int codec_test_vectors(void); +int vid_codec_test(void); +int vid_dev_test(void); +int vid_port_test(void); extern pj_pool_factory *mem; void app_perror(pj_status_t status, const char *title); diff --git a/pjmedia/src/test/vid_codec_test.c b/pjmedia/src/test/vid_codec_test.c new file mode 100644 index 00000000..36ca19ac --- /dev/null +++ b/pjmedia/src/test/vid_codec_test.c @@ -0,0 +1,467 @@ +#include "test.h" +#include <pjmedia-codec/ffmpeg_codecs.h> +#include <pjmedia-videodev/videodev.h> +#include <pjmedia/vid_codec.h> +#include <pjmedia/port.h> + +#define THIS_FILE "vid_codec.c" + +#define BYPASS_CODEC 0 +#define BYPASS_PACKETIZER 0 + +/* + * Capture device setting: + * -1 = colorbar, + * -2 = any non-colorbar capture device (first found) + * x = specified capture device id + */ +#define CAPTURE_DEV -1 + + +typedef struct codec_port_data_t +{ + pjmedia_vid_codec *codec; + pjmedia_vid_port *rdr_port; + pj_uint8_t *enc_buf; + pj_size_t enc_buf_size; + pj_uint8_t *pack_buf; + pj_size_t pack_buf_size; +} codec_port_data_t; + +static pj_status_t codec_on_event(pjmedia_event_subscription *esub, + pjmedia_event *event) +{ + codec_port_data_t *port_data = (codec_port_data_t*)esub->user_data; + + if (event->type == PJMEDIA_EVENT_FMT_CHANGED) { + pjmedia_vid_codec *codec = port_data->codec; + pjmedia_vid_codec_param codec_param; + pj_status_t status; + + ++event->proc_cnt; + + status = pjmedia_vid_codec_get_param(codec, &codec_param); + if (status != PJ_SUCCESS) + return status; + + status = pjmedia_vid_dev_stream_set_cap( + pjmedia_vid_port_get_stream(port_data->rdr_port), + PJMEDIA_VID_DEV_CAP_FORMAT, + &codec_param.dec_fmt); + if (status != PJ_SUCCESS) + return status; + } + + return PJ_SUCCESS; +} + +static pj_status_t codec_put_frame(pjmedia_port *port, + pjmedia_frame *frame) +{ + codec_port_data_t *port_data = (codec_port_data_t*)port->port_data.pdata; + pj_status_t status; + +#if !BYPASS_CODEC + { + pjmedia_vid_codec *codec = port_data->codec; + pjmedia_frame enc_frame; + + enc_frame.buf = port_data->enc_buf; + enc_frame.size = port_data->enc_buf_size; + + status = pjmedia_vid_codec_encode(codec, frame, enc_frame.size, + &enc_frame); + if (status != PJ_SUCCESS) goto on_error; + +#if !BYPASS_PACKETIZER + if (enc_frame.size) { + unsigned pos = 0; + pj_bool_t packetized = PJ_FALSE; + unsigned unpack_pos = 0; + + while (pos < enc_frame.size) { + pj_uint8_t *payload; + pj_size_t payload_len; + + status = pjmedia_vid_codec_packetize( + codec, + (pj_uint8_t*)enc_frame.buf, + enc_frame.size, &pos, + (const pj_uint8_t**)&payload, + &payload_len); + if (status == PJ_ENOTSUP) + break; + if (status != PJ_SUCCESS) + goto on_error; + + status = pjmedia_vid_codec_unpacketize( + codec, payload, payload_len, + port_data->pack_buf, + port_data->pack_buf_size, + &unpack_pos); + if (status != PJ_SUCCESS) + goto on_error; + + // what happen if the bitstream is broken? + //if (i++ != 1) unpack_pos -= 10; + + packetized = PJ_TRUE; + } + + if (packetized) { + enc_frame.buf = port_data->pack_buf; + enc_frame.size = unpack_pos; + } + } +#endif + + status = pjmedia_vid_codec_decode(codec, &enc_frame, + frame->size, frame); + if (status != PJ_SUCCESS) goto on_error; + } +#endif + + status = pjmedia_port_put_frame( + pjmedia_vid_port_get_passive_port(port_data->rdr_port), + frame); + if (status != PJ_SUCCESS) goto on_error; + + return PJ_SUCCESS; + +on_error: + pj_perror(3, THIS_FILE, status, "codec_put_frame() error"); + return status; +} + +static const char* dump_codec_info(const pjmedia_vid_codec_info *info) +{ + static char str[80]; + unsigned i; + char *p = str; + + /* Raw format ids */ + for (i=0; (i<info->dec_fmt_id_cnt) && (p-str+5<sizeof(str)); ++i) { + pj_memcpy(p, &info->dec_fmt_id[i], 4); + p += 4; + *p++ = ' '; + } + *p = '\0'; + + return str; +} + +static int enum_codecs() +{ + unsigned i, cnt; + pjmedia_vid_codec_info info[PJMEDIA_CODEC_MGR_MAX_CODECS]; + pj_status_t status; + + PJ_LOG(3, (THIS_FILE, " codec enums")); + cnt = PJ_ARRAY_SIZE(info); + status = pjmedia_vid_codec_mgr_enum_codecs(NULL, &cnt, info, NULL); + if (status != PJ_SUCCESS) + return 100; + + for (i = 0; i < cnt; ++i) { + PJ_LOG(3, (THIS_FILE, " %-16.*s %c%c %s", + info[i].encoding_name.slen, info[i].encoding_name.ptr, + (info[i].dir & PJMEDIA_DIR_ENCODING? 'E' : ' '), + (info[i].dir & PJMEDIA_DIR_DECODING? 'D' : ' '), + dump_codec_info(&info[i]))); + } + + return PJ_SUCCESS; +} + +static int encode_decode_test(pj_pool_t *pool, const char *codec_id) +{ + const pj_str_t port_name = {"codec", 5}; + + pjmedia_vid_codec *codec=NULL; + pjmedia_port codec_port; + codec_port_data_t codec_port_data; + pjmedia_vid_codec_param codec_param; + const pjmedia_vid_codec_info *codec_info; + + pjmedia_vid_dev_index cap_idx, rdr_idx; + pjmedia_vid_port *capture=NULL, *renderer=NULL; + pjmedia_vid_port_param vport_param; + pjmedia_video_format_detail *vfd; + pjmedia_event_subscription esub; + pj_status_t status; + int rc = 0; + + PJ_LOG(3, (THIS_FILE, " encode decode test")); + + /* Lookup codec */ + { + pj_str_t codec_id_st; + unsigned info_cnt = 1; + + /* Lookup codec */ + pj_cstr(&codec_id_st, codec_id); + status = pjmedia_vid_codec_mgr_find_codecs_by_id(NULL, &codec_id_st, + &info_cnt, + &codec_info, NULL); + if (status != PJ_SUCCESS) { + rc = 205; goto on_return; + } + } + + +#if CAPTURE_DEV == -1 + /* Lookup colorbar source */ + status = pjmedia_vid_dev_lookup("Colorbar", "Colorbar generator", &cap_idx); + if (status != PJ_SUCCESS) { + rc = 206; goto on_return; + } +#elif CAPTURE_DEV == -2 + /* Lookup any first non-colorbar source */ + { + unsigned i, cnt; + pjmedia_vid_dev_info info; + + cap_idx = -1; + cnt = pjmedia_vid_dev_count(); + for (i = 0; i < cnt; ++i) { + status = pjmedia_vid_dev_get_info(i, &info); + if (status != PJ_SUCCESS) { + rc = 206; goto on_return; + } + if (info.dir & PJMEDIA_DIR_CAPTURE && + pj_ansi_stricmp(info.driver, "Colorbar")) + { + cap_idx = i; + break; + } + } + + if (cap_idx == -1) { + status = PJ_ENOTFOUND; + rc = 206; goto on_return; + } + } +#else + cap_idx = CAPTURE_DEV; +#endif + + /* Lookup SDL renderer */ + status = pjmedia_vid_dev_lookup("SDL", "SDL renderer", &rdr_idx); + if (status != PJ_SUCCESS) { + rc = 207; goto on_return; + } + + /* Prepare codec */ + { + pj_str_t codec_id_st; + unsigned info_cnt = 1; + const pjmedia_vid_codec_info *codec_info; + + /* Lookup codec */ + pj_cstr(&codec_id_st, codec_id); + status = pjmedia_vid_codec_mgr_find_codecs_by_id(NULL, &codec_id_st, + &info_cnt, + &codec_info, NULL); + if (status != PJ_SUCCESS) { + rc = 245; goto on_return; + } + status = pjmedia_vid_codec_mgr_get_default_param(NULL, codec_info, + &codec_param); + if (status != PJ_SUCCESS) { + rc = 246; goto on_return; + } + +#if !BYPASS_CODEC + + /* Open codec */ + status = pjmedia_vid_codec_mgr_alloc_codec(NULL, codec_info, + &codec); + if (status != PJ_SUCCESS) { + rc = 250; goto on_return; + } + + status = pjmedia_vid_codec_init(codec, pool); + if (status != PJ_SUCCESS) { + rc = 251; goto on_return; + } + + status = pjmedia_vid_codec_open(codec, &codec_param); + if (status != PJ_SUCCESS) { + rc = 252; goto on_return; + } + + /* After opened, codec will update the param, let's sync encoder & + * decoder format detail. + */ + codec_param.dec_fmt.det = codec_param.enc_fmt.det; + + /* Subscribe to codec events */ + pjmedia_event_subscription_init(&esub, &codec_on_event, + &codec_port_data); + pjmedia_event_subscribe(&codec->epub, &esub); +#endif /* !BYPASS_CODEC */ + } + + pjmedia_vid_port_param_default(&vport_param); + + /* Create capture, set it to active (master) */ + status = pjmedia_vid_dev_default_param(pool, cap_idx, + &vport_param.vidparam); + if (status != PJ_SUCCESS) { + rc = 220; goto on_return; + } + pjmedia_format_copy(&vport_param.vidparam.fmt, &codec_param.dec_fmt); + vport_param.vidparam.dir = PJMEDIA_DIR_CAPTURE; + vport_param.active = PJ_TRUE; + + if (vport_param.vidparam.fmt.detail_type != PJMEDIA_FORMAT_DETAIL_VIDEO) { + rc = 221; goto on_return; + } + + vfd = pjmedia_format_get_video_format_detail(&vport_param.vidparam.fmt, + PJ_TRUE); + if (vfd == NULL) { + rc = 225; goto on_return; + } + + status = pjmedia_vid_port_create(pool, &vport_param, &capture); + if (status != PJ_SUCCESS) { + rc = 226; goto on_return; + } + + /* Create renderer, set it to passive (slave) */ + vport_param.active = PJ_FALSE; + vport_param.vidparam.dir = PJMEDIA_DIR_RENDER; + vport_param.vidparam.rend_id = rdr_idx; + vport_param.vidparam.disp_size = vfd->size; + + status = pjmedia_vid_port_create(pool, &vport_param, &renderer); + if (status != PJ_SUCCESS) { + rc = 230; goto on_return; + } + + /* Init codec port */ + pj_bzero(&codec_port, sizeof(codec_port)); + status = pjmedia_port_info_init2(&codec_port.info, &port_name, 0x1234, + PJMEDIA_DIR_ENCODING, + &codec_param.dec_fmt); + if (status != PJ_SUCCESS) { + rc = 260; goto on_return; + } + + codec_port_data.codec = codec; + codec_port_data.rdr_port = renderer; + codec_port_data.enc_buf_size = codec_param.dec_fmt.det.vid.size.w * + codec_param.dec_fmt.det.vid.size.h * 4; + codec_port_data.enc_buf = pj_pool_alloc(pool, + codec_port_data.enc_buf_size); + codec_port_data.pack_buf_size = codec_port_data.enc_buf_size; + codec_port_data.pack_buf = pj_pool_alloc(pool, + codec_port_data.pack_buf_size); + + codec_port.put_frame = &codec_put_frame; + codec_port.port_data.pdata = &codec_port_data; + + /* Connect capture to codec port */ + status = pjmedia_vid_port_connect(capture, + &codec_port, + PJ_FALSE); + if (status != PJ_SUCCESS) { + rc = 270; goto on_return; + } + +#if BYPASS_CODEC + PJ_LOG(3, (THIS_FILE, " starting loopback test: %c%c%c%c %dx%d", + ((codec_param.dec_fmt.id & 0x000000FF) >> 0), + ((codec_param.dec_fmt.id & 0x0000FF00) >> 8), + ((codec_param.dec_fmt.id & 0x00FF0000) >> 16), + ((codec_param.dec_fmt.id & 0xFF000000) >> 24), + codec_param.dec_fmt.det.vid.size.w, + codec_param.dec_fmt.det.vid.size.h + )); +#else + PJ_LOG(3, (THIS_FILE, " starting codec test: %c%c%c%c<->%.*s %dx%d", + ((codec_param.dec_fmt.id & 0x000000FF) >> 0), + ((codec_param.dec_fmt.id & 0x0000FF00) >> 8), + ((codec_param.dec_fmt.id & 0x00FF0000) >> 16), + ((codec_param.dec_fmt.id & 0xFF000000) >> 24), + codec_info->encoding_name.slen, + codec_info->encoding_name.ptr, + codec_param.dec_fmt.det.vid.size.w, + codec_param.dec_fmt.det.vid.size.h + )); +#endif + + /* Start streaming.. */ + status = pjmedia_vid_port_start(renderer); + if (status != PJ_SUCCESS) { + rc = 275; goto on_return; + } + status = pjmedia_vid_port_start(capture); + if (status != PJ_SUCCESS) { + rc = 280; goto on_return; + } + + /* Sleep while the video is being displayed... */ + pj_thread_sleep(10000); + +on_return: + if (status != PJ_SUCCESS) { + PJ_PERROR(3, (THIS_FILE, status, " error")); + } + if (capture) { + pjmedia_vid_port_stop(capture); + pjmedia_vid_port_destroy(capture); + } + if (renderer) { + pjmedia_vid_port_stop(renderer); + pjmedia_vid_port_destroy(renderer); + } + if (codec) { + pjmedia_vid_codec_close(codec); + pjmedia_vid_codec_mgr_dealloc_codec(NULL, codec); + } + + return rc; +} + +int vid_codec_test(void) +{ + pj_pool_t *pool; + int rc = 0; + pj_status_t status; + int orig_log_level; + + orig_log_level = pj_log_get_level(); + pj_log_set_level(6); + + PJ_LOG(3, (THIS_FILE, "Performing video codec tests..")); + + pool = pj_pool_create(mem, "Vid codec test", 256, 256, 0); + + status = pjmedia_vid_dev_subsys_init(mem); + if (status != PJ_SUCCESS) + return -10; + + status = pjmedia_codec_ffmpeg_init(NULL, mem); + if (status != PJ_SUCCESS) + return -20; + + rc = enum_codecs(); + if (rc != 0) + goto on_return; + + rc = encode_decode_test(pool, "h263-1998"); + if (rc != 0) + goto on_return; + +on_return: + pjmedia_codec_ffmpeg_deinit(); + pjmedia_vid_dev_subsys_shutdown(); + pj_pool_release(pool); + pj_log_set_level(orig_log_level); + + return rc; +} + + diff --git a/pjmedia/src/test/vid_dev_test.c b/pjmedia/src/test/vid_dev_test.c new file mode 100644 index 00000000..36966047 --- /dev/null +++ b/pjmedia/src/test/vid_dev_test.c @@ -0,0 +1,292 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "test.h" +#include <pjmedia-audiodev/audiodev.h> +#include <pjmedia-codec/ffmpeg_codecs.h> +#include <pjmedia/vid_codec.h> +#include <pjmedia_videodev.h> + +#define THIS_FILE "vid_dev_test.c" +#define LOOP_DURATION 10 + +static pj_bool_t is_quitting = PJ_FALSE; + +static const char *vid_dir_name(pjmedia_dir dir) +{ + switch (dir) { + case PJMEDIA_DIR_CAPTURE: + return "capture"; + case PJMEDIA_DIR_RENDER: + return "render"; + case PJMEDIA_DIR_CAPTURE_RENDER: + return "capture & render"; + default: + return "unknown"; + } +} + +static int enum_devs(void) +{ + unsigned i, dev_cnt; + pj_status_t status; + + PJ_LOG(3, (THIS_FILE, " Enum video devices:")); + dev_cnt = pjmedia_vid_dev_count(); + for (i = 0; i < dev_cnt; ++i) { + pjmedia_vid_dev_info di; + status = pjmedia_vid_dev_get_info(i, &di); + if (status == PJ_SUCCESS) { + unsigned j; + + PJ_LOG(3, (THIS_FILE, " %3d: %s (%s) - %s", i, di.name, di.driver, + vid_dir_name(di.dir))); + + PJ_LOG(3,(THIS_FILE, " Supported formats:")); + for (j=0; j<di.fmt_cnt; ++j) { + const pjmedia_video_format_info *vfi; + + vfi = pjmedia_get_video_format_info(NULL, di.fmt[j].id); + PJ_LOG(3,(THIS_FILE, " %s", + (vfi ? vfi->name : "unknown"))); + } + } + } + + return PJ_SUCCESS; +} + +static pj_status_t vid_event_cb(pjmedia_event_subscription *esub, + pjmedia_event *event) +{ + if (event->type == PJMEDIA_EVENT_WND_CLOSED) + is_quitting = PJ_TRUE; + + return PJ_SUCCESS; +} + +static int capture_render_loopback(int cap_dev_id, int rend_dev_id, + const pjmedia_format *fmt) +{ + pj_pool_t *pool; + pjmedia_vid_port *capture=NULL, *renderer=NULL; + pjmedia_vid_dev_info cdi, rdi; + pjmedia_vid_port_param param; + pjmedia_video_format_detail *vfd; + pjmedia_event_subscription esub; + pj_status_t status; + int rc = 0, i; + + pool = pj_pool_create(mem, "vidloop", 1000, 1000, NULL); + + status = pjmedia_vid_dev_get_info(cap_dev_id, &cdi); + if (status != PJ_SUCCESS) + goto on_return; + + status = pjmedia_vid_dev_get_info(rend_dev_id, &rdi); + if (status != PJ_SUCCESS) + goto on_return; + + PJ_LOG(3,(THIS_FILE, + " %s (%s) ===> %s (%s)\t%s\t%dx%d\t@%d:%d fps", + cdi.name, cdi.driver, rdi.name, rdi.driver, + pjmedia_get_video_format_info(NULL, fmt->id)->name, + fmt->det.vid.size.w, fmt->det.vid.size.h, + fmt->det.vid.fps.num, fmt->det.vid.fps.denum)); + + pjmedia_vid_port_param_default(¶m); + + /* Create capture, set it to active (master) */ + status = pjmedia_vid_dev_default_param(pool, cap_dev_id, + ¶m.vidparam); + if (status != PJ_SUCCESS) { + rc = 100; goto on_return; + } + param.vidparam.dir = PJMEDIA_DIR_CAPTURE; + param.vidparam.fmt = *fmt; + param.active = PJ_TRUE; + + if (param.vidparam.fmt.detail_type != PJMEDIA_FORMAT_DETAIL_VIDEO) { + rc = 103; goto on_return; + } + + vfd = pjmedia_format_get_video_format_detail(¶m.vidparam.fmt, PJ_TRUE); + if (vfd == NULL) { + rc = 105; goto on_return; + } + + status = pjmedia_vid_port_create(pool, ¶m, &capture); + if (status != PJ_SUCCESS) { + rc = 110; goto on_return; + } + + /* Create renderer, set it to passive (slave) */ + status = pjmedia_vid_dev_default_param(pool, rend_dev_id, + ¶m.vidparam); + if (status != PJ_SUCCESS) { + rc = 120; goto on_return; + } + + param.active = PJ_FALSE; + param.vidparam.dir = PJMEDIA_DIR_RENDER; + param.vidparam.rend_id = rend_dev_id; + param.vidparam.fmt = *fmt; + param.vidparam.disp_size = vfd->size; + + status = pjmedia_vid_port_create(pool, ¶m, &renderer); + if (status != PJ_SUCCESS) { + rc = 130; goto on_return; + } + + /* Set event handler */ + pjmedia_event_subscription_init(&esub, &vid_event_cb, NULL); + pjmedia_event_subscribe( + pjmedia_vid_port_get_event_publisher(renderer), + &esub); + + /* Connect capture to renderer */ + status = pjmedia_vid_port_connect( + capture, + pjmedia_vid_port_get_passive_port(renderer), + PJ_FALSE); + if (status != PJ_SUCCESS) { + rc = 140; goto on_return; + } + + /* Start streaming.. */ + status = pjmedia_vid_port_start(renderer); + if (status != PJ_SUCCESS) { + rc = 150; goto on_return; + } + status = pjmedia_vid_port_start(capture); + if (status != PJ_SUCCESS) { + rc = 160; goto on_return; + } + + /* Sleep while the webcam is being displayed... */ + for (i = 0; i < LOOP_DURATION*10 && (!is_quitting); i++) { + pj_thread_sleep(100); + } + +on_return: + if (status != PJ_SUCCESS) + PJ_PERROR(3, (THIS_FILE, status, " error")); + + if (capture) + pjmedia_vid_port_destroy(capture); + if (renderer) + pjmedia_vid_port_destroy(renderer); + + pj_pool_release(pool); + return rc; +} + +static int loopback_test(void) +{ + unsigned count, i; + pjmedia_format_id test_fmts[] = { + PJMEDIA_FORMAT_YUY2 + }; + pjmedia_rect_size test_sizes[] = { + {176,144}, /* QCIF */ + {352,288}, /* CIF */ + {704,576} /* 4CIF */ + }; + pjmedia_ratio test_fpses[] = { + {25, 1}, + {30, 1}, + }; + pj_status_t status; + + PJ_LOG(3, (THIS_FILE, " Loopback tests (prepare you webcams):")); + + count = pjmedia_vid_dev_count(); + for (i=0; i<count; ++i) { + pjmedia_vid_dev_info cdi; + unsigned j; + + status = pjmedia_vid_dev_get_info(i, &cdi); + if (status != PJ_SUCCESS) + return -300; + + /* Only interested with capture device */ + if ((cdi.dir & PJMEDIA_DIR_CAPTURE) == 0) + continue; + + for (j=i+1; j<count; ++j) { + pjmedia_vid_dev_info rdi; + unsigned k; + + status = pjmedia_vid_dev_get_info(j, &rdi); + if (status != PJ_SUCCESS) + return -310; + + /* Only interested with render device */ + if ((rdi.dir & PJMEDIA_DIR_RENDER) == 0) + continue; + + /* Test with the format, size, and fps combinations */ + for (k=0; k<PJ_ARRAY_SIZE(test_fmts); ++k) { + unsigned l; + + for (l=0; l<PJ_ARRAY_SIZE(test_sizes); ++l) { + unsigned m; + + for (m=0; m<PJ_ARRAY_SIZE(test_fpses); ++m) { + pjmedia_format fmt; + + pjmedia_format_init_video(&fmt, test_fmts[k], + test_sizes[l].w, + test_sizes[l].h, + test_fpses[m].num, + test_fpses[m].denum); + + capture_render_loopback(i, j, &fmt); + } + } + } /* k */ + + } + } + + return 0; +} + +int vid_dev_test(void) +{ + int rc = 0; + pj_status_t status; + + status = pjmedia_vid_dev_subsys_init(mem); + if (status != PJ_SUCCESS) + return -10; + + rc = enum_devs(); + if (rc != 0) + goto on_return; + + rc = loopback_test(); + if (rc != 0) + goto on_return; + +on_return: + pjmedia_vid_dev_subsys_shutdown(); + + return rc; +} diff --git a/pjmedia/src/test/vid_port_test.c b/pjmedia/src/test/vid_port_test.c new file mode 100644 index 00000000..cd7acd8d --- /dev/null +++ b/pjmedia/src/test/vid_port_test.c @@ -0,0 +1,241 @@ +/* $Id$ */ +/* + * Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "test.h" +#include <pjmedia-audiodev/audiodev.h> +#include <pjmedia-codec/ffmpeg_codecs.h> +#include <pjmedia/vid_codec.h> +#include <pjmedia_videodev.h> + +#define THIS_FILE "vid_dev_test.c" +#define LOOP_DURATION 6 + +static pj_bool_t is_quitting = PJ_FALSE; + +static pj_status_t vid_event_cb(pjmedia_event_subscription *esub, + pjmedia_event *event) +{ + if (event->type == PJMEDIA_EVENT_WND_CLOSED) + is_quitting = PJ_TRUE; + + return PJ_SUCCESS; +} + +static int capture_render_loopback(pj_bool_t active, + int cap_dev_id, int rend_dev_id, + const pjmedia_format *fmt) +{ + pj_pool_t *pool; + pjmedia_vid_port *capture=NULL, *renderer=NULL; + pjmedia_vid_dev_info cdi, rdi; + pjmedia_vid_port_param param; + pjmedia_video_format_detail *vfd; + pjmedia_vid_dev_cb cb; + pjmedia_event_subscription esub; + pj_status_t status; + int rc = 0, i; + + pool = pj_pool_create(mem, "vidportloop", 1000, 1000, NULL); + + status = pjmedia_vid_dev_get_info(cap_dev_id, &cdi); + if (status != PJ_SUCCESS) + goto on_return; + + status = pjmedia_vid_dev_get_info(rend_dev_id, &rdi); + if (status != PJ_SUCCESS) + goto on_return; + + PJ_LOG(3,(THIS_FILE, + " %s (%s) ===> %s (%s)\t%s\t%dx%d\t@%d:%d fps", + cdi.name, cdi.driver, rdi.name, rdi.driver, + pjmedia_get_video_format_info(NULL, fmt->id)->name, + fmt->det.vid.size.w, fmt->det.vid.size.h, + fmt->det.vid.fps.num, fmt->det.vid.fps.denum)); + + pjmedia_vid_port_param_default(¶m); + + /* Create capture, set it to active (master) */ + status = pjmedia_vid_dev_default_param(pool, cap_dev_id, + ¶m.vidparam); + if (status != PJ_SUCCESS) { + rc = 100; goto on_return; + } + param.vidparam.dir = PJMEDIA_DIR_CAPTURE; + param.vidparam.fmt = *fmt; + param.active = (active? PJ_TRUE: PJ_FALSE); + + if (param.vidparam.fmt.detail_type != PJMEDIA_FORMAT_DETAIL_VIDEO) { + rc = 103; goto on_return; + } + + vfd = pjmedia_format_get_video_format_detail(¶m.vidparam.fmt, PJ_TRUE); + if (vfd == NULL) { + rc = 105; goto on_return; + } + + status = pjmedia_vid_port_create(pool, ¶m, &capture); + if (status != PJ_SUCCESS) { + rc = 110; goto on_return; + } + + /* Create renderer, set it to passive (slave) */ + status = pjmedia_vid_dev_default_param(pool, rend_dev_id, + ¶m.vidparam); + if (status != PJ_SUCCESS) { + rc = 120; goto on_return; + } + + param.active = (active? PJ_FALSE: PJ_TRUE); + param.vidparam.dir = PJMEDIA_DIR_RENDER; + param.vidparam.rend_id = rend_dev_id; + param.vidparam.fmt = *fmt; + param.vidparam.disp_size = vfd->size; + + status = pjmedia_vid_port_create(pool, ¶m, &renderer); + if (status != PJ_SUCCESS) { + rc = 130; goto on_return; + } + + /* Set event handler */ + pjmedia_event_subscription_init(&esub, &vid_event_cb, NULL); + pjmedia_event_subscribe( + pjmedia_vid_port_get_event_publisher(renderer), + &esub); + + /* Connect capture to renderer */ + status = pjmedia_vid_port_connect( + (active? capture: renderer), + pjmedia_vid_port_get_passive_port(active? renderer: capture), + PJ_FALSE); + if (status != PJ_SUCCESS) { + rc = 140; goto on_return; + } + + /* Start streaming.. */ + status = pjmedia_vid_port_start(renderer); + if (status != PJ_SUCCESS) { + rc = 150; goto on_return; + } + status = pjmedia_vid_port_start(capture); + if (status != PJ_SUCCESS) { + rc = 160; goto on_return; + } + + /* Sleep while the webcam is being displayed... */ + for (i = 0; i < LOOP_DURATION*10 && (!is_quitting); i++) { + pj_thread_sleep(100); + } + +on_return: + if (status != PJ_SUCCESS) + PJ_PERROR(3, (THIS_FILE, status, " error")); + + if (capture) + pjmedia_vid_port_destroy(capture); + if (renderer) + pjmedia_vid_port_destroy(renderer); + + pj_pool_release(pool); + return rc; +} + +static int find_device(pjmedia_dir dir, + pj_bool_t has_callback) +{ + unsigned i, count = pjmedia_vid_dev_count(); + + for (i = 0; i < count; ++i) { + pjmedia_vid_dev_info cdi; + + if (pjmedia_vid_dev_get_info(i, &cdi) != PJ_SUCCESS) + continue; + if ((cdi.dir & dir) != 0 && cdi.has_callback == has_callback) + return i; + } + + return -999; +} + +static int vidport_test(void) +{ + int i, j, k, l; + int cap_id, rend_id; + pjmedia_format_id test_fmts[] = { + PJMEDIA_FORMAT_RGBA, + PJMEDIA_FORMAT_I420 + }; + pj_status_t status; + + PJ_LOG(3, (THIS_FILE, " Video port tests:")); + + /* Capturer's role: active/passive. */ + for (i = 1; i >= 0; i--) { + /* Capturer's device has_callback: TRUE/FALSE. */ + for (j = 1; j >= 0; j--) { + cap_id = find_device(PJMEDIA_DIR_CAPTURE, j); + if (cap_id < 0) + continue; + + /* Renderer's device has callback: TRUE/FALSE. */ + for (k = 1; k >= 0; k--) { + rend_id = find_device(PJMEDIA_DIR_RENDER, k); + if (rend_id < 0) + continue; + + /* Check various formats to test format conversion. */ + for (l = 0; l < PJ_ARRAY_SIZE(test_fmts); ++l) { + pjmedia_format fmt; + + PJ_LOG(3,(THIS_FILE, + "capturer %s (stream: %s) ===> " + "renderer %s (stream: %s)", + (i? "active": "passive"), + (j? "active": "passive"), + (i? "passive": "active"), + (k? "active": "passive"))); + + pjmedia_format_init_video(&fmt, test_fmts[l], + 640, 480, 25, 1); + capture_render_loopback(i, cap_id, rend_id, &fmt); + } + } + } + } + + return 0; +} + +int vid_port_test(void) +{ + int rc = 0; + pj_status_t status; + + status = pjmedia_vid_dev_subsys_init(mem); + if (status != PJ_SUCCESS) + return -10; + + rc = vidport_test(); + if (rc != 0) + goto on_return; + +on_return: + pjmedia_vid_dev_subsys_shutdown(); + + return rc; +} |