Index: pjproject/trunk/pjmedia/build/Makefile
===================================================================
--- pjproject/trunk/pjmedia/build/Makefile	(revision 5117)
+++ pjproject/trunk/pjmedia/build/Makefile	(revision 5118)
@@ -107,5 +107,6 @@
 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)
Index: pjproject/trunk/pjmedia/src/pjmedia-videodev/ios_dev.m
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia-videodev/ios_dev.m	(revision 5117)
+++ pjproject/trunk/pjmedia/src/pjmedia-videodev/ios_dev.m	(revision 5118)
@@ -17,4 +17,5 @@
  * 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>
@@ -37,4 +38,9 @@
 #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
@@ -109,4 +115,8 @@
     NSLock 		   *frame_lock;
     void                   *capture_buf;
+    void		   *frame_buf;
+    
+    pjmedia_vid_dev_conv    conv;
+    pjmedia_rect_size	    vid_size;
     
     AVCaptureSession		*cap_session;
@@ -314,4 +324,5 @@
                                                        ios_sizes[m].preset_str])
                     {
+                        /* Landscape video */
                         fmt = &qdi->info.fmt[qdi->info.fmt_cnt++];
                         pjmedia_format_init_video(fmt,
@@ -319,4 +330,11 @@
                                                   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);
                     }
@@ -436,4 +454,6 @@
 {
     CVImageBufferRef img;
+    pj_status_t status;
+    void *frame_buf;
 
     /* Refrain from calling pjlib functions which require thread registration
@@ -452,5 +472,4 @@
     /* Lock the base address of the pixel buffer */
     CVPixelBufferLockBaseAddress(img, kCVPixelBufferLock_ReadOnly);
-    
 
     [stream->frame_lock lock];
@@ -464,8 +483,17 @@
              */
             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;
@@ -476,7 +504,8 @@
             } 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;
                 }
             }
@@ -493,11 +522,11 @@
             } 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,4 +535,11 @@
         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;
     }
 
@@ -528,5 +564,5 @@
     
     [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];
     
@@ -646,6 +682,8 @@
  
         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;
@@ -655,7 +693,16 @@
         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;
@@ -724,4 +771,5 @@
 	
 	strm->capture_buf = pj_pool_alloc(strm->pool, strm->frame_size);
+	strm->frame_buf = strm->capture_buf;
 	strm->frame_lock = [[NSLock alloc]init];
 	
@@ -730,4 +778,17 @@
             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);
         }
         
@@ -988,17 +1049,82 @@
         }
             
-        /* 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;
         }
@@ -1141,4 +1267,6 @@
         stream->frame_lock = nil;
     }
+    
+    pjmedia_vid_dev_conv_destroy_converter(&stream->conv);
 
     pj_pool_release(stream->pool);
Index: pjproject/trunk/pjmedia/src/pjmedia-videodev/util.c
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia-videodev/util.c	(revision 5118)
+++ pjproject/trunk/pjmedia/src/pjmedia-videodev/util.c	(revision 5118)
@@ -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;
+    }
+}
Index: pjproject/trunk/pjmedia/src/pjmedia-videodev/util.h
===================================================================
--- pjproject/trunk/pjmedia/src/pjmedia-videodev/util.h	(revision 5118)
+++ pjproject/trunk/pjmedia/src/pjmedia-videodev/util.h	(revision 5118)
@@ -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__ */
Index: pjproject/trunk/pjsip-apps/src/pjsua/ios/ipjsua/ipjsuaAppDelegate.m
===================================================================
--- pjproject/trunk/pjsip-apps/src/pjsua/ios/ipjsua/ipjsuaAppDelegate.m	(revision 5117)
+++ pjproject/trunk/pjsip-apps/src/pjsua/ios/ipjsua/ipjsuaAppDelegate.m	(revision 5118)
@@ -40,6 +40,4 @@
 static char           **restartArgv;
 static int              restartArgc;
-static pj_thread_desc   a_thread_desc;
-static pj_thread_t     *a_thread;
 
 static void displayMsg(const char *msg)
@@ -118,4 +116,11 @@
         }
     
+        /* Setup device orientation change notification */
+        [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
+        [[NSNotificationCenter defaultCenter] addObserver:app
+            selector:@selector(orientationChanged:)
+            name:UIDeviceOrientationDidChangeNotification
+            object:[UIDevice currentDevice]];
+        
         status = pjsua_app_run(PJ_TRUE);
         if (status != PJ_SUCCESS) {
@@ -124,4 +129,6 @@
             displayMsg(errmsg);
         }
+        
+        [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
     
         pjsua_app_destroy();
@@ -158,9 +165,44 @@
 }
 
+- (void)orientationChanged:(NSNotification *)note
+{
+#if PJSUA_HAS_VIDEO
+    const pjmedia_orient pj_ori[4] =
+    {
+        PJMEDIA_ORIENT_ROTATE_90DEG,  /* UIDeviceOrientationPortrait */
+        PJMEDIA_ORIENT_ROTATE_270DEG, /* UIDeviceOrientationPortraitUpsideDown */
+        PJMEDIA_ORIENT_ROTATE_180DEG, /* UIDeviceOrientationLandscapeLeft,
+                                         home button on the right side */
+        PJMEDIA_ORIENT_NATURAL        /* UIDeviceOrientationLandscapeRight,
+                                         home button on the left side */
+    };
+    static pj_thread_desc a_thread_desc;
+    static pj_thread_t *a_thread;
+    static UIDeviceOrientation prev_ori = 0;
+    UIDeviceOrientation dev_ori = [[UIDevice currentDevice] orientation];
+    
+    if (dev_ori == prev_ori && !note) return;
+    
+    NSLog(@"Device orientation changed: %d", (prev_ori = dev_ori));
+    
+    if (dev_ori >= UIDeviceOrientationPortrait &&
+        dev_ori <= UIDeviceOrientationLandscapeRight)
+    {
+        if (!pj_thread_is_registered()) {
+            pj_thread_register("ipjsua", a_thread_desc, &a_thread);
+        }
+        
+        pjsua_vid_dev_set_orient(PJMEDIA_VID_DEFAULT_CAPTURE_DEV,
+                                 pj_ori[dev_ori-1]);
+    }
+#endif
+}
+
 - (void)keepAlive {
+    static pj_thread_desc a_thread_desc;
+    static pj_thread_t *a_thread;
     int i;
     
-    if (!pj_thread_is_registered())
-    {
+    if (!pj_thread_is_registered()) {
 	pj_thread_register("ipjsua", a_thread_desc, &a_thread);
     }
@@ -230,4 +272,6 @@
     i = (wid == PJSUA_INVALID_ID) ? 0 : wid;
     last = (wid == PJSUA_INVALID_ID) ? PJSUA_MAX_VID_WINS : wid+1;
+
+    [app orientationChanged:NULL];
     
     for (;i < last; ++i) {
Index: pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h
===================================================================
--- pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h	(revision 5117)
+++ pjproject/trunk/pjsip/include/pjsua-lib/pjsua.h	(revision 5118)
@@ -6601,4 +6601,30 @@
 
 /**
+ * Check whether the video capture device is currently active, i.e. if
+ * a video preview has been started or there is a video call using
+ * the device. This function will return PJ_FALSE for video renderer device.
+ *
+ * @param id		The video device index.
+ *
+ * @return		PJ_TRUE if active, PJ_FALSE otherwise.
+ */
+PJ_DECL(pj_bool_t) pjsua_vid_dev_is_active(pjmedia_vid_dev_index id);
+
+/**
+ * Set the orientation of the video device. The function only works
+ * for video capture device and if the device is currently active (i.e.
+ * a video preview has been started or there is a video call using the device).
+ * Application can check if a video device is active by calling
+ * #pjsua_vid_dev_is_active().
+ *
+ * @param id		The video device index.
+ * @param orient	Video device orientation.
+ *
+ * @return		PJ_SUCCESS on success, or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pjsua_vid_dev_set_orient(pjmedia_vid_dev_index id,
+					      pjmedia_orient orient);
+
+/**
  * Enum all video devices installed in the system.
  *
Index: pjproject/trunk/pjsip/src/pjsua-lib/pjsua_vid.c
===================================================================
--- pjproject/trunk/pjsip/src/pjsua-lib/pjsua_vid.c	(revision 5117)
+++ pjproject/trunk/pjsip/src/pjsua-lib/pjsua_vid.c	(revision 5118)
@@ -33,4 +33,6 @@
 
 
+static pjsua_vid_win_id vid_preview_get_win(pjmedia_vid_dev_index id,
+                                            pj_bool_t running_only);
 static void free_vid_win(pjsua_vid_win_id wid);
 
@@ -212,4 +214,39 @@
 {
     return pjmedia_vid_dev_get_info(id, vdi);
+}
+
+/*
+ * Check whether the video device is currently active.
+ */
+PJ_DEF(pj_bool_t) pjsua_vid_dev_is_active(pjmedia_vid_dev_index id)
+{
+    pjsua_vid_win_id wid = vid_preview_get_win(id, PJ_FALSE);
+    
+    return (wid != PJSUA_INVALID_ID? PJ_TRUE: PJ_FALSE);
+}
+
+/*
+ * Set the orientation of the video device.
+ */
+PJ_DEF(pj_status_t) pjsua_vid_dev_set_orient( pjmedia_vid_dev_index id,
+					      pjmedia_orient orient)
+{
+    pjsua_vid_win *w;
+    pjmedia_vid_dev_stream *cap_dev;
+    pjsua_vid_win_id wid = vid_preview_get_win(id, PJ_FALSE);
+    
+    if (wid == PJSUA_INVALID_ID) {
+	PJ_LOG(3, (THIS_FILE, "Unable to set orientation for video dev %d: "
+			      "device not active", id));
+	return PJ_ENOTFOUND;
+    }
+
+    w = &pjsua_var.win[wid];
+	
+    cap_dev = pjmedia_vid_port_get_stream(w->vp_cap);
+
+    return pjmedia_vid_dev_stream_set_cap(cap_dev,
+					  PJMEDIA_VID_DEV_CAP_ORIENTATION,
+    				   	  &orient);
 }
 
