diff options
author | Benny Prijono <bennylp@teluu.com> | 2014-04-10 10:01:07 +0000 |
---|---|---|
committer | Benny Prijono <bennylp@teluu.com> | 2014-04-10 10:01:07 +0000 |
commit | 0aa83d8efcf477675669569b037f291464c4f146 (patch) | |
tree | 063ff3ade6100cb7e2a0693b153045027422fc53 /pjmedia/src/pjmedia-codec | |
parent | e7e444203e67583806aee77c0fc7d94115094efe (diff) |
Re #1758: Initial implementation of OpenH264 wrapper. Supports:
- library detection via autoconf
- CBP
- packetization modes: 0, 1
- key frame request and indication
- obey remote's fmtp
Also added video codec test in samples (similar to the one in pjmedia test though).
And there are some fixes here and there too (e.g. in vid_codec_util.c).
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@4815 74dad513-b988-da41-8d7b-12977e46ad98
Diffstat (limited to 'pjmedia/src/pjmedia-codec')
-rw-r--r-- | pjmedia/src/pjmedia-codec/openh264.cpp | 1080 |
1 files changed, 1080 insertions, 0 deletions
diff --git a/pjmedia/src/pjmedia-codec/openh264.cpp b/pjmedia/src/pjmedia-codec/openh264.cpp new file mode 100644 index 00000000..2f91ceb5 --- /dev/null +++ b/pjmedia/src/pjmedia-codec/openh264.cpp @@ -0,0 +1,1080 @@ +/* $Id$ */ +/* + * Copyright (C)2014 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/openh264.h> +#include <pjmedia-codec/h264_packetizer.h> +#include <pjmedia/vid_codec_util.h> +#include <pjmedia/errno.h> +#include <pj/log.h> + +#if defined(PJMEDIA_HAS_OPENH264_CODEC) && \ + PJMEDIA_HAS_OPENH264_CODEC != 0 && \ + defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0) + +/* OpenH264: */ +#include <wels/codec_api.h> +#include <wels/codec_app_def.h> + +/* + * Constants + */ +#define THIS_FILE "openh264.cpp" +#define DEFAULT_WIDTH 720 +#define DEFAULT_HEIGHT 480 +#define DEFAULT_FPS 15 +#define DEFAULT_AVG_BITRATE 256000 +#define DEFAULT_MAX_BITRATE 256000 + +#define MAX_RX_WIDTH 1200 +#define MAX_RX_HEIGHT 800 + + +/* + * Factory operations. + */ +static pj_status_t oh264_test_alloc(pjmedia_vid_codec_factory *factory, + const pjmedia_vid_codec_info *info ); +static pj_status_t oh264_default_attr(pjmedia_vid_codec_factory *factory, + const pjmedia_vid_codec_info *info, + pjmedia_vid_codec_param *attr ); +static pj_status_t oh264_enum_info(pjmedia_vid_codec_factory *factory, + unsigned *count, + pjmedia_vid_codec_info codecs[]); +static pj_status_t oh264_alloc_codec(pjmedia_vid_codec_factory *factory, + const pjmedia_vid_codec_info *info, + pjmedia_vid_codec **p_codec); +static pj_status_t oh264_dealloc_codec(pjmedia_vid_codec_factory *factory, + pjmedia_vid_codec *codec ); + + +/* + * Codec operations + */ +static pj_status_t oh264_codec_init(pjmedia_vid_codec *codec, + pj_pool_t *pool ); +static pj_status_t oh264_codec_open(pjmedia_vid_codec *codec, + pjmedia_vid_codec_param *param ); +static pj_status_t oh264_codec_close(pjmedia_vid_codec *codec); +static pj_status_t oh264_codec_modify(pjmedia_vid_codec *codec, + const pjmedia_vid_codec_param *param); +static pj_status_t oh264_codec_get_param(pjmedia_vid_codec *codec, + pjmedia_vid_codec_param *param); +static pj_status_t oh264_codec_encode_begin(pjmedia_vid_codec *codec, + const pjmedia_vid_encode_opt *opt, + const pjmedia_frame *input, + unsigned out_size, + pjmedia_frame *output, + pj_bool_t *has_more); +static pj_status_t oh264_codec_encode_more(pjmedia_vid_codec *codec, + unsigned out_size, + pjmedia_frame *output, + pj_bool_t *has_more); +static pj_status_t oh264_codec_decode(pjmedia_vid_codec *codec, + pj_size_t count, + pjmedia_frame packets[], + unsigned out_size, + pjmedia_frame *output); + +/* Definition for OpenH264 codecs operations. */ +static pjmedia_vid_codec_op oh264_codec_op = +{ + &oh264_codec_init, + &oh264_codec_open, + &oh264_codec_close, + &oh264_codec_modify, + &oh264_codec_get_param, + &oh264_codec_encode_begin, + &oh264_codec_encode_more, + &oh264_codec_decode, + NULL +}; + +/* Definition for OpenH264 codecs factory operations. */ +static pjmedia_vid_codec_factory_op oh264_factory_op = +{ + &oh264_test_alloc, + &oh264_default_attr, + &oh264_enum_info, + &oh264_alloc_codec, + &oh264_dealloc_codec +}; + + +static struct oh264_factory +{ + pjmedia_vid_codec_factory base; + pjmedia_vid_codec_mgr *mgr; + pj_pool_factory *pf; + pj_pool_t *pool; +} oh264_factory; + + +typedef struct oh264_codec_data +{ + pj_pool_t *pool; + pjmedia_vid_codec_param *prm; + pj_bool_t whole; + pjmedia_h264_packetizer *pktz; + + /* Encoder state */ + ISVCEncoder *enc; + SSourcePicture *esrc_pic; + unsigned enc_input_size; + pj_uint8_t *enc_frame_whole; + unsigned enc_frame_size; + unsigned enc_processed; + pj_timestamp ets; + SFrameBSInfo bsi; + int ilayer; + + /* Decoder state */ + ISVCDecoder *dec; + pj_uint8_t *dec_buf; + unsigned dec_buf_size; +} oh264_codec_data; + +struct SLayerPEncCtx +{ + pj_int32_t iDLayerQp; + SSliceConfig sSliceCfg; +}; + +PJ_DEF(pj_status_t) pjmedia_codec_openh264_vid_init(pjmedia_vid_codec_mgr *mgr, + pj_pool_factory *pf) +{ + const pj_str_t h264_name = { (char*)"H264", 4}; + pj_status_t status; + + if (oh264_factory.pool != NULL) { + /* Already initialized. */ + return PJ_SUCCESS; + } + + if (!mgr) mgr = pjmedia_vid_codec_mgr_instance(); + PJ_ASSERT_RETURN(mgr, PJ_EINVAL); + + /* Create OpenH264 codec factory. */ + oh264_factory.base.op = &oh264_factory_op; + oh264_factory.base.factory_data = NULL; + oh264_factory.mgr = mgr; + oh264_factory.pf = pf; + oh264_factory.pool = pj_pool_create(pf, "oh264factory", 256, 256, NULL); + if (!oh264_factory.pool) + return PJ_ENOMEM; + + /* Registering format match for SDP negotiation */ + status = pjmedia_sdp_neg_register_fmt_match_cb( + &h264_name, + &pjmedia_vid_codec_h264_match_sdp); + pj_assert(status == PJ_SUCCESS); + + /* Register codec factory to codec manager. */ + status = pjmedia_vid_codec_mgr_register_factory(mgr, + &oh264_factory.base); + + PJ_LOG(4,(THIS_FILE, "OpenH264 codec initialized")); + + /* Done. */ + return PJ_SUCCESS; + +on_error: + pj_pool_release(oh264_factory.pool); + oh264_factory.pool = NULL; + return status; +} + +/* + * Unregister OpenH264 codecs factory from pjmedia endpoint. + */ +PJ_DEF(pj_status_t) pjmedia_codec_openh264_vid_deinit(void) +{ + pj_status_t status = PJ_SUCCESS; + + if (oh264_factory.pool == NULL) { + /* Already deinitialized */ + return PJ_SUCCESS; + } + + /* Unregister OpenH264 codecs factory. */ + status = pjmedia_vid_codec_mgr_unregister_factory(oh264_factory.mgr, + &oh264_factory.base); + + /* Destroy pool. */ + pj_pool_release(oh264_factory.pool); + oh264_factory.pool = NULL; + + return status; +} + +static pj_status_t oh264_test_alloc(pjmedia_vid_codec_factory *factory, + const pjmedia_vid_codec_info *info ) +{ + PJ_ASSERT_RETURN(factory == &oh264_factory.base, PJ_EINVAL); + + if (info->fmt_id == PJMEDIA_FORMAT_H264 && + info->pt != 0) + { + return PJ_SUCCESS; + } + + return PJMEDIA_CODEC_EUNSUP; +} + +static pj_status_t oh264_default_attr(pjmedia_vid_codec_factory *factory, + const pjmedia_vid_codec_info *info, + pjmedia_vid_codec_param *attr ) +{ + PJ_ASSERT_RETURN(factory == &oh264_factory.base, PJ_EINVAL); + PJ_ASSERT_RETURN(info && attr, PJ_EINVAL); + + pj_bzero(attr, sizeof(pjmedia_vid_codec_param)); + + attr->dir = PJMEDIA_DIR_ENCODING_DECODING; + attr->packing = PJMEDIA_VID_PACKING_PACKETS; + + /* Encoded format */ + pjmedia_format_init_video(&attr->enc_fmt, PJMEDIA_FORMAT_H264, + DEFAULT_WIDTH, DEFAULT_HEIGHT, + DEFAULT_FPS, 1); + + /* Decoded format */ + pjmedia_format_init_video(&attr->dec_fmt, PJMEDIA_FORMAT_I420, + DEFAULT_WIDTH, DEFAULT_HEIGHT, + DEFAULT_FPS, 1); + + /* Decoding fmtp */ + attr->dec_fmtp.cnt = 2; + attr->dec_fmtp.param[0].name = pj_str((char*)"profile-level-id"); + attr->dec_fmtp.param[0].val = pj_str((char*)"42e01e"); + attr->dec_fmtp.param[1].name = pj_str((char*)" packetization-mode"); + attr->dec_fmtp.param[1].val = pj_str((char*)"1"); + + /* Bitrate */ + attr->enc_fmt.det.vid.avg_bps = DEFAULT_AVG_BITRATE; + attr->enc_fmt.det.vid.max_bps = DEFAULT_MAX_BITRATE; + + /* Encoding MTU */ + attr->enc_mtu = PJMEDIA_MAX_VID_PAYLOAD_SIZE; + + return PJ_SUCCESS; +} + +static pj_status_t oh264_enum_info(pjmedia_vid_codec_factory *factory, + unsigned *count, + pjmedia_vid_codec_info info[]) +{ + PJ_ASSERT_RETURN(info && *count > 0, PJ_EINVAL); + PJ_ASSERT_RETURN(factory == &oh264_factory.base, PJ_EINVAL); + + *count = 1; + info->fmt_id = PJMEDIA_FORMAT_H264; + info->pt = PJMEDIA_RTP_PT_H264; + info->encoding_name = pj_str((char*)"H264"); + info->encoding_desc = pj_str((char*)"OpenH264 codec"); + info->clock_rate = 90000; + info->dir = PJMEDIA_DIR_ENCODING_DECODING; + info->dec_fmt_id_cnt = 1; + info->dec_fmt_id[0] = PJMEDIA_FORMAT_I420; + info->packings = PJMEDIA_VID_PACKING_PACKETS | + PJMEDIA_VID_PACKING_WHOLE; + info->fps_cnt = 3; + info->fps[0].num = 15; + info->fps[0].denum = 1; + info->fps[1].num = 25; + info->fps[1].denum = 1; + info->fps[2].num = 30; + info->fps[2].denum = 1; + + return PJ_SUCCESS; + +} + +static pj_status_t oh264_alloc_codec(pjmedia_vid_codec_factory *factory, + const pjmedia_vid_codec_info *info, + pjmedia_vid_codec **p_codec) +{ + pj_pool_t *pool; + pjmedia_vid_codec *codec; + oh264_codec_data *oh264_data; + int rc; + + PJ_ASSERT_RETURN(factory == &oh264_factory.base && info && p_codec, + PJ_EINVAL); + + *p_codec = NULL; + + pool = pj_pool_create(oh264_factory.pf, "oh264%p", 512, 512, NULL); + if (!pool) + return PJ_ENOMEM; + + /* codec instance */ + codec = PJ_POOL_ZALLOC_T(pool, pjmedia_vid_codec); + codec->factory = factory; + codec->op = &oh264_codec_op; + + /* codec data */ + oh264_data = PJ_POOL_ZALLOC_T(pool, oh264_codec_data); + oh264_data->pool = pool; + codec->codec_data = oh264_data; + + /* encoder allocation */ + rc = CreateSVCEncoder(&oh264_data->enc); + if (rc != 0) + goto on_error; + + oh264_data->esrc_pic = PJ_POOL_ZALLOC_T(pool, SSourcePicture); + + /* decoder allocation */ + rc = CreateDecoder(&oh264_data->dec); + if (rc != 0) + goto on_error; + + *p_codec = codec; + return PJ_SUCCESS; + +on_error: + oh264_dealloc_codec(factory, codec); + return PJMEDIA_CODEC_EFAILED; +} + +static pj_status_t oh264_dealloc_codec(pjmedia_vid_codec_factory *factory, + pjmedia_vid_codec *codec ) +{ + oh264_codec_data *oh264_data; + + PJ_ASSERT_RETURN(codec, PJ_EINVAL); + + oh264_data = (oh264_codec_data*) codec->codec_data; + if (oh264_data->enc) { + DestroySVCEncoder(oh264_data->enc); + oh264_data->enc = NULL; + } + if (oh264_data->dec) { + oh264_data->dec->Uninitialize(); + DestroyDecoder(oh264_data->dec); + oh264_data->dec = NULL; + } + pj_pool_release(oh264_data->pool); + return PJ_SUCCESS; +} + +static pj_status_t oh264_codec_init(pjmedia_vid_codec *codec, + pj_pool_t *pool ) +{ + PJ_ASSERT_RETURN(codec && pool, PJ_EINVAL); + PJ_UNUSED_ARG(codec); + PJ_UNUSED_ARG(pool); + return PJ_SUCCESS; +} + +static pj_status_t oh264_codec_open(pjmedia_vid_codec *codec, + pjmedia_vid_codec_param *codec_param ) +{ + oh264_codec_data *oh264_data; + pjmedia_vid_codec_param *param; + pjmedia_h264_packetizer_cfg pktz_cfg; + pjmedia_vid_codec_h264_fmtp h264_fmtp; + SEncParamExt eprm; + SSpatialLayerConfig *elayer = &eprm.sSpatialLayers[0]; + SLayerPEncCtx elayer_ctx; + SDecodingParam sDecParam = {0}; + int rc; + pj_status_t status; + + PJ_ASSERT_RETURN(codec && codec_param, PJ_EINVAL); + + PJ_LOG(5,(THIS_FILE, "Opening codec..")); + + oh264_data = (oh264_codec_data*) codec->codec_data; + oh264_data->prm = pjmedia_vid_codec_param_clone( oh264_data->pool, + codec_param); + param = oh264_data->prm; + + /* Parse remote fmtp */ + pj_bzero(&h264_fmtp, sizeof(h264_fmtp)); + status = pjmedia_vid_codec_h264_parse_fmtp(¶m->enc_fmtp, &h264_fmtp); + if (status != PJ_SUCCESS) + return status; + + /* Apply SDP fmtp to format in codec param */ + if (!param->ignore_fmtp) { + status = pjmedia_vid_codec_h264_apply_fmtp(param); + if (status != PJ_SUCCESS) + return status; + } + + pj_bzero(&pktz_cfg, sizeof(pktz_cfg)); + pktz_cfg.mtu = param->enc_mtu; + /* Packetization mode */ +#if 0 + if (h264_fmtp.packetization_mode == 0) + pktz_cfg.mode = PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL; + else if (h264_fmtp.packetization_mode == 1) + pktz_cfg.mode = PJMEDIA_H264_PACKETIZER_MODE_NON_INTERLEAVED; + else + return PJ_ENOTSUP; +#else + if (h264_fmtp.packetization_mode!= + PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL && + h264_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(oh264_data->pool, &pktz_cfg, + &oh264_data->pktz); + if (status != PJ_SUCCESS) + return status; + + oh264_data->whole = (param->packing == PJMEDIA_VID_PACKING_WHOLE); + + /* + * Encoder + */ + + /* Init encoder parameters */ + pj_bzero(&eprm, sizeof(eprm)); + eprm.iInputCsp = videoFormatI420; + eprm.sSpatialLayers[0].uiProfileIdc = 66; // PRO_BASELINE + eprm.iPicWidth = param->enc_fmt.det.vid.size.w; + eprm.iPicHeight = param->enc_fmt.det.vid.size.h; + eprm.fMaxFrameRate = (param->enc_fmt.det.vid.fps.num * 1.0 / + param->enc_fmt.det.vid.fps.denum); + eprm.uiFrameToBeCoded = -1; + eprm.iTemporalLayerNum = 1; + eprm.uiIntraPeriod = 0; /* I-Frame interval in frames */ + eprm.bEnableSpsPpsIdAddition = (oh264_data->whole? false : true); + eprm.bEnableFrameCroppingFlag = true; + eprm.iLoopFilterDisableIdc = 0; + eprm.iLoopFilterAlphaC0Offset = 0; + eprm.iLoopFilterBetaOffset = 0; + eprm.iMultipleThreadIdc = 1; + eprm.bEnableRc = 1; + eprm.iTargetBitrate = param->enc_fmt.det.vid.avg_bps; + eprm.bEnableFrameSkip = 1; + eprm.bEnableDenoise = 0; + eprm.bEnableSceneChangeDetect = 1; + eprm.bEnableBackgroundDetection = 1; + eprm.bEnableAdaptiveQuant = 1; + eprm.bEnableLongTermReference = 0; + eprm.iLtrMarkPeriod = 30; + eprm.bPrefixNalAddingCtrl = false; + eprm.iSpatialLayerNum = 1; + + pj_bzero(&elayer_ctx, sizeof (SLayerPEncCtx)); + elayer_ctx.iDLayerQp = 24; + elayer_ctx.sSliceCfg.uiSliceMode = (oh264_data->whole ? + SM_SINGLE_SLICE : SM_DYN_SLICE); + elayer_ctx.sSliceCfg.sSliceArgument.uiSliceSizeConstraint = param->enc_mtu; + elayer_ctx.sSliceCfg.sSliceArgument.uiSliceNum = 1; + elayer_ctx.sSliceCfg.sSliceArgument.uiSliceMbNum[0] = 960; + elayer_ctx.sSliceCfg.sSliceArgument.uiSliceMbNum[1] = 0; + elayer_ctx.sSliceCfg.sSliceArgument.uiSliceMbNum[2] = 0; + elayer_ctx.sSliceCfg.sSliceArgument.uiSliceMbNum[3] = 0; + elayer_ctx.sSliceCfg.sSliceArgument.uiSliceMbNum[4] = 0; + elayer_ctx.sSliceCfg.sSliceArgument.uiSliceMbNum[5] = 0; + elayer_ctx.sSliceCfg.sSliceArgument.uiSliceMbNum[6] = 0; + elayer_ctx.sSliceCfg.sSliceArgument.uiSliceMbNum[7] = 0; + + elayer->iVideoWidth = eprm.iPicWidth; + elayer->iVideoHeight = eprm.iPicHeight; + elayer->fFrameRate = eprm.fMaxFrameRate; + elayer->uiProfileIdc = eprm.sSpatialLayers[0].uiProfileIdc; + elayer->iSpatialBitrate = eprm.iTargetBitrate; + elayer->iDLayerQp = elayer_ctx.iDLayerQp; + elayer->sSliceCfg.uiSliceMode = elayer_ctx.sSliceCfg.uiSliceMode; + + memcpy( &elayer->sSliceCfg, + &elayer_ctx.sSliceCfg, + sizeof (SSliceConfig)); + memcpy( &elayer->sSliceCfg.sSliceArgument.uiSliceMbNum[0], + &elayer_ctx.sSliceCfg.sSliceArgument.uiSliceMbNum[0], + sizeof (elayer_ctx.sSliceCfg.sSliceArgument.uiSliceMbNum)); + + /* Init input picture */ + oh264_data->esrc_pic->iColorFormat = videoFormatI420; + oh264_data->esrc_pic->uiTimeStamp = 0; + oh264_data->esrc_pic->iPicWidth = eprm.iPicWidth; + oh264_data->esrc_pic->iPicHeight = eprm.iPicHeight; + oh264_data->esrc_pic->iStride[0] = oh264_data->esrc_pic->iPicWidth; + oh264_data->esrc_pic->iStride[1] = + oh264_data->esrc_pic->iStride[2] = + oh264_data->esrc_pic->iStride[0]>>1; + + oh264_data->enc_input_size = oh264_data->esrc_pic->iPicWidth * + oh264_data->esrc_pic->iPicHeight * 3 >> 1; + + /* Initialize encoder */ + rc = oh264_data->enc->InitializeExt (&eprm); + if (rc != cmResultSuccess) { + PJ_LOG(4,(THIS_FILE, "SVC encoder Initialize failed, rc=%d", rc)); + return PJMEDIA_CODEC_EFAILED; + } + + /* + * Decoder + */ + sDecParam.sVideoProperty.size = sizeof (sDecParam.sVideoProperty); + sDecParam.iOutputColorFormat = videoFormatI420; + sDecParam.uiTargetDqLayer = (pj_uint8_t) - 1; + sDecParam.uiEcActiveFlag = 1; + sDecParam.sVideoProperty.eVideoBsType = VIDEO_BITSTREAM_DEFAULT; + + //TODO: + // Apply "sprop-parameter-sets" here + + rc = CreateDecoder(&oh264_data->dec); + if (rc) { + PJ_LOG(4,(THIS_FILE, "Unable to create OpenH264 decoder")); + return PJMEDIA_CODEC_EFAILED; + } + + rc = oh264_data->dec->Initialize (&sDecParam); + if (rc) { + PJ_LOG(4,(THIS_FILE, "Decoder initialization failed, rc=%d", rc)); + return PJMEDIA_CODEC_EFAILED; + } + + int32_t color_fmt = videoFormatI420; + rc = oh264_data->dec->SetOption (DECODER_OPTION_DATAFORMAT, &color_fmt); + if (rc) { + PJ_LOG(4,(THIS_FILE, + "Warning: SetOption(DECODER_OPTION_DATAFORMAT) failed, rc=%d", + rc)); + } + + oh264_data->dec_buf_size = (MAX_RX_WIDTH * MAX_RX_HEIGHT * 3 >> 1) + + (MAX_RX_WIDTH); + oh264_data->dec_buf = (pj_uint8_t*)pj_pool_alloc(oh264_data->pool, + oh264_data->dec_buf_size); + + /* Need to update param back after values are negotiated */ + pj_memcpy(codec_param, param, sizeof(*codec_param)); + + return PJ_SUCCESS; +} + +static pj_status_t oh264_codec_close(pjmedia_vid_codec *codec) +{ + PJ_ASSERT_RETURN(codec, PJ_EINVAL); + PJ_UNUSED_ARG(codec); + return PJ_SUCCESS; +} + +static pj_status_t oh264_codec_modify(pjmedia_vid_codec *codec, + const pjmedia_vid_codec_param *param) +{ + PJ_ASSERT_RETURN(codec && param, PJ_EINVAL); + PJ_UNUSED_ARG(codec); + PJ_UNUSED_ARG(param); + return PJ_EINVALIDOP; +} + +static pj_status_t oh264_codec_get_param(pjmedia_vid_codec *codec, + pjmedia_vid_codec_param *param) +{ + struct oh264_codec_data *oh264_data; + + PJ_ASSERT_RETURN(codec && param, PJ_EINVAL); + + oh264_data = (oh264_codec_data*) codec->codec_data; + pj_memcpy(param, oh264_data->prm, sizeof(*param)); + + return PJ_SUCCESS; +} + +static pj_status_t oh264_codec_encode_begin(pjmedia_vid_codec *codec, + const pjmedia_vid_encode_opt *opt, + const pjmedia_frame *input, + unsigned out_size, + pjmedia_frame *output, + pj_bool_t *has_more) +{ + struct oh264_codec_data *oh264_data; + int rc; + + PJ_ASSERT_RETURN(codec && input && out_size && output && has_more, + PJ_EINVAL); + + oh264_data = (oh264_codec_data*) codec->codec_data; + + PJ_ASSERT_RETURN(input->size == oh264_data->enc_input_size, + PJMEDIA_CODEC_EFRMINLEN); + + if (opt && opt->force_keyframe) { + oh264_data->enc->ForceIntraFrame(true); + } + + oh264_data->esrc_pic->pData[0] = (pj_uint8_t*)input->buf; + oh264_data->esrc_pic->pData[1] = oh264_data->esrc_pic->pData[0] + + (oh264_data->esrc_pic->iPicWidth * + oh264_data->esrc_pic->iPicHeight); + oh264_data->esrc_pic->pData[2] = oh264_data->esrc_pic->pData[1] + + (oh264_data->esrc_pic->iPicWidth * + oh264_data->esrc_pic->iPicHeight >>2); + + pj_memset (&oh264_data->bsi, 0, sizeof (SFrameBSInfo)); + rc = oh264_data->enc->EncodeFrame( oh264_data->esrc_pic, &oh264_data->bsi); + if (rc != cmResultSuccess) { + PJ_LOG(5,(THIS_FILE, "EncodeFrame() error, ret: %d", rc)); + return PJMEDIA_CODEC_EFAILED; + } + + if (oh264_data->bsi.eOutputFrameType == videoFrameTypeSkip) { + output->size = 0; + output->type = PJMEDIA_FRAME_TYPE_NONE; + output->timestamp = input->timestamp; + return PJ_SUCCESS; + } + + oh264_data->ets = input->timestamp; + oh264_data->ilayer = 0; + oh264_data->enc_frame_size = oh264_data->enc_processed = 0; + + if (oh264_data->whole) { + SLayerBSInfo* pLayerBsInfo; + pj_uint8_t *payload; + unsigned i, payload_size = 0; + + *has_more = PJ_FALSE; + + /* Find which layer with biggest payload */ + oh264_data->ilayer = 0; + payload_size = oh264_data->bsi.sLayerInfo[0].iNalLengthInByte[0]; + for (i=0; i < (unsigned)oh264_data->bsi.iLayerNum; ++i) { + unsigned j; + pLayerBsInfo = &oh264_data->bsi.sLayerInfo[i]; + for (j=0; j < (unsigned)pLayerBsInfo->iNalCount; ++j) { + if (pLayerBsInfo->iNalLengthInByte[j] > (int)payload_size) { + payload_size = pLayerBsInfo->iNalLengthInByte[j]; + oh264_data->ilayer = i; + } + } + } + + pLayerBsInfo = &oh264_data->bsi.sLayerInfo[oh264_data->ilayer]; + if (pLayerBsInfo == NULL) { + output->size = 0; + output->type = PJMEDIA_FRAME_TYPE_NONE; + return PJ_SUCCESS; + } + + payload = pLayerBsInfo->pBsBuf; + payload_size = 0; + for (int inal = pLayerBsInfo->iNalCount - 1; inal >= 0; --inal) { + payload_size += pLayerBsInfo->iNalLengthInByte[inal]; + } + + if (payload_size > out_size) + return PJMEDIA_CODEC_EFRMTOOSHORT; + + output->type = PJMEDIA_FRAME_TYPE_VIDEO; + output->size = payload_size; + output->timestamp = input->timestamp; + pj_memcpy(output->buf, payload, payload_size); + + return PJ_SUCCESS; + } + + return oh264_codec_encode_more(codec, out_size, output, has_more); +} + + +static pj_status_t oh264_codec_encode_more(pjmedia_vid_codec *codec, + unsigned out_size, + pjmedia_frame *output, + pj_bool_t *has_more) +{ + struct oh264_codec_data *oh264_data; + const pj_uint8_t *payload; + pj_size_t payload_len; + pj_status_t status; + + PJ_ASSERT_RETURN(codec && out_size && output && has_more, + PJ_EINVAL); + + oh264_data = (oh264_codec_data*) codec->codec_data; + + if (oh264_data->enc_processed < oh264_data->enc_frame_size) { + /* We have outstanding frame in packetizer */ + status = pjmedia_h264_packetize(oh264_data->pktz, + oh264_data->enc_frame_whole, + oh264_data->enc_frame_size, + &oh264_data->enc_processed, + &payload, &payload_len); + if (status != PJ_SUCCESS) { + /* Reset */ + oh264_data->enc_frame_size = oh264_data->enc_processed = 0; + *has_more = (oh264_data->enc_processed < + oh264_data->enc_frame_size) || + (oh264_data->ilayer < oh264_data->bsi.iLayerNum); + + PJ_PERROR(3,(THIS_FILE, status, "pjmedia_h264_packetize() error")); + return status; + } + + PJ_ASSERT_RETURN(payload_len <= out_size, PJMEDIA_CODEC_EFRMTOOSHORT); + + output->type = PJMEDIA_FRAME_TYPE_VIDEO; + pj_memcpy(output->buf, payload, payload_len); + output->size = payload_len; + + if (oh264_data->bsi.eOutputFrameType == videoFrameTypeIDR) { + output->bit_info |= PJMEDIA_VID_FRM_KEYFRAME; + } + + *has_more = (oh264_data->enc_processed < oh264_data->enc_frame_size) || + (oh264_data->ilayer < oh264_data->bsi.iLayerNum); + return PJ_SUCCESS; + } + + if (oh264_data->ilayer >= oh264_data->bsi.iLayerNum) { + /* No more unretrieved frame */ + goto no_frame; + } + + SLayerBSInfo* pLayerBsInfo; + pLayerBsInfo = &oh264_data->bsi.sLayerInfo[oh264_data->ilayer++]; + if (pLayerBsInfo == NULL) { + goto no_frame; + } + + oh264_data->enc_frame_size = 0; + for (int inal = pLayerBsInfo->iNalCount - 1; inal >= 0; --inal) { + oh264_data->enc_frame_size += pLayerBsInfo->iNalLengthInByte[inal]; + } + + oh264_data->enc_frame_whole = pLayerBsInfo->pBsBuf; + oh264_data->enc_processed = 0; + + + status = pjmedia_h264_packetize(oh264_data->pktz, + oh264_data->enc_frame_whole, + oh264_data->enc_frame_size, + &oh264_data->enc_processed, + &payload, &payload_len); + if (status != PJ_SUCCESS) { + /* Reset */ + oh264_data->enc_frame_size = oh264_data->enc_processed = 0; + *has_more = (oh264_data->ilayer < oh264_data->bsi.iLayerNum); + + PJ_PERROR(3,(THIS_FILE, status, "pjmedia_h264_packetize() error [2]")); + return status; + } + + PJ_ASSERT_RETURN(payload_len <= out_size, PJMEDIA_CODEC_EFRMTOOSHORT); + + output->type = PJMEDIA_FRAME_TYPE_VIDEO; + pj_memcpy(output->buf, payload, payload_len); + output->size = payload_len; + + if (oh264_data->bsi.eOutputFrameType == videoFrameTypeIDR) { + output->bit_info |= PJMEDIA_VID_FRM_KEYFRAME; + } + + *has_more = (oh264_data->enc_processed < oh264_data->enc_frame_size) || + (oh264_data->ilayer < oh264_data->bsi.iLayerNum); + + return PJ_SUCCESS; + +no_frame: + *has_more = PJ_FALSE; + output->size = 0; + output->type = PJMEDIA_FRAME_TYPE_NONE; + return PJ_SUCCESS; +} + +static int write_yuv(pj_uint8_t *buf, + unsigned dst_len, + unsigned char* pData[3], + int iStride[2], + int iWidth, + int iHeight) +{ + unsigned req_size; + pj_uint8_t *dst = buf; + pj_uint8_t *max = dst + dst_len; + int i; + unsigned char* pPtr = NULL; + + req_size = (iWidth * iHeight) + (iWidth / 2 * iHeight / 2) + + (iWidth / 2 * iHeight / 2); + if (dst_len < req_size) + return -1; + + pPtr = pData[0]; + for (i = 0; i < iHeight && (dst + iWidth < max); i++) { + pj_memcpy(dst, pPtr, iWidth); + pPtr += iStride[0]; + dst += iWidth; + } + + if (i < iHeight) + return -1; + + iHeight = iHeight / 2; + iWidth = iWidth / 2; + pPtr = pData[1]; + for (i = 0; i < iHeight && (dst + iWidth <= max); i++) { + pj_memcpy(dst, pPtr, iWidth); + pPtr += iStride[1]; + dst += iWidth; + } + + if (i < iHeight) + return -1; + + pPtr = pData[2]; + for (i = 0; i < iHeight && (dst + iWidth <= max); i++) { + pj_memcpy(dst, pPtr, iWidth); + pPtr += iStride[1]; + dst += iWidth; + } + + if (i < iHeight) + return -1; + + return dst - buf; +} + +static pj_status_t oh264_got_decoded_frame(pjmedia_vid_codec *codec, + struct oh264_codec_data *oh264_data, + void *pData[3], + SBufferInfo *sDstBufInfo, + pj_timestamp *timestamp, + unsigned out_size, + pjmedia_frame *output) +{ + pj_uint8_t* pDst[3] = {NULL}; + + pDst[0] = (pj_uint8_t*)pData[0]; + pDst[1] = (pj_uint8_t*)pData[1]; + pDst[2] = (pj_uint8_t*)pData[2]; + + /* Do not reset size as it may already contain frame + output->size = 0; + */ + + if (!pDst[0] || !pDst[1] || !pDst[2]) { + return PJ_SUCCESS; + } + + int iStride[2]; + int iWidth = sDstBufInfo->UsrData.sSystemBuffer.iWidth; + int iHeight = sDstBufInfo->UsrData.sSystemBuffer.iHeight; + + iStride[0] = sDstBufInfo->UsrData.sSystemBuffer.iStride[0]; + iStride[1] = sDstBufInfo->UsrData.sSystemBuffer.iStride[1]; + + int len = write_yuv((pj_uint8_t *)output->buf, out_size, + pDst, iStride, iWidth, iHeight); + if (len > 0) { + output->timestamp = *timestamp; + output->size = len; + output->type = PJMEDIA_FRAME_TYPE_VIDEO; + } else { + /* buffer is damaged, reset size */ + output->size = 0; + return PJMEDIA_CODEC_EFRMTOOSHORT; + } + + /* Detect format change */ + if (iWidth != (int)oh264_data->prm->dec_fmt.det.vid.size.w || + iHeight != (int)oh264_data->prm->dec_fmt.det.vid.size.h) + { + pjmedia_event event; + + PJ_LOG(4,(THIS_FILE, "Frame size changed: %dx%d --> %dx%d", + oh264_data->prm->dec_fmt.det.vid.size.w, + oh264_data->prm->dec_fmt.det.vid.size.h, + iWidth, iHeight)); + + oh264_data->prm->dec_fmt.det.vid.size.w = iWidth; + oh264_data->prm->dec_fmt.det.vid.size.h = iHeight; + + /* Broadcast format changed event */ + pjmedia_event_init(&event, PJMEDIA_EVENT_FMT_CHANGED, + timestamp, codec); + event.data.fmt_changed.dir = PJMEDIA_DIR_DECODING; + pjmedia_format_copy(&event.data.fmt_changed.new_fmt, + &oh264_data->prm->dec_fmt); + pjmedia_event_publish(NULL, codec, &event, + PJMEDIA_EVENT_PUBLISH_DEFAULT); + } + + return PJ_SUCCESS; +} + +static pj_status_t oh264_codec_decode(pjmedia_vid_codec *codec, + pj_size_t count, + pjmedia_frame packets[], + unsigned out_size, + pjmedia_frame *output) +{ + struct oh264_codec_data *oh264_data; + void* pData[3] = {NULL}; + const pj_uint8_t nal_start[] = { 0, 0, 1 }; + SBufferInfo sDstBufInfo; + pj_bool_t has_frame = PJ_FALSE; + unsigned buf_pos, whole_len = 0; + unsigned i, frm_cnt; + pj_status_t status = PJ_SUCCESS; + + PJ_ASSERT_RETURN(codec && count && packets && out_size && output, + PJ_EINVAL); + PJ_ASSERT_RETURN(output->buf, PJ_EINVAL); + + oh264_data = (oh264_codec_data*) codec->codec_data; + + /* + * Step 1: unpacketize the packets/frames + */ + whole_len = 0; + if (oh264_data->whole) { + for (i=0; i<count; ++i) { + if (whole_len + packets[i].size > oh264_data->dec_buf_size) { + PJ_LOG(4,(THIS_FILE, "Decoding buffer overflow [1]")); + return PJMEDIA_CODEC_EFRMTOOSHORT; + } + + pj_memcpy( oh264_data->dec_buf + whole_len, + (pj_uint8_t*)packets[i].buf, + packets[i].size); + whole_len += packets[i].size; + } + + } else { + for (i=0; i<count; ++i) { + + if (whole_len + packets[i].size + sizeof(nal_start) > + oh264_data->dec_buf_size) + { + PJ_LOG(4,(THIS_FILE, "Decoding buffer overflow [1]")); + return PJMEDIA_CODEC_EFRMTOOSHORT; + } + + status = pjmedia_h264_unpacketize( oh264_data->pktz, + (pj_uint8_t*)packets[i].buf, + packets[i].size, + oh264_data->dec_buf, + oh264_data->dec_buf_size, + &whole_len); + if (status != PJ_SUCCESS) { + PJ_PERROR(4,(THIS_FILE, status, "Unpacketize error")); + continue; + } + } + } + + if (whole_len + sizeof(nal_start) > oh264_data->dec_buf_size) { + PJ_LOG(4,(THIS_FILE, "Decoding buffer overflow [2]")); + return PJMEDIA_CODEC_EFRMTOOSHORT; + } + + /* Dummy NAL sentinel */ + pj_memcpy( oh264_data->dec_buf + whole_len, nal_start, sizeof(nal_start)); + + /* + * Step 2: parse the individual NAL and give to decoder + */ + buf_pos = 0; + for ( frm_cnt=0; ; ++frm_cnt) { + unsigned frm_size; + unsigned char *start; + + for (i = 0; buf_pos + i < whole_len; i++) { + if (oh264_data->dec_buf[buf_pos + i] == 0 && + oh264_data->dec_buf[buf_pos + i + 1] == 0 && + oh264_data->dec_buf[buf_pos + i + 2] == 1 && + i > 1) + { + break; + } + } + frm_size = i; + + pj_bzero( pData, sizeof(pData)); + pj_bzero( &sDstBufInfo, sizeof (SBufferInfo)); + + start = oh264_data->dec_buf + buf_pos; + + /* Decode */ + oh264_data->dec->DecodeFrame2( start, frm_size, pData, &sDstBufInfo); + + if (sDstBufInfo.iBufferStatus == 1) { + /* May overwrite existing frame but that's ok. */ + status = oh264_got_decoded_frame(codec, oh264_data, pData, + &sDstBufInfo, + &packets[0].timestamp, out_size, + output); + has_frame = (status==PJ_SUCCESS && output->size != 0); + } + + if (buf_pos + frm_size >= whole_len) + break; + + buf_pos += frm_size; + } + + /* Signal that we have no more frames */ + int32_t iEndOfStreamFlag; + iEndOfStreamFlag = true; + oh264_data->dec->SetOption( DECODER_OPTION_END_OF_STREAM, + (void*)&iEndOfStreamFlag); + + /* Retrieve the decoded frame */ + pj_bzero(pData, sizeof(pData)); + pj_bzero(&sDstBufInfo, sizeof (SBufferInfo)); + oh264_data->dec->DecodeFrame2 (NULL, 0, pData, &sDstBufInfo); + + if (sDstBufInfo.iBufferStatus == 1) { + /* Overwrite existing output frame and that's ok, because we assume + * newer frame have better quality because it has more NALs + */ + status = oh264_got_decoded_frame(codec, oh264_data, pData, + &sDstBufInfo, &packets[0].timestamp, + out_size, output); + has_frame = (status==PJ_SUCCESS && output->size != 0); + } + + if (!has_frame) { + pjmedia_event event; + + /* Broadcast missing keyframe event */ + pjmedia_event_init(&event, PJMEDIA_EVENT_KEYFRAME_MISSING, + &packets[0].timestamp, codec); + pjmedia_event_publish(NULL, codec, &event, + PJMEDIA_EVENT_PUBLISH_DEFAULT); + + PJ_LOG(5,(THIS_FILE, "Decode couldn't produce picture, " + "input nframes=%d, concatenated size=%d bytes", + count, whole_len)); + + output->type = PJMEDIA_FRAME_TYPE_NONE; + output->size = 0; + output->timestamp = packets[0].timestamp; + } + + return status; +} + +#endif /* PJMEDIA_HAS_OPENH264_CODEC */ |