/* $Id$ */ /* * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) * Copyright (C) 2003-2008 Benny Prijono * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0) #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 /* Default frame rate, if not specified */ #define DEFAULT_H264_FPS_NUM 10 #define DEFAULT_H264_FPS_DENUM 1 /* Default aspect ratio, if not specified */ #define DEFAULT_H264_RATIO_NUM 4 #define DEFAULT_H264_RATIO_DENUM 3 /* 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}}, }; #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 /* 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 && i32) 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; icnt; ++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; jparam[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; } static unsigned fps_to_mpi(const pjmedia_ratio *fps) { unsigned mpi; /* Original formula = (fps->denum * 30000) / (fps->num * 1001) */ mpi = (fps->denum*30000 + fps->num*1001/2) / (fps->num*1001); /* Normalize, should be in the range of 1-32 */ if (mpi > 32) mpi = 32; if (mpi < 1) mpi = 1; return mpi; } 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; vfd = pjmedia_format_get_video_format_detail(¶m->enc_fmt, PJ_TRUE); /* Get local param */ // Local param should be fetched from "param->enc_fmt" instead of // "param->dec_fmtp". //status = pjmedia_vid_codec_parse_h263_fmtp(¶m->dec_fmtp, // &fmtp_loc); //if (status != PJ_SUCCESS) // return status; fmtp_loc.mpi_cnt = 1; fmtp_loc.mpi[0].size = vfd->size; fmtp_loc.mpi[0].val = fps_to_mpi(&vfd->fps); /* 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_sqsize = 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; } /* 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 max_br; /* Max bitrate (kbps). */ } h264_level_info_t; /* Init H264 parameters based on profile-level-id */ static pj_status_t init_h264_profile(const pj_str_t *profile, pjmedia_vid_codec_h264_fmtp *fmtp) { const h264_level_info_t level_info[] = { { 10, 1485, 99, 64 }, { 9, 1485, 99, 128 }, /*< level 1b */ { 11, 3000, 396, 192 }, { 12, 6000, 396, 384 }, { 13, 11880, 396, 768 }, { 20, 11880, 396, 2000 }, { 21, 19800, 792, 4000 }, { 22, 20250, 1620, 4000 }, { 30, 40500, 1620, 10000 }, { 31, 108000, 3600, 14000 }, { 32, 216000, 5120, 20000 }, { 40, 245760, 8192, 20000 }, { 41, 245760, 8192, 50000 }, { 42, 522240, 8704, 50000 }, { 50, 589824, 22080, 135000 }, { 51, 983040, 36864, 240000 }, }; unsigned i, tmp; pj_str_t endst; const h264_level_info_t *li = NULL; if (profile->slen != 6) return PJMEDIA_SDP_EINFMTP; tmp = pj_strtoul2(profile, &endst, 16); if (endst.slen) return PJMEDIA_SDP_EINFMTP; fmtp->profile_idc = (pj_uint8_t)((tmp >> 16) & 0xFF); fmtp->profile_iop = (pj_uint8_t)((tmp >> 8) & 0xFF); fmtp->level = (pj_uint8_t)(tmp & 0xFF); for (i = 0; i < PJ_ARRAY_SIZE(level_info); ++i) { if (level_info[i].id == fmtp->level) { li = &level_info[i]; break; } } if (li == NULL) return PJMEDIA_SDP_EINFMTP; /* Init profile level spec */ if (fmtp->max_br == 0) fmtp->max_br = li->max_br; if (fmtp->max_mbps == 0) fmtp->max_mbps = li->max_mbps; if (fmtp->max_fs == 0) fmtp->max_fs = li->max_mb; 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_status_t status; pj_bzero(h264_fmtp, sizeof(*h264_fmtp)); for (i=0; icnt; ++i) { unsigned tmp; if (pj_stricmp(&fmtp->param[i].name, &PROFILE_LEVEL_ID)==0) { /* Init H264 parameters based on level, if not set yet */ status = init_h264_profile(&fmtp->param[i].val, h264_fmtp); if (status != PJ_SUCCESS) return status; } else if (pj_stricmp(&fmtp->param[i].name, &PACKETIZATION_MODE)==0) { tmp = pj_strtoul(&fmtp->param[i].val); if (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 = PJ_MAX(tmp, h264_fmtp->max_mbps); } else if (pj_stricmp(&fmtp->param[i].name, &MAX_FS)==0) { tmp = pj_strtoul(&fmtp->param[i].val); h264_fmtp->max_fs = PJ_MAX(tmp, h264_fmtp->max_fs); } else if (pj_stricmp(&fmtp->param[i].name, &MAX_CPB)==0) { tmp = pj_strtoul(&fmtp->param[i].val); h264_fmtp->max_cpb = PJ_MAX(tmp, h264_fmtp->max_cpb); } else if (pj_stricmp(&fmtp->param[i].name, &MAX_DPB)==0) { tmp = pj_strtoul(&fmtp->param[i].val); h264_fmtp->max_dpb = PJ_MAX(tmp, h264_fmtp->max_dpb); } else if (pj_stricmp(&fmtp->param[i].name, &MAX_BR)==0) { tmp = pj_strtoul(&fmtp->param[i].val); h264_fmtp->max_br = PJ_MAX(tmp, h264_fmtp->max_br); } 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; /* 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) - (int)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) { const pj_str_t DEF_PROFILE = {"42000A", 6}; status = init_h264_profile(&DEF_PROFILE, h264_fmtp); if (status != PJ_SUCCESS) return status; } 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; } /* Find greatest common divisor (GCD) */ static unsigned gcd (unsigned a, unsigned b) { unsigned c; while (b) { c = a % b; a = b; b = c; } return a; } /* Find highest resolution possible for the specified H264 fmtp and * aspect ratio. */ static pj_status_t find_highest_res(pjmedia_vid_codec_h264_fmtp *fmtp, const pjmedia_ratio *fps, const pjmedia_ratio *ratio, pjmedia_rect_size *size, pj_bool_t is_decoding) { pjmedia_ratio def_ratio = { DEFAULT_H264_RATIO_NUM, DEFAULT_H264_RATIO_DENUM }; pjmedia_ratio def_fps = { DEFAULT_H264_FPS_NUM, DEFAULT_H264_FPS_DENUM }; pjmedia_ratio asp_ratio, the_fps; unsigned max_fs, g, scale; pj_assert(size); /* Get the ratio, or just use default if not provided. */ if (ratio && ratio->num && ratio->denum) { asp_ratio = *ratio; } else { asp_ratio = def_ratio; } /* Normalize the aspect ratio */ g = gcd(asp_ratio.num, asp_ratio.denum); asp_ratio.num /= g; asp_ratio.denum /= g; /* Get the frame rate, or just use default if not provided. */ if (fps && fps->num && fps->denum) { the_fps = *fps; } else { the_fps = def_fps; } /* Calculate maximum size (in macroblocks) */ max_fs = fmtp->max_mbps * the_fps.denum / the_fps.num; max_fs = PJ_MIN(max_fs, fmtp->max_fs); /* Check if the specified ratio is using big numbers * (not normalizable), override it! */ if ((int)max_fs < asp_ratio.num * asp_ratio.denum) { if ((int)max_fs >= def_ratio.num * def_ratio.denum) asp_ratio = def_ratio; else asp_ratio.num = asp_ratio.denum = 1; } /* Calculate the scale factor for size */ scale = pj_isqrt(max_fs / asp_ratio.denum / asp_ratio.num); /* Calculate the size, note that the frame size is in macroblock units */ size->w = asp_ratio.num * scale * 16; size->h = asp_ratio.denum * scale * 16; /* #1769: for decoding, size is usually used for allocating buffer, * so we need to make sure that frame size is not less than max_fs. */ if (is_decoding && ((size->w * size->h) >> 8) < max_fs) { /* Size is less than max_fs, recalculate using ratio 1:1 and * round up the scale. */ scale = pj_isqrt(max_fs) + 1; size->w = size->h = scale * 16; } return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjmedia_vid_codec_h264_apply_fmtp( pjmedia_vid_codec_param *param) { if (param->dir & PJMEDIA_DIR_ENCODING) { pjmedia_vid_codec_h264_fmtp fmtp; pjmedia_video_format_detail *vfd; pj_status_t status; /* Get remote param */ status = pjmedia_vid_codec_h264_parse_fmtp(¶m->enc_fmtp, &fmtp); if (status != PJ_SUCCESS) return status; /* Adjust fps, size, and bitrate to conform to H.264 level * specified by remote SDP fmtp. */ vfd = pjmedia_format_get_video_format_detail(¶m->enc_fmt, PJ_TRUE); if (vfd->fps.num == 0 || vfd->fps.denum == 0) { vfd->fps.num = DEFAULT_H264_FPS_NUM; vfd->fps.denum = DEFAULT_H264_FPS_DENUM; } if (vfd->size.w && vfd->size.h) { unsigned mb, mbps; /* Scale down the resolution if it exceeds profile spec */ mb = CALC_H264_MB_NUM(vfd->size); mbps = CALC_H264_MBPS(vfd->size, vfd->fps); if (mb > fmtp.max_fs || mbps > fmtp.max_mbps) { pjmedia_ratio r; r.num = vfd->size.w; r.denum = vfd->size.h; find_highest_res(&fmtp, &vfd->fps, &r, &vfd->size, PJ_FALSE); } } else { /* When not specified, just use the highest res possible*/ pjmedia_ratio r; r.num = vfd->size.w; r.denum = vfd->size.h; find_highest_res(&fmtp, &vfd->fps, &r, &vfd->size, PJ_FALSE); } /* Encoding bitrate must not be higher than H264 level spec */ if (vfd->avg_bps > fmtp.max_br * 1000) vfd->avg_bps = fmtp.max_br * 1000; if (vfd->max_bps > fmtp.max_br * 1000) vfd->max_bps = fmtp.max_br * 1000; } 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; pjmedia_ratio r; pjmedia_rect_size highest_size; pj_status_t status; status = pjmedia_vid_codec_h264_parse_fmtp(¶m->dec_fmtp, &fmtp); if (status != PJ_SUCCESS) return status; vfd = pjmedia_format_get_video_format_detail(¶m->dec_fmt, PJ_TRUE); if (vfd->fps.num == 0 || vfd->fps.denum == 0) { vfd->fps.num = DEFAULT_H264_FPS_NUM; vfd->fps.denum = DEFAULT_H264_FPS_DENUM; } /* Normalize decoding resolution, i.e: it must not be lower than * the H264 profile level setting used, as this may be used by * app to allocate buffer. */ r.num = vfd->size.w; r.denum = vfd->size.h; find_highest_res(&fmtp, &vfd->fps, &r, &highest_size, PJ_TRUE); if (vfd->size.w * vfd->size.h < highest_size.w * highest_size.h) vfd->size = highest_size; /* Normalize decoding bitrate based on H264 level spec */ if (vfd->avg_bps < fmtp.max_br * 1000) vfd->avg_bps = fmtp.max_br * 1000; if (vfd->max_bps < fmtp.max_br * 1000) vfd->max_bps = fmtp.max_br * 1000; } return PJ_SUCCESS; } /* VPX fmtp parser */ PJ_DEF(pj_status_t) pjmedia_vid_codec_vpx_parse_fmtp( const pjmedia_codec_fmtp *fmtp, pjmedia_vid_codec_vpx_fmtp *vpx_fmtp) { const pj_str_t PROFILE_ID = {"profile-id", 10}; const pj_str_t MAX_FR = {"max-fr", 6}; const pj_str_t MAX_FS = {"max-fs", 6}; unsigned i; pj_bzero(vpx_fmtp, sizeof(*vpx_fmtp)); for (i = 0; i < fmtp->cnt; ++i) { unsigned tmp; if (pj_stricmp(&fmtp->param[i].name, &MAX_FS) == 0) { tmp = pj_strtoul(&fmtp->param[i].val); vpx_fmtp->max_fs = PJ_MAX(tmp, vpx_fmtp->max_fs); } else if (pj_stricmp(&fmtp->param[i].name, &MAX_FR) == 0) { tmp = pj_strtoul(&fmtp->param[i].val); vpx_fmtp->max_fr = PJ_MAX(tmp, vpx_fmtp->max_fr); } else if (pj_stricmp(&fmtp->param[i].name, &PROFILE_ID) == 0) { tmp = pj_strtoul(&fmtp->param[i].val); vpx_fmtp->profile_id = (pj_uint8_t) PJ_MAX(tmp, vpx_fmtp->profile_id); } } return PJ_SUCCESS; } PJ_DEF(pj_status_t) pjmedia_vid_codec_vpx_apply_fmtp( pjmedia_vid_codec_param *param) { if (param->dir & PJMEDIA_DIR_ENCODING) { pjmedia_vid_codec_vpx_fmtp fmtp; pjmedia_video_format_detail *vfd; pj_status_t status; /* Get remote param */ status = pjmedia_vid_codec_vpx_parse_fmtp(¶m->enc_fmtp, &fmtp); if (status != PJ_SUCCESS) return status; /* Adjust fps and size to conform to the parameter * specified by remote SDP fmtp. */ vfd = pjmedia_format_get_video_format_detail(¶m->enc_fmt, PJ_TRUE); if (fmtp.max_fr > 0) { if ((float)vfd->fps.num/vfd->fps.denum > (float)fmtp.max_fr) { vfd->fps.num = fmtp.max_fr; vfd->fps.denum = 1; } } if (fmtp.max_fs > 0) { unsigned max_res = ((int)pj_isqrt(fmtp.max_fs * 8)) * 16; if (vfd->size.w > max_res || vfd->size.h > max_res) { /* Here we maintain the aspect ratio. Or should we scale down * to some predetermined resolution instead (for example, * if the requested resolution is 640x480 and max_res is * 600, should we scale down to 480x360)? */ unsigned larger = (vfd->size.w > vfd->size.h)? vfd->size.w: vfd->size.h; float scale = (float)max_res/larger; vfd->size.w = (int)(scale * vfd->size.w); vfd->size.h = (int)(scale * vfd->size.h); } } } if (param->dir & PJMEDIA_DIR_DECODING) { /* Here we just want to find the highest fps and resolution possible * from the fmtp and set it as the decoder param. */ pjmedia_vid_codec_vpx_fmtp fmtp; pjmedia_video_format_detail *vfd; pj_status_t status; /* Get remote param */ status = pjmedia_vid_codec_vpx_parse_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.max_fr > 0) { vfd->fps.num = fmtp.max_fr; vfd->fps.denum = 1; } if (fmtp.max_fs > 0) { unsigned max_res = ((int)pj_isqrt(fmtp.max_fs * 8)) * 16; vfd->size.w = max_res; vfd->size.h = max_res; } } return PJ_SUCCESS; } #endif /* PJMEDIA_HAS_VIDEO */