Alexandre Lision | 8af73cb | 2013-12-10 14:11:20 -0500 | [diff] [blame] | 1 | /* $Id$ */ |
| 2 | /* |
| 3 | * Copyright (C) 2011 Teluu Inc. (http://www.teluu.com) |
| 4 | * |
| 5 | * This program is free software; you can redistribute it and/or modify |
| 6 | * it under the terms of the GNU General Public License as published by |
| 7 | * the Free Software Foundation; either version 2 of the License, or |
| 8 | * (at your option) any later version. |
| 9 | * |
| 10 | * This program is distributed in the hope that it will be useful, |
| 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 13 | * GNU General Public License for more details. |
| 14 | * |
| 15 | * You should have received a copy of the GNU General Public License |
| 16 | * along with this program; if not, write to the Free Software |
| 17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| 18 | */ |
| 19 | #include <pjmedia/vid_tee.h> |
| 20 | #include <pjmedia/converter.h> |
| 21 | #include <pjmedia/errno.h> |
| 22 | #include <pj/array.h> |
| 23 | #include <pj/log.h> |
| 24 | #include <pj/pool.h> |
| 25 | |
| 26 | |
| 27 | #if defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0) |
| 28 | |
| 29 | |
| 30 | #define TEE_PORT_NAME "vid_tee" |
| 31 | #define TEE_PORT_SIGN PJMEDIA_SIG_PORT_VID_TEE |
| 32 | |
| 33 | #define THIS_FILE "vid_tee.c" |
| 34 | |
| 35 | typedef struct vid_tee_dst_port |
| 36 | { |
| 37 | pjmedia_port *dst; |
| 38 | unsigned option; |
| 39 | } vid_tee_dst_port; |
| 40 | |
| 41 | |
| 42 | typedef struct vid_tee_port |
| 43 | { |
| 44 | pjmedia_port base; |
| 45 | pj_pool_t *pool; |
| 46 | pj_pool_factory *pf; |
| 47 | pj_pool_t *buf_pool; |
| 48 | void *buf[2]; |
| 49 | unsigned buf_cnt; |
| 50 | pj_size_t buf_size; |
| 51 | unsigned dst_port_maxcnt; |
| 52 | unsigned dst_port_cnt; |
| 53 | vid_tee_dst_port *dst_ports; |
| 54 | pj_uint8_t *put_frm_flag; |
| 55 | |
| 56 | struct vid_tee_conv_t { |
| 57 | pjmedia_converter *conv; |
| 58 | pj_size_t conv_buf_size; |
| 59 | } *tee_conv; |
| 60 | } vid_tee_port; |
| 61 | |
| 62 | |
| 63 | static pj_status_t tee_put_frame(pjmedia_port *port, pjmedia_frame *frame); |
| 64 | static pj_status_t tee_get_frame(pjmedia_port *port, pjmedia_frame *frame); |
| 65 | static pj_status_t tee_destroy(pjmedia_port *port); |
| 66 | |
| 67 | /* |
| 68 | * Create a video tee port with the specified source media port. |
| 69 | */ |
| 70 | PJ_DEF(pj_status_t) pjmedia_vid_tee_create( pj_pool_t *pool, |
| 71 | const pjmedia_format *fmt, |
| 72 | unsigned max_dst_cnt, |
| 73 | pjmedia_port **p_vid_tee) |
| 74 | { |
| 75 | vid_tee_port *tee; |
| 76 | pj_str_t name_st; |
| 77 | const pjmedia_video_format_info *vfi; |
| 78 | pjmedia_video_apply_fmt_param vafp; |
| 79 | pj_status_t status; |
| 80 | |
| 81 | PJ_ASSERT_RETURN(pool && fmt && p_vid_tee, PJ_EINVAL); |
| 82 | PJ_ASSERT_RETURN(fmt->type == PJMEDIA_TYPE_VIDEO, PJ_EINVAL); |
| 83 | |
| 84 | /* Allocate video tee structure */ |
| 85 | tee = PJ_POOL_ZALLOC_T(pool, vid_tee_port); |
| 86 | tee->pf = pool->factory; |
| 87 | tee->pool = pj_pool_create(tee->pf, "video tee", 500, 500, NULL); |
| 88 | |
| 89 | /* Initialize video tee structure */ |
| 90 | tee->dst_port_maxcnt = max_dst_cnt; |
| 91 | tee->dst_ports = (vid_tee_dst_port*) |
| 92 | pj_pool_calloc(pool, max_dst_cnt, |
| 93 | sizeof(vid_tee_dst_port)); |
| 94 | tee->tee_conv = (struct vid_tee_conv_t *) |
| 95 | pj_pool_calloc(pool, max_dst_cnt, |
| 96 | sizeof(struct vid_tee_conv_t)); |
| 97 | tee->put_frm_flag = (pj_uint8_t*) |
| 98 | pj_pool_calloc(pool, max_dst_cnt, |
| 99 | sizeof(tee->put_frm_flag[0])); |
| 100 | |
| 101 | /* Initialize video tee buffer, its size is one frame */ |
| 102 | vfi = pjmedia_get_video_format_info(NULL, fmt->id); |
| 103 | if (vfi == NULL) |
| 104 | return PJMEDIA_EBADFMT; |
| 105 | |
| 106 | pj_bzero(&vafp, sizeof(vafp)); |
| 107 | vafp.size = fmt->det.vid.size; |
| 108 | status = vfi->apply_fmt(vfi, &vafp); |
| 109 | if (status != PJ_SUCCESS) |
| 110 | return status; |
| 111 | |
| 112 | tee->buf_size = vafp.framebytes; |
| 113 | |
| 114 | /* Initialize video tee port */ |
| 115 | status = pjmedia_port_info_init2(&tee->base.info, |
| 116 | pj_strset2(&name_st, (char*)TEE_PORT_NAME), |
| 117 | TEE_PORT_SIGN, |
| 118 | PJMEDIA_DIR_ENCODING, |
| 119 | fmt); |
| 120 | if (status != PJ_SUCCESS) |
| 121 | return status; |
| 122 | |
| 123 | tee->base.get_frame = &tee_get_frame; |
| 124 | tee->base.put_frame = &tee_put_frame; |
| 125 | tee->base.on_destroy = &tee_destroy; |
| 126 | |
| 127 | /* Done */ |
| 128 | *p_vid_tee = &tee->base; |
| 129 | |
| 130 | return PJ_SUCCESS; |
| 131 | } |
| 132 | |
| 133 | static void realloc_buf(vid_tee_port *vid_tee, |
| 134 | unsigned buf_cnt, pj_size_t buf_size) |
| 135 | { |
| 136 | unsigned i; |
| 137 | |
| 138 | if (buf_cnt > vid_tee->buf_cnt) |
| 139 | vid_tee->buf_cnt = buf_cnt; |
| 140 | |
| 141 | if (buf_size > vid_tee->buf_size) { |
| 142 | /* We need a larger buffer here. */ |
| 143 | vid_tee->buf_size = buf_size; |
| 144 | if (vid_tee->buf_pool) { |
| 145 | pj_pool_release(vid_tee->buf_pool); |
| 146 | vid_tee->buf_pool = NULL; |
| 147 | } |
| 148 | vid_tee->buf[0] = vid_tee->buf[1] = NULL; |
| 149 | } |
| 150 | |
| 151 | if (!vid_tee->buf_pool) { |
| 152 | vid_tee->buf_pool = pj_pool_create(vid_tee->pf, "video tee buffer", |
| 153 | 1000, 1000, NULL); |
| 154 | } |
| 155 | |
| 156 | for (i = 0; i < vid_tee->buf_cnt; i++) { |
| 157 | if (!vid_tee->buf[i]) |
| 158 | vid_tee->buf[i] = pj_pool_alloc(vid_tee->buf_pool, |
| 159 | vid_tee->buf_size); |
| 160 | } |
| 161 | } |
| 162 | |
| 163 | /* |
| 164 | * Add a destination media port to the video tee. |
| 165 | */ |
| 166 | PJ_DEF(pj_status_t) pjmedia_vid_tee_add_dst_port(pjmedia_port *vid_tee, |
| 167 | unsigned option, |
| 168 | pjmedia_port *port) |
| 169 | { |
| 170 | vid_tee_port *tee = (vid_tee_port*)vid_tee; |
| 171 | pjmedia_video_format_detail *vfd; |
| 172 | |
| 173 | PJ_ASSERT_RETURN(vid_tee && vid_tee->info.signature==TEE_PORT_SIGN, |
| 174 | PJ_EINVAL); |
| 175 | |
| 176 | if (tee->dst_port_cnt >= tee->dst_port_maxcnt) |
| 177 | return PJ_ETOOMANY; |
| 178 | |
| 179 | if (vid_tee->info.fmt.id != port->info.fmt.id) |
| 180 | return PJMEDIA_EBADFMT; |
| 181 | |
| 182 | vfd = pjmedia_format_get_video_format_detail(&port->info.fmt, PJ_TRUE); |
| 183 | if (vfd->size.w != vid_tee->info.fmt.det.vid.size.w || |
| 184 | vfd->size.h != vid_tee->info.fmt.det.vid.size.h) |
| 185 | { |
| 186 | return PJMEDIA_EBADFMT; |
| 187 | } |
| 188 | |
| 189 | realloc_buf(tee, (option & PJMEDIA_VID_TEE_DST_DO_IN_PLACE_PROC)? |
| 190 | 1: 0, tee->buf_size); |
| 191 | |
| 192 | pj_bzero(&tee->tee_conv[tee->dst_port_cnt], sizeof(tee->tee_conv[0])); |
| 193 | tee->dst_ports[tee->dst_port_cnt].dst = port; |
| 194 | tee->dst_ports[tee->dst_port_cnt].option = option; |
| 195 | ++tee->dst_port_cnt; |
| 196 | |
| 197 | return PJ_SUCCESS; |
| 198 | } |
| 199 | |
| 200 | |
| 201 | /* |
| 202 | * Add a destination media port to the video tee. Create a converter if |
| 203 | * necessary. |
| 204 | */ |
| 205 | PJ_DEF(pj_status_t) pjmedia_vid_tee_add_dst_port2(pjmedia_port *vid_tee, |
| 206 | unsigned option, |
| 207 | pjmedia_port *port) |
| 208 | { |
| 209 | vid_tee_port *tee = (vid_tee_port*)vid_tee; |
| 210 | pjmedia_video_format_detail *vfd; |
| 211 | |
| 212 | PJ_ASSERT_RETURN(vid_tee && vid_tee->info.signature==TEE_PORT_SIGN, |
| 213 | PJ_EINVAL); |
| 214 | |
| 215 | if (tee->dst_port_cnt >= tee->dst_port_maxcnt) |
| 216 | return PJ_ETOOMANY; |
| 217 | |
| 218 | pj_bzero(&tee->tee_conv[tee->dst_port_cnt], sizeof(tee->tee_conv[0])); |
| 219 | |
| 220 | /* Check if we need to create a converter. */ |
| 221 | vfd = pjmedia_format_get_video_format_detail(&port->info.fmt, PJ_TRUE); |
| 222 | if (vid_tee->info.fmt.id != port->info.fmt.id || |
| 223 | vfd->size.w != vid_tee->info.fmt.det.vid.size.w || |
| 224 | vfd->size.h != vid_tee->info.fmt.det.vid.size.h) |
| 225 | { |
| 226 | const pjmedia_video_format_info *vfi; |
| 227 | pjmedia_video_apply_fmt_param vafp; |
| 228 | pjmedia_conversion_param conv_param; |
| 229 | pj_status_t status; |
| 230 | |
| 231 | vfi = pjmedia_get_video_format_info(NULL, port->info.fmt.id); |
| 232 | if (vfi == NULL) |
| 233 | return PJMEDIA_EBADFMT; |
| 234 | |
| 235 | pj_bzero(&vafp, sizeof(vafp)); |
| 236 | vafp.size = port->info.fmt.det.vid.size; |
| 237 | status = vfi->apply_fmt(vfi, &vafp); |
| 238 | if (status != PJ_SUCCESS) |
| 239 | return status; |
| 240 | |
| 241 | realloc_buf(tee, (option & PJMEDIA_VID_TEE_DST_DO_IN_PLACE_PROC)? |
| 242 | 2: 1, vafp.framebytes); |
| 243 | |
| 244 | pjmedia_format_copy(&conv_param.src, &vid_tee->info.fmt); |
| 245 | pjmedia_format_copy(&conv_param.dst, &port->info.fmt); |
| 246 | |
| 247 | status = pjmedia_converter_create( |
| 248 | NULL, tee->pool, &conv_param, |
| 249 | &tee->tee_conv[tee->dst_port_cnt].conv); |
| 250 | if (status != PJ_SUCCESS) |
| 251 | return status; |
| 252 | |
| 253 | tee->tee_conv[tee->dst_port_cnt].conv_buf_size = vafp.framebytes; |
| 254 | } else { |
| 255 | realloc_buf(tee, (option & PJMEDIA_VID_TEE_DST_DO_IN_PLACE_PROC)? |
| 256 | 1: 0, tee->buf_size); |
| 257 | } |
| 258 | |
| 259 | tee->dst_ports[tee->dst_port_cnt].dst = port; |
| 260 | tee->dst_ports[tee->dst_port_cnt].option = option; |
| 261 | ++tee->dst_port_cnt; |
| 262 | |
| 263 | return PJ_SUCCESS; |
| 264 | } |
| 265 | |
| 266 | |
| 267 | /* |
| 268 | * Remove a destination media port from the video tee. |
| 269 | */ |
| 270 | PJ_DEF(pj_status_t) pjmedia_vid_tee_remove_dst_port(pjmedia_port *vid_tee, |
| 271 | pjmedia_port *port) |
| 272 | { |
| 273 | vid_tee_port *tee = (vid_tee_port*)vid_tee; |
| 274 | unsigned i; |
| 275 | |
| 276 | PJ_ASSERT_RETURN(vid_tee && vid_tee->info.signature==TEE_PORT_SIGN, |
| 277 | PJ_EINVAL); |
| 278 | |
| 279 | for (i = 0; i < tee->dst_port_cnt; ++i) { |
| 280 | if (tee->dst_ports[i].dst == port) { |
| 281 | if (tee->tee_conv[i].conv) |
| 282 | pjmedia_converter_destroy(tee->tee_conv[i].conv); |
| 283 | |
| 284 | pj_array_erase(tee->dst_ports, sizeof(tee->dst_ports[0]), |
| 285 | tee->dst_port_cnt, i); |
| 286 | pj_array_erase(tee->tee_conv, sizeof(tee->tee_conv[0]), |
| 287 | tee->dst_port_cnt, i); |
| 288 | --tee->dst_port_cnt; |
| 289 | return PJ_SUCCESS; |
| 290 | } |
| 291 | } |
| 292 | |
| 293 | return PJ_ENOTFOUND; |
| 294 | } |
| 295 | |
| 296 | |
| 297 | static pj_status_t tee_put_frame(pjmedia_port *port, pjmedia_frame *frame) |
| 298 | { |
| 299 | vid_tee_port *tee = (vid_tee_port*)port; |
| 300 | unsigned i, j; |
| 301 | const pj_uint8_t PUT_FRM_DONE = 1; |
| 302 | |
| 303 | pj_bzero(tee->put_frm_flag, tee->dst_port_cnt * |
| 304 | sizeof(tee->put_frm_flag[0])); |
| 305 | |
| 306 | for (i = 0; i < tee->dst_port_cnt; ++i) { |
| 307 | pjmedia_frame frame_ = *frame; |
| 308 | |
| 309 | if (tee->put_frm_flag[i]) |
| 310 | continue; |
| 311 | |
| 312 | if (tee->tee_conv[i].conv) { |
| 313 | pj_status_t status; |
| 314 | |
| 315 | frame_.buf = tee->buf[0]; |
| 316 | frame_.size = tee->tee_conv[i].conv_buf_size; |
| 317 | status = pjmedia_converter_convert(tee->tee_conv[i].conv, |
| 318 | frame, &frame_); |
| 319 | if (status != PJ_SUCCESS) { |
| 320 | PJ_LOG(3, (THIS_FILE, |
| 321 | "Failed to convert frame for destination" |
| 322 | " port %d (%.*s)", i, |
| 323 | tee->dst_ports[i].dst->info.name.slen, |
| 324 | tee->dst_ports[i].dst->info.name.ptr)); |
| 325 | continue; |
| 326 | } |
| 327 | } |
| 328 | |
| 329 | /* Find other destination ports which has the same format so |
| 330 | * we don't need to do the same conversion twice. |
| 331 | */ |
| 332 | for (j = i; j < tee->dst_port_cnt; ++j) { |
| 333 | pjmedia_frame framep; |
| 334 | |
| 335 | if (tee->put_frm_flag[j] || |
| 336 | (tee->dst_ports[j].dst->info.fmt.id != |
| 337 | tee->dst_ports[i].dst->info.fmt.id) || |
| 338 | (tee->dst_ports[j].dst->info.fmt.det.vid.size.w != |
| 339 | tee->dst_ports[i].dst->info.fmt.det.vid.size.w) || |
| 340 | (tee->dst_ports[j].dst->info.fmt.det.vid.size.h != |
| 341 | tee->dst_ports[i].dst->info.fmt.det.vid.size.h)) |
| 342 | { |
| 343 | continue; |
| 344 | } |
| 345 | |
| 346 | framep = frame_; |
| 347 | /* For dst_ports that do in-place processing, we need to duplicate |
| 348 | * the data source first. |
| 349 | */ |
| 350 | if (tee->dst_ports[j].option & PJMEDIA_VID_TEE_DST_DO_IN_PLACE_PROC) |
| 351 | { |
| 352 | PJ_ASSERT_RETURN(tee->buf_size <= frame_.size, PJ_ETOOBIG); |
| 353 | framep.buf = tee->buf[tee->buf_cnt-1]; |
| 354 | framep.size = frame_.size; |
| 355 | pj_memcpy(framep.buf, frame_.buf, frame_.size); |
| 356 | } |
| 357 | |
| 358 | /* Deliver the data */ |
| 359 | pjmedia_port_put_frame(tee->dst_ports[j].dst, &framep); |
| 360 | tee->put_frm_flag[j] = PUT_FRM_DONE; |
| 361 | |
| 362 | if (!tee->tee_conv[i].conv) |
| 363 | break; |
| 364 | } |
| 365 | } |
| 366 | |
| 367 | return PJ_SUCCESS; |
| 368 | } |
| 369 | |
| 370 | static pj_status_t tee_get_frame(pjmedia_port *port, pjmedia_frame *frame) |
| 371 | { |
| 372 | PJ_UNUSED_ARG(port); |
| 373 | PJ_UNUSED_ARG(frame); |
| 374 | |
| 375 | pj_assert(!"Bug! Tee port get_frame() shouldn't be called."); |
| 376 | |
| 377 | return PJ_EBUG; |
| 378 | } |
| 379 | |
| 380 | static pj_status_t tee_destroy(pjmedia_port *port) |
| 381 | { |
| 382 | vid_tee_port *tee = (vid_tee_port*)port; |
| 383 | |
| 384 | PJ_ASSERT_RETURN(port && port->info.signature==TEE_PORT_SIGN, PJ_EINVAL); |
| 385 | |
| 386 | pj_pool_release(tee->pool); |
| 387 | if (tee->buf_pool) |
| 388 | pj_pool_release(tee->buf_pool); |
| 389 | |
| 390 | pj_bzero(tee, sizeof(*tee)); |
| 391 | |
| 392 | return PJ_SUCCESS; |
| 393 | } |
| 394 | |
| 395 | |
| 396 | #endif /* PJMEDIA_HAS_VIDEO */ |