summaryrefslogtreecommitdiff
path: root/pjmedia
diff options
context:
space:
mode:
authorLiong Sauw Ming <ming@teluu.com>2015-06-25 08:17:52 +0000
committerLiong Sauw Ming <ming@teluu.com>2015-06-25 08:17:52 +0000
commit97f362db7fa1fad9a0258bdb5e218b80dab93031 (patch)
tree135ac5d783604db13ee17e6d78551851c4020b1d /pjmedia
parent6138b1938ddf639566e8cdccb016ad6be1da39fb (diff)
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
Diffstat (limited to 'pjmedia')
-rw-r--r--pjmedia/build/Makefile3
-rw-r--r--pjmedia/src/pjmedia-videodev/ios_dev.m174
-rw-r--r--pjmedia/src/pjmedia-videodev/util.c357
-rw-r--r--pjmedia/src/pjmedia-videodev/util.h96
4 files changed, 606 insertions, 24 deletions
diff --git a/pjmedia/build/Makefile b/pjmedia/build/Makefile
index 54395c38..702efdcb 100644
--- a/pjmedia/build/Makefile
+++ b/pjmedia/build/Makefile
@@ -106,7 +106,8 @@ export PJMEDIA_AUDIODEV_LDFLAGS += $(PJLIB_LDLIB) \
#
export PJMEDIA_VIDEODEV_SRCDIR = ../src/pjmedia-videodev
export PJMEDIA_VIDEODEV_OBJS += errno.o videodev.o avi_dev.o ffmpeg_dev.o \
- colorbar_dev.o v4l2_dev.o opengl_dev.o
+ colorbar_dev.o v4l2_dev.o opengl_dev.o \
+ util.o
export PJMEDIA_VIDEODEV_CFLAGS += $(_CFLAGS)
export PJMEDIA_VIDEODEV_CXXFLAGS += $(_CXXFLAGS)
export PJMEDIA_VIDEODEV_LDFLAGS += $(PJLIB_LDLIB) \
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 <pjmedia-videodev/videodev_imp.h>
#include <pj/assert.h>
#include <pj/log.h>
@@ -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 (;i<stream->size.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,
&param->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,
+ &param->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 <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;
+ }
+}
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 <pjmedia/converter.h>
+#include <pjmedia/format.h>
+#include <pjmedia/types.h>
+
+/*
+ * 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__ */