/* $Id$ */ /* * Copyright (C)2019 Teluu Inc. (http://www.teluu.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #if defined(PJMEDIA_HAS_VPX_CODEC) && \ PJMEDIA_HAS_VPX_CODEC != 0 && \ defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0) #ifdef _MSC_VER # pragma comment( lib, "vpx.lib") #endif /* VPX */ #include #include #include #include /* * Constants */ #define THIS_FILE "vpx.c" #if (defined(PJ_DARWINOS) && PJ_DARWINOS != 0 && TARGET_OS_IPHONE) || \ defined(__ANDROID__) # define DEFAULT_WIDTH 352 # define DEFAULT_HEIGHT 288 #else # define DEFAULT_WIDTH 720 # define DEFAULT_HEIGHT 480 #endif #define DEFAULT_FPS 15 #define DEFAULT_AVG_BITRATE 200000 #define DEFAULT_MAX_BITRATE 200000 #define MAX_RX_RES 1200 /* * Factory operations. */ static pj_status_t vpx_test_alloc(pjmedia_vid_codec_factory *factory, const pjmedia_vid_codec_info *info ); static pj_status_t vpx_default_attr(pjmedia_vid_codec_factory *factory, const pjmedia_vid_codec_info *info, pjmedia_vid_codec_param *attr ); static pj_status_t vpx_enum_info(pjmedia_vid_codec_factory *factory, unsigned *count, pjmedia_vid_codec_info codecs[]); static pj_status_t vpx_alloc_codec(pjmedia_vid_codec_factory *factory, const pjmedia_vid_codec_info *info, pjmedia_vid_codec **p_codec); static pj_status_t vpx_dealloc_codec(pjmedia_vid_codec_factory *factory, pjmedia_vid_codec *codec ); /* * Codec operations */ static pj_status_t vpx_codec_init(pjmedia_vid_codec *codec, pj_pool_t *pool ); static pj_status_t vpx_codec_open(pjmedia_vid_codec *codec, pjmedia_vid_codec_param *param ); static pj_status_t vpx_codec_close(pjmedia_vid_codec *codec); static pj_status_t vpx_codec_modify(pjmedia_vid_codec *codec, const pjmedia_vid_codec_param *param); static pj_status_t vpx_codec_get_param(pjmedia_vid_codec *codec, pjmedia_vid_codec_param *param); static pj_status_t vpx_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 vpx_codec_encode_more(pjmedia_vid_codec *codec, unsigned out_size, pjmedia_frame *output, pj_bool_t *has_more); static pj_status_t vpx_codec_decode_(pjmedia_vid_codec *codec, pj_size_t count, pjmedia_frame packets[], unsigned out_size, pjmedia_frame *output); /* Definition for VPX codecs operations. */ static pjmedia_vid_codec_op vpx_codec_op = { &vpx_codec_init, &vpx_codec_open, &vpx_codec_close, &vpx_codec_modify, &vpx_codec_get_param, &vpx_codec_encode_begin, &vpx_codec_encode_more, &vpx_codec_decode_, NULL }; /* Definition for VPX codecs factory operations. */ static pjmedia_vid_codec_factory_op vpx_factory_op = { &vpx_test_alloc, &vpx_default_attr, &vpx_enum_info, &vpx_alloc_codec, &vpx_dealloc_codec }; static struct vpx_factory { pjmedia_vid_codec_factory base; pjmedia_vid_codec_mgr *mgr; pj_pool_factory *pf; pj_pool_t *pool; } vpx_factory; typedef struct vpx_codec_data { pj_pool_t *pool; pjmedia_vid_codec_param *prm; pj_bool_t whole; /* Encoder */ vpx_codec_iface_t *(*enc_if)(); vpx_codec_ctx_t enc; vpx_codec_iter_t enc_iter; unsigned enc_input_size; pj_uint8_t *enc_frame_whole; unsigned enc_frame_size; unsigned enc_processed; pj_bool_t enc_frame_is_keyframe; pj_timestamp ets; /* Decoder */ vpx_codec_iface_t *(*dec_if)(); vpx_codec_ctx_t dec; pj_uint8_t *dec_buf; unsigned dec_buf_size; } vpx_codec_data; PJ_DEF(pj_status_t) pjmedia_codec_vpx_vid_init(pjmedia_vid_codec_mgr *mgr, pj_pool_factory *pf) { pj_status_t status; if (vpx_factory.pool != NULL) { /* Already initialized. */ return PJ_SUCCESS; } if (!mgr) mgr = pjmedia_vid_codec_mgr_instance(); PJ_ASSERT_RETURN(mgr, PJ_EINVAL); /* Create VPX codec factory. */ vpx_factory.base.op = &vpx_factory_op; vpx_factory.base.factory_data = NULL; vpx_factory.mgr = mgr; vpx_factory.pf = pf; vpx_factory.pool = pj_pool_create(pf, "vpxfactory", 256, 256, NULL); if (!vpx_factory.pool) return PJ_ENOMEM; /* Register codec factory to codec manager. */ status = pjmedia_vid_codec_mgr_register_factory(mgr, &vpx_factory.base); if (status != PJ_SUCCESS) goto on_error; PJ_LOG(4,(THIS_FILE, "VPX codec initialized")); /* Done. */ return PJ_SUCCESS; on_error: pj_pool_release(vpx_factory.pool); vpx_factory.pool = NULL; return status; } /* * Unregister VPX codecs factory from pjmedia endpoint. */ PJ_DEF(pj_status_t) pjmedia_codec_vpx_vid_deinit(void) { pj_status_t status = PJ_SUCCESS; if (vpx_factory.pool == NULL) { /* Already deinitialized */ return PJ_SUCCESS; } /* Unregister VPX codecs factory. */ status = pjmedia_vid_codec_mgr_unregister_factory(vpx_factory.mgr, &vpx_factory.base); /* Destroy pool. */ pj_pool_release(vpx_factory.pool); vpx_factory.pool = NULL; return status; } static pj_status_t vpx_test_alloc(pjmedia_vid_codec_factory *factory, const pjmedia_vid_codec_info *info ) { PJ_ASSERT_RETURN(factory == &vpx_factory.base, PJ_EINVAL); if ((info->fmt_id == PJMEDIA_FORMAT_VP8 || info->fmt_id == PJMEDIA_FORMAT_VP9) && info->pt != 0) { return PJ_SUCCESS; } return PJMEDIA_CODEC_EUNSUP; } static pj_status_t vpx_default_attr(pjmedia_vid_codec_factory *factory, const pjmedia_vid_codec_info *info, pjmedia_vid_codec_param *attr ) { PJ_ASSERT_RETURN(factory == &vpx_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, info->fmt_id, 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 */ /* If the implementation is willing to receive media, both parameters * MUST be provided. */ attr->dec_fmtp.cnt = 2; attr->dec_fmtp.param[0].name = pj_str((char*)"max-fr"); attr->dec_fmtp.param[0].val = pj_str((char*)"30"); attr->dec_fmtp.param[1].name = pj_str((char*)" max-fs"); attr->dec_fmtp.param[1].val = pj_str((char*)"580"); /* 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 vpx_enum_info(pjmedia_vid_codec_factory *factory, unsigned *count, pjmedia_vid_codec_info info[]) { unsigned i = 0; PJ_ASSERT_RETURN(info && *count > 0, PJ_EINVAL); PJ_ASSERT_RETURN(factory == &vpx_factory.base, PJ_EINVAL); #if PJMEDIA_HAS_VPX_CODEC_VP8 info[i].fmt_id = PJMEDIA_FORMAT_VP8; info[i].pt = PJMEDIA_RTP_PT_VP8; info[i].encoding_name = pj_str((char*)"VP8"); info[i].encoding_desc = pj_str((char*)"VPX VP8 codec"); i++; #endif #if PJMEDIA_HAS_VPX_CODEC_VP9 if (i + 1 < *count) { info[i].fmt_id = PJMEDIA_FORMAT_VP9; info[i].pt = PJMEDIA_RTP_PT_VP9; info[i].encoding_name = pj_str((char*)"VP9"); info[i].encoding_desc = pj_str((char*)"VPX VP9 codec"); i++; } #endif *count = i; for (i = 0; i < *count; i++) { info[i].clock_rate = 90000; info[i].dir = PJMEDIA_DIR_ENCODING_DECODING; info[i].dec_fmt_id_cnt = 1; info[i].dec_fmt_id[0] = PJMEDIA_FORMAT_I420; info[i].packings = PJMEDIA_VID_PACKING_PACKETS; info[i].fps_cnt = 3; info[i].fps[0].num = 15; info[i].fps[0].denum = 1; info[i].fps[1].num = 25; info[i].fps[1].denum = 1; info[i].fps[2].num = 30; info[i].fps[2].denum = 1; } return PJ_SUCCESS; } static pj_status_t vpx_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; vpx_codec_data *vpx_data; PJ_ASSERT_RETURN(factory == &vpx_factory.base && info && p_codec, PJ_EINVAL); *p_codec = NULL; pool = pj_pool_create(vpx_factory.pf, "vpx%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 = &vpx_codec_op; /* codec data */ vpx_data = PJ_POOL_ZALLOC_T(pool, vpx_codec_data); vpx_data->pool = pool; codec->codec_data = vpx_data; /* encoder and decoder interfaces */ if (info->fmt_id == PJMEDIA_FORMAT_VP8) { vpx_data->enc_if = &vpx_codec_vp8_cx; vpx_data->dec_if = &vpx_codec_vp8_dx; } else if (info->fmt_id == PJMEDIA_FORMAT_VP9) { vpx_data->enc_if = &vpx_codec_vp9_cx; vpx_data->dec_if = &vpx_codec_vp9_dx; } *p_codec = codec; return PJ_SUCCESS; } static pj_status_t vpx_dealloc_codec(pjmedia_vid_codec_factory *factory, pjmedia_vid_codec *codec ) { vpx_codec_data *vpx_data; PJ_ASSERT_RETURN(codec, PJ_EINVAL); PJ_UNUSED_ARG(factory); vpx_data = (vpx_codec_data*) codec->codec_data; vpx_data->enc_if = NULL; vpx_data->dec_if = NULL; pj_pool_release(vpx_data->pool); return PJ_SUCCESS; } static pj_status_t vpx_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 vpx_codec_open(pjmedia_vid_codec *codec, pjmedia_vid_codec_param *codec_param ) { vpx_codec_data *vpx_data; pjmedia_vid_codec_param *param; pjmedia_vid_codec_vpx_fmtp vpx_fmtp; vpx_codec_enc_cfg_t cfg; vpx_codec_err_t res; unsigned max_res = MAX_RX_RES; pj_status_t status; PJ_ASSERT_RETURN(codec && codec_param, PJ_EINVAL); PJ_LOG(5,(THIS_FILE, "Opening codec..")); vpx_data = (vpx_codec_data*) codec->codec_data; vpx_data->prm = pjmedia_vid_codec_param_clone(vpx_data->pool, codec_param); param = vpx_data->prm; vpx_data->whole = (param->packing == PJMEDIA_VID_PACKING_WHOLE); /* Apply SDP fmtp to format in codec param */ if (!param->ignore_fmtp) { status = pjmedia_vid_codec_vpx_apply_fmtp(param); if (status != PJ_SUCCESS) return status; } /* * Encoder */ /* Init encoder parameters */ res = vpx_codec_enc_config_default(vpx_data->enc_if(), &cfg, 0); if (res) { PJ_LOG(3, (THIS_FILE, "Failed to get encoder default config")); return PJMEDIA_CODEC_EFAILED; } cfg.g_w = vpx_data->prm->enc_fmt.det.vid.size.w; cfg.g_h = vpx_data->prm->enc_fmt.det.vid.size.h; /* timebase is the inverse of fps */ cfg.g_timebase.num = vpx_data->prm->enc_fmt.det.vid.fps.denum; cfg.g_timebase.den = vpx_data->prm->enc_fmt.det.vid.fps.num; /* bitrate in KBps */ cfg.rc_target_bitrate = vpx_data->prm->enc_fmt.det.vid.avg_bps / 1000; cfg.g_pass = VPX_RC_ONE_PASS; cfg.rc_end_usage = VPX_CBR; cfg.g_threads = 4; cfg.g_lag_in_frames = 0; cfg.g_error_resilient = 0; cfg.rc_undershoot_pct = 95; cfg.rc_min_quantizer = 4; cfg.rc_max_quantizer = 56; cfg.rc_buf_initial_sz = 400; cfg.rc_buf_optimal_sz = 500; cfg.rc_buf_sz = 600; /* kf max distance is 60s. */ cfg.kf_max_dist = 60 * vpx_data->prm->enc_fmt.det.vid.fps.num/ vpx_data->prm->enc_fmt.det.vid.fps.denum; cfg.rc_resize_allowed = 0; cfg.rc_dropframe_thresh = 25; vpx_data->enc_input_size = cfg.g_w * cfg.g_h * 3 >> 1; /* Initialize encoder */ res = vpx_codec_enc_init(&vpx_data->enc, vpx_data->enc_if(), &cfg, 0); if (res) { PJ_LOG(3, (THIS_FILE, "Failed to initialize encoder")); return PJMEDIA_CODEC_EFAILED; } /* Values greater than 0 will increase encoder speed at the expense of * quality. * Valid range for VP8: -16..16 * Valid range for VP9: -9..9 */ vpx_codec_control(&vpx_data->enc, VP8E_SET_CPUUSED, 9); /* * Decoder */ res = vpx_codec_dec_init(&vpx_data->dec, vpx_data->dec_if(), NULL, 0); if (res) { PJ_LOG(3, (THIS_FILE, "Failed to initialize decoder")); return PJMEDIA_CODEC_EFAILED; } /* Parse local fmtp */ status = pjmedia_vid_codec_vpx_parse_fmtp(¶m->dec_fmtp, &vpx_fmtp); if (status != PJ_SUCCESS) return status; if (vpx_fmtp.max_fs > 0) { max_res = ((int)pj_isqrt(vpx_fmtp.max_fs * 8)) * 16; } vpx_data->dec_buf_size = (max_res * max_res * 3 >> 1) + (max_res); vpx_data->dec_buf = (pj_uint8_t*)pj_pool_alloc(vpx_data->pool, vpx_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 vpx_codec_close(pjmedia_vid_codec *codec) { struct vpx_codec_data *vpx_data; PJ_ASSERT_RETURN(codec, PJ_EINVAL); vpx_data = (vpx_codec_data*) codec->codec_data; vpx_codec_destroy(&vpx_data->enc); vpx_codec_destroy(&vpx_data->dec); return PJ_SUCCESS; } static pj_status_t vpx_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 vpx_codec_get_param(pjmedia_vid_codec *codec, pjmedia_vid_codec_param *param) { struct vpx_codec_data *vpx_data; PJ_ASSERT_RETURN(codec && param, PJ_EINVAL); vpx_data = (vpx_codec_data*) codec->codec_data; pj_memcpy(param, vpx_data->prm, sizeof(*param)); return PJ_SUCCESS; } static pj_status_t vpx_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 vpx_codec_data *vpx_data; vpx_image_t img; vpx_enc_frame_flags_t flags = 0; vpx_codec_err_t res; PJ_ASSERT_RETURN(codec && input && out_size && output && has_more, PJ_EINVAL); vpx_data = (vpx_codec_data*) codec->codec_data; PJ_ASSERT_RETURN(input->size == vpx_data->enc_input_size, PJMEDIA_CODEC_EFRMINLEN); vpx_img_wrap(&img, VPX_IMG_FMT_I420, vpx_data->prm->enc_fmt.det.vid.size.w, vpx_data->prm->enc_fmt.det.vid.size.h, 1, (unsigned char *)input->buf); if (opt && opt->force_keyframe) { flags |= VPX_EFLAG_FORCE_KF; } vpx_data->ets = input->timestamp; vpx_data->enc_frame_size = vpx_data->enc_processed = 0; vpx_data->enc_iter = NULL; res = vpx_codec_encode(&vpx_data->enc, &img, vpx_data->ets.u64, 1, flags, VPX_DL_REALTIME); if (res) { PJ_LOG(4, (THIS_FILE, "Failed to encode frame %s", vpx_codec_error(&vpx_data->enc))); return PJMEDIA_CODEC_EFAILED; } do { const vpx_codec_cx_pkt_t *pkt; pkt = vpx_codec_get_cx_data(&vpx_data->enc, &vpx_data->enc_iter); if (!pkt) break; if (pkt->kind == VPX_CODEC_CX_FRAME_PKT) { /* We have a valid frame packet */ vpx_data->enc_frame_whole = pkt->data.frame.buf; vpx_data->enc_frame_size = pkt->data.frame.sz; vpx_data->enc_processed = 0; if (pkt->data.frame.flags & VPX_FRAME_IS_KEY) vpx_data->enc_frame_is_keyframe = PJ_TRUE; else vpx_data->enc_frame_is_keyframe = PJ_FALSE; break; } } while (1); if (vpx_data->enc_frame_size == 0) { *has_more = PJ_FALSE; output->size = 0; output->type = PJMEDIA_FRAME_TYPE_NONE; if (vpx_data->enc.err) { PJ_LOG(4, (THIS_FILE, "Failed to get encoded frame %s", vpx_codec_error(&vpx_data->enc))); } else { return PJ_SUCCESS; } } if (vpx_data->whole) { *has_more = PJ_FALSE; if (vpx_data->enc_frame_size > out_size) return PJMEDIA_CODEC_EFRMTOOSHORT; output->timestamp = vpx_data->ets; output->type = PJMEDIA_FRAME_TYPE_VIDEO; output->size = vpx_data->enc_frame_size; output->bit_info = 0; if (vpx_data->enc_frame_is_keyframe) { output->bit_info |= PJMEDIA_VID_FRM_KEYFRAME; } pj_memcpy(output->buf, vpx_data->enc_frame_whole, output->size); return PJ_SUCCESS; } return vpx_codec_encode_more(codec, out_size, output, has_more); } static pj_status_t vpx_codec_encode_more(pjmedia_vid_codec *codec, unsigned out_size, pjmedia_frame *output, pj_bool_t *has_more) { struct vpx_codec_data *vpx_data; PJ_ASSERT_RETURN(codec && out_size && output && has_more, PJ_EINVAL); vpx_data = (vpx_codec_data*) codec->codec_data; if (vpx_data->enc_processed < vpx_data->enc_frame_size) { unsigned payload_desc_size = 1; unsigned max_size = vpx_data->prm->enc_mtu - payload_desc_size; unsigned remaining_size = vpx_data->enc_frame_size - vpx_data->enc_processed; unsigned payload_len = PJ_MIN(remaining_size, max_size); pj_uint8_t *p = (pj_uint8_t *)output->buf; if (payload_len + payload_desc_size > out_size) return PJMEDIA_CODEC_EFRMTOOSHORT; output->timestamp = vpx_data->ets; output->type = PJMEDIA_FRAME_TYPE_VIDEO; output->bit_info = 0; if (vpx_data->enc_frame_is_keyframe) { output->bit_info |= PJMEDIA_VID_FRM_KEYFRAME; } /* Set payload header */ p[0] = 0; if (vpx_data->prm->enc_fmt.id == PJMEDIA_FORMAT_VP8) { /* Set N: Non-reference frame */ if (!vpx_data->enc_frame_is_keyframe) p[0] |= 0x20; /* Set S: Start of VP8 partition. */ if (vpx_data->enc_processed == 0) p[0] |= 0x10; } else if (vpx_data->prm->enc_fmt.id == PJMEDIA_FORMAT_VP9) { /* Set P: Inter-picture predicted frame */ if (!vpx_data->enc_frame_is_keyframe) p[0] |= 0x40; /* Set B: Start of a frame */ if (vpx_data->enc_processed == 0) p[0] |= 0x8; /* Set E: End of a frame */ if (vpx_data->enc_processed + payload_len == vpx_data->enc_frame_size) { p[0] |= 0x4; } } pj_memcpy(p + payload_desc_size, (vpx_data->enc_frame_whole + vpx_data->enc_processed), payload_len); output->size = payload_len + payload_desc_size; vpx_data->enc_processed += payload_len; *has_more = (vpx_data->enc_processed < vpx_data->enc_frame_size); } return PJ_SUCCESS; } static pj_status_t vpx_unpacketize(struct vpx_codec_data *vpx_data, const pj_uint8_t *buf, pj_size_t packet_size, unsigned *p_desc_len) { unsigned desc_len = 1; pj_uint8_t *p = (pj_uint8_t *)buf; #define INC_DESC_LEN() {if (++desc_len >= packet_size) return PJ_ETOOSMALL;} if (packet_size <= desc_len) return PJ_ETOOSMALL; if (vpx_data->prm->enc_fmt.id == PJMEDIA_FORMAT_VP8) { /* 0 1 2 3 4 5 6 7 * +-+-+-+-+-+-+-+-+ * |X|R|N|S|R| PID | (REQUIRED) */ /* X: Extended control bits present. */ if (p[0] & 0x80) { INC_DESC_LEN(); /* |I|L|T|K| RSV | */ /* I: PictureID present. */ if (p[1] & 0x80) { INC_DESC_LEN(); /* If M bit is set, the PID field MUST contain 15 bits. */ if (p[2] & 0x80) INC_DESC_LEN(); } /* L: TL0PICIDX present. */ if (p[1] & 0x40) INC_DESC_LEN(); /* T: TID present or K: KEYIDX present. */ if ((p[1] & 0x20) || (p[1] & 0x10)) INC_DESC_LEN(); } } else if (vpx_data->prm->enc_fmt.id == PJMEDIA_FORMAT_VP9) { /* 0 1 2 3 4 5 6 7 * +-+-+-+-+-+-+-+-+ * |I|P|L|F|B|E|V|-| (REQUIRED) */ /* I: Picture ID (PID) present. */ if (p[0] & 0x80) { INC_DESC_LEN(); /* If M bit is set, the PID field MUST contain 15 bits. */ if (p[1] & 0x80) INC_DESC_LEN(); } /* L: Layer indices present. */ if (p[0] & 0x20) { INC_DESC_LEN(); if (!(p[0] & 0x10)) INC_DESC_LEN(); } /* F: Flexible mode. * I must also be set to 1, and if P is set, there's up to 3 * reference index. */ if ((p[0] & 0x10) && (p[0] & 0x80) && (p[0] & 0x40)) { unsigned char *q = p + desc_len; INC_DESC_LEN(); if (*q & 0x1) { q++; INC_DESC_LEN(); if (*q & 0x1) { q++; INC_DESC_LEN(); } } } /* V: Scalability structure (SS) data present. */ if (p[0] & 0x2) { unsigned char *q = p + desc_len; unsigned N_S = (*q >> 5) + 1; INC_DESC_LEN(); /* Y: Each spatial layer's frame resolution present. */ if (*q & 0x10) desc_len += N_S * 4; /* G: PG description present flag. */ if (*q & 0x8) { unsigned j; unsigned N_G = *(p + desc_len); INC_DESC_LEN(); for (j = 0; j< N_G; j++) { unsigned R; q = p + desc_len; INC_DESC_LEN(); R = (*q & 0x0F) >> 2; desc_len += R; if (desc_len >= packet_size) return PJ_ETOOSMALL; } } } } #undef INC_DESC_LEN *p_desc_len = desc_len; return PJ_SUCCESS; } static pj_status_t vpx_codec_decode_(pjmedia_vid_codec *codec, pj_size_t count, pjmedia_frame packets[], unsigned out_size, pjmedia_frame *output) { struct vpx_codec_data *vpx_data; pj_bool_t has_frame = PJ_FALSE; unsigned i, whole_len = 0; vpx_codec_iter_t iter = NULL; vpx_image_t *img = NULL; vpx_codec_err_t res; unsigned pos = 0; int plane; PJ_ASSERT_RETURN(codec && count && packets && out_size && output, PJ_EINVAL); PJ_ASSERT_RETURN(output->buf, PJ_EINVAL); vpx_data = (vpx_codec_data*) codec->codec_data; /* * Step 1: unpacketize the packets/frames */ whole_len = 0; if (vpx_data->whole) { for (i = 0; i < count; ++i) { if (whole_len + packets[i].size > vpx_data->dec_buf_size) { PJ_LOG(4,(THIS_FILE, "Decoding buffer overflow [1]")); return PJMEDIA_CODEC_EFRMTOOSHORT; } pj_memcpy( vpx_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) { unsigned desc_len; unsigned packet_size = packets[i].size; pj_status_t status; status = vpx_unpacketize(vpx_data, packets[i].buf, packet_size, &desc_len); if (status != PJ_SUCCESS) { PJ_LOG(4,(THIS_FILE, "Unpacketize error")); return status; } packet_size -= desc_len; if (whole_len + packet_size > vpx_data->dec_buf_size) { PJ_LOG(4,(THIS_FILE, "Decoding buffer overflow [2]")); return PJMEDIA_CODEC_EFRMTOOSHORT; } pj_memcpy(vpx_data->dec_buf + whole_len, (char *)packets[i].buf + desc_len, packet_size); whole_len += packet_size; } } /* Decode */ res = vpx_codec_decode(&vpx_data->dec, vpx_data->dec_buf, whole_len, 0, VPX_DL_REALTIME); if (res) { PJ_LOG(4, (THIS_FILE, "Failed to decode frame %s", vpx_codec_error(&vpx_data->dec))); goto on_return; } img = vpx_codec_get_frame(&vpx_data->dec, &iter); if (!img) { PJ_LOG(4, (THIS_FILE, "Failed to get decoded frame %s", vpx_codec_error(&vpx_data->dec))); goto on_return; } has_frame = PJ_TRUE; /* Detect format change */ if (img->d_w != vpx_data->prm->dec_fmt.det.vid.size.w || img->d_h != vpx_data->prm->dec_fmt.det.vid.size.h) { pjmedia_event event; PJ_LOG(4,(THIS_FILE, "Frame size changed: %dx%d --> %dx%d", vpx_data->prm->dec_fmt.det.vid.size.w, vpx_data->prm->dec_fmt.det.vid.size.h, img->d_w, img->d_h)); vpx_data->prm->dec_fmt.det.vid.size.w = img->d_w; vpx_data->prm->dec_fmt.det.vid.size.h = img->d_h; /* Broadcast format changed event */ pjmedia_event_init(&event, PJMEDIA_EVENT_FMT_CHANGED, &packets[0].timestamp, codec); event.data.fmt_changed.dir = PJMEDIA_DIR_DECODING; pjmedia_format_copy(&event.data.fmt_changed.new_fmt, &vpx_data->prm->dec_fmt); pjmedia_event_publish(NULL, codec, &event, PJMEDIA_EVENT_PUBLISH_DEFAULT); } if (img->d_w * img->d_h * 3/2 > output->size) return PJMEDIA_CODEC_EFRMTOOSHORT; output->type = PJMEDIA_FRAME_TYPE_VIDEO; output->timestamp = packets[0].timestamp; for (plane = 0; plane < 3; ++plane) { const unsigned char *buf = img->planes[plane]; const int stride = img->stride[plane]; const int w = (plane? img->d_w / 2: img->d_w); const int h = (plane? img->d_h / 2: img->d_h); int y; for (y = 0; y < h; ++y) { pj_memcpy((char *)output->buf + pos, buf, w); pos += w; buf += stride; } } output->size = pos; on_return: 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(4,(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 PJ_SUCCESS; } #endif /* PJMEDIA_HAS_VPX_CODEC */