From 97f362db7fa1fad9a0258bdb5e218b80dab93031 Mon Sep 17 00:00:00 2001 From: Liong Sauw Ming Date: Thu, 25 Jun 2015 08:17:52 +0000 Subject: Re #1861: Initial implementation of video orientation support - Utility to resize and rotate video frame - Support for iOS + sample - pjsua API to set video device's orientation git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@5118 74dad513-b988-da41-8d7b-12977e46ad98 --- pjmedia/src/pjmedia-videodev/ios_dev.m | 174 +++++++++++++--- pjmedia/src/pjmedia-videodev/util.c | 357 +++++++++++++++++++++++++++++++++ pjmedia/src/pjmedia-videodev/util.h | 96 +++++++++ 3 files changed, 604 insertions(+), 23 deletions(-) create mode 100644 pjmedia/src/pjmedia-videodev/util.c create mode 100644 pjmedia/src/pjmedia-videodev/util.h (limited to 'pjmedia/src') diff --git a/pjmedia/src/pjmedia-videodev/ios_dev.m b/pjmedia/src/pjmedia-videodev/ios_dev.m index 79ece95a..ac4166c7 100644 --- a/pjmedia/src/pjmedia-videodev/ios_dev.m +++ b/pjmedia/src/pjmedia-videodev/ios_dev.m @@ -16,6 +16,7 @@ * 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 #include #include @@ -37,6 +38,11 @@ #define DEFAULT_HEIGHT 288 #define DEFAULT_FPS 15 +/* Define whether we should maintain the aspect ratio when rotating the image. + * For more details, please refer to vid_util.h. + */ +#define MAINTAIN_ASPECT_RATIO PJ_TRUE + typedef struct ios_fmt_info { pjmedia_format_id pjmedia_format; @@ -108,6 +114,10 @@ struct ios_stream pj_bool_t is_planar; NSLock *frame_lock; void *capture_buf; + void *frame_buf; + + pjmedia_vid_dev_conv conv; + pjmedia_rect_size vid_size; AVCaptureSession *cap_session; AVCaptureDeviceInput *dev_input; @@ -313,12 +323,20 @@ static pj_status_t ios_factory_init(pjmedia_vid_dev_factory *f) if ([dev supportsAVCaptureSessionPreset: ios_sizes[m].preset_str]) { + /* Landscape video */ fmt = &qdi->info.fmt[qdi->info.fmt_cnt++]; pjmedia_format_init_video(fmt, ios_fmts[l].pjmedia_format, ios_sizes[m].supported_size_w, ios_sizes[m].supported_size_h, DEFAULT_FPS, 1); + /* Portrait video */ + fmt = &qdi->info.fmt[qdi->info.fmt_cnt++]; + pjmedia_format_init_video(fmt, + ios_fmts[l].pjmedia_format, + ios_sizes[m].supported_size_h, + ios_sizes[m].supported_size_w, + DEFAULT_FPS, 1); } } } @@ -435,6 +453,8 @@ static pj_status_t ios_factory_default_param(pj_pool_t *pool, fromConnection:(AVCaptureConnection *)connection { CVImageBufferRef img; + pj_status_t status; + void *frame_buf; /* Refrain from calling pjlib functions which require thread registration * here, since according to the doc, dispatch queue cannot guarantee @@ -451,7 +471,6 @@ static pj_status_t ios_factory_default_param(pj_pool_t *pool, /* Lock the base address of the pixel buffer */ CVPixelBufferLockBaseAddress(img, kCVPixelBufferLock_ReadOnly); - [stream->frame_lock lock]; if (stream->is_planar && stream->capture_buf) { @@ -463,10 +482,19 @@ static pj_status_t ios_factory_default_param(pj_pool_t *pool, * air, at 352*288 the image stride is 384. */ pj_size_t stride = CVPixelBufferGetBytesPerRowOfPlane(img, 0); - pj_bool_t need_clip = (stride != stream->size.w); + pj_size_t height = CVPixelBufferGetHeight(img); + pj_bool_t need_clip; + + /* Auto detect rotation */ + if (height != stream->vid_size.h) { + stream->vid_size.w = stream->vid_size.h; + stream->vid_size.h = height; + } + + need_clip = (stride != stream->vid_size.w); p = (pj_uint8_t*)CVPixelBufferGetBaseAddressOfPlane(img, 0); - p_len = stream->size.w * stream->size.h; + p_len = stream->vid_size.w * stream->vid_size.h; Y = (pj_uint8_t*)stream->capture_buf; U = Y + p_len; V = U + p_len/4; @@ -475,9 +503,10 @@ static pj_status_t ios_factory_default_param(pj_pool_t *pool, pj_memcpy(Y, p, p_len); } else { int i = 0; - for (;isize.h;++i) { - pj_memcpy(Y+(i*stream->size.w), p+(i*stride), - stream->size.w); + for (; i < stream->vid_size.h; ++i) { + pj_memcpy(Y, p, stream->vid_size.w); + Y += stream->vid_size.w; + p += stride; } } @@ -492,13 +521,13 @@ static pj_status_t ios_factory_default_param(pj_pool_t *pool, } } else { int i = 0; - for (;i<(stream->size.h)/2;++i) { + for (;i<(stream->vid_size.h)/2;++i) { int y=0; - for (;y<(stream->size.w)/2;++y) { + for (;y<(stream->vid_size.w)/2;++y) { *U++ = *p++; *V++ = *p++; } - p += (stride - stream->size.w); + p += (stride - stream->vid_size.w); } } } @@ -506,6 +535,13 @@ static pj_status_t ios_factory_default_param(pj_pool_t *pool, pj_memcpy(stream->capture_buf, CVPixelBufferGetBaseAddress(img), stream->frame_size); } + + status = pjmedia_vid_dev_conv_resize_and_rotate(&stream->conv, + stream->capture_buf, + &frame_buf); + if (status == PJ_SUCCESS) { + stream->frame_buf = frame_buf; + } stream->frame_ts.u64 += stream->ts_inc; [stream->frame_lock unlock]; @@ -527,7 +563,7 @@ static pj_status_t ios_stream_get_frame(pjmedia_vid_dev_stream *strm, frame->timestamp.u64 = stream->frame_ts.u64; [stream->frame_lock lock]; - pj_memcpy(frame->buf, stream->capture_buf, stream->frame_size); + pj_memcpy(frame->buf, stream->frame_buf, stream->frame_size); [stream->frame_lock unlock]; return PJ_SUCCESS; @@ -645,8 +681,10 @@ static pj_status_t ios_factory_create_stream( AVCaptureDevice *dev = qf->dev_info[param->cap_id].dev; for (i = PJ_ARRAY_SIZE(ios_sizes)-1; i > 0; --i) { - if ((vfd->size.w == ios_sizes[i].supported_size_w) && - (vfd->size.h == ios_sizes[i].supported_size_h)) + if (((vfd->size.w == ios_sizes[i].supported_size_w) && + (vfd->size.h == ios_sizes[i].supported_size_h)) || + ((vfd->size.w == ios_sizes[i].supported_size_h) && + (vfd->size.h == ios_sizes[i].supported_size_w))) { break; } @@ -654,9 +692,18 @@ static pj_status_t ios_factory_create_stream( strm->cap_session.sessionPreset = ios_sizes[i].preset_str; - vfd->size.w = ios_sizes[i].supported_size_w; - vfd->size.h = ios_sizes[i].supported_size_h; + /* If the requested size is portrait (or landscape), we make + * our natural orientation portrait (or landscape) as well. + */ + if (vfd->size.w > vfd->size.h) { + vfd->size.w = ios_sizes[i].supported_size_w; + vfd->size.h = ios_sizes[i].supported_size_h; + } else { + vfd->size.h = ios_sizes[i].supported_size_w; + vfd->size.w = ios_sizes[i].supported_size_h; + } strm->size = vfd->size; + strm->vid_size = vfd->size; strm->bytes_per_row = strm->size.w * vfi->bpp / 8; strm->frame_size = strm->bytes_per_row * strm->size.h; @@ -723,6 +770,7 @@ static pj_status_t ios_factory_create_stream( } strm->capture_buf = pj_pool_alloc(strm->pool, strm->frame_size); + strm->frame_buf = strm->capture_buf; strm->frame_lock = [[NSLock alloc]init]; /* Native preview */ @@ -730,6 +778,19 @@ static pj_status_t ios_factory_create_stream( ios_stream_set_cap(&strm->base, PJMEDIA_VID_DEV_CAP_INPUT_PREVIEW, ¶m->native_preview); } + + /* 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; + ios_stream_set_cap(&strm->base, PJMEDIA_VID_DEV_CAP_ORIENTATION, + ¶m->orient); + } } else if (param->dir & PJMEDIA_DIR_RENDER) { @@ -987,19 +1048,84 @@ static pj_status_t ios_stream_set_cap(pjmedia_vid_dev_stream *s, return PJ_SUCCESS; } - /* TODO: orientation for capture device */ case PJMEDIA_VID_DEV_CAP_ORIENTATION: { + pjmedia_orient orient = *(pjmedia_orient *)pval; + + pj_assert(orient >= PJMEDIA_ORIENT_UNKNOWN && + orient <= PJMEDIA_ORIENT_ROTATE_270DEG); + + if (orient == PJMEDIA_ORIENT_UNKNOWN) + return PJ_EINVAL; + pj_memcpy(&strm->param.orient, pval, sizeof(strm->param.orient)); - if (strm->param.orient == PJMEDIA_ORIENT_UNKNOWN) - return PJ_SUCCESS; - - dispatch_async(dispatch_get_main_queue(), ^{ - strm->render_view.transform = - CGAffineTransformMakeRotation( - ((int)strm->param.orient-1) * -M_PI_2); - }); + + if (strm->param.dir == PJMEDIA_DIR_RENDER) { + dispatch_async(dispatch_get_main_queue(), ^{ + strm->render_view.transform = + CGAffineTransformMakeRotation( + ((int)strm->param.orient-1) * -M_PI_2); + }); + + return PJ_SUCCESS; + } + + const AVCaptureVideoOrientation cap_ori[4] = + { + AVCaptureVideoOrientationLandscapeLeft, /* NATURAL */ + AVCaptureVideoOrientationPortrait, /* 90DEG */ + AVCaptureVideoOrientationLandscapeRight, /* 180DEG */ + AVCaptureVideoOrientationPortraitUpsideDown, /* 270DEG */ + }; + AVCaptureConnection *vidcon; + pj_bool_t support_ori = PJ_TRUE; + + pj_assert(strm->param.dir == PJMEDIA_DIR_CAPTURE); + + if (!strm->video_output) + return PJMEDIA_EVID_NOTREADY; + + vidcon = [strm->video_output + connectionWithMediaType:AVMediaTypeVideo]; + if ([vidcon isVideoOrientationSupported]) { + vidcon.videoOrientation = cap_ori[strm->param.orient-1]; + } else { + support_ori = PJ_FALSE; + } + + if (!strm->conv.conv) { + pj_status_t status; + pjmedia_rect_size orig_size; + + /* Original native size of device is landscape */ + orig_size.w = (strm->size.w > strm->size.h? strm->size.w : + strm->size.h); + orig_size.h = (strm->size.w > strm->size.h? strm->size.h : + strm->size.w); + + if (!support_ori) { + PJ_LOG(4, (THIS_FILE, "Native video capture orientation " + "unsupported, will use converter's " + "rotation.")); + } + + status = pjmedia_vid_dev_conv_create_converter( + &strm->conv, strm->pool, + &strm->param.fmt, + orig_size, strm->size, + (support_ori?PJ_FALSE:PJ_TRUE), + MAINTAIN_ASPECT_RATIO); + + if (status != PJ_SUCCESS) + return status; + } + + pjmedia_vid_dev_conv_set_rotation(&strm->conv, strm->param.orient); + + PJ_LOG(5, (THIS_FILE, "Video capture orientation set to %d", + strm->param.orient)); + return PJ_SUCCESS; } @@ -1140,6 +1266,8 @@ static pj_status_t ios_stream_destroy(pjmedia_vid_dev_stream *strm) [stream->frame_lock release]; stream->frame_lock = nil; } + + pjmedia_vid_dev_conv_destroy_converter(&stream->conv); pj_pool_release(stream->pool); 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 +#include +#include + +#if defined(PJMEDIA_HAS_LIBYUV) && PJMEDIA_HAS_LIBYUV != 0 + #include + #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; + } +} diff --git a/pjmedia/src/pjmedia-videodev/util.h b/pjmedia/src/pjmedia-videodev/util.h new file mode 100644 index 00000000..e0b8b21a --- /dev/null +++ b/pjmedia/src/pjmedia-videodev/util.h @@ -0,0 +1,96 @@ +/* $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 + */ +#ifndef __PJMEDIA_VIDEODEV_UTIL_H__ +#define __PJMEDIA_VIDEODEV_UTIL_H__ + +#include +#include +#include + +/* + * Video device utility functions to resize and rotate video frames. + */ + +typedef struct pjmedia_vid_dev_conv +{ + pjmedia_converter *conv; + pjmedia_format fmt; + pjmedia_rect_size src_size; + pjmedia_rect_size dst_size; + pjmedia_rect_size res_size; /* Size after resizing */ + pjmedia_orient rotation; + pjmedia_rect_size rot_size; /* Size after rotation */ + + void *conv_buf; + pj_size_t src_frame_size; + pj_size_t conv_frame_size; + + pj_bool_t fit_to_h; + pj_bool_t handle_rotation; + pj_bool_t maintain_aspect_ratio; + pj_bool_t match_src_dst; + pj_int32_t pad; + pj_size_t wxh; +} pjmedia_vid_dev_conv; + +/** + * Create converter. + * The process: + * frame --> resize --> rotate --> center + * (if handle_rotation (if maintain_aspect_ratio + * == PJ_TRUE) == PJ_TRUE) + * + * handle_rotation will specify whether the converter will need to do the + * rotation as well. If PJ_FALSE, the video device will handle the rotation + * and pass the already-rotated frame. + * + * maintain_aspect_ratio defines whether aspect ratio should be maintained + * when rotating the image. + * If PJ_TRUE, a frame of size w x h will be resized and rotated to + * a new frame of size new_w x new_h (new_h and new_h have the same + * aspect ratio as w x h). Then the new frame will be centered-fit into + * the original frame with black area inserted to fill the gaps. + * Disabling this setting will only resize the frame of size w x h to h x w, + * and then rotate it to fit the original size of w x h. It will achieve + * a slightly faster performance but the resulting image will be stretched. + * The feature to maintain aspect ratio is only supported for certain formats + * (currently, only if fmt.id equals to I420). + */ +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); + +/* Set rotation */ +void pjmedia_vid_dev_conv_set_rotation(pjmedia_vid_dev_conv *conv, + pjmedia_orient rotation); + +/* Resize the buffer and rotate it, if necessary */ +pj_status_t pjmedia_vid_dev_conv_resize_and_rotate(pjmedia_vid_dev_conv *conv, + void *src_buf, + void **result); + +/* Destroy converter */ +void pjmedia_vid_dev_conv_destroy_converter(pjmedia_vid_dev_conv *conv); + +#endif /* __PJMEDIA_VIDEODEV_UTIL_H__ */ -- cgit v1.2.3