/* * Add a destination media port to the video tee. */ 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_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; }
/* API: create stream */ pj_status_t pjmedia_vid_dev_opengl_imp_create_stream(pj_pool_t *pool, pjmedia_vid_dev_param *param, const pjmedia_vid_dev_cb *cb, void *user_data, pjmedia_vid_dev_stream **p_vid_strm) { struct andgl_stream *strm; const pjmedia_video_format_detail *vfd; pj_status_t status = PJ_SUCCESS; strm = PJ_POOL_ZALLOC_T(pool, struct andgl_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->display = EGL_NO_DISPLAY; vfd = pjmedia_format_get_video_format_detail(&strm->param.fmt, PJ_TRUE); strm->ts_inc = PJMEDIA_SPF2(param->clock_rate, &vfd->fps, 1); /* Set video format */ status = andgl_stream_set_cap(&strm->base, PJMEDIA_VID_DEV_CAP_FORMAT, ¶m->fmt); if (status != PJ_SUCCESS) goto on_error; status = job_queue_create(pool, &strm->jq); if (status != PJ_SUCCESS) goto on_error; if (param->flags & PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW) { status = andgl_stream_set_cap(&strm->base, PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW, ¶m->window); } if (status != PJ_SUCCESS) { PJ_LOG(3, (THIS_FILE, "Failed to initialize OpenGL with the specified" " output window")); goto on_error; } PJ_LOG(4, (THIS_FILE, "Android OpenGL ES renderer successfully created")); /* Done */ strm->base.op = &stream_op; *p_vid_strm = &strm->base; return PJ_SUCCESS; on_error: andgl_stream_destroy((pjmedia_vid_dev_stream *)strm); return status; }
/* H263 pre-open */ static pj_status_t h263_preopen(ffmpeg_private *ff) { h263_data *data; pjmedia_h263_packetizer_cfg pktz_cfg; pj_status_t status; data = PJ_POOL_ZALLOC_T(ff->pool, h263_data); ff->data = data; /* Create packetizer */ pktz_cfg.mtu = ff->param.enc_mtu; pktz_cfg.mode = PJMEDIA_H263_PACKETIZER_MODE_RFC4629; status = pjmedia_h263_packetizer_create(ff->pool, &pktz_cfg, &data->pktz); if (status != PJ_SUCCESS) return status; /* Apply fmtp settings to codec param */ if (!ff->param.ignore_fmtp) { status = pjmedia_vid_codec_h263_apply_fmtp(&ff->param); } /* Override generic params after applying SDP fmtp */ if (ff->param.dir & PJMEDIA_DIR_ENCODING) { pjmedia_video_format_detail *vfd; AVCodecContext *ctx = ff->enc_ctx; vfd = pjmedia_format_get_video_format_detail(&ff->param.enc_fmt, PJ_TRUE); /* Override generic params after applying SDP fmtp */ ctx->width = vfd->size.w; ctx->height = vfd->size.h; ctx->time_base.num = vfd->fps.denum; ctx->time_base.den = vfd->fps.num; } return status; }
/* 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; }
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; }
/* API: create stream */ static pj_status_t and_factory_create_stream( pjmedia_vid_dev_factory *ff, pjmedia_vid_dev_param *param, const pjmedia_vid_dev_cb *cb, void *user_data, pjmedia_vid_dev_stream **p_vid_strm) { and_factory *f = (and_factory*)ff; pj_pool_t *pool; and_stream *strm; and_dev_info *adi; const pjmedia_video_format_detail *vfd; const pjmedia_video_format_info *vfi; pjmedia_video_apply_fmt_param vafp; pj_uint32_t and_fmt; unsigned convert_to_i420 = 0; pj_status_t status = PJ_SUCCESS; JNIEnv *jni_env; pj_bool_t with_attach; jobject jcam; 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)); adi = &f->dev_info[param->cap_id]; vfd = pjmedia_format_get_video_format_detail(¶m->fmt, PJ_TRUE); vfi = pjmedia_get_video_format_info(NULL, param->fmt.id); if (param->fmt.id == PJMEDIA_FORMAT_I420 && adi->forced_i420) { /* Not really support I420, need to convert it from YV12/NV21 */ if (adi->has_nv21) { and_fmt = pj_fmt_to_and(PJMEDIA_FORMAT_NV21); convert_to_i420 = 1; } else if (adi->has_yv12) { and_fmt = pj_fmt_to_and(PJMEDIA_FORMAT_YV12); convert_to_i420 = 2; } else pj_assert(!"Bug!"); } else { and_fmt = pj_fmt_to_and(param->fmt.id); } if (!vfi || !and_fmt) return PJMEDIA_EVID_BADFORMAT; vafp.size = vfd->size; if (vfi->apply_fmt(vfi, &vafp) != PJ_SUCCESS) return PJMEDIA_EVID_BADFORMAT; /* Create and Initialize stream descriptor */ pool = pj_pool_create(f->pf, "and-dev", 512, 512, NULL); PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); strm = PJ_POOL_ZALLOC_T(pool, and_stream); pj_memcpy(&strm->param, param, sizeof(*param)); strm->pool = pool; strm->factory = f; pj_memcpy(&strm->vid_cb, cb, sizeof(*cb)); strm->user_data = user_data; pj_memcpy(&strm->vafp, &vafp, sizeof(vafp)); strm->ts_inc = PJMEDIA_SPF2(param->clock_rate, &vfd->fps, 1); /* Allocate buffer for YV12 -> I420 conversion */ if (convert_to_i420) { pj_assert(vfi->plane_cnt > 1); strm->convert_to_i420 = convert_to_i420; strm->convert_buf = pj_pool_alloc(pool, vafp.plane_bytes[1]); } /* Native preview */ if (param->flags & PJMEDIA_VID_DEV_CAP_INPUT_PREVIEW) { } with_attach = jni_get_env(&jni_env); /* Instantiate PjCamera */ strm->cam_size.w = (vfd->size.w > vfd->size.h? vfd->size.w: vfd->size.h); strm->cam_size.h = (vfd->size.w > vfd->size.h? vfd->size.h: vfd->size.w); jcam = (*jni_env)->NewObject(jni_env, jobjs.cam.cls, jobjs.cam.m_init, adi->dev_idx, /* idx */ strm->cam_size.w, /* w */ strm->cam_size.h, /* h */ and_fmt, /* fmt */ vfd->fps.num*1000/ vfd->fps.denum, /* fps */ (jlong)(intptr_t)strm, /* user data */ NULL /* SurfaceView */ ); if (jcam == NULL) { PJ_LOG(3, (THIS_FILE, "Unable to create PjCamera instance")); status = PJMEDIA_EVID_SYSERR; goto on_return; } strm->jcam = (jobject)(*jni_env)->NewGlobalRef(jni_env, jcam); (*jni_env)->DeleteLocalRef(jni_env, jcam); if (strm->jcam == NULL) { PJ_LOG(3, (THIS_FILE, "Unable to create global ref to PjCamera")); status = PJMEDIA_EVID_SYSERR; goto on_return; } /* Video orientation. * If we send in portrait, we need to set up orientation converter * as well. */ if ((param->flags & PJMEDIA_VID_DEV_CAP_ORIENTATION) || (vfd->size.h > vfd->size.w)) { if (param->orient == PJMEDIA_ORIENT_UNKNOWN) param->orient = PJMEDIA_ORIENT_NATURAL; and_stream_set_cap(&strm->base, PJMEDIA_VID_DEV_CAP_ORIENTATION, ¶m->orient); } on_return: jni_detach_env(with_attach); /* Success */ if (status == PJ_SUCCESS) { strm->base.op = &stream_op; *p_vid_strm = &strm->base; } return status; }
/* API: refresh the list of devices */ static pj_status_t and_factory_refresh(pjmedia_vid_dev_factory *ff) { and_factory *f = (and_factory*)ff; pj_status_t status = PJ_SUCCESS; JNIEnv *jni_env; pj_bool_t with_attach, found_front = PJ_FALSE; int i, dev_count = 0; /* Clean up device info and pool */ f->dev_count = 0; pj_pool_reset(f->dev_pool); with_attach = jni_get_env(&jni_env); /* dev_count = PjCameraInfo::GetCameraCount() */ dev_count = (*jni_env)->CallStaticIntMethod(jni_env, jobjs.cam_info.cls, jobjs.cam_info.m_get_cnt); if (dev_count < 0) { PJ_LOG(3, (THIS_FILE, "Failed to get camera count")); status = PJMEDIA_EVID_SYSERR; goto on_return; } /* Start querying device info */ f->dev_info = (and_dev_info*) pj_pool_calloc(f->dev_pool, dev_count, sizeof(and_dev_info)); for (i = 0; i < dev_count; i++) { and_dev_info *adi = &f->dev_info[f->dev_count]; pjmedia_vid_dev_info *vdi = &adi->info; jobject jdev_info; jobject jtmp; int facing, max_fmt_cnt = PJMEDIA_VID_DEV_INFO_FMT_CNT; /* jdev_info = PjCameraInfo::GetCameraInfo(i) */ jdev_info = (*jni_env)->CallStaticObjectMethod( jni_env, jobjs.cam_info.cls, jobjs.cam_info.m_get_info, i); if (jdev_info == NULL) continue; /* Get camera facing: 0=back 1=front */ facing = (*jni_env)->GetIntField(jni_env, jdev_info, jobjs.cam_info.f_facing); if (facing < 0) goto on_skip_dev; /* Set device ID, direction, and has_callback info */ adi->dev_idx = i; vdi->id = f->dev_count; vdi->dir = PJMEDIA_DIR_CAPTURE; vdi->has_callback = PJ_TRUE; vdi->caps = PJMEDIA_VID_DEV_CAP_SWITCH | PJMEDIA_VID_DEV_CAP_ORIENTATION; /* Set driver & name info */ pj_ansi_strncpy(vdi->driver, "Android", sizeof(vdi->driver)); adi->facing = facing; if (facing == 0) { pj_ansi_strncpy(vdi->name, "Back camera", sizeof(vdi->name)); } else { pj_ansi_strncpy(vdi->name, "Front camera", sizeof(vdi->name)); } /* Get supported sizes */ jtmp = (*jni_env)->GetObjectField(jni_env, jdev_info, jobjs.cam_info.f_sup_size); if (jtmp) { jintArray jiarray = (jintArray*)jtmp; jint *sizes; jsize cnt, j; cnt = (*jni_env)->GetArrayLength(jni_env, jiarray); sizes = (*jni_env)->GetIntArrayElements(jni_env, jiarray, 0); adi->sup_size_cnt = cnt/2; adi->sup_size = pj_pool_calloc(f->dev_pool, adi->sup_size_cnt, sizeof(adi->sup_size[0])); for (j = 0; j < adi->sup_size_cnt; j++) { adi->sup_size[j].w = sizes[j*2]; adi->sup_size[j].h = sizes[j*2+1]; } (*jni_env)->ReleaseIntArrayElements(jni_env, jiarray, sizes, 0); (*jni_env)->DeleteLocalRef(jni_env, jtmp); } else { goto on_skip_dev; } /* Get supported formats */ jtmp = (*jni_env)->GetObjectField(jni_env, jdev_info, jobjs.cam_info.f_sup_fmt); if (jtmp) { jintArray jiarray = (jintArray*)jtmp; jint *fmts; jsize cnt, j; pj_bool_t has_i420 = PJ_FALSE; cnt = (*jni_env)->GetArrayLength(jni_env, jiarray); fmts = (*jni_env)->GetIntArrayElements(jni_env, jiarray, 0); for (j = 0; j < cnt; j++) { int k; pjmedia_format_id fmt = and_fmt_to_pj((pj_uint32_t)fmts[j]); /* Check for any duplicate */ for (k = 0; k < vdi->fmt_cnt; k++) { if (fmt == 0 || fmt == vdi->fmt[k].id) { fmt = 0; break; } } /* Make sure we recognize this format */ if (fmt == 0) continue; /* Check formats for I420 conversion */ if (fmt == PJMEDIA_FORMAT_I420) has_i420 = PJ_TRUE; else if (fmt == PJMEDIA_FORMAT_YV12) adi->has_yv12 = PJ_TRUE; else if (fmt == PJMEDIA_FORMAT_NV21) adi->has_nv21 = PJ_TRUE; for (k = 0; k < adi->sup_size_cnt && vdi->fmt_cnt < max_fmt_cnt-1; k++) { /* Landscape video */ pjmedia_format_init_video(&vdi->fmt[vdi->fmt_cnt++], fmt, adi->sup_size[k].w, adi->sup_size[k].h, DEFAULT_FPS, 1); /* Portrait video */ pjmedia_format_init_video(&vdi->fmt[vdi->fmt_cnt++], fmt, adi->sup_size[k].h, adi->sup_size[k].w, DEFAULT_FPS, 1); } } (*jni_env)->ReleaseIntArrayElements(jni_env, jiarray, fmts, JNI_ABORT); (*jni_env)->DeleteLocalRef(jni_env, jtmp); /* Pretend to support I420/IYUV, only if we support YV12/NV21 */ if (!has_i420 && (adi->has_yv12 || adi->has_nv21) && vdi->fmt_cnt < PJ_ARRAY_SIZE(vdi->fmt)) { int k; adi->forced_i420 = PJ_TRUE; for (k = 0; k < adi->sup_size_cnt && vdi->fmt_cnt < max_fmt_cnt-1; k++) { pjmedia_format_init_video(&vdi->fmt[vdi->fmt_cnt++], PJMEDIA_FORMAT_I420, adi->sup_size[k].w, adi->sup_size[k].h, DEFAULT_FPS, 1); pjmedia_format_init_video(&vdi->fmt[vdi->fmt_cnt++], PJMEDIA_FORMAT_I420, adi->sup_size[k].h, adi->sup_size[k].w, DEFAULT_FPS, 1); } } } else { goto on_skip_dev; } /* If this is front camera, set it as first/default (if not yet) */ if (facing == 1) { if (!found_front && f->dev_count > 0) { /* Swap this front cam info with one whose idx==0 */ and_dev_info tmp_adi; pj_memcpy(&tmp_adi, &f->dev_info[0], sizeof(tmp_adi)); pj_memcpy(&f->dev_info[0], adi, sizeof(tmp_adi)); pj_memcpy(adi, &tmp_adi, sizeof(tmp_adi)); f->dev_info[0].info.id = 0; f->dev_info[f->dev_count].info.id = f->dev_count; } found_front = PJ_TRUE; } f->dev_count++; on_skip_dev: (*jni_env)->DeleteLocalRef(jni_env, jdev_info); } PJ_LOG(4, (THIS_FILE, "Android video capture initialized with %d device(s):", f->dev_count)); for (i = 0; i < f->dev_count; i++) { and_dev_info *adi = &f->dev_info[i]; char tmp_str[2048], *p; int j, plen, slen; PJ_LOG(4, (THIS_FILE, "%2d: %s", i, f->dev_info[i].info.name)); /* Print supported formats */ p = tmp_str; plen = sizeof(tmp_str); for (j = 0; j < adi->info.fmt_cnt; j++) { char tmp_str2[5]; const pjmedia_video_format_detail *vfd = pjmedia_format_get_video_format_detail(&adi->info.fmt[j], 0); pjmedia_fourcc_name(adi->info.fmt[j].id, tmp_str2); slen = pj_ansi_snprintf(p, plen, "%s/%dx%d ", tmp_str2, vfd->size.w, vfd->size.h); if (slen < 0 || slen >= plen) break; plen -= slen; p += slen; } PJ_LOG(4, (THIS_FILE, " supported format = %s", tmp_str)); } on_return: jni_detach_env(with_attach); return status; }
static pj_status_t h264_preopen(ffmpeg_private *ff) { h264_data *data; pjmedia_h264_packetizer_cfg pktz_cfg; pj_status_t status; data = PJ_POOL_ZALLOC_T(ff->pool, h264_data); ff->data = data; /* Parse remote fmtp */ status = pjmedia_vid_codec_h264_parse_fmtp(&ff->param.enc_fmtp, &data->fmtp); if (status != PJ_SUCCESS) return status; /* Create packetizer */ pktz_cfg.mtu = ff->param.enc_mtu; #if 0 if (data->fmtp.packetization_mode == 0) pktz_cfg.mode = PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL; else if (data->fmtp.packetization_mode == 1) pktz_cfg.mode = PJMEDIA_H264_PACKETIZER_MODE_NON_INTERLEAVED; else return PJ_ENOTSUP; #else if (data->fmtp.packetization_mode!= PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL && data->fmtp.packetization_mode!= PJMEDIA_H264_PACKETIZER_MODE_NON_INTERLEAVED) { return PJ_ENOTSUP; } /* Better always send in single NAL mode for better compatibility */ pktz_cfg.mode = PJMEDIA_H264_PACKETIZER_MODE_SINGLE_NAL; #endif status = pjmedia_h264_packetizer_create(ff->pool, &pktz_cfg, &data->pktz); if (status != PJ_SUCCESS) return status; /* Apply SDP fmtp to format in codec param */ if (!ff->param.ignore_fmtp) { status = pjmedia_vid_codec_h264_apply_fmtp(&ff->param); if (status != PJ_SUCCESS) return status; } if (ff->param.dir & PJMEDIA_DIR_ENCODING) { pjmedia_video_format_detail *vfd; AVCodecContext *ctx = ff->enc_ctx; const char *profile = NULL; vfd = pjmedia_format_get_video_format_detail(&ff->param.enc_fmt, PJ_TRUE); /* Override generic params after applying SDP fmtp */ ctx->width = vfd->size.w; ctx->height = vfd->size.h; ctx->time_base.num = vfd->fps.denum; ctx->time_base.den = vfd->fps.num; /* Apply profile. */ ctx->profile = data->fmtp.profile_idc; switch (ctx->profile) { case PROFILE_H264_BASELINE: profile = "baseline"; break; case PROFILE_H264_MAIN: profile = "main"; break; default: break; } if (profile && av_set_string3(ctx->priv_data, "profile", profile, 0, NULL)) { PJ_LOG(3, (THIS_FILE, "Failed to set H264 profile")); } /* Apply profile constraint bits. */ //PJ_TODO(set_h264_constraint_bits_properly_in_ffmpeg); if (data->fmtp.profile_iop) { #if defined(FF_PROFILE_H264_CONSTRAINED) ctx->profile |= FF_PROFILE_H264_CONSTRAINED; #endif } /* Apply profile level. */ ctx->level = data->fmtp.level; /* Limit NAL unit size as we prefer single NAL unit packetization */ if (!av_set_int(ctx->priv_data, "slice-max-size", ff->param.enc_mtu)) { PJ_LOG(3, (THIS_FILE, "Failed to set H264 max NAL size to %d", ff->param.enc_mtu)); } /* Apply intra-refresh */ if (!av_set_int(ctx->priv_data, "intra-refresh", 1)) { PJ_LOG(3, (THIS_FILE, "Failed to set x264 intra-refresh")); } /* Misc x264 settings (performance, quality, latency, etc). * Let's just use the x264 predefined preset & tune. */ if (av_set_string3(ctx->priv_data, "preset", "veryfast", 0, NULL)) { PJ_LOG(3, (THIS_FILE, "Failed to set x264 preset 'veryfast'")); } if (av_set_string3(ctx->priv_data, "tune", "animation+zerolatency", 0, NULL)) { PJ_LOG(3, (THIS_FILE, "Failed to set x264 tune 'zerolatency'")); } } if (ff->param.dir & PJMEDIA_DIR_DECODING) { AVCodecContext *ctx = ff->dec_ctx; /* Apply the "sprop-parameter-sets" fmtp from remote SDP to * extradata of ffmpeg codec context. */ if (data->fmtp.sprop_param_sets_len) { ctx->extradata_size = data->fmtp.sprop_param_sets_len; ctx->extradata = data->fmtp.sprop_param_sets; } } return PJ_SUCCESS; }
static int encode_decode_test(pj_pool_t *pool, const char *codec_id, pjmedia_vid_packing packing) { 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; const char *packing_name; 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; char codec_name[5]; pj_status_t status; int rc = 0; switch (packing) { case PJMEDIA_VID_PACKING_PACKETS: packing_name = "framed"; break; case PJMEDIA_VID_PACKING_WHOLE: packing_name = "whole"; break; default: packing_name = "unknown"; break; } PJ_LOG(3, (THIS_FILE, " encode decode test: codec=%s, packing=%s", codec_id, packing_name)); /* 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; } codec_param.packing = packing; /* 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_subscribe(NULL, &codec_on_event, &codec_port_data, 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; } PJ_LOG(3, (THIS_FILE, " starting codec test: %s<->%.*s %dx%d", pjmedia_fourcc_name(codec_param.dec_fmt.id, codec_name), 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 )); /* 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); if (renderer) pjmedia_vid_port_stop(renderer); if (capture) pjmedia_vid_port_destroy(capture); if (renderer) pjmedia_vid_port_destroy(renderer); if (codec) { pjmedia_event_unsubscribe(NULL, &codec_on_event, &codec_port_data, codec); pjmedia_vid_codec_close(codec); pjmedia_vid_codec_mgr_dealloc_codec(NULL, codec); } return rc; }
/* 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; unsigned max_bitrate = 0; 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; pjmedia_video_format_detail *vfd; 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].packings & PJMEDIA_VID_PACKING_PACKETS) == 0 || 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; } /* Find maximum bitrate in this media */ vfd = pjmedia_format_get_video_format_detail(&codec_param.enc_fmt, PJ_TRUE); if (vfd && max_bitrate < vfd->max_bps) max_bitrate = vfd->max_bps; } /* Put bandwidth info in media level using bandwidth modifier "TIAS" * (RFC3890). */ if (max_bitrate) { const pj_str_t STR_BANDW_MODIFIER = { "TIAS", 4 }; pjmedia_sdp_bandw *b; b = PJ_POOL_ALLOC_T(pool, pjmedia_sdp_bandw); b->modifier = STR_BANDW_MODIFIER; b->value = max_bitrate; m->bandw[m->bandw_count++] = b; } *p_m = m; return PJ_SUCCESS; }
/* Dump media session */ static void dump_media_session(const char *indent, char *buf, unsigned maxlen, pjsua_call *call) { unsigned i; char *p = buf, *end = buf+maxlen; int len; for (i=0; i<call->med_cnt; ++i) { pjsua_call_media *call_med = &call->media[i]; pjmedia_rtcp_stat stat; pj_bool_t has_stat; pjmedia_transport_info tp_info; char rem_addr_buf[80]; char codec_info[32] = {'0'}; char rx_info[80] = {'\0'}; char tx_info[80] = {'\0'}; const char *rem_addr; const char *dir_str; const char *media_type_str; switch (call_med->type) { case PJMEDIA_TYPE_AUDIO: media_type_str = "audio"; break; case PJMEDIA_TYPE_VIDEO: media_type_str = "video"; break; case PJMEDIA_TYPE_APPLICATION: media_type_str = "application"; break; default: media_type_str = "unknown"; break; } /* Check if the stream is deactivated */ if (call_med->tp == NULL || (!call_med->strm.a.stream && !call_med->strm.v.stream)) { len = pj_ansi_snprintf(p, end-p, "%s #%d %s deactivated\n", indent, i, media_type_str); if (len < 1 || len > end-p) { *p = '\0'; return; } p += len; continue; } pjmedia_transport_info_init(&tp_info); pjmedia_transport_get_info(call_med->tp, &tp_info); // rem_addr will contain actual address of RTP originator, instead of // remote RTP address specified by stream which is fetched from the SDP. // Please note that we are assuming only one stream per call. //rem_addr = pj_sockaddr_print(&info.stream_info[i].rem_addr, // rem_addr_buf, sizeof(rem_addr_buf), 3); if (pj_sockaddr_has_addr(&tp_info.src_rtp_name)) { rem_addr = pj_sockaddr_print(&tp_info.src_rtp_name, rem_addr_buf, sizeof(rem_addr_buf), 3); } else { pj_ansi_snprintf(rem_addr_buf, sizeof(rem_addr_buf), "-"); rem_addr = rem_addr_buf; } if (call_med->dir == PJMEDIA_DIR_NONE) { /* To handle when the stream that is currently being paused * (http://trac.pjsip.org/repos/ticket/1079) */ dir_str = "inactive"; } else if (call_med->dir == PJMEDIA_DIR_ENCODING) dir_str = "sendonly"; else if (call_med->dir == PJMEDIA_DIR_DECODING) dir_str = "recvonly"; else if (call_med->dir == PJMEDIA_DIR_ENCODING_DECODING) dir_str = "sendrecv"; else dir_str = "inactive"; if (call_med->type == PJMEDIA_TYPE_AUDIO) { pjmedia_stream *stream = call_med->strm.a.stream; pjmedia_stream_info info; pjmedia_stream_get_stat(stream, &stat); has_stat = PJ_TRUE; pjmedia_stream_get_info(stream, &info); pj_ansi_snprintf(codec_info, sizeof(codec_info), " %.*s @%dkHz", (int)info.fmt.encoding_name.slen, info.fmt.encoding_name.ptr, info.fmt.clock_rate / 1000); pj_ansi_snprintf(rx_info, sizeof(rx_info), "pt=%d,", info.rx_pt); pj_ansi_snprintf(tx_info, sizeof(tx_info), "pt=%d, ptime=%d,", info.tx_pt, info.param->setting.frm_per_pkt* info.param->info.frm_ptime); #if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0) } else if (call_med->type == PJMEDIA_TYPE_VIDEO) { pjmedia_vid_stream *stream = call_med->strm.v.stream; pjmedia_vid_stream_info info; pjmedia_vid_stream_get_stat(stream, &stat); has_stat = PJ_TRUE; pjmedia_vid_stream_get_info(stream, &info); pj_ansi_snprintf(codec_info, sizeof(codec_info), " %.*s", (int)info.codec_info.encoding_name.slen, info.codec_info.encoding_name.ptr); if (call_med->dir & PJMEDIA_DIR_DECODING) { pjmedia_video_format_detail *vfd; vfd = pjmedia_format_get_video_format_detail( &info.codec_param->dec_fmt, PJ_TRUE); pj_ansi_snprintf(rx_info, sizeof(rx_info), "pt=%d, size=%dx%d, fps=%.2f,", info.rx_pt, vfd->size.w, vfd->size.h, vfd->fps.num*1.0/vfd->fps.denum); } if (call_med->dir & PJMEDIA_DIR_ENCODING) { pjmedia_video_format_detail *vfd; vfd = pjmedia_format_get_video_format_detail( &info.codec_param->enc_fmt, PJ_TRUE); pj_ansi_snprintf(tx_info, sizeof(tx_info), "pt=%d, size=%dx%d, fps=%.2f,", info.tx_pt, vfd->size.w, vfd->size.h, vfd->fps.num*1.0/vfd->fps.denum); } #endif /* PJMEDIA_HAS_VIDEO */ } else { has_stat = PJ_FALSE; } len = pj_ansi_snprintf(p, end-p, "%s #%d %s%s, %s, peer=%s\n", indent, call_med->idx, media_type_str, codec_info, dir_str, rem_addr); if (len < 1 || len > end-p) { *p = '\0'; return; } p += len; /* Get and ICE SRTP status */ if (call_med->tp) { pjmedia_transport_info tp_info; pjmedia_transport_info_init(&tp_info); pjmedia_transport_get_info(call_med->tp, &tp_info); if (tp_info.specific_info_cnt > 0) { unsigned j; for (j = 0; j < tp_info.specific_info_cnt; ++j) { if (tp_info.spc_info[j].type == PJMEDIA_TRANSPORT_TYPE_SRTP) { pjmedia_srtp_info *srtp_info = (pjmedia_srtp_info*) tp_info.spc_info[j].buffer; len = pj_ansi_snprintf(p, end-p, " %s SRTP status: %s Crypto-suite: %s", indent, (srtp_info->active?"Active":"Not active"), srtp_info->tx_policy.name.ptr); if (len > 0 && len < end-p) { p += len; *p++ = '\n'; *p = '\0'; } } else if (tp_info.spc_info[j].type==PJMEDIA_TRANSPORT_TYPE_ICE) { const pjmedia_ice_transport_info *ii; unsigned jj; ii = (const pjmedia_ice_transport_info*) tp_info.spc_info[j].buffer; len = pj_ansi_snprintf(p, end-p, " %s ICE role: %s, state: %s, comp_cnt: %u", indent, pj_ice_sess_role_name(ii->role), pj_ice_strans_state_name(ii->sess_state), ii->comp_cnt); if (len > 0 && len < end-p) { p += len; *p++ = '\n'; *p = '\0'; } for (jj=0; ii->sess_state==PJ_ICE_STRANS_STATE_RUNNING && jj<2; ++jj) { const char *type1 = pj_ice_get_cand_type_name(ii->comp[jj].lcand_type); const char *type2 = pj_ice_get_cand_type_name(ii->comp[jj].rcand_type); char addr1[PJ_INET6_ADDRSTRLEN+10]; char addr2[PJ_INET6_ADDRSTRLEN+10]; if (pj_sockaddr_has_addr(&ii->comp[jj].lcand_addr)) pj_sockaddr_print(&ii->comp[jj].lcand_addr, addr1, sizeof(addr1), 3); else strcpy(addr1, "0.0.0.0:0"); if (pj_sockaddr_has_addr(&ii->comp[jj].rcand_addr)) pj_sockaddr_print(&ii->comp[jj].rcand_addr, addr2, sizeof(addr2), 3); else strcpy(addr2, "0.0.0.0:0"); len = pj_ansi_snprintf(p, end-p, " %s [%d]: L:%s (%c) --> R:%s (%c)\n", indent, jj, addr1, type1[0], addr2, type2[0]); if (len > 0 && len < end-p) { p += len; *p = '\0'; } } } } } } if (has_stat) { len = dump_media_stat(indent, p, end-p, &stat, rx_info, tx_info); p += len; } #if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0) # define SAMPLES_TO_USEC(usec, samples, clock_rate) \ do { \ if (samples <= 4294) \ usec = samples * 1000000 / clock_rate; \ else { \ usec = samples * 1000 / clock_rate; \ usec *= 1000; \ } \ } while(0) # define PRINT_VOIP_MTC_VAL(s, v) \ if (v == 127) \ sprintf(s, "(na)"); \ else \ sprintf(s, "%d", v) # define VALIDATE_PRINT_BUF() \ if (len < 1 || len > end-p) { *p = '\0'; return; } \ p += len; *p++ = '\n'; *p = '\0' if (call_med->type == PJMEDIA_TYPE_AUDIO) { pjmedia_stream_info info; char last_update[64]; char loss[16], dup[16]; char jitter[80]; char toh[80]; char plc[16], jba[16], jbr[16]; char signal_lvl[16], noise_lvl[16], rerl[16]; char r_factor[16], ext_r_factor[16], mos_lq[16], mos_cq[16]; pjmedia_rtcp_xr_stat xr_stat; unsigned clock_rate; pj_time_val now; if (pjmedia_stream_get_stat_xr(call_med->strm.a.stream, &xr_stat) != PJ_SUCCESS) { continue; } if (pjmedia_stream_get_info(call_med->strm.a.stream, &info) != PJ_SUCCESS) { continue; } clock_rate = info.fmt.clock_rate; pj_gettimeofday(&now); len = pj_ansi_snprintf(p, end-p, "\n%s Extended reports:", indent); VALIDATE_PRINT_BUF(); /* Statistics Summary */ len = pj_ansi_snprintf(p, end-p, "%s Statistics Summary", indent); VALIDATE_PRINT_BUF(); if (xr_stat.rx.stat_sum.l) sprintf(loss, "%d", xr_stat.rx.stat_sum.lost); else sprintf(loss, "(na)"); if (xr_stat.rx.stat_sum.d) sprintf(dup, "%d", xr_stat.rx.stat_sum.dup); else sprintf(dup, "(na)"); if (xr_stat.rx.stat_sum.j) { unsigned jmin, jmax, jmean, jdev; SAMPLES_TO_USEC(jmin, xr_stat.rx.stat_sum.jitter.min, clock_rate); SAMPLES_TO_USEC(jmax, xr_stat.rx.stat_sum.jitter.max, clock_rate); SAMPLES_TO_USEC(jmean, xr_stat.rx.stat_sum.jitter.mean, clock_rate); SAMPLES_TO_USEC(jdev, pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.jitter), clock_rate); sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f", jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0); } else sprintf(jitter, "(report not available)"); if (xr_stat.rx.stat_sum.t) { sprintf(toh, "%11d %11d %11d %11d", xr_stat.rx.stat_sum.toh.min, xr_stat.rx.stat_sum.toh.mean, xr_stat.rx.stat_sum.toh.max, pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh)); } else sprintf(toh, "(report not available)"); if (xr_stat.rx.stat_sum.update.sec == 0) strcpy(last_update, "never"); else { pj_gettimeofday(&now); PJ_TIME_VAL_SUB(now, xr_stat.rx.stat_sum.update); sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago", now.sec / 3600, (now.sec % 3600) / 60, now.sec % 60, now.msec); } len = pj_ansi_snprintf(p, end-p, "%s RX last update: %s\n" "%s begin seq=%d, end seq=%d\n" "%s pkt loss=%s, dup=%s\n" "%s (msec) min avg max dev\n" "%s jitter : %s\n" "%s toh : %s", indent, last_update, indent, xr_stat.rx.stat_sum.begin_seq, xr_stat.rx.stat_sum.end_seq, indent, loss, dup, indent, indent, jitter, indent, toh ); VALIDATE_PRINT_BUF(); if (xr_stat.tx.stat_sum.l) sprintf(loss, "%d", xr_stat.tx.stat_sum.lost); else sprintf(loss, "(na)"); if (xr_stat.tx.stat_sum.d) sprintf(dup, "%d", xr_stat.tx.stat_sum.dup); else sprintf(dup, "(na)"); if (xr_stat.tx.stat_sum.j) { unsigned jmin, jmax, jmean, jdev; SAMPLES_TO_USEC(jmin, xr_stat.tx.stat_sum.jitter.min, clock_rate); SAMPLES_TO_USEC(jmax, xr_stat.tx.stat_sum.jitter.max, clock_rate); SAMPLES_TO_USEC(jmean, xr_stat.tx.stat_sum.jitter.mean, clock_rate); SAMPLES_TO_USEC(jdev, pj_math_stat_get_stddev(&xr_stat.tx.stat_sum.jitter), clock_rate); sprintf(jitter, "%7.3f %7.3f %7.3f %7.3f", jmin/1000.0, jmean/1000.0, jmax/1000.0, jdev/1000.0); } else sprintf(jitter, "(report not available)"); if (xr_stat.tx.stat_sum.t) { sprintf(toh, "%11d %11d %11d %11d", xr_stat.tx.stat_sum.toh.min, xr_stat.tx.stat_sum.toh.mean, xr_stat.tx.stat_sum.toh.max, pj_math_stat_get_stddev(&xr_stat.rx.stat_sum.toh)); } else sprintf(toh, "(report not available)"); if (xr_stat.tx.stat_sum.update.sec == 0) strcpy(last_update, "never"); else { pj_gettimeofday(&now); PJ_TIME_VAL_SUB(now, xr_stat.tx.stat_sum.update); sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago", now.sec / 3600, (now.sec % 3600) / 60, now.sec % 60, now.msec); } len = pj_ansi_snprintf(p, end-p, "%s TX last update: %s\n" "%s begin seq=%d, end seq=%d\n" "%s pkt loss=%s, dup=%s\n" "%s (msec) min avg max dev\n" "%s jitter : %s\n" "%s toh : %s", indent, last_update, indent, xr_stat.tx.stat_sum.begin_seq, xr_stat.tx.stat_sum.end_seq, indent, loss, dup, indent, indent, jitter, indent, toh ); VALIDATE_PRINT_BUF(); /* VoIP Metrics */ len = pj_ansi_snprintf(p, end-p, "%s VoIP Metrics", indent); VALIDATE_PRINT_BUF(); PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.rx.voip_mtc.signal_lvl); PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.rx.voip_mtc.noise_lvl); PRINT_VOIP_MTC_VAL(rerl, xr_stat.rx.voip_mtc.rerl); PRINT_VOIP_MTC_VAL(r_factor, xr_stat.rx.voip_mtc.r_factor); PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.rx.voip_mtc.ext_r_factor); PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.rx.voip_mtc.mos_lq); PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.rx.voip_mtc.mos_cq); switch ((xr_stat.rx.voip_mtc.rx_config>>6) & 3) { case PJMEDIA_RTCP_XR_PLC_DIS: sprintf(plc, "DISABLED"); break; case PJMEDIA_RTCP_XR_PLC_ENH: sprintf(plc, "ENHANCED"); break; case PJMEDIA_RTCP_XR_PLC_STD: sprintf(plc, "STANDARD"); break; case PJMEDIA_RTCP_XR_PLC_UNK: default: sprintf(plc, "UNKNOWN"); break; } switch ((xr_stat.rx.voip_mtc.rx_config>>4) & 3) { case PJMEDIA_RTCP_XR_JB_FIXED: sprintf(jba, "FIXED"); break; case PJMEDIA_RTCP_XR_JB_ADAPTIVE: sprintf(jba, "ADAPTIVE"); break; default: sprintf(jba, "UNKNOWN"); break; } sprintf(jbr, "%d", xr_stat.rx.voip_mtc.rx_config & 0x0F); if (xr_stat.rx.voip_mtc.update.sec == 0) strcpy(last_update, "never"); else { pj_gettimeofday(&now); PJ_TIME_VAL_SUB(now, xr_stat.rx.voip_mtc.update); sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago", now.sec / 3600, (now.sec % 3600) / 60, now.sec % 60, now.msec); } len = pj_ansi_snprintf(p, end-p, "%s RX last update: %s\n" "%s packets : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n" "%s burst : density=%d (%.2f%%), duration=%d%s\n" "%s gap : density=%d (%.2f%%), duration=%d%s\n" "%s delay : round trip=%d%s, end system=%d%s\n" "%s level : signal=%s%s, noise=%s%s, RERL=%s%s\n" "%s quality : R factor=%s, ext R factor=%s\n" "%s MOS LQ=%s, MOS CQ=%s\n" "%s config : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n" "%s JB delay : cur=%d%s, max=%d%s, abs max=%d%s", indent, last_update, /* packets */ indent, xr_stat.rx.voip_mtc.loss_rate, xr_stat.rx.voip_mtc.loss_rate*100.0/256, xr_stat.rx.voip_mtc.discard_rate, xr_stat.rx.voip_mtc.discard_rate*100.0/256, /* burst */ indent, xr_stat.rx.voip_mtc.burst_den, xr_stat.rx.voip_mtc.burst_den*100.0/256, xr_stat.rx.voip_mtc.burst_dur, "ms", /* gap */ indent, xr_stat.rx.voip_mtc.gap_den, xr_stat.rx.voip_mtc.gap_den*100.0/256, xr_stat.rx.voip_mtc.gap_dur, "ms", /* delay */ indent, xr_stat.rx.voip_mtc.rnd_trip_delay, "ms", xr_stat.rx.voip_mtc.end_sys_delay, "ms", /* level */ indent, signal_lvl, "dB", noise_lvl, "dB", rerl, "", /* quality */ indent, r_factor, ext_r_factor, indent, mos_lq, mos_cq, /* config */ indent, plc, jba, jbr, xr_stat.rx.voip_mtc.gmin, /* JB delay */ indent, xr_stat.rx.voip_mtc.jb_nom, "ms", xr_stat.rx.voip_mtc.jb_max, "ms", xr_stat.rx.voip_mtc.jb_abs_max, "ms" ); VALIDATE_PRINT_BUF(); PRINT_VOIP_MTC_VAL(signal_lvl, xr_stat.tx.voip_mtc.signal_lvl); PRINT_VOIP_MTC_VAL(noise_lvl, xr_stat.tx.voip_mtc.noise_lvl); PRINT_VOIP_MTC_VAL(rerl, xr_stat.tx.voip_mtc.rerl); PRINT_VOIP_MTC_VAL(r_factor, xr_stat.tx.voip_mtc.r_factor); PRINT_VOIP_MTC_VAL(ext_r_factor, xr_stat.tx.voip_mtc.ext_r_factor); PRINT_VOIP_MTC_VAL(mos_lq, xr_stat.tx.voip_mtc.mos_lq); PRINT_VOIP_MTC_VAL(mos_cq, xr_stat.tx.voip_mtc.mos_cq); switch ((xr_stat.tx.voip_mtc.rx_config>>6) & 3) { case PJMEDIA_RTCP_XR_PLC_DIS: sprintf(plc, "DISABLED"); break; case PJMEDIA_RTCP_XR_PLC_ENH: sprintf(plc, "ENHANCED"); break; case PJMEDIA_RTCP_XR_PLC_STD: sprintf(plc, "STANDARD"); break; case PJMEDIA_RTCP_XR_PLC_UNK: default: sprintf(plc, "unknown"); break; } switch ((xr_stat.tx.voip_mtc.rx_config>>4) & 3) { case PJMEDIA_RTCP_XR_JB_FIXED: sprintf(jba, "FIXED"); break; case PJMEDIA_RTCP_XR_JB_ADAPTIVE: sprintf(jba, "ADAPTIVE"); break; default: sprintf(jba, "unknown"); break; } sprintf(jbr, "%d", xr_stat.tx.voip_mtc.rx_config & 0x0F); if (xr_stat.tx.voip_mtc.update.sec == 0) strcpy(last_update, "never"); else { pj_gettimeofday(&now); PJ_TIME_VAL_SUB(now, xr_stat.tx.voip_mtc.update); sprintf(last_update, "%02ldh:%02ldm:%02ld.%03lds ago", now.sec / 3600, (now.sec % 3600) / 60, now.sec % 60, now.msec); } len = pj_ansi_snprintf(p, end-p, "%s TX last update: %s\n" "%s packets : loss rate=%d (%.2f%%), discard rate=%d (%.2f%%)\n" "%s burst : density=%d (%.2f%%), duration=%d%s\n" "%s gap : density=%d (%.2f%%), duration=%d%s\n" "%s delay : round trip=%d%s, end system=%d%s\n" "%s level : signal=%s%s, noise=%s%s, RERL=%s%s\n" "%s quality : R factor=%s, ext R factor=%s\n" "%s MOS LQ=%s, MOS CQ=%s\n" "%s config : PLC=%s, JB=%s, JB rate=%s, Gmin=%d\n" "%s JB delay : cur=%d%s, max=%d%s, abs max=%d%s", indent, last_update, /* pakcets */ indent, xr_stat.tx.voip_mtc.loss_rate, xr_stat.tx.voip_mtc.loss_rate*100.0/256, xr_stat.tx.voip_mtc.discard_rate, xr_stat.tx.voip_mtc.discard_rate*100.0/256, /* burst */ indent, xr_stat.tx.voip_mtc.burst_den, xr_stat.tx.voip_mtc.burst_den*100.0/256, xr_stat.tx.voip_mtc.burst_dur, "ms", /* gap */ indent, xr_stat.tx.voip_mtc.gap_den, xr_stat.tx.voip_mtc.gap_den*100.0/256, xr_stat.tx.voip_mtc.gap_dur, "ms", /* delay */ indent, xr_stat.tx.voip_mtc.rnd_trip_delay, "ms", xr_stat.tx.voip_mtc.end_sys_delay, "ms", /* level */ indent, signal_lvl, "dB", noise_lvl, "dB", rerl, "", /* quality */ indent, r_factor, ext_r_factor, indent, mos_lq, mos_cq, /* config */ indent, plc, jba, jbr, xr_stat.tx.voip_mtc.gmin, /* JB delay */ indent, xr_stat.tx.voip_mtc.jb_nom, "ms", xr_stat.tx.voip_mtc.jb_max, "ms", xr_stat.tx.voip_mtc.jb_abs_max, "ms" ); VALIDATE_PRINT_BUF(); /* RTT delay (by receiver side) */ len = pj_ansi_snprintf(p, end-p, "%s RTT (from recv) min avg max last dev", indent); VALIDATE_PRINT_BUF(); len = pj_ansi_snprintf(p, end-p, "%s RTT msec : %7.3f %7.3f %7.3f %7.3f %7.3f", indent, xr_stat.rtt.min / 1000.0, xr_stat.rtt.mean / 1000.0, xr_stat.rtt.max / 1000.0, xr_stat.rtt.last / 1000.0, pj_math_stat_get_stddev(&xr_stat.rtt) / 1000.0 ); VALIDATE_PRINT_BUF(); } /* if audio */; #endif }
/* API: set capability */ static pj_status_t andgl_stream_set_cap(pjmedia_vid_dev_stream *s, pjmedia_vid_dev_cap cap, const void *pval) { struct andgl_stream *strm = (struct andgl_stream*)s; PJ_UNUSED_ARG(strm); PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); if (cap==PJMEDIA_VID_DEV_CAP_FORMAT) { const pjmedia_video_format_info *vfi; pjmedia_video_format_detail *vfd; pjmedia_format *fmt = (pjmedia_format *)pval; andgl_fmt_info *ifi; if (!(ifi = get_andgl_format_info(fmt->id))) return PJMEDIA_EVID_BADFORMAT; vfi = pjmedia_get_video_format_info(pjmedia_video_format_mgr_instance(), fmt->id); if (!vfi) return PJMEDIA_EVID_BADFORMAT; pjmedia_format_copy(&strm->param.fmt, fmt); vfd = pjmedia_format_get_video_format_detail(fmt, PJ_TRUE); pj_memcpy(&strm->vid_size, &vfd->size, sizeof(vfd->size)); if (strm->param.disp_size.w == 0 || strm->param.disp_size.h == 0) pj_memcpy(&strm->param.disp_size, &vfd->size, sizeof(vfd->size)); return PJ_SUCCESS; } else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW) { pj_bool_t is_running = strm->is_running; pj_status_t status = PJ_SUCCESS; pjmedia_vid_dev_hwnd *wnd = (pjmedia_vid_dev_hwnd *)pval; ANativeWindow *native_wnd = (ANativeWindow *)wnd->info.android.window; if (strm->window == native_wnd) return PJ_SUCCESS; /* Stop the stream and re-init OpenGL */ andgl_stream_stop(s); job_queue_post_job(strm->jq, deinit_opengl, strm, 0, NULL); strm->window = strm->param.window.info.android.window = native_wnd; if (strm->window) { job_queue_post_job(strm->jq, init_opengl, strm, 0, &status); } PJ_LOG(3, (THIS_FILE, "Re-initializing OpenGL with window %p: %s", strm->window, (status == PJ_SUCCESS? "success": "failed"))); if (is_running) andgl_stream_start(s); return status; } else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE) { pj_memcpy(&strm->param.disp_size, pval, sizeof(strm->param.disp_size)); return PJ_SUCCESS; } else if (cap == PJMEDIA_VID_DEV_CAP_ORIENTATION) { pj_memcpy(&strm->param.orient, pval, sizeof(strm->param.orient)); if (strm->param.orient == PJMEDIA_ORIENT_UNKNOWN) return PJ_SUCCESS; return PJ_SUCCESS; } return PJMEDIA_EVID_INVCAP; }
static void vid_handle_menu(char *menuin) { char *argv[8]; int argc = 0; /* Tokenize */ argv[argc] = strtok(menuin, " \t\r\n"); while (argv[argc] && *argv[argc]) { argc++; argv[argc] = strtok(NULL, " \t\r\n"); } if (argc == 1 || strcmp(argv[1], "help")==0) { vid_show_help(); } else if (argc == 2 && (strcmp(argv[1], "enable")==0 || strcmp(argv[1], "disable")==0)) { pj_bool_t enabled = (strcmp(argv[1], "enable")==0); app_config.vid.vid_cnt = (enabled ? 1 : 0); PJ_LOG(3,(THIS_FILE, "Video will be %s in next offer/answer", (enabled?"enabled":"disabled"))); } else if (strcmp(argv[1], "acc")==0) { pjsua_acc_config acc_cfg; pj_bool_t changed = PJ_FALSE; pj_pool_t *tmp_pool = pjsua_pool_create("tmp-pjsua", 1000, 1000); pjsua_acc_get_config(current_acc, tmp_pool, &acc_cfg); if (argc == 3 && strcmp(argv[2], "show")==0) { app_config_show_video(current_acc, &acc_cfg); } else if (argc == 4 && strcmp(argv[2], "autorx")==0) { int on = (strcmp(argv[3], "on")==0); acc_cfg.vid_in_auto_show = on; changed = PJ_TRUE; } else if (argc == 4 && strcmp(argv[2], "autotx")==0) { int on = (strcmp(argv[3], "on")==0); acc_cfg.vid_out_auto_transmit = on; changed = PJ_TRUE; } else if (argc == 4 && strcmp(argv[2], "cap")==0) { int dev = atoi(argv[3]); acc_cfg.vid_cap_dev = dev; changed = PJ_TRUE; } else if (argc == 4 && strcmp(argv[2], "rend")==0) { int dev = atoi(argv[3]); acc_cfg.vid_rend_dev = dev; changed = PJ_TRUE; } else { pj_pool_release(tmp_pool); goto on_error; } if (changed) { pj_status_t status = pjsua_acc_modify(current_acc, &acc_cfg); if (status != PJ_SUCCESS) PJ_PERROR(1,(THIS_FILE, status, "Error modifying account %d", current_acc)); } pj_pool_release(tmp_pool); } else if (strcmp(argv[1], "call")==0) { pjsua_call_vid_strm_op_param param; pj_status_t status = PJ_SUCCESS; pjsua_call_vid_strm_op_param_default(¶m); if (argc == 5 && strcmp(argv[2], "rx")==0) { pjsua_stream_info si; pj_bool_t on = (strcmp(argv[3], "on") == 0); param.med_idx = atoi(argv[4]); if (pjsua_call_get_stream_info(current_call, param.med_idx, &si) || si.type != PJMEDIA_TYPE_VIDEO) { PJ_PERROR(1,(THIS_FILE, PJ_EINVAL, "Invalid stream")); return; } if (on) param.dir = (si.info.vid.dir | PJMEDIA_DIR_DECODING); else param.dir = (si.info.vid.dir & PJMEDIA_DIR_ENCODING); status = pjsua_call_set_vid_strm(current_call, PJSUA_CALL_VID_STRM_CHANGE_DIR, ¶m); } else if (argc == 5 && strcmp(argv[2], "tx")==0) { pj_bool_t on = (strcmp(argv[3], "on") == 0); pjsua_call_vid_strm_op op = on? PJSUA_CALL_VID_STRM_START_TRANSMIT : PJSUA_CALL_VID_STRM_STOP_TRANSMIT; param.med_idx = atoi(argv[4]); status = pjsua_call_set_vid_strm(current_call, op, ¶m); } else if (argc == 3 && strcmp(argv[2], "add")==0) { status = pjsua_call_set_vid_strm(current_call, PJSUA_CALL_VID_STRM_ADD, NULL); } else if (argc >= 3 && (strcmp(argv[2], "disable")==0 || strcmp(argv[2], "enable")==0)) { pj_bool_t enable = (strcmp(argv[2], "enable") == 0); pjsua_call_vid_strm_op op = enable? PJSUA_CALL_VID_STRM_CHANGE_DIR : PJSUA_CALL_VID_STRM_REMOVE; param.med_idx = argc >= 4? atoi(argv[3]) : -1; param.dir = PJMEDIA_DIR_ENCODING_DECODING; status = pjsua_call_set_vid_strm(current_call, op, ¶m); } else if (argc >= 3 && strcmp(argv[2], "cap")==0) { param.med_idx = argc >= 4? atoi(argv[3]) : -1; param.cap_dev = argc >= 5? atoi(argv[4]) : PJMEDIA_VID_DEFAULT_CAPTURE_DEV; status = pjsua_call_set_vid_strm(current_call, PJSUA_CALL_VID_STRM_CHANGE_CAP_DEV, ¶m); } else goto on_error; if (status != PJ_SUCCESS) { PJ_PERROR(1,(THIS_FILE, status, "Error modifying video stream")); } } else if (argc >= 3 && strcmp(argv[1], "dev")==0) { if (strcmp(argv[2], "list")==0) { vid_list_devs(); } else if (strcmp(argv[2], "refresh")==0) { pjmedia_vid_dev_refresh(); } else if (strcmp(argv[2], "prev")==0) { if (argc != 5) { goto on_error; } else { pj_bool_t on = (strcmp(argv[3], "on") == 0); int dev_id = atoi(argv[4]); if (on) { pjsua_vid_preview_param param; pjsua_vid_preview_param_default(¶m); param.wnd_flags = PJMEDIA_VID_DEV_WND_BORDER | PJMEDIA_VID_DEV_WND_RESIZABLE; pjsua_vid_preview_start(dev_id, ¶m); arrange_window(pjsua_vid_preview_get_win(dev_id)); } else { pjsua_vid_win_id wid; wid = pjsua_vid_preview_get_win(dev_id); if (wid != PJSUA_INVALID_ID) { /* Preview window hiding once it is stopped is * responsibility of app */ pjsua_vid_win_set_show(wid, PJ_FALSE); pjsua_vid_preview_stop(dev_id); } } } } else goto on_error; } else if (strcmp(argv[1], "win")==0) { pj_status_t status = PJ_SUCCESS; if (argc==3 && strcmp(argv[2], "list")==0) { pjsua_vid_win_id wids[PJSUA_MAX_VID_WINS]; unsigned i, cnt = PJ_ARRAY_SIZE(wids); pjsua_vid_enum_wins(wids, &cnt); PJ_LOG(3,(THIS_FILE, "Found %d video windows:", cnt)); PJ_LOG(3,(THIS_FILE, "WID show pos size")); PJ_LOG(3,(THIS_FILE, "------------------------------")); for (i = 0; i < cnt; ++i) { pjsua_vid_win_info wi; pjsua_vid_win_get_info(wids[i], &wi); PJ_LOG(3,(THIS_FILE, "%3d %c (%d,%d) %dx%d", wids[i], (wi.show?'Y':'N'), wi.pos.x, wi.pos.y, wi.size.w, wi.size.h)); } } else if (argc==4 && (strcmp(argv[2], "show")==0 || strcmp(argv[2], "hide")==0)) { pj_bool_t show = (strcmp(argv[2], "show")==0); pjsua_vid_win_id wid = atoi(argv[3]); status = pjsua_vid_win_set_show(wid, show); } else if (argc==6 && strcmp(argv[2], "move")==0) { pjsua_vid_win_id wid = atoi(argv[3]); pjmedia_coord pos; pos.x = atoi(argv[4]); pos.y = atoi(argv[5]); status = pjsua_vid_win_set_pos(wid, &pos); } else if (argc==6 && strcmp(argv[2], "resize")==0) { pjsua_vid_win_id wid = atoi(argv[3]); pjmedia_rect_size size; size.w = atoi(argv[4]); size.h = atoi(argv[5]); status = pjsua_vid_win_set_size(wid, &size); } else if (argc==3 && strcmp(argv[2], "arrange")==0) { arrange_window(PJSUA_INVALID_ID); } else goto on_error; if (status != PJ_SUCCESS) { PJ_PERROR(1,(THIS_FILE, status, "Window operation error")); } } else if (strcmp(argv[1], "codec")==0) { pjsua_codec_info ci[PJMEDIA_CODEC_MGR_MAX_CODECS]; unsigned count = PJ_ARRAY_SIZE(ci); pj_status_t status; if (argc==3 && strcmp(argv[2], "list")==0) { status = pjsua_vid_enum_codecs(ci, &count); if (status != PJ_SUCCESS) { PJ_PERROR(1,(THIS_FILE, status, "Error enumerating codecs")); } else { unsigned i; PJ_LOG(3,(THIS_FILE, "Found %d video codecs:", count)); PJ_LOG(3,(THIS_FILE, "codec id prio fps bw(kbps) size")); PJ_LOG(3,(THIS_FILE, "------------------------------------------")); for (i=0; i<count; ++i) { pjmedia_vid_codec_param cp; pjmedia_video_format_detail *vfd; status = pjsua_vid_codec_get_param(&ci[i].codec_id, &cp); if (status != PJ_SUCCESS) continue; vfd = pjmedia_format_get_video_format_detail(&cp.enc_fmt, PJ_TRUE); PJ_LOG(3,(THIS_FILE, "%.*s%.*s %3d %7.2f %4d/%4d %dx%d", (int)ci[i].codec_id.slen, ci[i].codec_id.ptr, 13-(int)ci[i].codec_id.slen, " ", ci[i].priority, (vfd->fps.num*1.0/vfd->fps.denum), vfd->avg_bps/1000, vfd->max_bps/1000, vfd->size.w, vfd->size.h)); } } } else if (argc==5 && strcmp(argv[2], "prio")==0) { pj_str_t cid; int prio; cid = pj_str(argv[3]); prio = atoi(argv[4]); status = pjsua_vid_codec_set_priority(&cid, (pj_uint8_t)prio); if (status != PJ_SUCCESS) PJ_PERROR(1,(THIS_FILE, status, "Set codec priority error")); } else if (argc==6 && strcmp(argv[2], "fps")==0) { pjmedia_vid_codec_param cp; pj_str_t cid; int M, N; cid = pj_str(argv[3]); M = atoi(argv[4]); N = atoi(argv[5]); status = pjsua_vid_codec_get_param(&cid, &cp); if (status == PJ_SUCCESS) { cp.enc_fmt.det.vid.fps.num = M; cp.enc_fmt.det.vid.fps.denum = N; status = pjsua_vid_codec_set_param(&cid, &cp); } if (status != PJ_SUCCESS) PJ_PERROR(1,(THIS_FILE, status, "Set codec framerate error")); } else if (argc==6 && strcmp(argv[2], "bw")==0) { pjmedia_vid_codec_param cp; pj_str_t cid; int M, N; cid = pj_str(argv[3]); M = atoi(argv[4]); N = atoi(argv[5]); status = pjsua_vid_codec_get_param(&cid, &cp); if (status == PJ_SUCCESS) { cp.enc_fmt.det.vid.avg_bps = M * 1000; cp.enc_fmt.det.vid.max_bps = N * 1000; status = pjsua_vid_codec_set_param(&cid, &cp); } if (status != PJ_SUCCESS) PJ_PERROR(1,(THIS_FILE, status, "Set codec bitrate error")); } else if (argc==6 && strcmp(argv[2], "size")==0) { pjmedia_vid_codec_param cp; pj_str_t cid; int M, N; cid = pj_str(argv[3]); M = atoi(argv[4]); N = atoi(argv[5]); status = pjsua_vid_codec_get_param(&cid, &cp); if (status == PJ_SUCCESS) { cp.enc_fmt.det.vid.size.w = M; cp.enc_fmt.det.vid.size.h = N; status = pjsua_vid_codec_set_param(&cid, &cp); } if (status != PJ_SUCCESS) PJ_PERROR(1,(THIS_FILE, status, "Set codec size error")); } else goto on_error; } else goto on_error; return; on_error: PJ_LOG(1,(THIS_FILE, "Invalid command, use 'vid help'")); }
/* API: set capability */ static pj_status_t andgl_stream_set_cap(pjmedia_vid_dev_stream *s, pjmedia_vid_dev_cap cap, const void *pval) { struct andgl_stream *strm = (struct andgl_stream*)s; PJ_UNUSED_ARG(strm); PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); if (cap==PJMEDIA_VID_DEV_CAP_FORMAT) { const pjmedia_video_format_info *vfi; pjmedia_video_format_detail *vfd; pjmedia_format *fmt = (pjmedia_format *)pval; andgl_fmt_info *ifi; if (!(ifi = get_andgl_format_info(fmt->id))) return PJMEDIA_EVID_BADFORMAT; vfi = pjmedia_get_video_format_info(pjmedia_video_format_mgr_instance(), fmt->id); if (!vfi) return PJMEDIA_EVID_BADFORMAT; /* Re-init OpenGL */ if (strm->window) job_queue_post_job(strm->jq, deinit_opengl, strm, 0, NULL); pjmedia_format_copy(&strm->param.fmt, fmt); vfd = pjmedia_format_get_video_format_detail(fmt, PJ_TRUE); pj_memcpy(&strm->vid_size, &vfd->size, sizeof(vfd->size)); pj_memcpy(&strm->param.disp_size, &vfd->size, sizeof(vfd->size)); if (strm->window) job_queue_post_job(strm->jq, init_opengl, strm, 0, NULL); PJ_LOG(4, (THIS_FILE, "Re-initializing OpenGL due to format change")); return PJ_SUCCESS; } else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW) { pj_status_t status = PJ_SUCCESS; pjmedia_vid_dev_hwnd *wnd = (pjmedia_vid_dev_hwnd *)pval; ANativeWindow *native_wnd = (ANativeWindow *)wnd->info.android.window; if (strm->window == native_wnd) return PJ_SUCCESS; /* Re-init OpenGL */ job_queue_post_job(strm->jq, deinit_opengl, strm, 0, NULL); if (strm->window) ANativeWindow_release(strm->window); strm->window = strm->param.window.info.android.window = native_wnd; if (strm->window) { job_queue_post_job(strm->jq, init_opengl, strm, 0, &status); } PJ_LOG(4, (THIS_FILE, "Re-initializing OpenGL with native window" " %p: %s", strm->window, (status == PJ_SUCCESS? "success": "failed"))); return status; } return PJMEDIA_EVID_INVCAP; }
/* * 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 payload type for receiving direction */ si->rx_pt = pt; /* Get codec info and payload type for transmitting direction. */ if (pt < 96) { /* For static payload types, get the codec info from codec manager. */ 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 transmitting direction. * For static payload type, pt's are symetric. */ si->tx_pt = pt; } else { const pjmedia_sdp_attr *attr; pjmedia_sdp_rtpmap *rtpmap; pjmedia_codec_id codec_id; pj_str_t codec_id_st; 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; /* For dynamic payload types, get codec name from the rtpmap */ attr = pjmedia_sdp_media_find_attr(local_m, &ID_RTPMAP, &local_m->desc.fmt[0]); if (attr == NULL) return PJMEDIA_EMISSINGRTPMAP; status = pjmedia_sdp_attr_to_rtpmap(pool, attr, &rtpmap); if (status != PJ_SUCCESS) return status; /* Then get the codec info from the codec manager */ pj_ansi_snprintf(codec_id, sizeof(codec_id), "%.*s/", (int)rtpmap->enc_name.slen, rtpmap->enc_name.ptr); codec_id_st = pj_str(codec_id); i = 1; status = pjmedia_vid_codec_mgr_find_codecs_by_id(mgr, &codec_id_st, &i, &p_info, NULL); if (status != PJ_SUCCESS) return status; si->codec_info = *p_info; } /* Request for codec with the correct packing for streaming */ si->codec_info.packings = PJMEDIA_VID_PACKING_PACKETS; /* 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); /* Adjust encoding bitrate, if higher than remote preference. The remote * bitrate preference is read from SDP "b=TIAS" line in media level. */ if ((si->dir & PJMEDIA_DIR_ENCODING) && rem_m->bandw_count) { unsigned i, bandw = 0; for (i = 0; i < rem_m->bandw_count; ++i) { const pj_str_t STR_BANDW_MODIFIER_TIAS = { "TIAS", 4 }; if (!pj_stricmp(&rem_m->bandw[i]->modifier, &STR_BANDW_MODIFIER_TIAS)) { bandw = rem_m->bandw[i]->value; break; } } if (bandw) { pjmedia_video_format_detail *enc_vfd; enc_vfd = pjmedia_format_get_video_format_detail( &si->codec_param->enc_fmt, PJ_TRUE); if (!enc_vfd->avg_bps || enc_vfd->avg_bps > bandw) enc_vfd->avg_bps = bandw * 3 / 4; if (!enc_vfd->max_bps || enc_vfd->max_bps > bandw) enc_vfd->max_bps = bandw; } } /* 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; }
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]; char fmt_name[5]; 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; 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; pjmedia_fourcc_name(vparam.fmt.id, fmt_name); 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), fmt_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, fmt_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_subscribe(NULL, &vidstream_event_cb, vp, vp->strm); if (vp->dir & PJMEDIA_DIR_CAPTURE) { pjmedia_format_copy(&vp->conv.conv_param.src, &vparam.fmt); pjmedia_format_copy(&vp->conv.conv_param.dst, &prm->vidparam.fmt); } else { pjmedia_format_copy(&vp->conv.conv_param.src, &prm->vidparam.fmt); pjmedia_format_copy(&vp->conv.conv_param.dst, &vparam.fmt); } status = create_converter(vp); if (status != PJ_SUCCESS) goto on_error; if (vp->role==ROLE_ACTIVE && ((vp->dir & PJMEDIA_DIR_ENCODING) || 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. For encoding direction, * we also want to create our own clock since the device's clock * may run at a different rate. */ 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); 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_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_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->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; }
/* * main() */ int main(int argc, char *argv[]) { pj_caching_pool cp; pjmedia_endpt *med_endpt; pj_pool_t *pool; pjmedia_vid_stream *stream = NULL; pjmedia_port *enc_port, *dec_port; pj_status_t status; pjmedia_vid_port *capture=NULL, *renderer=NULL; pjmedia_vid_port_param vpp; #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) /* SRTP variables */ pj_bool_t use_srtp = PJ_FALSE; char tmp_tx_key[64]; char tmp_rx_key[64]; pj_str_t srtp_tx_key = {NULL, 0}; pj_str_t srtp_rx_key = {NULL, 0}; pj_str_t srtp_crypto_suite = {NULL, 0}; int tmp_key_len; #endif /* Default values */ const pjmedia_vid_codec_info *codec_info; pjmedia_vid_codec_param codec_param; pjmedia_dir dir = PJMEDIA_DIR_DECODING; pj_sockaddr_in remote_addr; pj_uint16_t local_port = 4000; char *codec_id = NULL; pjmedia_rect_size tx_size = {0}; pj_int8_t rx_pt = -1, tx_pt = -1; play_file_data play_file = { NULL }; pjmedia_port *play_port = NULL; pjmedia_vid_codec *play_decoder = NULL; pjmedia_clock *play_clock = NULL; enum { OPT_CODEC = 'c', OPT_LOCAL_PORT = 'p', OPT_REMOTE = 'r', OPT_PLAY_FILE = 'f', OPT_SEND_RECV = 'b', OPT_SEND_ONLY = 's', OPT_RECV_ONLY = 'i', OPT_SEND_WIDTH = 'W', OPT_SEND_HEIGHT = 'H', OPT_RECV_PT = 't', OPT_SEND_PT = 'T', #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) OPT_USE_SRTP = 'S', #endif OPT_SRTP_TX_KEY = 'x', OPT_SRTP_RX_KEY = 'y', OPT_HELP = 'h', }; struct pj_getopt_option long_options[] = { { "codec", 1, 0, OPT_CODEC }, { "local-port", 1, 0, OPT_LOCAL_PORT }, { "remote", 1, 0, OPT_REMOTE }, { "play-file", 1, 0, OPT_PLAY_FILE }, { "send-recv", 0, 0, OPT_SEND_RECV }, { "send-only", 0, 0, OPT_SEND_ONLY }, { "recv-only", 0, 0, OPT_RECV_ONLY }, { "send-width", 1, 0, OPT_SEND_WIDTH }, { "send-height", 1, 0, OPT_SEND_HEIGHT }, { "recv-pt", 1, 0, OPT_RECV_PT }, { "send-pt", 1, 0, OPT_SEND_PT }, #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) { "use-srtp", 2, 0, OPT_USE_SRTP }, { "srtp-tx-key", 1, 0, OPT_SRTP_TX_KEY }, { "srtp-rx-key", 1, 0, OPT_SRTP_RX_KEY }, #endif { "help", 0, 0, OPT_HELP }, { NULL, 0, 0, 0 }, }; int c; int option_index; pj_bzero(&remote_addr, sizeof(remote_addr)); /* init PJLIB : */ status = pj_init(); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); /* Parse arguments */ pj_optind = 0; while((c=pj_getopt_long(argc,argv, "h", long_options, &option_index))!=-1) { switch (c) { case OPT_CODEC: codec_id = pj_optarg; break; case OPT_LOCAL_PORT: local_port = (pj_uint16_t) atoi(pj_optarg); if (local_port < 1) { printf("Error: invalid local port %s\n", pj_optarg); return 1; } break; case OPT_REMOTE: { pj_str_t ip = pj_str(strtok(pj_optarg, ":")); pj_uint16_t port = (pj_uint16_t) atoi(strtok(NULL, ":")); status = pj_sockaddr_in_init(&remote_addr, &ip, port); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "Invalid remote address", status); return 1; } } break; case OPT_PLAY_FILE: play_file.file_name = pj_optarg; break; case OPT_SEND_RECV: dir = PJMEDIA_DIR_ENCODING_DECODING; break; case OPT_SEND_ONLY: dir = PJMEDIA_DIR_ENCODING; break; case OPT_RECV_ONLY: dir = PJMEDIA_DIR_DECODING; break; case OPT_SEND_WIDTH: tx_size.w = (unsigned)atoi(pj_optarg); break; case OPT_SEND_HEIGHT: tx_size.h = (unsigned)atoi(pj_optarg); break; case OPT_RECV_PT: rx_pt = (pj_int8_t)atoi(pj_optarg); break; case OPT_SEND_PT: tx_pt = (pj_int8_t)atoi(pj_optarg); break; #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) case OPT_USE_SRTP: use_srtp = PJ_TRUE; if (pj_optarg) { pj_strset(&srtp_crypto_suite, pj_optarg, strlen(pj_optarg)); } else { srtp_crypto_suite = pj_str("AES_CM_128_HMAC_SHA1_80"); } break; case OPT_SRTP_TX_KEY: tmp_key_len = hex_string_to_octet_string(tmp_tx_key, pj_optarg, strlen(pj_optarg)); pj_strset(&srtp_tx_key, tmp_tx_key, tmp_key_len/2); break; case OPT_SRTP_RX_KEY: tmp_key_len = hex_string_to_octet_string(tmp_rx_key, pj_optarg, strlen(pj_optarg)); pj_strset(&srtp_rx_key, tmp_rx_key, tmp_key_len/2); break; #endif case OPT_HELP: usage(); return 1; default: printf("Invalid options %s\n", argv[pj_optind]); return 1; } } /* Verify arguments. */ if (dir & PJMEDIA_DIR_ENCODING) { if (remote_addr.sin_addr.s_addr == 0) { printf("Error: remote address must be set\n"); return 1; } } if (play_file.file_name != NULL && dir != PJMEDIA_DIR_ENCODING) { printf("Direction is set to --send-only because of --play-file\n"); dir = PJMEDIA_DIR_ENCODING; } #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) /* SRTP validation */ if (use_srtp) { if (!srtp_tx_key.slen || !srtp_rx_key.slen) { printf("Error: Key for each SRTP stream direction must be set\n"); return 1; } } #endif /* Must create a pool factory before we can allocate any memory. */ pj_caching_pool_init(&cp, &pj_pool_factory_default_policy, 0); /* * Initialize media endpoint. * This will implicitly initialize PJMEDIA too. */ status = pjmedia_endpt_create(&cp.factory, NULL, 1, &med_endpt); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); /* Create memory pool for application purpose */ pool = pj_pool_create( &cp.factory, /* pool factory */ "app", /* pool name. */ 4000, /* init size */ 4000, /* increment size */ NULL /* callback on error */ ); /* Init video format manager */ pjmedia_video_format_mgr_create(pool, 64, 0, NULL); /* Init video converter manager */ pjmedia_converter_mgr_create(pool, NULL); /* Init event manager */ pjmedia_event_mgr_create(pool, 0, NULL); /* Init video codec manager */ pjmedia_vid_codec_mgr_create(pool, NULL); /* Init video subsystem */ pjmedia_vid_dev_subsys_init(&cp.factory); /* Register all supported codecs */ status = init_codecs(&cp.factory); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); /* Find which codec to use. */ if (codec_id) { unsigned count = 1; pj_str_t str_codec_id = pj_str(codec_id); status = pjmedia_vid_codec_mgr_find_codecs_by_id(NULL, &str_codec_id, &count, &codec_info, NULL); if (status != PJ_SUCCESS) { printf("Error: unable to find codec %s\n", codec_id); return 1; } } else { static pjmedia_vid_codec_info info[1]; unsigned count = PJ_ARRAY_SIZE(info); /* Default to first codec */ pjmedia_vid_codec_mgr_enum_codecs(NULL, &count, info, NULL); codec_info = &info[0]; } /* Get codec default param for info */ status = pjmedia_vid_codec_mgr_get_default_param(NULL, codec_info, &codec_param); pj_assert(status == PJ_SUCCESS); /* Set outgoing video size */ if (tx_size.w && tx_size.h) codec_param.enc_fmt.det.vid.size = tx_size; #if DEF_RENDERER_WIDTH && DEF_RENDERER_HEIGHT /* Set incoming video size */ if (DEF_RENDERER_WIDTH > codec_param.dec_fmt.det.vid.size.w) codec_param.dec_fmt.det.vid.size.w = DEF_RENDERER_WIDTH; if (DEF_RENDERER_HEIGHT > codec_param.dec_fmt.det.vid.size.h) codec_param.dec_fmt.det.vid.size.h = DEF_RENDERER_HEIGHT; #endif if (play_file.file_name) { pjmedia_video_format_detail *file_vfd; pjmedia_clock_param clock_param; char fmt_name[5]; /* Create file player */ status = create_file_player(pool, play_file.file_name, &play_port); if (status != PJ_SUCCESS) goto on_exit; /* Collect format info */ file_vfd = pjmedia_format_get_video_format_detail(&play_port->info.fmt, PJ_TRUE); PJ_LOG(2, (THIS_FILE, "Reading video stream %dx%d %s @%.2ffps", file_vfd->size.w, file_vfd->size.h, pjmedia_fourcc_name(play_port->info.fmt.id, fmt_name), (1.0*file_vfd->fps.num/file_vfd->fps.denum))); /* Allocate file read buffer */ play_file.read_buf_size = PJMEDIA_MAX_VIDEO_ENC_FRAME_SIZE; play_file.read_buf = pj_pool_zalloc(pool, play_file.read_buf_size); /* Create decoder, if the file and the stream uses different codec */ if (codec_info->fmt_id != (pjmedia_format_id)play_port->info.fmt.id) { const pjmedia_video_format_info *dec_vfi; pjmedia_video_apply_fmt_param dec_vafp = {0}; const pjmedia_vid_codec_info *codec_info2; pjmedia_vid_codec_param codec_param2; /* Find decoder */ status = pjmedia_vid_codec_mgr_get_codec_info2(NULL, play_port->info.fmt.id, &codec_info2); if (status != PJ_SUCCESS) goto on_exit; /* Init decoder */ status = pjmedia_vid_codec_mgr_alloc_codec(NULL, codec_info2, &play_decoder); if (status != PJ_SUCCESS) goto on_exit; status = play_decoder->op->init(play_decoder, pool); if (status != PJ_SUCCESS) goto on_exit; /* Open decoder */ status = pjmedia_vid_codec_mgr_get_default_param(NULL, codec_info2, &codec_param2); if (status != PJ_SUCCESS) goto on_exit; codec_param2.dir = PJMEDIA_DIR_DECODING; status = play_decoder->op->open(play_decoder, &codec_param2); if (status != PJ_SUCCESS) goto on_exit; /* Get decoder format info and apply param */ dec_vfi = pjmedia_get_video_format_info(NULL, codec_info2->dec_fmt_id[0]); if (!dec_vfi || !dec_vfi->apply_fmt) { status = PJ_ENOTSUP; goto on_exit; } dec_vafp.size = file_vfd->size; (*dec_vfi->apply_fmt)(dec_vfi, &dec_vafp); /* Allocate buffer to receive decoder output */ play_file.dec_buf_size = dec_vafp.framebytes; play_file.dec_buf = pj_pool_zalloc(pool, play_file.dec_buf_size); } /* Create player clock */ clock_param.usec_interval = PJMEDIA_PTIME(&file_vfd->fps); clock_param.clock_rate = codec_info->clock_rate; status = pjmedia_clock_create2(pool, &clock_param, PJMEDIA_CLOCK_NO_HIGHEST_PRIO, &clock_cb, &play_file, &play_clock); if (status != PJ_SUCCESS) goto on_exit; /* Override stream codec param for encoding direction */ codec_param.enc_fmt.det.vid.size = file_vfd->size; codec_param.enc_fmt.det.vid.fps = file_vfd->fps; } else { pjmedia_vid_port_param_default(&vpp); /* Set as active for all video devices */ vpp.active = PJ_TRUE; /* Create video device port. */ if (dir & PJMEDIA_DIR_ENCODING) { /* Create capture */ status = pjmedia_vid_dev_default_param( pool, PJMEDIA_VID_DEFAULT_CAPTURE_DEV, &vpp.vidparam); if (status != PJ_SUCCESS) goto on_exit; pjmedia_format_copy(&vpp.vidparam.fmt, &codec_param.enc_fmt); vpp.vidparam.fmt.id = codec_param.dec_fmt.id; vpp.vidparam.dir = PJMEDIA_DIR_CAPTURE; status = pjmedia_vid_port_create(pool, &vpp, &capture); if (status != PJ_SUCCESS) goto on_exit; } if (dir & PJMEDIA_DIR_DECODING) { /* Create renderer */ status = pjmedia_vid_dev_default_param( pool, PJMEDIA_VID_DEFAULT_RENDER_DEV, &vpp.vidparam); if (status != PJ_SUCCESS) goto on_exit; pjmedia_format_copy(&vpp.vidparam.fmt, &codec_param.dec_fmt); vpp.vidparam.dir = PJMEDIA_DIR_RENDER; vpp.vidparam.disp_size = vpp.vidparam.fmt.det.vid.size; vpp.vidparam.flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS; vpp.vidparam.window_flags = PJMEDIA_VID_DEV_WND_BORDER | PJMEDIA_VID_DEV_WND_RESIZABLE; status = pjmedia_vid_port_create(pool, &vpp, &renderer); if (status != PJ_SUCCESS) goto on_exit; } } /* Set to ignore fmtp */ codec_param.ignore_fmtp = PJ_TRUE; /* Create stream based on program arguments */ status = create_stream(pool, med_endpt, codec_info, &codec_param, dir, rx_pt, tx_pt, local_port, &remote_addr, #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) use_srtp, &srtp_crypto_suite, &srtp_tx_key, &srtp_rx_key, #endif &stream); if (status != PJ_SUCCESS) goto on_exit; /* Get the port interface of the stream */ status = pjmedia_vid_stream_get_port(stream, PJMEDIA_DIR_ENCODING, &enc_port); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); status = pjmedia_vid_stream_get_port(stream, PJMEDIA_DIR_DECODING, &dec_port); PJ_ASSERT_RETURN(status == PJ_SUCCESS, 1); /* Start streaming */ status = pjmedia_vid_stream_start(stream); if (status != PJ_SUCCESS) goto on_exit; /* Start renderer */ if (renderer) { status = pjmedia_vid_port_connect(renderer, dec_port, PJ_FALSE); if (status != PJ_SUCCESS) goto on_exit; status = pjmedia_vid_port_start(renderer); if (status != PJ_SUCCESS) goto on_exit; } /* Start capture */ if (capture) { status = pjmedia_vid_port_connect(capture, enc_port, PJ_FALSE); if (status != PJ_SUCCESS) goto on_exit; status = pjmedia_vid_port_start(capture); if (status != PJ_SUCCESS) goto on_exit; } /* Start playing file */ if (play_file.file_name) { #if HAS_LOCAL_RENDERER_FOR_PLAY_FILE /* Create local renderer */ pjmedia_vid_port_param_default(&vpp); vpp.active = PJ_FALSE; status = pjmedia_vid_dev_default_param( pool, PJMEDIA_VID_DEFAULT_RENDER_DEV, &vpp.vidparam); if (status != PJ_SUCCESS) goto on_exit; vpp.vidparam.dir = PJMEDIA_DIR_RENDER; pjmedia_format_copy(&vpp.vidparam.fmt, &codec_param.dec_fmt); vpp.vidparam.fmt.det.vid.size = play_port->info.fmt.det.vid.size; vpp.vidparam.fmt.det.vid.fps = play_port->info.fmt.det.vid.fps; vpp.vidparam.disp_size = vpp.vidparam.fmt.det.vid.size; status = pjmedia_vid_port_create(pool, &vpp, &renderer); if (status != PJ_SUCCESS) goto on_exit; status = pjmedia_vid_port_start(renderer); if (status != PJ_SUCCESS) goto on_exit; #endif /* Init play file data */ play_file.play_port = play_port; play_file.stream_port = enc_port; play_file.decoder = play_decoder; if (renderer) { play_file.renderer = pjmedia_vid_port_get_passive_port(renderer); } status = pjmedia_clock_start(play_clock); if (status != PJ_SUCCESS) goto on_exit; } /* Done */ if (dir == PJMEDIA_DIR_DECODING) printf("Stream is active, dir is recv-only, local port is %d\n", local_port); else if (dir == PJMEDIA_DIR_ENCODING) printf("Stream is active, dir is send-only, sending to %s:%d\n", pj_inet_ntoa(remote_addr.sin_addr), pj_ntohs(remote_addr.sin_port)); else printf("Stream is active, send/recv, local port is %d, " "sending to %s:%d\n", local_port, pj_inet_ntoa(remote_addr.sin_addr), pj_ntohs(remote_addr.sin_port)); if (dir & PJMEDIA_DIR_ENCODING) PJ_LOG(2, (THIS_FILE, "Sending %dx%d %.*s @%.2ffps", codec_param.enc_fmt.det.vid.size.w, codec_param.enc_fmt.det.vid.size.h, codec_info->encoding_name.slen, codec_info->encoding_name.ptr, (1.0*codec_param.enc_fmt.det.vid.fps.num/ codec_param.enc_fmt.det.vid.fps.denum))); for (;;) { char tmp[10]; puts(""); puts("Commands:"); puts(" q Quit"); puts(""); printf("Command: "); fflush(stdout); if (fgets(tmp, sizeof(tmp), stdin) == NULL) { puts("EOF while reading stdin, will quit now.."); break; } if (tmp[0] == 'q') break; } /* Start deinitialization: */ on_exit: /* Stop and destroy file clock */ if (play_clock) { pjmedia_clock_stop(play_clock); pjmedia_clock_destroy(play_clock); } /* Destroy file reader/player */ if (play_port) pjmedia_port_destroy(play_port); /* Destroy file decoder */ if (play_decoder) { play_decoder->op->close(play_decoder); pjmedia_vid_codec_mgr_dealloc_codec(NULL, play_decoder); } /* Destroy video devices */ if (capture) pjmedia_vid_port_destroy(capture); if (renderer) pjmedia_vid_port_destroy(renderer); /* Destroy stream */ if (stream) { pjmedia_transport *tp; tp = pjmedia_vid_stream_get_transport(stream); pjmedia_vid_stream_destroy(stream); pjmedia_transport_close(tp); } /* Deinit codecs */ deinit_codecs(); /* Shutdown video subsystem */ pjmedia_vid_dev_subsys_shutdown(); /* Destroy event manager */ pjmedia_event_mgr_destroy(NULL); /* Release application pool */ pj_pool_release( pool ); /* Destroy media endpoint. */ pjmedia_endpt_destroy( med_endpt ); /* Destroy pool factory */ pj_caching_pool_destroy( &cp ); /* Shutdown PJLIB */ pj_shutdown(); return (status == PJ_SUCCESS) ? 0 : 1; }
/** * Find the closest supported format from the specific requested format. * The algo is to find a supported size with the matching format id, width and * lowest diff_ratio. * --- * For format id matching, the priority is: * 1. Find exact match * 2. Find format with the same color space * 3. Use the first supported format. * --- * For ratio matching: * Find the lowest difference of the aspect ratio between the requested and * the supported format. */ static struct fmt_prop find_closest_fmt(pj_uint32_t req_fmt_id, pjmedia_rect_size *req_fmt_size, pjmedia_ratio *req_fmt_fps, pjmedia_vid_dev_info *di) { unsigned i, match_idx = 0; pj_uint32_t match_fmt_id; float req_ratio, min_diff_ratio = 0.0; struct fmt_prop ret_prop; pj_bool_t found_exact_match = PJ_FALSE; #define GET_DIFF(x, y) ((x) > (y)? (x-y) : (y-x)) /* This will contain the supported format with lowest width difference */ pjmedia_rect_size nearest_width[PJMEDIA_VID_PORT_MATCH_WIDTH_ARRAY_SIZE]; /* Initialize the list. */ for (i=0;i<PJMEDIA_VID_PORT_MATCH_WIDTH_ARRAY_SIZE;++i) { nearest_width[i].w = 0xFFFFFFFF; nearest_width[i].h = 0; } /* Get the matching format id. We assume each format will support all * image size. */ match_fmt_id = get_match_format_id(req_fmt_id, di); /* Search from the supported format, the smallest diff width. Stop the * search if exact match is found. */ for (i=0;i<di->fmt_cnt;++i) { pjmedia_video_format_detail *vfd; unsigned diff_width1, diff_width2; /* Ignore supported format with different format id. */ if (di->fmt[i].id != match_fmt_id) continue; vfd = pjmedia_format_get_video_format_detail(&di->fmt[i], PJ_TRUE); /* Exact match found. */ if ((vfd->size.w == req_fmt_size->w) && (vfd->size.h == req_fmt_size->h)) { nearest_width[0] = vfd->size; found_exact_match = PJ_TRUE; break; } diff_width1 = GET_DIFF(vfd->size.w, req_fmt_size->w); diff_width2 = GET_DIFF(nearest_width[0].w, req_fmt_size->w); /* Fill the nearest width list. */ if (diff_width1 <= diff_width2) { int k = 1; pjmedia_rect_size tmp_size = vfd->size; while(((GET_DIFF(tmp_size.w, req_fmt_size->w) < (GET_DIFF(nearest_width[k].w, req_fmt_size->w))) && (k < PJ_ARRAY_SIZE(nearest_width)))) { nearest_width[k-1] = nearest_width[k]; ++k; } nearest_width[k-1] = tmp_size; } } /* No need to calculate ratio if exact match is found. */ if (!found_exact_match) { pj_bool_t found_match = PJ_FALSE; /* We have the list of supported format with nearest width. Now get the * best ratio. */ req_ratio = (float)req_fmt_size->w / (float)req_fmt_size->h; for (i=0;i<PJ_ARRAY_SIZE(nearest_width);++i) { float sup_ratio, diff_ratio; if (nearest_width[i].w == 0xFFFFFFFF) continue; sup_ratio = (float)nearest_width[i].w / (float)nearest_width[i].h; diff_ratio = GET_DIFF(sup_ratio, req_ratio); if ((!found_match) || (diff_ratio <= min_diff_ratio)) { found_match = PJ_TRUE; match_idx = i; min_diff_ratio = diff_ratio; } } } ret_prop.id = match_fmt_id; ret_prop.size = nearest_width[match_idx]; ret_prop.fps = *req_fmt_fps; return ret_prop; }
/* 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; stream->fd = v4l2_open(vdi->dev_name, O_RDWR, 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; }
static pj_status_t open_openh264_codec(openh264_private *ff, pj_mutex_t *ff_mutex) { pjmedia_video_format_detail *vfd; pj_bool_t enc_opened = PJ_FALSE, dec_opened = PJ_FALSE; pj_status_t status; vfd = pjmedia_format_get_video_format_detail(&ff->param.enc_fmt, PJ_TRUE); /* Override generic params or apply specific params before opening * the codec. */ if (ff->desc->preopen) { status = (*ff->desc->preopen)(ff); if (status != PJ_SUCCESS) goto on_error; } /* Open encoder */ if (ff->param.dir & PJMEDIA_DIR_ENCODING) { int err; SEncParamExt *param = &ff->enc_param; const openh264_codec_desc *desc = &ff->desc[0]; bool disable = 0; int iIndexLayer = 0; SSourcePicture *srcPic; pj_mutex_lock(ff_mutex); memset(param, 0x00, sizeof(SEncParamExt)); CreateSVCEncoder(&ff->enc); /* Test for temporal, spatial, SNR scalability */ param->fMaxFrameRate = (float)vfd->fps.num; // input frame rate param->iPicWidth = vfd->size.w; // width of picture in samples param->iPicHeight = vfd->size.h; // height of picture in samples param->iTargetBitrate = desc->avg_bps; // target bitrate desired param->bEnableRc = PJ_TRUE; // rc mode control param->iTemporalLayerNum = 3; // layer number at temporal level param->iSpatialLayerNum = 1; // layer number at spatial level param->bEnableDenoise = PJ_TRUE; // denoise control param->bEnableBackgroundDetection = PJ_TRUE; // background detection control param->bEnableAdaptiveQuant = PJ_TRUE; // adaptive quantization control param->bEnableFrameSkip = PJ_TRUE; // frame skipping param->bEnableLongTermReference = PJ_FALSE; // long term reference control param->bEnableFrameCroppingFlag = PJ_FALSE; param->iLoopFilterDisableIdc = 0; param->iInputCsp = videoFormatI420; // color space of input sequence param->uiIntraPeriod = 300; // period of Intra frame param->bEnableSpsPpsIdAddition = 0; param->bPrefixNalAddingCtrl = 0; param->sSpatialLayers[iIndexLayer].iVideoWidth = vfd->size.w; param->sSpatialLayers[iIndexLayer].iVideoHeight = vfd->size.h; param->sSpatialLayers[iIndexLayer].fFrameRate = (float)vfd->fps.num; param->sSpatialLayers[iIndexLayer].iSpatialBitrate = desc->avg_bps; // param->sSpatialLayers[iIndexLayer].iDLayerQp = 50; param->sSpatialLayers[iIndexLayer].uiProfileIdc = 66; param->sSpatialLayers[iIndexLayer].sSliceCfg.uiSliceMode = 4; param->sSpatialLayers[iIndexLayer].sSliceCfg.sSliceArgument.uiSliceSizeConstraint = PJMEDIA_MAX_VID_PAYLOAD_SIZE; err = callWelsEncoderFn(ff->enc)->InitializeExt(ff->enc, param); if (err == cmResultSuccess) { callWelsEncoderFn(ff->enc)->SetOption(ff->enc, ENCODER_OPTION_ENABLE_SSEI, &disable); enc_opened = PJ_TRUE; } srcPic = malloc(sizeof(SSourcePicture)); memset(srcPic, 0x00, sizeof(SSourcePicture)); srcPic->iColorFormat = param->iInputCsp; srcPic->iPicWidth = param->iPicWidth; srcPic->iPicHeight = param->iPicHeight; srcPic->iStride[0] = param->iPicWidth; srcPic->iStride[1] = param->iPicWidth / 2; srcPic->iStride[2] = param->iPicWidth / 2; ff->srcPic = srcPic; pj_mutex_unlock(ff_mutex); } /* Open decoder */ if (ff->param.dir & PJMEDIA_DIR_DECODING) { SDecodingParam sDecParam = {0}; pj_mutex_lock(ff_mutex); CreateDecoder(&ff->dec); sDecParam.iOutputColorFormat = videoFormatI420; sDecParam.uiTargetDqLayer = (unsigned char)-1; sDecParam.uiEcActiveFlag = 1; sDecParam.sVideoProperty.eVideoBsType = VIDEO_BITSTREAM_DEFAULT; callWelsDecoderFn(ff->dec)->Initialize(ff->dec, &sDecParam); pj_mutex_unlock(ff_mutex); dec_opened = PJ_TRUE; } /* Let the codec apply specific params after the codec opened */ if (ff->desc->postopen) { status = (*ff->desc->postopen)(ff); if (status != PJ_SUCCESS) goto on_error; } return PJ_SUCCESS; on_error: return status; }
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; 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_subscribe(NULL, &vid_event_cb, NULL, renderer); /* 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_stop(capture); if (renderer) pjmedia_vid_port_stop(renderer); if (capture) pjmedia_vid_port_destroy(capture); if (renderer) { pjmedia_event_unsubscribe(NULL, &vid_event_cb, NULL, renderer); pjmedia_vid_port_destroy(renderer); } pj_pool_release(pool); return rc; }
int CCameraPjsip::OnOpen(VideoInfo* pVideoInfo) { int nRet = 0; pj_status_t status = PJ_FALSE; if (m_pVidDevStream) { LOG_MODEL_ERROR("CCamera", "camera has opened"); return -1; } pj_caching_pool_init(&m_Caching_pool, &pj_pool_factory_default_policy, 0); pj_pool_t* pPool = pj_pool_create(&m_Caching_pool.factory, "carmeraPara", 1000, 1000, NULL); if (NULL == pPool) { LOG_MODEL_ERROR("CCamera", "pj_pool_create fail"); return -2; } pjmedia_vid_dev_param param; status = pjmedia_vid_dev_default_param(pPool, m_nIndex, ¶m); if (PJ_SUCCESS != status) { LOG_MODEL_ERROR("CCamera", "pjmedia_vid_dev_default_param fail"); pj_pool_release(pPool); return -3; } pj_pool_release(pPool); if (param.fmt.detail_type != PJMEDIA_FORMAT_DETAIL_VIDEO) { LOG_MODEL_ERROR("CCamera", "deivce isn't video capture"); return -4; } if (pVideoInfo) { pjmedia_format fmt; pjmedia_format_init_video(&fmt, pVideoInfo->Format, pVideoInfo->nWidth, pVideoInfo->nHeight, pVideoInfo->nRatio, 1 ); param.fmt = fmt; m_VideoInfo = *pVideoInfo; } param.dir = PJMEDIA_DIR_CAPTURE; status = pjmedia_vid_dev_stream_create(¶m, &m_VidDevCb, this, &m_pVidDevStream); if (PJ_SUCCESS != status) { LOG_MODEL_ERROR("CCamera", "pjmedia_vid_dev_stream_create faile"); return -5; } //检查设备指定的格式 pjmedia_video_format_detail *vfd; vfd = pjmedia_format_get_video_format_detail(¶m.fmt, PJ_FALSE); if (vfd == NULL) { LOG_MODEL_ERROR("CCamera", "deivce don't contains the format"); Close(); return -6; } m_VideoInfo.nWidth = vfd->size.w; m_VideoInfo.nHeight = vfd->size.h; m_VideoInfo.nRatio = vfd->fps.num; m_VideoInfo.Format = (VideoFormat) param.fmt.id; return nRet; }
static int aviplay(pj_pool_t *pool, const char *fname) { pjmedia_vid_port *renderer=NULL; pjmedia_vid_port_param param; const pjmedia_video_format_info *vfi; pjmedia_video_format_detail *vfd; pjmedia_snd_port *snd_port = NULL; pj_status_t status; int rc = 0; pjmedia_avi_streams *avi_streams; pjmedia_avi_stream *vid_stream, *aud_stream; pjmedia_port *vid_port = NULL, *aud_port = NULL; pjmedia_vid_codec *codec=NULL; avi_port_t avi_port; pj_bzero(&avi_port, sizeof(avi_port)); status = pjmedia_avi_player_create_streams(pool, fname, 0, &avi_streams); if (status != PJ_SUCCESS) { PJ_PERROR(2,("", status, " Error playing %s", fname)); rc = 210; goto on_return; } vid_stream = pjmedia_avi_streams_get_stream_by_media(avi_streams, 0, PJMEDIA_TYPE_VIDEO); vid_port = pjmedia_avi_stream_get_port(vid_stream); if (vid_port) { pjmedia_vid_port_param_default(¶m); status = pjmedia_vid_dev_default_param(pool, PJMEDIA_VID_DEFAULT_RENDER_DEV, ¶m.vidparam); if (status != PJ_SUCCESS) { rc = 220; goto on_return; } /* Create renderer, set it to active */ param.active = PJ_TRUE; param.vidparam.dir = PJMEDIA_DIR_RENDER; vfd = pjmedia_format_get_video_format_detail(&vid_port->info.fmt, PJ_TRUE); pjmedia_format_init_video(¶m.vidparam.fmt, vid_port->info.fmt.id, vfd->size.w, vfd->size.h, vfd->fps.num, vfd->fps.denum); vfi = pjmedia_get_video_format_info( pjmedia_video_format_mgr_instance(), vid_port->info.fmt.id); /* Check whether the frame is encoded */ if (!vfi || vfi->bpp == 0) { /* Yes, prepare codec */ pj_str_t codec_id_st; unsigned info_cnt = 1, i, k; const pjmedia_vid_codec_info *codec_info; pj_str_t port_name = {"codec", 5}; pj_uint8_t *enc_buf = NULL; pj_size_t enc_buf_size = 0; pjmedia_vid_dev_info rdr_info; pjmedia_port codec_port; codec_port_data_t codec_port_data; pjmedia_vid_codec_param codec_param; struct codec_fmt *codecp = NULL; /* Lookup codec */ for (i = 0; i < sizeof(codec_fmts)/sizeof(codec_fmts[0]); i++) { if (vid_port->info.fmt.id == codec_fmts[i].pjmedia_id) { codecp = &codec_fmts[i]; break; } } if (!codecp) { rc = 242; goto on_return; } pj_cstr(&codec_id_st, codecp->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; } pjmedia_format_copy(&codec_param.enc_fmt, ¶m.vidparam.fmt); pjmedia_vid_dev_get_info(param.vidparam.rend_id, &rdr_info); for (i=0; i<codec_info->dec_fmt_id_cnt; ++i) { for (k=0; k<rdr_info.fmt_cnt; ++k) { if (codec_info->dec_fmt_id[i]==(int)rdr_info.fmt[k].id) { param.vidparam.fmt.id = codec_info->dec_fmt_id[i]; i = codec_info->dec_fmt_id_cnt; break; } } } /* 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; } pjmedia_format_copy(&codec_param.dec_fmt, ¶m.vidparam.fmt); codec_param.dir = PJMEDIA_DIR_DECODING; codec_param.packing = PJMEDIA_VID_PACKING_WHOLE; status = pjmedia_vid_codec_open(codec, &codec_param); if (status != PJ_SUCCESS) { rc = 252; goto on_return; } /* Alloc encoding buffer */ enc_buf_size = codec_param.dec_fmt.det.vid.size.w * codec_param.dec_fmt.det.vid.size.h * 4 + 16; /*< padding, just in case */ enc_buf = pj_pool_alloc(pool,enc_buf_size); /* 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; } pj_bzero(&codec_port_data, sizeof(codec_port_data)); codec_port_data.codec = codec; codec_port_data.src_port = vid_port; codec_port_data.enc_buf = enc_buf; codec_port_data.enc_buf_size = enc_buf_size; codec_port.get_frame = &codec_get_frame; codec_port.port_data.pdata = &codec_port_data; /* Check whether we need to convert the decoded frame */ if (codecp->need_conversion) { pjmedia_conversion_param conv_param; pjmedia_format_copy(&conv_param.src, ¶m.vidparam.fmt); pjmedia_format_copy(&conv_param.dst, ¶m.vidparam.fmt); conv_param.dst.id = codecp->dst_fmt; param.vidparam.fmt.id = conv_param.dst.id; status = pjmedia_converter_create(NULL, pool, &conv_param, &codec_port_data.conv); if (status != PJ_SUCCESS) { rc = 270; goto on_return; } } status = pjmedia_vid_port_create(pool, ¶m, &renderer); if (status != PJ_SUCCESS) { rc = 230; goto on_return; } status = pjmedia_vid_port_connect(renderer, &codec_port, PJ_FALSE); } else { status = pjmedia_vid_port_create(pool, ¶m, &renderer); if (status != PJ_SUCCESS) { rc = 230; goto on_return; } /* Connect avi port to renderer */ status = pjmedia_vid_port_connect(renderer, vid_port, PJ_FALSE); } if (status != PJ_SUCCESS) { rc = 240; goto on_return; } } aud_stream = pjmedia_avi_streams_get_stream_by_media(avi_streams, 0, PJMEDIA_TYPE_AUDIO); aud_port = pjmedia_avi_stream_get_port(aud_stream); if (aud_port) { /* Create sound player port. */ status = pjmedia_snd_port_create_player( pool, /* pool */ -1, /* use default dev. */ PJMEDIA_PIA_SRATE(&aud_port->info),/* clock rate. */ PJMEDIA_PIA_CCNT(&aud_port->info), /* # of channels. */ PJMEDIA_PIA_SPF(&aud_port->info), /* samples per frame. */ PJMEDIA_PIA_BITS(&aud_port->info), /* bits per sample. */ 0, /* options */ &snd_port /* returned port */ ); if (status != PJ_SUCCESS) { rc = 310; goto on_return; } /* Connect file port to the sound player. * Stream playing will commence immediately. */ status = pjmedia_snd_port_connect(snd_port, aud_port); if (status != PJ_SUCCESS) { rc = 330; goto on_return; } } if (vid_port) { pjmedia_vid_dev_cb cb; pj_bzero(&cb, sizeof(cb)); avi_port.snd_port = snd_port; avi_port.vid_port = renderer; avi_port.is_running = PJ_TRUE; pjmedia_vid_port_set_cb(renderer, &cb, &avi_port); /* subscribe events */ pjmedia_event_subscribe(NULL, &avi_event_cb, &avi_port, renderer); if (snd_port) { /* Synchronize video rendering and audio playback */ pjmedia_vid_port_set_clock_src( renderer, pjmedia_snd_port_get_clock_src( snd_port, PJMEDIA_DIR_PLAYBACK)); } /* Start video streaming.. */ status = pjmedia_vid_port_start(renderer); if (status != PJ_SUCCESS) { rc = 270; goto on_return; } } while (!avi_port.is_quitting) { pj_thread_sleep(100); } on_return: if (snd_port) { pjmedia_snd_port_disconnect(snd_port); /* Without this sleep, Windows/DirectSound will repeteadly * play the last frame during destroy. */ pj_thread_sleep(100); pjmedia_snd_port_destroy(snd_port); } if (renderer) { pjmedia_event_unsubscribe(NULL, &avi_event_cb, &avi_port, renderer); pjmedia_vid_port_destroy(renderer); } if (aud_port) pjmedia_port_destroy(aud_port); if (vid_port) pjmedia_port_destroy(vid_port); if (codec) { pjmedia_vid_codec_close(codec); pjmedia_vid_codec_mgr_dealloc_codec(NULL, codec); } return rc; }
static pj_status_t open_ffmpeg_codec(ffmpeg_private *ff, pj_mutex_t *ff_mutex) { enum PixelFormat pix_fmt; pjmedia_video_format_detail *vfd; pj_bool_t enc_opened = PJ_FALSE, dec_opened = PJ_FALSE; pj_status_t status; /* Get decoded pixel format */ status = pjmedia_format_id_to_PixelFormat(ff->param.dec_fmt.id, &pix_fmt); if (status != PJ_SUCCESS) return status; ff->expected_dec_fmt = pix_fmt; /* Get video format detail for shortcut access to encoded format */ vfd = pjmedia_format_get_video_format_detail(&ff->param.enc_fmt, PJ_TRUE); /* Allocate ffmpeg codec context */ if (ff->param.dir & PJMEDIA_DIR_ENCODING) { #if LIBAVCODEC_VER_AT_LEAST(53,20) ff->enc_ctx = avcodec_alloc_context3(ff->enc); #else ff->enc_ctx = avcodec_alloc_context(); #endif if (ff->enc_ctx == NULL) goto on_error; } if (ff->param.dir & PJMEDIA_DIR_DECODING) { #if LIBAVCODEC_VER_AT_LEAST(53,20) ff->dec_ctx = avcodec_alloc_context3(ff->dec); #else ff->dec_ctx = avcodec_alloc_context(); #endif if (ff->dec_ctx == NULL) goto on_error; } /* Init generic encoder params */ if (ff->param.dir & PJMEDIA_DIR_ENCODING) { AVCodecContext *ctx = ff->enc_ctx; ctx->pix_fmt = pix_fmt; ctx->width = vfd->size.w; ctx->height = vfd->size.h; ctx->time_base.num = vfd->fps.denum; ctx->time_base.den = vfd->fps.num; if (vfd->avg_bps) { ctx->bit_rate = vfd->avg_bps; if (vfd->max_bps > vfd->avg_bps) ctx->bit_rate_tolerance = vfd->max_bps - vfd->avg_bps; } ctx->strict_std_compliance = FF_COMPLIANCE_STRICT; ctx->workaround_bugs = FF_BUG_AUTODETECT; ctx->opaque = ff; /* Set no delay, note that this may cause some codec functionals * not working (e.g: rate control). */ #if LIBAVCODEC_VER_AT_LEAST(52,113) ctx->rc_lookahead = 0; #endif } /* Init generic decoder params */ if (ff->param.dir & PJMEDIA_DIR_DECODING) { AVCodecContext *ctx = ff->dec_ctx; /* Width/height may be overriden by ffmpeg after first decoding. */ ctx->width = ctx->coded_width = ff->param.dec_fmt.det.vid.size.w; ctx->height = ctx->coded_height = ff->param.dec_fmt.det.vid.size.h; ctx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL; ctx->workaround_bugs = FF_BUG_AUTODETECT; ctx->opaque = ff; } /* Override generic params or apply specific params before opening * the codec. */ if (ff->desc->preopen) { status = (*ff->desc->preopen)(ff); if (status != PJ_SUCCESS) goto on_error; } /* Open encoder */ if (ff->param.dir & PJMEDIA_DIR_ENCODING) { int err; pj_mutex_lock(ff_mutex); err = avcodec_open(ff->enc_ctx, ff->enc); pj_mutex_unlock(ff_mutex); if (err < 0) { print_ffmpeg_err(err); status = PJMEDIA_CODEC_EFAILED; goto on_error; } enc_opened = PJ_TRUE; } /* Open decoder */ if (ff->param.dir & PJMEDIA_DIR_DECODING) { int err; pj_mutex_lock(ff_mutex); err = avcodec_open(ff->dec_ctx, ff->dec); pj_mutex_unlock(ff_mutex); if (err < 0) { print_ffmpeg_err(err); status = PJMEDIA_CODEC_EFAILED; goto on_error; } dec_opened = PJ_TRUE; } /* Let the codec apply specific params after the codec opened */ if (ff->desc->postopen) { status = (*ff->desc->postopen)(ff); if (status != PJ_SUCCESS) goto on_error; } return PJ_SUCCESS; on_error: if (ff->enc_ctx) { if (enc_opened) avcodec_close(ff->enc_ctx); av_free(ff->enc_ctx); ff->enc_ctx = NULL; } if (ff->dec_ctx) { if (dec_opened) avcodec_close(ff->dec_ctx); av_free(ff->dec_ctx); ff->dec_ctx = NULL; } return status; }