diff options
Diffstat (limited to 'pjmedia/src/pjmedia-videodev/util.c')
-rw-r--r-- | pjmedia/src/pjmedia-videodev/util.c | 357 |
1 files changed, 357 insertions, 0 deletions
diff --git a/pjmedia/src/pjmedia-videodev/util.c b/pjmedia/src/pjmedia-videodev/util.c new file mode 100644 index 00000000..d66a90a8 --- /dev/null +++ b/pjmedia/src/pjmedia-videodev/util.c @@ -0,0 +1,357 @@ +/* $Id$ */ +/* + * Copyright (C) 2014-2015 Teluu Inc. (http://www.teluu.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "util.h" + +#include <pj/assert.h> +#include <pj/errno.h> +#include <pj/log.h> + +#if defined(PJMEDIA_HAS_LIBYUV) && PJMEDIA_HAS_LIBYUV != 0 + #include <libyuv.h> + #define HAS_ROTATION 1 +#else + #define HAS_ROTATION 0 +#endif + +#define THIS_FILE "vid_util.c" + +pj_status_t +pjmedia_vid_dev_conv_create_converter(pjmedia_vid_dev_conv *conv, + pj_pool_t *pool, + pjmedia_format *fmt, + pjmedia_rect_size src_size, + pjmedia_rect_size dst_size, + pj_bool_t handle_rotation, + pj_bool_t maintain_aspect_ratio) +{ + pj_status_t status; + pjmedia_conversion_param conv_param; + const pjmedia_video_format_info *vfi; + + pj_assert((src_size.w == dst_size.w || src_size.h == dst_size.h) || + (src_size.w == dst_size.h || src_size.h == dst_size.w)); + + if (conv->conv) + return PJ_SUCCESS; + + if (fmt->id != PJMEDIA_FORMAT_I420 && fmt->id != PJMEDIA_FORMAT_BGRA) + return PJ_EINVAL; + + /* Currently, for BGRA format, device must handle the rotation. */ + if (fmt->id == PJMEDIA_FORMAT_BGRA && handle_rotation) + return PJ_ENOTSUP; + + if (handle_rotation) { +#if !HAS_ROTATION + return PJ_ENOTSUP; +#endif + } + + conv->src_size = src_size; + conv->dst_size = dst_size; + conv->handle_rotation = handle_rotation; + pjmedia_format_copy(&conv->fmt, fmt); + pjmedia_format_copy(&conv_param.src, fmt); + pjmedia_format_copy(&conv_param.dst, fmt); + + /* If we do the rotation, the conversion's source size must be the same + * as the device's original size. Otherwise, frames that require conversion + * are the ones of which orientation differ by 90 or 270 degrees from the + * destination size. + */ + if (handle_rotation) { + conv_param.src.det.vid.size = src_size; + } else { + conv_param.src.det.vid.size.w = dst_size.h; + conv_param.src.det.vid.size.h = dst_size.w; + } + + /* Maintaining aspect ratio requires filling the left&right / + * top&bottom area with black color. + * Currently it is only supported for I420. + * TODO: support BGRA as well + */ + if (fmt->id != PJMEDIA_FORMAT_I420) + maintain_aspect_ratio = PJ_FALSE; + + /* Calculate the size after rotation. + * If aspect ratio doesn't need to be maintained, rot_size is simply equal + * to the destination size. Otherwise, we need to fit the rotated frame + * to height or to width. + */ + conv->maintain_aspect_ratio = maintain_aspect_ratio; + if (maintain_aspect_ratio) { + conv->fit_to_h = (dst_size.w >= dst_size.h? PJ_TRUE: PJ_FALSE); + if (conv->fit_to_h) { /* Fit to height */ + conv->rot_size.h = dst_size.h; + conv->rot_size.w = dst_size.h * dst_size.h / dst_size.w; + /* Make sure the width difference is divisible by four + * so we can have equal padding left and right. + */ + conv->rot_size.w += (dst_size.w - conv->rot_size.w) % 4; + conv->pad = (conv->dst_size.w - conv->rot_size.w) / 2; + } else { /* Fit to width */ + conv->rot_size.w = dst_size.w; + conv->rot_size.h = dst_size.w * dst_size.w / dst_size.h; + conv->rot_size.h += (dst_size.h - conv->rot_size.h) % 4; + conv->pad = (conv->dst_size.h - conv->rot_size.h) / 2; + } + } else { + conv->rot_size = dst_size; + } + + /* Calculate the size after resizing. */ + if (handle_rotation) { + /* If we do the rotation, conversion is done before rotation. */ + if (maintain_aspect_ratio) { + /* Since aspect ratio is maintained, the long side after + * conversion must be the same as before conversion. + * For example: 352x288 will be converted to 288x236 + */ + pj_size_t long_s = (conv->rot_size.h > conv->rot_size.w? + conv->rot_size.h: conv->rot_size.w); + pj_size_t short_s = (conv->rot_size.h > conv->rot_size.w? + conv->rot_size.w: conv->rot_size.h); + if (src_size.w > src_size.h) { + conv->res_size.w = long_s; + conv->res_size.h = short_s; + } else { + conv->res_size.w = short_s; + conv->res_size.h = long_s; + } + } else { + /* We don't need to maintain aspect ratio, + * so just swap the width and height. + * For example: 352x288 will be resized to 288x352 + */ + conv->res_size.w = src_size.h; + conv->res_size.h = src_size.w; + } + conv_param.dst.det.vid.size = conv->res_size; + } else { + conv->res_size = conv->rot_size; + conv_param.dst.det.vid.size = conv->rot_size; + } + + status = pjmedia_converter_create(NULL, pool, &conv_param, + &conv->conv); + if (status != PJ_SUCCESS) { + PJ_LOG(3, (THIS_FILE, "Error creating converter")); + return status; + } + + vfi = pjmedia_get_video_format_info(NULL, fmt->id); + pj_assert(vfi); + + conv->wxh = conv->dst_size.w * conv->dst_size.h; + conv->src_frame_size = dst_size.w * dst_size.h * vfi->bpp / 8; + conv->conv_frame_size = conv->rot_size.w * conv->rot_size.h; + conv->conv_frame_size *= vfi->bpp / 8; + conv->conv_buf = pj_pool_alloc(pool, conv->src_frame_size); + + pjmedia_vid_dev_conv_set_rotation(conv, PJMEDIA_ORIENT_NATURAL); + + PJ_LOG(4, (THIS_FILE, "Orientation converter created: %dx%d to %dx%d", + conv_param.src.det.vid.size.w, + conv_param.src.det.vid.size.h, + conv_param.dst.det.vid.size.w, + conv_param.dst.det.vid.size.h)); + + return PJ_SUCCESS; +} + +void pjmedia_vid_dev_conv_set_rotation(pjmedia_vid_dev_conv *conv, + pjmedia_orient rotation) +{ + pjmedia_rect_size new_size = conv->src_size; + + conv->rotation = rotation; + + if (rotation == PJMEDIA_ORIENT_ROTATE_90DEG || + rotation == PJMEDIA_ORIENT_ROTATE_270DEG) + { + new_size.w = conv->src_size.h; + new_size.h = conv->src_size.w; + } + + /* Check whether new size (size after rotation) and destination + * are both portrait or both landscape. If yes, resize will not + * be required in pjmedia_vid_dev_conv_resize_and_rotate() below. + * For example, 352x288 frame rotated 270 degrees will fit into + * a destination frame of 288x352 (no resize needed). + */ + if ((new_size.w > new_size.h && conv->dst_size.w > conv->dst_size.h) || + (new_size.h > new_size.w && conv->dst_size.h > conv->dst_size.w)) + { + conv->match_src_dst = PJ_TRUE; + } else { + conv->match_src_dst = PJ_FALSE; + } +} + +pj_status_t pjmedia_vid_dev_conv_resize_and_rotate(pjmedia_vid_dev_conv *conv, + void *src_buf, + void **result) +{ +#define swap(a, b) {pj_uint8_t *c = a; a = b; b = c;} + + pj_status_t status; + pjmedia_frame src_frame, dst_frame; + pjmedia_rect_size src_size = conv->src_size; + pj_uint8_t *src = src_buf; + pj_uint8_t *dst = conv->conv_buf; + + pj_assert(src_buf); + + if (!conv->conv) return PJ_EINVALIDOP; + + if (!conv->match_src_dst) { + /* We need to resize. */ + src_frame.buf = src; + dst_frame.buf = dst; + src_frame.size = conv->src_frame_size; + dst_frame.size = conv->conv_frame_size; + + status = pjmedia_converter_convert(conv->conv, &src_frame, &dst_frame); + if (status != PJ_SUCCESS) { + PJ_LOG(3, (THIS_FILE, "Failed to convert frame")); + return status; + } + + src_size = conv->res_size; + + swap(src, dst); + } + + if (conv->handle_rotation && conv->rotation != PJMEDIA_ORIENT_NATURAL) { + /* We need to do rotation. */ + if (conv->fmt.id == PJMEDIA_FORMAT_I420) { + pjmedia_rect_size dst_size = src_size; + pj_size_t p_len = src_size.w * src_size.h; + + if (conv->rotation == PJMEDIA_ORIENT_ROTATE_90DEG || + conv->rotation == PJMEDIA_ORIENT_ROTATE_270DEG) + { + dst_size.w = src_size.h; + dst_size.h = src_size.w; + } + +#if defined(PJMEDIA_HAS_LIBYUV) && PJMEDIA_HAS_LIBYUV != 0 + enum RotationMode mode; + + switch (conv->rotation) { + case PJMEDIA_ORIENT_ROTATE_90DEG: + mode = kRotate90; + break; + case PJMEDIA_ORIENT_ROTATE_180DEG: + mode = kRotate180; + break; + case PJMEDIA_ORIENT_ROTATE_270DEG: + mode = kRotate270; + break; + default: + mode = kRotate0; + } + + I420Rotate(src, src_size.w, + src+p_len, src_size.w/2, + src+p_len+p_len/4, src_size.w/2, + dst, dst_size.w, + dst+p_len, dst_size.w/2, + dst+p_len+p_len/4, dst_size.w/2, + src_size.w, src_size.h, mode); + + swap(src, dst); +#endif + } + } + + if (!conv->match_src_dst && conv->maintain_aspect_ratio) { + /* Center the frame and fill the area with black color */ + if (conv->fmt.id == PJMEDIA_FORMAT_I420) { + int i = 0; + pj_uint8_t *pdst = dst; + pj_uint8_t *psrc = src; + pj_size_t p_len_src, p_len_dst; + int pad = conv->pad; + + p_len_dst = conv->wxh; + pj_bzero(pdst, p_len_dst); + + if (conv->fit_to_h) { + /* Fill the left and right with black */ + for (; i < conv->dst_size.h; ++i) { + pdst += pad; + pj_memcpy(pdst, psrc, conv->rot_size.w); + pdst += conv->rot_size.w; + psrc += conv->rot_size.w; + pdst += pad; + } + } else { + /* Fill the top and bottom with black */ + p_len_src = conv->rot_size.w * conv->rot_size.h; + pj_memcpy(pdst + conv->rot_size.w * pad, psrc, p_len_src); + psrc += p_len_src; + pdst += p_len_dst; + } + + /* Fill the U&V components with 0x80 to make it black. + * Bzero-ing will make the area look green instead. + */ + pj_memset(pdst, 0x80, p_len_dst/2); + pad /= 2; + if (conv->fit_to_h) { + p_len_src = conv->rot_size.w / 2; + for (i = conv->dst_size.h; i > 0; --i) { + pdst += pad; + pj_memcpy(pdst, psrc, p_len_src); + pdst += p_len_src; + psrc += p_len_src; + pdst += pad; + } + } else { + pj_uint8_t *U, *V; + pj_size_t gap = conv->rot_size.w * pad / 2; + + p_len_src /= 4; + U = pdst; + V = U + p_len_dst/4; + + pj_memcpy(U + gap, psrc, p_len_src); + psrc += p_len_src; + pj_memcpy(V + gap, psrc, p_len_src); + } + + swap(src, dst); + } + } + + *result = src; + + return PJ_SUCCESS; +} + +void pjmedia_vid_dev_conv_destroy_converter(pjmedia_vid_dev_conv *conv) +{ + if (conv->conv) { + pjmedia_converter_destroy(conv->conv); + conv->conv = NULL; + } +} |