Index: /trunk/include/VBox/err.h
===================================================================
--- /trunk/include/VBox/err.h	(revision 105005)
+++ /trunk/include/VBox/err.h	(revision 105006)
@@ -3192,4 +3192,8 @@
 /** Encoding data failed. */
 #define VERR_RECORDING_ENCODING_FAILED              (-6908)
+/** Encoding skipped.
+ *  Can happen if e.g. a frame is not visible or does
+ *  not need to be encoded for whatever reason. */
+#define VWRN_RECORDING_ENCODING_SKIPPED             (6908)
 /** @} */
 
Index: /trunk/src/VBox/Main/include/ConsoleImpl.h
===================================================================
--- /trunk/src/VBox/Main/include/ConsoleImpl.h	(revision 105005)
+++ /trunk/src/VBox/Main/include/ConsoleImpl.h	(revision 105006)
@@ -204,4 +204,5 @@
     int i_recordingStart(util::AutoWriteLock *pAutoLock = NULL);
     int i_recordingStop(util::AutoWriteLock *pAutoLock = NULL);
+    int i_recordingCursorShapeChange(bool fVisible, bool fAlpha, uint32_t xHot, uint32_t yHot, uint32_t uWidth, uint32_t uHeight, const uint8_t *pu8Shape, uint32_t cbShape);
 # ifdef VBOX_WITH_AUDIO_RECORDING
     AudioVideoRec *i_recordingGetAudioDrv(void) const { return mRecording.mAudioRec; }
Index: /trunk/src/VBox/Main/include/DisplayImpl.h
===================================================================
--- /trunk/src/VBox/Main/include/DisplayImpl.h	(revision 105005)
+++ /trunk/src/VBox/Main/include/DisplayImpl.h	(revision 105006)
@@ -44,4 +44,7 @@
 #include "GuestScreenInfoWrap.h"
 
+#ifdef VBOX_WITH_RECORDING
+# include "RecordingInternals.h"
+#endif
 
 class Console;
@@ -93,11 +96,4 @@
     VBVAHOSTFLAGS RT_UNTRUSTED_VOLATILE_GUEST *pVBVAHostFlags;
 #endif /* VBOX_WITH_HGSMI */
-
-#ifdef VBOX_WITH_RECORDING
-    struct
-    {
-        ComPtr<IDisplaySourceBitmap> pSourceBitmap;
-    } Recording;
-#endif /* VBOX_WITH_RECORDING */
 
     /** Description of the currently plugged monitor with preferred mode,
@@ -187,11 +183,13 @@
     void VideoAccelFlushVMMDev(void);
 
-    void i_UpdateDeviceCursorCapabilities(void);
+    void i_updateDeviceCursorCapabilities(void);
 
 #ifdef VBOX_WITH_RECORDING
-    int  i_recordingStart(void);
-    int  i_recordingStop(void);
-    int  i_recordingInvalidate(bool fForce = false);
-    void i_recordingScreenChanged(unsigned uScreenId);
+    int i_recordingStart(void);
+    int i_recordingStop(void);
+    int i_recordingInvalidate(bool fForce = false);
+    int i_recordingScreenChanged(unsigned uScreenId, const DISPLAYFBINFO *pFBInfo);
+    int i_recordingScreenUpdate(unsigned uScreenId, PRECORDINGVIDEOFRAME pFrame);
+    int i_recordingCursorPositionChange(unsigned uScreenId, uint32_t fFlags, int32_t x, int32_t y);
 #endif
 
Index: /trunk/src/VBox/Main/include/MouseImpl.h
===================================================================
--- /trunk/src/VBox/Main/include/MouseImpl.h	(revision 105005)
+++ /trunk/src/VBox/Main/include/MouseImpl.h	(revision 105006)
@@ -37,4 +37,93 @@
 #include <VBox/vmm/pdmdrv.h>
 
+/**
+ * Structure for keeping mouse pointer data.
+ */
+struct MousePointerData
+{
+    MousePointerData()
+        : fVisible(false)
+        , hotX(0)
+        , hotY(0)
+        , width(0)
+        , height(0)
+        , pu8Shape(NULL)
+        , cbShape(0) { }
+
+    virtual ~MousePointerData()
+    {
+        Destroy();
+    }
+
+    /**
+     * Initialize pointer shape data.
+     *
+     * @returns VBox status code.
+     * @param   afVisible           Whether the mouse cursor actually is visible or not.
+     * @param   afAlpha             Whether the pixel data contains an alpha mask or not.
+     * @param   auHotX              X hot position (in pixel) of the new cursor.
+     * @param   auHotY              Y hot position (in pixel) of the new cursor.
+     * @param   auWidth             Width (in pixel) of the new cursor.
+     * @param   auHeight            Height (in pixel) of the new cursor.
+     * @param   apu8Shape           Pixel data of the new cursor.
+     * @param   acbShape            Size of \a apu8Shape (in bytes).
+     */
+    int Init(bool afVisible, bool afAlpha, uint32_t auHotX, uint32_t auHotY, uint32_t auWidth, uint32_t auHeight,
+             const uint8_t *apu8Shape, uint32_t acbShape)
+    {
+        AssertMsgReturn(pu8Shape == NULL, ("Already initialized!\n"), VERR_WRONG_ORDER);
+
+        fVisible = afVisible;
+        fAlpha   = afAlpha;
+        hotX     = auHotX;
+        hotY     = auHotY;
+        width    = auWidth;
+        height   = auHeight;
+        if (acbShape)
+        {
+            pu8Shape = (uint8_t *)RTMemDup(apu8Shape, acbShape);
+            AssertPtrReturn(pu8Shape, VERR_NO_MEMORY);
+            cbShape = acbShape;
+        }
+
+        return VINF_SUCCESS;
+    }
+
+    /**
+     * Initialize pointer shape data with another pointer shape data instance.
+     *
+     * @returns VBox status code.
+     * @param   aThat           Pointer shape data instance to use for initialization.
+     */
+    int Init(const MousePointerData &aThat)
+    {
+        return Init(aThat.fVisible, aThat.fAlpha, aThat.hotX, aThat.hotY, aThat.width, aThat.height,
+                    aThat.pu8Shape, aThat.cbShape);
+    }
+
+    /**
+     * Destroys a pointer shape.
+     */
+    void Destroy(void)
+    {
+        if (pu8Shape)
+        {
+            Assert(cbShape);
+            RTMemFree(pu8Shape);
+            pu8Shape = NULL;
+        }
+        cbShape = 0;
+    }
+
+    bool fVisible;
+    bool fAlpha;
+    uint32_t hotX;
+    uint32_t hotY;
+    uint32_t width;
+    uint32_t height;
+    uint8_t *pu8Shape;
+    uint32_t cbShape;
+};
+
 /** Maximum number of devices supported */
 enum { MOUSE_MAX_DEVICES = 4 };
@@ -70,9 +159,9 @@
     }
 
-    void updateMousePointerShape(bool fVisible, bool fAlpha,
-                                 uint32_t hotX, uint32_t hotY,
-                                 uint32_t width, uint32_t height,
-                                 const uint8_t *pu8Shape, uint32_t cbShape);
-private:
+    int i_getPointerShape(MousePointerData &aData);
+    int i_updatePointerShape(bool fVisible, bool fAlpha,
+                             uint32_t xHot, uint32_t yHot,
+                             uint32_t uWidth, uint32_t uHeight,
+                             const uint8_t *pu8Shape, uint32_t cbShape);
 
     // Wrapped IMouse properties
@@ -104,5 +193,5 @@
                                      BOOL isTouchScreen,
                                      ULONG aScanTime);
-
+private:
 
     static DECLCALLBACK(void *) i_drvQueryInterface(PPDMIBASE pInterface, const char *pszIID);
@@ -148,15 +237,6 @@
 
     ComPtr<IMousePointerShape> mPointerShape;
-    struct
-    {
-        bool fVisible;
-        bool fAlpha;
-        uint32_t hotX;
-        uint32_t hotY;
-        uint32_t width;
-        uint32_t height;
-        uint8_t *pu8Shape;
-        uint32_t cbShape;
-    } mPointerData;
+    /** Current mouse pointer data. */
+    MousePointerData           mPointerData;
 
     const ComObjPtr<EventSource> mEventSource;
Index: /trunk/src/VBox/Main/include/Recording.h
===================================================================
--- /trunk/src/VBox/Main/include/Recording.h	(revision 105005)
+++ /trunk/src/VBox/Main/include/Recording.h	(revision 105006)
@@ -38,4 +38,35 @@
 
 class Console;
+
+/** No flags specified. */
+#define VBOX_RECORDING_CURSOR_F_NONE       0
+/** Cursor is visible. */
+#define VBOX_RECORDING_CURSOR_F_VISIBLE    RT_BIT(0)
+/** Cursor shape contains an alpha mask. */
+#define VBOX_RECORDING_CURSOR_F_ALPHA      RT_BIT(1)
+/** Cursor state flags valid mask. */
+#define VBOX_RECORDING_CURSOR_F_VALID_MASK 0x3
+
+/**
+ * Class for keeping a recording cursor state.
+ */
+class RecordingCursorState
+{
+public:
+
+    RecordingCursorState();
+    virtual ~RecordingCursorState();
+
+    void Destroy();
+
+    int CreateOrUpdate(bool fAlpha, uint32_t uWidth, uint32_t uHeight, const uint8_t *pu8Shape, size_t cbShape);
+
+    int Move(int32_t iX, int32_t iY);
+
+    /** Cursor state flags. */
+    uint32_t            m_fFlags;
+    /** The current cursor shape. */
+    RECORDINGVIDEOFRAME m_Shape;
+};
 
 /**
@@ -68,10 +99,12 @@
 
     int SendAudioFrame(const void *pvData, size_t cbData, uint64_t uTimestampMs);
-    int SendVideoFrame(uint32_t uScreen,
-                       uint32_t x, uint32_t y, uint32_t uPixelFormat, uint32_t uBPP,
-                       uint32_t uBytesPerLine, uint32_t uSrcWidth, uint32_t uSrcHeight,
-                       uint8_t *puSrcData, uint64_t msTimestamp);
-public:
-
+    int SendVideoFrame(uint32_t uScreen, PRECORDINGVIDEOFRAME pFrame, uint64_t msTimestamp);
+    int SendCursorPositionChange(uint32_t uScreen, int32_t x, int32_t y, uint64_t msTimestamp);
+    int SendCursorShapeChange(bool fVisible, bool fAlpha, uint32_t xHot, uint32_t yHot, uint32_t uWidth, uint32_t uHeight, const uint8_t *pu8Shape, size_t cbShape, uint64_t msTimestamp);
+    int SendScreenChange(uint32_t uScreen, PRECORDINGSURFACEINFO pInfo, uint64_t uTimestampMs);
+
+public:
+
+    uint64_t GetCurrentPTS(void) const;
     bool IsFeatureEnabled(RecordingFeature_T enmFeature);
     bool IsReady(void);
@@ -83,4 +116,9 @@
 
     DECLCALLBACK(int) OnLimitReached(uint32_t uScreen, int vrc);
+
+    /** The state mouse cursor state.
+     *  We currently only support one mouse cursor at a time. */
+    RecordingCursorState         m_Cursor;
+
 
 protected:
@@ -146,5 +184,6 @@
     /** Number of streams in vecStreams which currently are enabled for recording. */
     uint16_t                     m_cStreamsEnabled;
-    /** Timestamp (in ms) of when recording has been started. */
+    /** Timestamp (in ms) of when recording has been started.
+     *  Set to 0 if not started (yet). */
     uint64_t                     m_tsStartMs;
 #ifdef VBOX_WITH_AUDIO_RECORDING
Index: /trunk/src/VBox/Main/include/RecordingInternals.h
===================================================================
--- /trunk/src/VBox/Main/include/RecordingInternals.h	(revision 105005)
+++ /trunk/src/VBox/Main/include/RecordingInternals.h	(revision 105006)
@@ -76,4 +76,116 @@
 
 /**
+ * Enumeration for supported pixel formats.
+ */
+enum RECORDINGPIXELFMT
+{
+    /** Unknown pixel format. */
+    RECORDINGPIXELFMT_UNKNOWN    = 0,
+    /** BRGA 32. */
+    RECORDINGPIXELFMT_BRGA32     = 1,
+    /** The usual 32-bit hack. */
+    RECORDINGPIXELFMT_32BIT_HACK = 0x7fffffff
+};
+
+/**
+ * Structure for keeping recording surface information.
+ */
+typedef struct RECORDINGSURFACEINFO
+{
+    /** Width (in pixel). */
+    uint32_t          uWidth;
+    /** Height (in pixel). */
+    uint32_t          uHeight;
+    /** Bits per pixel. */
+    uint8_t           uBPP;
+    /** Pixel format. */
+    RECORDINGPIXELFMT enmPixelFmt;
+    /** Bytes per scan line (stride). */
+    uint32_t          uBytesPerLine;
+} RECORDINGSURFACEINFO;
+/** Pointer to recording surface information. */
+typedef RECORDINGSURFACEINFO *PRECORDINGSURFACEINFO;
+
+/**
+ * Structure for keeping recording relative or absolute position information.
+ */
+typedef struct RECORDINGPOS
+{
+    uint32_t x;
+    uint32_t y;
+} RECORDINGPOS;
+/** Pointer to a RECORDINGRECT. */
+typedef RECORDINGPOS *PRECORDINGPOS;
+
+/** No flags set. */
+#define RECORDINGVIDEOFRAME_F_NONE       UINT32_C(0)
+/** Frame is visible. */
+#define RECORDINGVIDEOFRAME_F_VISIBLE    RT_BIT(0)
+/** Use blitting with alpha blending. */
+#define RECORDINGVIDEOFRAME_F_BLIT_ALPHA RT_BIT(1)
+/** Validation mask. */
+#define RECORDINGVIDEOFRAME_F_VALID_MASK 0x3
+
+/**
+ * Structure for keeping a single recording video frame.
+ */
+typedef struct RECORDINGVIDEOFRAME
+{
+    /** Surface information of this frame. */
+    RECORDINGSURFACEINFO Info;
+    /** Pixel data buffer. */
+    uint8_t             *pau8Buf;
+    /** Size (in bytes) of \a pau8Buf. */
+    size_t               cbBuf;
+    /** X / Y positioning hint (in pixel) of this frame.
+     *  Note: This does *not* mean offset within \a pau8Buf! */
+    RECORDINGPOS         Pos;
+    /** Recording video frame flags of type RECORDINGVIDEOFRAME_F_XXX. */
+    uint32_t             fFlags;
+} RECORDINGVIDEOFRAME;
+/** Pointer to a video recording frame. */
+typedef RECORDINGVIDEOFRAME *PRECORDINGVIDEOFRAME;
+
+/**
+ * Enumeration for supported scaling methods.
+ */
+enum RECORDINGSCALINGMETHOD
+{
+    /** No scaling applied.
+     *  Bigger frame buffers will be cropped (centered),
+     *  smaller frame buffers will be centered. */
+    RECORDINGSCALINGMETHOD_NONE       = 0,
+    /** The usual 32-bit hack. */
+    RECORDINGSCALINGMETHOD_32BIT_HACK = 0x7fffffff
+};
+
+/**
+ * Structure for keeping recording scaling information.
+ */
+typedef struct RECORDINGSCALINGINFO
+{
+    /** The scaling method to use. */
+    RECORDINGSCALINGMETHOD enmMethod;
+    /** Union based on \a enmMethod. */
+    union
+    {
+        /** Cropping information. */
+        struct
+        {
+            /** X origin.
+             *  If negative, the frame buffer will be cropped with centering applied,
+             *  if positive, the frame buffer will be centered. */
+            int32_t m_iOriginX;
+            /** Y origin. *
+             * If negative, the frame buffer will be cropped with centering applied,
+             * if positive, the frame buffer will be centered. */
+            int32_t m_iOriginY;
+        } Crop;
+    } u;
+} RECORDINGSCALINGINFO;
+/** Pointer to a recording scaling information. */
+typedef RECORDINGSCALINGINFO *PRECORDINGSCALINGINFO;
+
+/**
  * Enumeration for specifying a (generic) codec type.
  */
@@ -117,5 +229,5 @@
      * @param   strOptions          Options string to parse.
      */
-    DECLCALLBACKMEMBER(int, pfnParseOptions, (PRECORDINGCODEC pCodec, const com::Utf8Str &strOptions));
+    DECLCALLBACKMEMBER(int, pfnParseOptions,  (PRECORDINGCODEC pCodec, const com::Utf8Str &strOptions));
 
     /**
@@ -124,9 +236,18 @@
      * @returns VBox status code.
      * @param   pCodec              Codec instance to use.
-     * @param   pFrame              Pointer to frame data to encode.
-     * @param   pcEncoded           Where to return the number of encoded blocks in \a pvDst on success. Optional.
-     * @param   pcbEncoded          Where to return the number of encoded bytes in \a pvDst on success. Optional.
-     */
-    DECLCALLBACKMEMBER(int, pfnEncode,       (PRECORDINGCODEC pCodec, const PRECORDINGFRAME pFrame, size_t *pcEncoded, size_t *pcbEncoded));
+     * @param   pFrame              Pointer to frame data to encode. Optional and can be NULL.
+     * @param   msTimestamp         Timestamp (PTS) of frame to encode.
+     * @param   pvUser              User data pointer. Optional and can be NULL.
+     */
+    DECLCALLBACKMEMBER(int, pfnEncode,       (PRECORDINGCODEC pCodec, const PRECORDINGFRAME pFrame, uint64_t msTimestamp, void *pvUser));
+
+    /**
+     * Tells the codec about a screen change. Optional.
+     *
+     * @returns VBox status code.
+     * @param   pCodec              Codec instance to use.
+     * @param   pInfo               Screen information to send.
+     */
+    DECLCALLBACKMEMBER(int, pfnScreenChange, (PRECORDINGCODEC pCodec, PRECORDINGSURFACEINFO pInfo));
 
     /**
@@ -146,5 +267,5 @@
 #define RECORDINGCODEC_ENC_F_BLOCK_IS_INVISIBLE RT_BIT_32(1)
 /** Encoding flags valid mask. */
-#define RECORDINGCODEC_ENC_F_VALID_MASK         0x1
+#define RECORDINGCODEC_ENC_F_VALID_MASK         0x3
 
 /**
@@ -189,12 +310,14 @@
         {
             /** Frames per second. */
-            uint8_t             uFPS;
+            uint8_t              uFPS;
             /** Target width (in pixels) of encoded video image. */
-            uint16_t            uWidth;
+            uint32_t             uWidth;
             /** Target height (in pixels) of encoded video image. */
-            uint16_t            uHeight;
+            uint32_t             uHeight;
             /** Minimal delay (in ms) between two video frames.
              *  This value is based on the configured FPS rate. */
-            uint32_t            uDelayMs;
+            uint32_t             uDelayMs;
+            /** Scaling information. */
+            RECORDINGSCALINGINFO Scaling;
         } Video;
         struct
@@ -203,5 +326,5 @@
             PDMAUDIOPCMPROPS    PCMProps;
         } Audio;
-    };
+    } u;
     /** Desired (average) bitrate (in kbps) to use, for codecs which support bitrate management.
      *  Set to 0 to use a variable bit rate (VBR) (if available, otherwise fall back to CBR). */
@@ -230,5 +353,6 @@
     /** VPX image context. */
     vpx_image_t         RawImage;
-    /** Pointer to the codec's internal YUV buffer. */
+    /** Pointer to the codec's internal YUV buffer.
+     *  VP8 works exclusively with an 8-bit YUV 4:2:0 image, so frame packed, where U and V are half resolution images. */
     uint8_t            *pu8YuvBuf;
     /** The encoder's deadline (in ms).
@@ -236,4 +360,19 @@
      *  result, in exchange for higher CPU usage and time spent encoding. */
     unsigned int        uEncoderDeadline;
+    /** Front buffer which is going to be encoded.
+     *  Matches Main's framebuffer pixel format for faster / easier conversion.
+     *  Not necessarily the same size as the encoder buffer. In such a case a scaling / cropping
+     *  operation has to be performed first. */
+    RECORDINGVIDEOFRAME Front;
+    /** Back buffer which holds the framebuffer data before we blit anything to it.
+     *  Needed for mouse cursor handling, if the mouse cursor is not actually part of the framebuffer data.
+     *  Always matches the front buffer (\a Front) in terms of size. */
+    RECORDINGVIDEOFRAME Back;
+    /** The current cursor shape to use.
+     *  Set to NULL if not used (yet). */
+    PRECORDINGVIDEOFRAME pCursorShape;
+    /** Old cursor position since last cursor position message. */
+    RECORDINGPOS        PosCursorOld;
+
 } RECORDINGCODECVPX;
 /** Pointer to a VPX encoder state. */
@@ -284,4 +423,6 @@
     /** Generic codec parameters. */
     RECORDINGCODECSTATE         State;
+    /** Crtitical section. */
+    RTCRITSECT                  CritSect;
 
 #ifdef VBOX_WITH_LIBVPX
@@ -301,76 +442,32 @@
 #endif /* VBOX_WITH_AUDIO_RECORDING */
 
-    /** Internal scratch buffer for en-/decoding steps. */
+    /** Internal scratch buffer for en-/decoding steps.
+     *  Might be NULL if not being used. */
     void               *pvScratch;
     /** Size (in bytes) of \a pvScratch. */
     uint32_t            cbScratch;
-
-#ifdef VBOX_WITH_STATISTICS /** @todo Register these values with STAM. */
-    struct
-    {
-        /** Number of frames encoded. */
-        uint64_t        cEncBlocks;
-        /** Total time (in ms) of already encoded audio data. */
-        uint64_t        msEncTotal;
-    } STAM;
-#endif
 } RECORDINGCODEC, *PRECORDINGCODEC;
 
 /**
- * Enumeration for supported pixel formats.
- */
-enum RECORDINGPIXELFMT
-{
-    /** Unknown pixel format. */
-    RECORDINGPIXELFMT_UNKNOWN    = 0,
-    /** RGB 24. */
-    RECORDINGPIXELFMT_RGB24      = 1,
-    /** RGB 24. */
-    RECORDINGPIXELFMT_RGB32      = 2,
-    /** RGB 565. */
-    RECORDINGPIXELFMT_RGB565     = 3,
-    /** The usual 32-bit hack. */
-    RECORDINGPIXELFMT_32BIT_HACK = 0x7fffffff
+ * Enumeration for a recording frame type.
+ */
+enum RECORDINGFRAME_TYPE
+{
+    /** Invalid frame type; do not use. */
+    RECORDINGFRAME_TYPE_INVALID       = 0,
+    /** Frame is an audio frame. */
+    RECORDINGFRAME_TYPE_AUDIO         = 1,
+    /** Frame is an video frame. */
+    RECORDINGFRAME_TYPE_VIDEO         = 2,
+    /** Frame is a cursor shape frame.
+     *  Send when a (mouse) cursor shape has changed. */
+    RECORDINGFRAME_TYPE_CURSOR_SHAPE  = 3,
+    /** Frame is a cursor position change request.
+     *  Sent when a (mouse) cursor position has changed. */
+    RECORDINGFRAME_TYPE_CURSOR_POS    = 4,
+    /** Screen change information.
+     *  Sent when the screen properties (resolution, BPP, ...) have changed. */
+    RECORDINGFRAME_TYPE_SCREEN_CHANGE = 5
 };
-
-/**
- * Enumeration for a recording frame type.
- */
-enum RECORDINGFRAME_TYPE
-{
-    /** Invalid frame type; do not use. */
-    RECORDINGFRAME_TYPE_INVALID   = 0,
-    /** Frame is an audio frame. */
-    RECORDINGFRAME_TYPE_AUDIO     = 1,
-    /** Frame is an video frame. */
-    RECORDINGFRAME_TYPE_VIDEO     = 2,
-    /** Frame contains a video frame pointer. */
-    RECORDINGFRAME_TYPE_VIDEO_PTR = 3
-};
-
-/**
- * Structure for keeping a single recording video frame.
- */
-typedef struct RECORDINGVIDEOFRAME
-{
-    /** X origin  (in pixel) of this frame. */
-    uint16_t            uX;
-    /** X origin  (in pixel) of this frame. */
-    uint16_t            uY;
-    /** X resolution (in pixel) of this frame. */
-    uint16_t            uWidth;
-    /** Y resolution (in pixel)  of this frame. */
-    uint16_t            uHeight;
-    /** Bits per pixel (BPP). */
-    uint8_t             uBPP;
-    /** Pixel format of this frame. */
-    RECORDINGPIXELFMT   enmPixelFmt;
-    /** Bytes per scan line. */
-    uint16_t            uBytesPerLine;
-    /** RGB buffer containing the unmodified frame buffer data from Main's display. */
-    uint8_t            *pu8RGBBuf;
-    /** Size (in bytes) of the RGB buffer. */
-    size_t              cbRGBBuf;
-} RECORDINGVIDEOFRAME, *PRECORDINGVIDEOFRAME;
 
 /**
@@ -386,5 +483,18 @@
 
 /**
- * Structure for keeping a single recording audio frame.
+ * Structure for keeping cursor information.
+ */
+typedef struct RECORDINGCURSORINFO
+{
+    /** Cursor ID. Currently always 0. */
+    uint8_t          Id;
+    /** Current position. */
+    RECORDINGPOS     Pos;
+} RECORDINGCURSORINFO;
+/** Pointer to a RECORDINGCURSORINFO. */
+typedef RECORDINGCURSORINFO *PRECORDINGCURSORINFO;
+
+/**
+ * Structure for keeping a single recording frame.
  */
 typedef struct RECORDINGFRAME
@@ -399,36 +509,42 @@
     /** Timestamp (PTS, in ms). */
     uint64_t                msTimestamp;
+    /** Union holding different frame types depending on \a enmType. */
     union
     {
 #ifdef VBOX_WITH_AUDIO_RECORDING
-        /** Audio frame data. */
+        /** Audio data.
+         *  Used by RECORDINGFRAME_TYPE_AUDIO. */
         RECORDINGAUDIOFRAME  Audio;
 #endif
-        /** Video frame data. */
+        /** A (weak) pointer to a video frame. */
         RECORDINGVIDEOFRAME  Video;
-        /** A (weak) pointer to a video frame. */
-        RECORDINGVIDEOFRAME *VideoPtr;
-    };
-} RECORDINGFRAME, *PRECORDINGFRAME;
-
-/**
- * Enumeration for specifying a video recording block type.
- */
-typedef enum RECORDINGBLOCKTYPE
-{
-    /** Uknown block type, do not use. */
-    RECORDINGBLOCKTYPE_UNKNOWN = 0,
-    /** The block is a video frame. */
-    RECORDINGBLOCKTYPE_VIDEO,
-    /** The block is an audio frame. */
-    RECORDINGBLOCKTYPE_AUDIO
-} RECORDINGBLOCKTYPE;
+        /** Recording screen information. */
+        RECORDINGSURFACEINFO ScreenInfo;
+        /** Cursor shape frame.
+         *  Used by RECORDINGFRAME_TYPE_CURSOR_SHAPE. */
+        RECORDINGVIDEOFRAME  CursorShape;
+        /** Cursor information.
+         *  Used by RECORDINGFRAME_TYPE_CURSOR_POS. */
+        RECORDINGCURSORINFO  Cursor;
+    } u;
+} RECORDINGFRAME;
+/** Pointer to a RECORDINGFRAME. */
+typedef RECORDINGFRAME *PRECORDINGFRAME;
+
+PRECORDINGVIDEOFRAME RecordingVideoFrameAlloc(void);
+PRECORDINGVIDEOFRAME RecordingVideoFrameAllocEx(const void *pvData, uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint8_t uBPP, RECORDINGPIXELFMT enmFmt);
+void RecordingVideoFrameFree(PRECORDINGVIDEOFRAME pFrame);
+int RecordingVideoFrameInit(PRECORDINGVIDEOFRAME pFrame, uint32_t fFlags, uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint8_t uBPP, RECORDINGPIXELFMT enmFmt);
+void RecordingVideoFrameDestroy(PRECORDINGVIDEOFRAME pFrame);
+PRECORDINGVIDEOFRAME RecordingVideoFrameDup(PRECORDINGVIDEOFRAME pFrame);
+void RecordingVideoFrameClear(PRECORDINGVIDEOFRAME pFrame);
+int RecordingVideoFrameBlitRawAlpha(PRECORDINGVIDEOFRAME pFrame, uint32_t uDstX, uint32_t uDstY, const uint8_t *pu8Src, size_t cbSrc, uint32_t uSrcX, uint32_t uSrcY, uint32_t uSrcWidth, uint32_t uSrcHeight, uint32_t uSrcBytesPerLine, uint8_t uSrcBPP, RECORDINGPIXELFMT enmFmt);
+int RecordingVideoFrameBlitRaw(PRECORDINGVIDEOFRAME pFrame, uint32_t uDstX, uint32_t uDstY, const uint8_t *pu8Src, size_t cbSrc, uint32_t uSrcX, uint32_t uSrcY, uint32_t uSrcWidth, uint32_t uSrcHeight, uint32_t uSrcBytesPerLine, uint8_t uSrcBPP, RECORDINGPIXELFMT enmFmt);
+int RecordingVideoFrameBlitFrame(PRECORDINGVIDEOFRAME pDstFrame, uint32_t uDstX, uint32_t uDstY, PRECORDINGVIDEOFRAME pSrcFrame, uint32_t uSrcX, uint32_t uSrcY, uint32_t uSrcWidth, uint32_t uSrcHeight);
 
 #ifdef VBOX_WITH_AUDIO_RECORDING
-int RecordingVideoFrameInit(PRECORDINGVIDEOFRAME pFrame, int w, int h, uint8_t uBPP, RECORDINGPIXELFMT enmPixelFmt);
-void RecordingVideoFrameDestroy(PRECORDINGVIDEOFRAME pFrame);
 void RecordingAudioFrameFree(PRECORDINGAUDIOFRAME pFrame);
 #endif
-void RecordingVideoFrameFree(PRECORDINGVIDEOFRAME pFrame);
+
 void RecordingFrameFree(PRECORDINGFRAME pFrame);
 
@@ -439,6 +555,5 @@
 {
     RecordingBlock()
-        : enmType(RECORDINGBLOCKTYPE_UNKNOWN)
-        , cRefs(0)
+        : cRefs(0)
         , uFlags(RECORDINGCODEC_ENC_F_NONE)
         , pvData(NULL)
@@ -447,29 +562,15 @@
     virtual ~RecordingBlock()
     {
-        Reset();
+        Destroy();
     }
 
-    void Reset(void)
-    {
-        switch (enmType)
-        {
-            case RECORDINGBLOCKTYPE_UNKNOWN:
-                break;
-
-            case RECORDINGBLOCKTYPE_VIDEO:
-                RecordingVideoFrameFree((PRECORDINGVIDEOFRAME)pvData);
-                break;
-
-#ifdef VBOX_WITH_AUDIO_RECORDING
-            case RECORDINGBLOCKTYPE_AUDIO:
-                RecordingAudioFrameFree((PRECORDINGAUDIOFRAME)pvData);
-                break;
-#endif
-            default:
-                AssertFailed();
-                break;
-        }
-
-        enmType = RECORDINGBLOCKTYPE_UNKNOWN;
+    void Destroy(void)
+    {
+        PRECORDINGFRAME pFrame = (PRECORDINGFRAME)pvData;
+        if (!pFrame)
+            return;
+
+        RecordingFrameFree(pFrame);
+
         cRefs   = 0;
         pvData  = NULL;
@@ -477,6 +578,4 @@
     }
 
-    /** The block's type. */
-    RECORDINGBLOCKTYPE enmType;
     /** Number of references held of this block. */
     uint16_t           cRefs;
@@ -485,5 +584,5 @@
     /** The (absolute) timestamp (in ms, PTS) of this block. */
     uint64_t           msTimestamp;
-    /** Opaque data block to the actual block data, depending on the block's type. */
+    /** Opaque data block to the actual block data. Currently only used with PRECORDINGFRAME. */
     void              *pvData;
     /** Size (in bytes) of the (opaque) data block. */
@@ -498,5 +597,7 @@
 int recordingCodecInit(const PRECORDINGCODEC pCodec, const PRECORDINGCODECCALLBACKS pCallbacks, const settings::RecordingScreenSettings &Settings);
 int recordingCodecDestroy(PRECORDINGCODEC pCodec);
-int recordingCodecEncode(PRECORDINGCODEC pCodec, const PRECORDINGFRAME pFrame, size_t *pcEncoded, size_t *pcbEncoded);
+int recordingCodecEncodeFrame(PRECORDINGCODEC pCodec, const PRECORDINGFRAME pFrame, uint64_t msTimestamp, void *pvUser);
+int recordingCodecEncodeCurrent(PRECORDINGCODEC pCodec, uint64_t msTimestamp);
+int recordingCodecScreenChange(PRECORDINGCODEC pCodec, PRECORDINGSURFACEINFO pInfo);
 int recordingCodecFinalize(PRECORDINGCODEC pCodec);
 bool recordingCodecIsInitialized(const PRECORDINGCODEC pCodec);
Index: /trunk/src/VBox/Main/include/RecordingStream.h
===================================================================
--- /trunk/src/VBox/Main/include/RecordingStream.h	(revision 105005)
+++ /trunk/src/VBox/Main/include/RecordingStream.h	(revision 105006)
@@ -129,8 +129,10 @@
     int Uninit(void);
 
-    int Process(RecordingBlockMap &mapBlocksCommon);
+    int ThreadMain(int rcWait, RecordingBlockMap &commonBlocks);
     int SendAudioFrame(const void *pvData, size_t cbData, uint64_t msTimestamp);
-    int SendVideoFrame(uint32_t x, uint32_t y, uint32_t uPixelFormat, uint32_t uBPP, uint32_t uBytesPerLine,
-                       uint32_t uSrcWidth, uint32_t uSrcHeight, uint8_t *puSrcData, uint64_t msTimestamp);
+    int SendCursorPos(uint8_t idCursor, PRECORDINGPOS pPos, uint64_t msTimestamp);
+    int SendCursorShape(uint8_t idCursor, PRECORDINGVIDEOFRAME pShape, uint64_t msTimestamp);
+    int SendVideoFrame(PRECORDINGVIDEOFRAME pFrame, uint64_t msTimestamp);
+    int SendScreenChange(PRECORDINGSURFACEINFO pInfo, uint64_t msTimestamp, bool fForce = false);
 
     const settings::RecordingScreenSettings &GetConfig(void) const;
@@ -163,4 +165,6 @@
     int iterateInternal(uint64_t msTimestamp);
 
+    int addFrame(PRECORDINGFRAME pFrame, uint64_t msTimestamp);
+    int process(const RecordingBlockSet &streamBlocks, RecordingBlockMap &commonBlocks);
     int codecWriteToWebM(PRECORDINGCODEC pCodec, const void *pvData, size_t cbData, uint64_t msAbsPTS, uint32_t uFlags);
 
@@ -223,6 +227,5 @@
     settings::RecordingScreenSettings
                         m_ScreenSettings;
-    /** Common set of recording (data) blocks, needed for
-     *  multiplexing to all recording streams. */
+    /** Set of recording (data) blocks for this stream. */
     RecordingBlockSet   m_Blocks;
 };
Index: /trunk/src/VBox/Main/include/RecordingUtils.h
===================================================================
--- /trunk/src/VBox/Main/include/RecordingUtils.h	(revision 105005)
+++ /trunk/src/VBox/Main/include/RecordingUtils.h	(revision 105006)
@@ -35,181 +35,14 @@
 
 
-/**
- * Iterator class for running through a BGRA32 image buffer and converting
- * it to RGB.
- */
-class ColorConvBGRA32Iter
-{
-private:
-    enum { PIX_SIZE = 4 };
-public:
-    ColorConvBGRA32Iter(unsigned aWidth, unsigned aHeight, uint8_t *aBuf)
-    {
-        mPos = 0;
-        mSize = aWidth * aHeight * PIX_SIZE;
-        mBuf = aBuf;
-    }
-
-    /**
-     * Convert the next pixel to RGB.
-     *
-     * @returns true on success, false if we have reached the end of the buffer
-     * @param   aRed            where to store the red value.
-     * @param   aGreen          where to store the green value.
-     * @param   aBlue           where to store the blue value.
-     */
-    bool getRGB(unsigned *aRed, unsigned *aGreen, unsigned *aBlue)
-    {
-        bool rc = false;
-        if (mPos + PIX_SIZE <= mSize)
-        {
-            *aRed   = mBuf[mPos + 2];
-            *aGreen = mBuf[mPos + 1];
-            *aBlue  = mBuf[mPos    ];
-            mPos += PIX_SIZE;
-            rc = true;
-        }
-        return rc;
-    }
-
-    /**
-     * Skip forward by a certain number of pixels.
-     *
-     * @param aPixels           How many pixels to skip.
-     */
-    void skip(unsigned aPixels)
-    {
-        mPos += PIX_SIZE * aPixels;
-    }
-private:
-    /** Size of the picture buffer. */
-    unsigned mSize;
-    /** Current position in the picture buffer. */
-    unsigned mPos;
-    /** Address of the picture buffer. */
-    uint8_t *mBuf;
-};
-
-/**
- * Iterator class for running through an BGR24 image buffer and converting
- * it to RGB.
- */
-class ColorConvBGR24Iter
-{
-private:
-    enum { PIX_SIZE = 3 };
-public:
-    ColorConvBGR24Iter(unsigned aWidth, unsigned aHeight, uint8_t *aBuf)
-    {
-        mPos = 0;
-        mSize = aWidth * aHeight * PIX_SIZE;
-        mBuf = aBuf;
-    }
-
-    /**
-     * Convert the next pixel to RGB.
-     *
-     * @returns true on success, false if we have reached the end of the buffer.
-     * @param   aRed            where to store the red value.
-     * @param   aGreen          where to store the green value.
-     * @param   aBlue           where to store the blue value.
-     */
-    bool getRGB(unsigned *aRed, unsigned *aGreen, unsigned *aBlue)
-    {
-        bool rc = false;
-        if (mPos + PIX_SIZE <= mSize)
-        {
-            *aRed   = mBuf[mPos + 2];
-            *aGreen = mBuf[mPos + 1];
-            *aBlue  = mBuf[mPos    ];
-            mPos += PIX_SIZE;
-            rc = true;
-        }
-        return rc;
-    }
-
-    /**
-     * Skip forward by a certain number of pixels.
-     *
-     * @param aPixels           How many pixels to skip.
-     */
-    void skip(unsigned aPixels)
-    {
-        mPos += PIX_SIZE * aPixels;
-    }
-private:
-    /** Size of the picture buffer. */
-    unsigned mSize;
-    /** Current position in the picture buffer. */
-    unsigned mPos;
-    /** Address of the picture buffer. */
-    uint8_t *mBuf;
-};
-
-/**
- * Iterator class for running through an BGR565 image buffer and converting
- * it to RGB.
- */
-class ColorConvBGR565Iter
-{
-private:
-    enum { PIX_SIZE = 2 };
-public:
-    ColorConvBGR565Iter(unsigned aWidth, unsigned aHeight, uint8_t *aBuf)
-    {
-        mPos = 0;
-        mSize = aWidth * aHeight * PIX_SIZE;
-        mBuf = aBuf;
-    }
-
-    /**
-     * Convert the next pixel to RGB.
-     *
-     * @returns true on success, false if we have reached the end of the buffer.
-     * @param   aRed            Where to store the red value.
-     * @param   aGreen          where to store the green value.
-     * @param   aBlue           where to store the blue value.
-     */
-    bool getRGB(unsigned *aRed, unsigned *aGreen, unsigned *aBlue)
-    {
-        bool rc = false;
-        if (mPos + PIX_SIZE <= mSize)
-        {
-            unsigned uFull =  (((unsigned) mBuf[mPos + 1]) << 8)
-                             | ((unsigned) mBuf[mPos]);
-            *aRed   = (uFull >> 8) & ~7;
-            *aGreen = (uFull >> 3) & ~3 & 0xff;
-            *aBlue  = (uFull << 3) & ~7 & 0xff;
-            mPos += PIX_SIZE;
-            rc = true;
-        }
-        return rc;
-    }
-
-    /**
-     * Skip forward by a certain number of pixels.
-     *
-     * @param aPixels           How many pixels to skip.
-     */
-    void skip(unsigned aPixels)
-    {
-        mPos += PIX_SIZE * aPixels;
-    }
-private:
-    /** Size of the picture buffer. */
-    unsigned mSize;
-    /** Current position in the picture buffer. */
-    unsigned mPos;
-    /** Address of the picture buffer. */
-    uint8_t *mBuf;
-};
-
-int RecordingUtilsRGBToYUV(RECORDINGPIXELFMT enmPixelFormat,
-                           uint8_t *paDst, uint32_t uDstWidth, uint32_t uDstHeight,
-                           uint8_t *paSrc, uint32_t uSrcWidth, uint32_t uSrcHeight);
+void RecordingUtilsConvBGRA32ToYUVI420(uint8_t *paDst, uint32_t uDstWidth, uint32_t uDstHeight,
+                                       uint8_t *paSrc, uint32_t uSrcWidth, uint32_t uSrcHeight);
+void RecordingUtilsConvBGRA32ToYUVI420Ex(uint8_t *paDst, uint32_t dx, uint32_t dy, uint32_t uDstWidth, uint32_t uDstHeight,
+                                         uint8_t *paSrc, uint32_t sx, uint32_t sy, uint32_t uSrcWidth, uint32_t uSrcHeight, uint32_t uSrcStride, uint8_t uBPP);
+int RecordingUtilsCoordsCropCenter(PRECORDINGCODECPARMS pCodecParms, int32_t *sx, int32_t *sy, int32_t *sw, int32_t *sh, int32_t *dx, int32_t *dy);
 
 #ifdef DEBUG
-int RecordingUtilsDbgDumpFrameEx(const uint8_t *pu8RGBBuf, size_t cbRGBBuf, const char *pszPath, const char *pszPrefx, uint16_t uWidth, uint32_t uHeight, uint8_t uBPP);
-int RecordingUtilsDbgDumpFrame(const PRECORDINGFRAME pFrame);
+int RecordingUtilsDbgDumpImageData(const uint8_t *pu8RGBBuf, size_t cbRGBBuf, const char *pszPath, const char *pszWhat, uint32_t uWidth, uint32_t uHeight, uint32_t uBytesPerLine, uint8_t uBPP);
+int RecordingUtilsDbgDumpVideoFrameEx(const PRECORDINGVIDEOFRAME pFrame, const char *pszPath, const char *pszWhat);
+int RecordingUtilsDbgDumpVideoFrame(const PRECORDINGVIDEOFRAME pFrame, const char *pszWhat);
 #endif
 
Index: /trunk/src/VBox/Main/src-client/ConsoleImpl.cpp
===================================================================
--- /trunk/src/VBox/Main/src-client/ConsoleImpl.cpp	(revision 105005)
+++ /trunk/src/VBox/Main/src-client/ConsoleImpl.cpp	(revision 105006)
@@ -7666,4 +7666,25 @@
 }
 
+/**
+ * Sends a cursor shape change to the recording context.
+ *
+ * @returns VBox status code.
+ * @param   fVisible            Whether the mouse cursor actually is visible or not.
+ * @param   fAlpha              Whether the pixel data contains alpha channel information or not.
+ * @param   xHot                X hot position (in pixel) of the new cursor.
+ * @param   yHot                Y hot position (in pixel) of the new cursor.
+ * @param   uWidth              Width (in pixel) of the new cursor.
+ * @param   uHeight             Height (in pixel) of the new cursor.
+ * @param   pu8Shape            Pixel data of the new cursor.
+ * @param   cbShape             Size of \a pu8Shape (in bytes).
+ */
+int Console::i_recordingCursorShapeChange(bool fVisible, bool fAlpha, uint32_t xHot, uint32_t yHot, uint32_t uWidth, uint32_t uHeight, const uint8_t *pu8Shape, uint32_t cbShape)
+{
+    if (!mRecording.mCtx.IsStarted())
+        return VINF_SUCCESS;
+
+    return mRecording.mCtx.SendCursorShapeChange(fVisible, fAlpha, xHot, yHot, uWidth, uHeight, pu8Shape, cbShape,
+                                                 mRecording.mCtx.GetCurrentPTS());
+}
 #endif /* VBOX_WITH_RECORDING */
 
@@ -7795,5 +7816,5 @@
 
     if (!mMouse.isNull())
-       mMouse->updateMousePointerShape(fVisible, fAlpha, xHot, yHot, width, height, pu8Shape, cbShape);
+       mMouse->i_updatePointerShape(fVisible, fAlpha, xHot, yHot, width, height, pu8Shape, cbShape);
 
     com::SafeArray<BYTE> shape(cbShape);
@@ -7801,4 +7822,8 @@
         memcpy(shape.raw(), pu8Shape, cbShape);
     ::FireMousePointerShapeChangedEvent(mEventSource, fVisible, fAlpha, xHot, yHot, width, height, ComSafeArrayAsInParam(shape));
+
+#ifdef VBOX_WITH_RECORDING
+    i_recordingCursorShapeChange(fVisible, fAlpha, xHot, yHot, width, height, pu8Shape, cbShape);
+#endif
 
 #if 0
Index: /trunk/src/VBox/Main/src-client/DisplayImpl.cpp
===================================================================
--- /trunk/src/VBox/Main/src-client/DisplayImpl.cpp	(revision 105005)
+++ /trunk/src/VBox/Main/src-client/DisplayImpl.cpp	(revision 105006)
@@ -34,4 +34,5 @@
 #include "ConsoleVRDPServer.h"
 #include "GuestImpl.h"
+#include "MouseImpl.h"
 #include "VMMDev.h"
 
@@ -61,4 +62,5 @@
 # include <iprt/path.h>
 # include "Recording.h"
+# include "RecordingUtils.h"
 
 # include <VBox/vmm/pdmapi.h>
@@ -590,7 +592,4 @@
         maFramebuffers[uScreenId].updateImage.cbLine = 0;
         maFramebuffers[uScreenId].pFramebuffer.setNull();
-#ifdef VBOX_WITH_RECORDING
-        maFramebuffers[uScreenId].Recording.pSourceBitmap.setNull();
-#endif
     }
 
@@ -797,11 +796,16 @@
     const bool fDisabled = pFBInfo->fDisabled;
 
+#ifdef VBOX_WITH_RECORDING
+    /* Recording needs to be called before releasing the display's lock below. */
+    i_recordingScreenChanged(uScreenId, pFBInfo);
+#endif
+
     alock.release();
 
     if (!pFramebuffer.isNull())
     {
-        HRESULT hr = pFramebuffer->NotifyChange(uScreenId, 0, 0, w, h); /** @todo origin */
-        LogFunc(("NotifyChange hr %08X\n", hr));
-        NOREF(hr);
+        HRESULT hrc = pFramebuffer->NotifyChange(uScreenId, 0, 0, w, h); /** @todo origin */
+        LogFunc(("NotifyChange hr %08X\n", hrc));
+        RT_NOREF(hrc);
     }
 
@@ -828,8 +832,4 @@
         i_handleSetVisibleRegion(mcRectVisibleRegion, mpRectVisibleRegion);
 
-#ifdef VBOX_WITH_RECORDING
-    i_recordingScreenChanged(uScreenId);
-#endif
-
     LogRelFlowFunc(("[%d]: default format %d\n", uScreenId, pFBInfo->fDefaultFormat));
 
@@ -909,4 +909,16 @@
             {
                 pFramebuffer->NotifyUpdate(x, y, w, h);
+
+#ifdef VBOX_WITH_RECORDING
+                RECORDINGVIDEOFRAME Frame =
+                {
+                    (uint16_t)w, (uint16_t)h,
+                    (uint8_t )pFBInfo->u16BitsPerPixel, RECORDINGPIXELFMT_BRGA32, pFBInfo->u32LineSize,
+                    pFBInfo->pu8FramebufferVRAM + (y * pFBInfo->u32LineSize + x * (pFBInfo->u16BitsPerPixel / 8)),
+                    pFBInfo->w * pFBInfo->u32LineSize,
+                    (uint16_t)x, (uint16_t)y
+                };
+                i_recordingScreenUpdate(uScreenId, &Frame);
+#endif
             }
         }
@@ -943,4 +955,15 @@
                             pFBInfo->updateImage.pu8Address = pAddress;
                             pFBInfo->updateImage.cbLine = ulBytesPerLine;
+#ifdef VBOX_WITH_RECORDING
+                            RECORDINGVIDEOFRAME Frame =
+                            {
+                                (uint16_t)ulWidth, (uint16_t)ulHeight,
+                                (uint8_t )ulBitsPerPixel, RECORDINGPIXELFMT_BRGA32, ulBytesPerLine,
+                                pAddress, ulHeight * ulBytesPerLine,
+                                0, 0
+                            };
+
+                            i_recordingScreenUpdate(uScreenId, &Frame);
+#endif
                         }
 
@@ -972,21 +995,35 @@
                     i_checkCoordBounds(&x, &y, &w, &h, ulWidth, ulHeight);
 
-                    if (w != 0 && h != 0)
+                    if (   w != 0
+                        && h != 0)
                     {
-                        const size_t cbData = w * h * 4;
+                        unsigned const uBytesPerPixel = ulBitsPerPixel / 8;
+
+                        const size_t cbData = w * h * uBytesPerPixel;
                         com::SafeArray<BYTE> image(cbData);
 
                         uint8_t *pu8Dst = image.raw();
-                        const uint8_t *pu8Src = pbAddress + ulBytesPerLine * y + x * 4;
-
-                        int i;
-                        for (i = y; i < y + h; ++i)
+                        const uint8_t *pu8Src = pbAddress + ulBytesPerLine * y + x * uBytesPerPixel;
+
+                        for (int i = y; i < y + h; ++i)
                         {
-                            memcpy(pu8Dst, pu8Src, w * 4);
-                            pu8Dst += w * 4;
+                            memcpy(pu8Dst, pu8Src, w * uBytesPerPixel);
+                            pu8Dst += w * uBytesPerPixel;
                             pu8Src += ulBytesPerLine;
                         }
 
                         pFramebuffer->NotifyUpdateImage(x, y, w, h, ComSafeArrayAsInParam(image));
+
+#ifdef VBOX_WITH_RECORDING
+                        RECORDINGVIDEOFRAME Frame =
+                        {
+                            (uint16_t)w, (uint16_t)h,
+                            (uint8_t)ulBitsPerPixel, RECORDINGPIXELFMT_BRGA32, ulBytesPerLine,
+                            pu8Dst, h * ulBytesPerLine,
+                            (uint16_t)x, (uint16_t)y
+                        };
+
+                        i_recordingScreenUpdate(uScreenId, &Frame);
+#endif
                     }
                 }
@@ -1128,5 +1165,5 @@
 /** Updates the device's view of the host cursor handling capabilities.
  *  Calls into mpDrv->pUpPort. */
-void Display::i_UpdateDeviceCursorCapabilities(void)
+void Display::i_updateDeviceCursorCapabilities(void)
 {
     bool fRenderCursor = true;
@@ -1172,5 +1209,5 @@
     alock.release();  /* Release before calling up for lock order reasons. */
     mfHostCursorCapabilities = fHostCursorCapabilities;
-    i_UpdateDeviceCursorCapabilities();
+    i_updateDeviceCursorCapabilities();
     return S_OK;
 }
@@ -1419,5 +1456,5 @@
 
     i_VideoAccelVRDP(fConnect, c);
-    i_UpdateDeviceCursorCapabilities();
+    i_updateDeviceCursorCapabilities();
 }
 
@@ -2150,4 +2187,6 @@
  * @returns VBox status code.
  * @param   fForce              Whether to force invalidation or not. Default is @c false.
+ *
+ * @note    Takes the display's read lock.
  */
 int Display::i_recordingInvalidate(bool fForce /* = false */)
@@ -2156,4 +2195,8 @@
     if (!pCtx)
         return VINF_SUCCESS;
+
+    LogFlowFuncEnter();
+
+    AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
 
     /*
@@ -2169,6 +2212,10 @@
         maRecordingEnabled[uScreen] = fStreamEnabled;
 
-        if (fChanged && uScreen < mcMonitors)
-            i_recordingScreenChanged(uScreen);
+        if (   fStreamEnabled
+            && fChanged)
+        {
+            DISPLAYFBINFO const *pFBInfo = &maFramebuffers[uScreen];
+            /* ignore rc */ i_recordingScreenChanged(uScreen, pFBInfo);
+        }
     }
 
@@ -2179,30 +2226,156 @@
  * Called when the recording state of a screen got changed.
  *
+ * @returns VBox status code.
  * @param   uScreenId           ID of screen for which the recording state got changed.
+ * @param   pFBInfo             Frame buffer information to use.
  */
-void Display::i_recordingScreenChanged(unsigned uScreenId)
-{
+int Display::i_recordingScreenChanged(unsigned uScreenId, const DISPLAYFBINFO *pFBInfo)
+{
+    AssertReturn(uScreenId < mcMonitors, VERR_INVALID_PARAMETER);
+
     RecordingContext *pCtx = mParent->i_recordingGetContext();
 
-    i_UpdateDeviceCursorCapabilities();
+    i_updateDeviceCursorCapabilities();
     if (   RT_LIKELY(!maRecordingEnabled[uScreenId])
         || !pCtx || !pCtx->IsStarted())
     {
         /* Skip recording this screen. */
-        return;
-    }
-
-    /* Get a new source bitmap which will be used by video recording code. */
-    ComPtr<IDisplaySourceBitmap> pSourceBitmap;
-    QuerySourceBitmap(uScreenId, pSourceBitmap.asOutParam());
-
-    int vrc2 = RTCritSectEnter(&mVideoRecLock);
-    if (RT_SUCCESS(vrc2))
-    {
-        maFramebuffers[uScreenId].Recording.pSourceBitmap = pSourceBitmap;
-
-        vrc2 = RTCritSectLeave(&mVideoRecLock);
-        AssertRC(vrc2);
-    }
+        return VINF_SUCCESS;
+    }
+
+    LogFlowFuncEnter();
+
+    RECORDINGSURFACEINFO ScreenInfo;
+    ScreenInfo.uWidth      = pFBInfo->w;
+    ScreenInfo.uHeight     = pFBInfo->h;
+    ScreenInfo.uBPP        = (uint8_t)pFBInfo->u16BitsPerPixel;
+    ScreenInfo.enmPixelFmt = RECORDINGPIXELFMT_BRGA32; /** @todo Does this apply everywhere? */
+
+    uint64_t const tsNowMs = pCtx->GetCurrentPTS();
+
+    int vrc = pCtx->SendScreenChange(uScreenId, &ScreenInfo, tsNowMs);
+    if (RT_SUCCESS(vrc))
+    {
+        /** @todo BUGBUG For whatever reason pFBInfo contains a wrong pFBInfo->u32LineSize
+         *        when shutting down a VM. So (re-)calculate the line size based on the framebuffer's
+         *        BPP + width parameters.
+         *
+         *        So fend off any requests here which look fishy before sending a full screen update. */
+        if (   !pFBInfo->u16BitsPerPixel
+            ||  pFBInfo->u16BitsPerPixel % 2 != 0
+            || !pFBInfo->w
+            || !pFBInfo->h
+            ||  pFBInfo->u32LineSize != pFBInfo->w * (pFBInfo->u16BitsPerPixel / 8))
+        {
+            vrc = VERR_INVALID_PARAMETER;
+        }
+        else
+        {
+            /* Make sure that we get the latest mouse pointer shape required for recording. */
+            MousePointerData pointerData;
+            mParent->i_getMouse()->i_getPointerShape(pointerData);
+            mParent->i_recordingCursorShapeChange(pointerData.fVisible, pointerData.fAlpha,
+                                                  pointerData.hotX, pointerData.hotY,
+                                                  pointerData.width, pointerData.height,
+                                                  pointerData.pu8Shape, pointerData.cbShape);
+            /* Send the full screen update. */
+            RECORDINGVIDEOFRAME Frame =
+            {
+                (uint16_t)pFBInfo->w, (uint16_t)pFBInfo->h,
+                (uint8_t)pFBInfo->u16BitsPerPixel, RECORDINGPIXELFMT_BRGA32, pFBInfo->u32LineSize, //pFBInfo->w * (pFBInfo->u16BitsPerPixel / 8),
+                pFBInfo->pu8FramebufferVRAM, pFBInfo->h * pFBInfo->u32LineSize, 0, 0
+            };
+
+            vrc = i_recordingScreenUpdate(uScreenId, &Frame);
+        }
+    }
+
+    LogFlowFuncLeaveRC(vrc);
+    return vrc;
+}
+
+/**
+ * Called when a part of a screen got updated.
+ *
+ * @returns VBox status code.
+ * @param   uScreenId           ID of screen which got updated.
+ * @param   pFrame              Video frama to send.
+ */
+int Display::i_recordingScreenUpdate(unsigned uScreenId, PRECORDINGVIDEOFRAME pFrame)
+{
+    if (!maRecordingEnabled[uScreenId])
+        return VINF_NO_CHANGE;
+
+    AssertPtr(mParent);
+    RecordingContext *pCtx = mParent->i_recordingGetContext();
+    if (!pCtx)
+        return VINF_SUCCESS;
+
+    /* If the recording context has reached the configured recording
+     * limit, disable recording. */
+    if (pCtx->IsLimitReached())
+    {
+        mParent->i_onRecordingChange(FALSE /* Disable */);
+        return VINF_SUCCESS;
+    }
+
+    uint64_t const tsNowMs = pCtx->GetCurrentPTS();
+
+    int vrc = VINF_NO_CHANGE;
+
+    if (   pCtx->IsStarted()
+        && pCtx->IsFeatureEnabled(RecordingFeature_Video))
+    {
+        STAM_PROFILE_START(&Stats.Monitor[uScreenId].Recording.profileRecording, a);
+
+        vrc = pCtx->SendVideoFrame(uScreenId, pFrame, tsNowMs);
+
+        STAM_PROFILE_STOP(&Stats.Monitor[uScreenId].Recording.profileRecording, a);
+    }
+
+    return vrc;
+}
+
+/**
+ * Called when the mouse cursor position has changed within the guest.
+ *
+ * @returns VBox status code.
+ * @param   uScreenId           ID of screen.
+ * @param   fFlags              Position flags. Not used yet.
+ * @param   x                   X position of the mouse cursor (within guest).
+ * @param   y                   Y position of the mouse cursor (within guest).
+ *
+ * @note    Requires Guest Additions installed + mouse integration enabled.
+ */
+int Display::i_recordingCursorPositionChange(unsigned uScreenId, uint32_t fFlags, int32_t x, int32_t y)
+{
+    RT_NOREF(fFlags);
+
+#if 0 /** @todo For whatever reason we always report SVGA_ID_INVALID here. Needs investigation. */
+    if (   uScreenId > mcMonitors /* Might be SVGA_ID_INVALID. */
+        || !maRecordingEnabled[uScreenId])
+        return VINF_SUCCESS;
+#endif
+
+    AssertPtr(mParent);
+    RecordingContext *pCtx = mParent->i_recordingGetContext();
+    if (!pCtx)
+        return VINF_SUCCESS;
+
+    /* If the recording context has reached the configured recording
+     * limit, disable recording. */
+    if (pCtx->IsLimitReached())
+    {
+        mParent->i_onRecordingChange(FALSE /* Disable */);
+        return VINF_SUCCESS;
+    }
+
+    if (   pCtx->IsStarted()
+        && pCtx->IsFeatureEnabled(RecordingFeature_Video))
+    {
+        return pCtx->SendCursorPositionChange(uScreenId, x, y, pCtx->GetCurrentPTS());
+    }
+
+    return VINF_SUCCESS;
 }
 #endif /* VBOX_WITH_RECORDING */
@@ -3024,85 +3197,4 @@
         }
     }
-
-#ifdef VBOX_WITH_RECORDING
-    AssertPtr(pDisplay->mParent);
-    RecordingContext *pCtx = pDisplay->mParent->i_recordingGetContext();
-
-    if (   pCtx
-        && pCtx->IsStarted()
-        && pCtx->IsFeatureEnabled(RecordingFeature_Video))
-    {
-        do
-        {
-            /* If the recording context has reached the configured recording
-             * limit, disable recording. */
-            if (pCtx->IsLimitReached())
-            {
-                pDisplay->mParent->i_onRecordingChange(FALSE /* Disable */);
-                break;
-            }
-
-            STAM_PROFILE_START(&pDisplay->Stats.Recording.profileRecording, b);
-
-            uint64_t tsNowMs = RTTimeProgramMilliTS();
-            for (uScreenId = 0; uScreenId < pDisplay->mcMonitors; uScreenId++)
-            {
-                if (!pDisplay->maRecordingEnabled[uScreenId])
-                    continue;
-
-                if (!pCtx->NeedsUpdate(uScreenId, tsNowMs))
-                    continue;
-
-                STAM_PROFILE_START(&pDisplay->Stats.Monitor[uScreenId].Recording.profileRecording, c);
-
-                DISPLAYFBINFO *pFBInfo = &pDisplay->maFramebuffers[uScreenId];
-                if (!pFBInfo->fDisabled)
-                {
-                    ComPtr<IDisplaySourceBitmap> pSourceBitmap;
-                    int vrc2 = RTCritSectEnter(&pDisplay->mVideoRecLock);
-                    if (RT_SUCCESS(vrc2))
-                    {
-                        pSourceBitmap = pFBInfo->Recording.pSourceBitmap;
-                        RTCritSectLeave(&pDisplay->mVideoRecLock);
-                    }
-
-                    if (!pSourceBitmap.isNull())
-                    {
-                        BYTE *pbAddress = NULL;
-                        ULONG ulWidth = 0;
-                        ULONG ulHeight = 0;
-                        ULONG ulBitsPerPixel = 0;
-                        ULONG ulBytesPerLine = 0;
-                        BitmapFormat_T bitmapFormat = BitmapFormat_Opaque;
-                        HRESULT hrc = pSourceBitmap->QueryBitmapInfo(&pbAddress,
-                                                                     &ulWidth,
-                                                                     &ulHeight,
-                                                                     &ulBitsPerPixel,
-                                                                     &ulBytesPerLine,
-                                                                     &bitmapFormat);
-                        if (SUCCEEDED(hrc) && pbAddress)
-                            vrc = pCtx->SendVideoFrame(uScreenId, 0, 0, BitmapFormat_BGR,
-                                                       ulBitsPerPixel, ulBytesPerLine, ulWidth, ulHeight,
-                                                       pbAddress, tsNowMs);
-                        else
-                            vrc = VERR_NOT_SUPPORTED;
-
-                        pSourceBitmap.setNull();
-                    }
-                    else
-                        vrc = VERR_NOT_SUPPORTED;
-
-                    if (vrc == VINF_TRY_AGAIN)
-                        break;
-                }
-
-                STAM_PROFILE_STOP(&pDisplay->Stats.Monitor[uScreenId].Recording.profileRecording, c);
-            }
-
-            STAM_PROFILE_STOP(&pDisplay->Stats.Recording.profileRecording, b);
-
-        } while (0);
-    }
-#endif /* VBOX_WITH_RECORDING */
 
     STAM_PROFILE_STOP(&pDisplay->Stats.profileDisplayRefreshCallback, a);
@@ -3693,4 +3785,8 @@
     }
     ::FireCursorPositionChangedEvent(pThis->mParent->i_getEventSource(), RT_BOOL(fFlags & VBVA_CURSOR_VALID_DATA), x, y);
+
+# ifdef VBOX_WITH_RECORDING
+    pThis->i_recordingCursorPositionChange(aScreenId, fFlags, x, y);
+# endif
 }
 
Index: /trunk/src/VBox/Main/src-client/DrvAudioRec.cpp
===================================================================
--- /trunk/src/VBox/Main/src-client/DrvAudioRec.cpp	(revision 105005)
+++ /trunk/src/VBox/Main/src-client/DrvAudioRec.cpp	(revision 105006)
@@ -318,5 +318,5 @@
             /* Make sure to let the driver backend know that we need the audio data in
              * a specific sampling rate the codec is optimized for. */
-            pCfgAcq->Props = pCodec->Parms.Audio.PCMProps;
+            pCfgAcq->Props = pCodec->Parms.u.Audio.PCMProps;
 
             /* Every codec frame marks a period for now. Optimize this later. */
@@ -491,6 +491,6 @@
                                                        const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
 {
-    RT_NOREF(pInterface);
-    PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
+    PDRVAUDIORECORDING pThis = RT_FROM_CPP_MEMBER(pInterface, DRVAUDIORECORDING, IHostAudio);
+    PAVRECSTREAM pStreamAV   = (PAVRECSTREAM)pStream;
     AssertPtrReturn(pStreamAV, VERR_INVALID_POINTER);
     if (cbBuf)
@@ -561,5 +561,6 @@
             if (cbSrc == cbFrame) /* Only send full codec frames. */
             {
-                vrc = pRecStream->SendAudioFrame(pStreamAV->pvSrcBuf, cbSrc, RTTimeProgramMilliTS());
+                AssertPtr(pThis->pRecCtx);
+                vrc = pRecStream->SendAudioFrame(pStreamAV->pvSrcBuf, cbSrc, pThis->pRecCtx->GetCurrentPTS());
                 if (RT_FAILURE(vrc))
                     break;
Index: /trunk/src/VBox/Main/src-client/MouseImpl.cpp
===================================================================
--- /trunk/src/VBox/Main/src-client/MouseImpl.cpp	(revision 105005)
+++ /trunk/src/VBox/Main/src-client/MouseImpl.cpp	(revision 105006)
@@ -323,31 +323,35 @@
 }
 
-void Mouse::updateMousePointerShape(bool fVisible, bool fAlpha,
-                                    uint32_t hotX, uint32_t hotY,
-                                    uint32_t width, uint32_t height,
-                                    const uint8_t *pu8Shape, uint32_t cbShape)
+/**
+ * Updates the pointer shape data.
+ *
+ * @returns VBox status code.
+ * @param   fVisible            Whether the mouse cursor actually is visible or not.
+ * @param   fAlpha              Whether the pixel data contains an alpha mask or not.
+ * @param   uHotX               X hot position (in pixel) of the new cursor.
+ * @param   uHotY               Y hot position (in pixel) of the new cursor.
+ * @param   uWidth              Width (in pixel) of the new cursor.
+ * @param   uHeight             Height (in pixel) of the new cursor.
+ * @param   pu8Shape            Pixel data of the new cursor.
+ * @param   cbShape             Size of \a pu8Shape (in bytes).
+ *
+ * @note    Takes the write lock.
+ */
+int Mouse::i_updatePointerShape(bool fVisible, bool fAlpha,
+                                uint32_t uHotX, uint32_t uHotY,
+                                uint32_t uWidth, uint32_t uHeight,
+                                const uint8_t *pu8Shape, uint32_t cbShape)
 {
     AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
 
-    RTMemFree(mPointerData.pu8Shape);
-    mPointerData.pu8Shape = NULL;
-    mPointerData.cbShape = 0;
-
-    mPointerData.fVisible = fVisible;
-    mPointerData.fAlpha   = fAlpha;
-    mPointerData.hotX     = hotX;
-    mPointerData.hotY     = hotY;
-    mPointerData.width    = width;
-    mPointerData.height   = height;
-    if (cbShape)
-    {
-        mPointerData.pu8Shape = (uint8_t *)RTMemDup(pu8Shape, cbShape);
-        if (mPointerData.pu8Shape)
-        {
-            mPointerData.cbShape = cbShape;
-        }
-    }
-
-    mPointerShape.setNull();
+    mPointerData.Destroy(); /* Destroy old data first. */
+
+    int vrc = mPointerData.Init(fVisible, fAlpha, uHotX, uHotY, uWidth, uHeight, pu8Shape, cbShape);
+    if (RT_SUCCESS(vrc))
+    {
+        mPointerShape.setNull();
+    }
+
+    return vrc;
 }
 
@@ -1124,4 +1128,18 @@
 {
     return RT_BOOL(mfVMMDevGuestCaps & VMMDEV_MOUSE_GUEST_NEEDS_HOST_CURSOR);
+}
+
+
+/**
+ * Returns the current mouse pointer data.
+ *
+ * @returns VBox status code.
+ * @param   aData               Where to return the current mouse pointer data.
+ */
+int Mouse::i_getPointerShape(MousePointerData &aData)
+{
+    AutoReadLock aLock(this COMMA_LOCKVAL_SRC_POS);
+
+    return aData.Init(mPointerData);
 }
 
Index: /trunk/src/VBox/Main/src-client/Recording.cpp
===================================================================
--- /trunk/src/VBox/Main/src-client/Recording.cpp	(revision 105005)
+++ /trunk/src/VBox/Main/src-client/Recording.cpp	(revision 105006)
@@ -68,4 +68,109 @@
 
 
+
+RecordingCursorState::RecordingCursorState()
+    : m_fFlags(VBOX_RECORDING_CURSOR_F_NONE)
+{
+    m_Shape.Pos.x = UINT16_MAX;
+    m_Shape.Pos.y = UINT16_MAX;
+
+    RT_ZERO(m_Shape);
+}
+
+RecordingCursorState::~RecordingCursorState()
+{
+    Destroy();
+}
+
+/**
+ * Destroys a cursor state.
+ */
+void RecordingCursorState::Destroy(void)
+{
+    RecordingVideoFrameDestroy(&m_Shape);
+}
+
+/**
+ * Creates or updates the cursor shape.
+ *
+ * @returns VBox status code.
+ * @param   fAlpha              Whether the pixel data contains alpha channel information or not.
+ * @param   uWidth              Width (in pixel) of new cursor shape.
+ * @param   uHeight             Height (in pixel) of new cursor shape.
+ * @param   pu8Shape            Pixel data of new cursor shape.
+ * @param   cbShape             Bytes of \a pu8Shape.
+ */
+int RecordingCursorState::CreateOrUpdate(bool fAlpha, uint32_t uWidth, uint32_t uHeight, const uint8_t *pu8Shape, size_t cbShape)
+{
+    int vrc;
+
+    uint32_t fFlags = RECORDINGVIDEOFRAME_F_VISIBLE;
+
+    const uint8_t uBPP = 32; /* Seems to be fixed. */
+
+    uint32_t offShape;
+    if (fAlpha)
+    {
+        /* Calculate the offset to the actual pixel data. */
+        offShape = (uWidth + 7) / 8 * uHeight; /* size of the AND mask */
+        offShape = (offShape + 3) & ~3;
+        AssertReturn(offShape <= cbShape, VERR_INVALID_PARAMETER);
+        fFlags |= RECORDINGVIDEOFRAME_F_BLIT_ALPHA;
+    }
+    else
+        offShape = 0;
+
+    /* Cursor shape size has become bigger? Reallocate. */
+    if (cbShape > m_Shape.cbBuf)
+    {
+        RecordingVideoFrameDestroy(&m_Shape);
+        vrc = RecordingVideoFrameInit(&m_Shape, fFlags, uWidth, uHeight, 0 /* posX */, 0 /* posY */,
+                                      uBPP, RECORDINGPIXELFMT_BRGA32);
+    }
+    else /* Otherwise just zero out first. */
+    {
+        RecordingVideoFrameClear(&m_Shape);
+        vrc = VINF_SUCCESS;
+    }
+
+    if (RT_SUCCESS(vrc))
+        vrc = RecordingVideoFrameBlitRaw(&m_Shape, 0, 0, &pu8Shape[offShape], cbShape - offShape, 0, 0, uWidth, uHeight, uWidth * 4 /* BPP */, uBPP,
+                                         m_Shape.Info.enmPixelFmt);
+#ifdef DEBUG_andy_disabled
+    RecordingUtilsDbgDumpVideoFrameEx(&m_Shape, "/tmp/recording", "cursor-update");
+#endif
+
+    return vrc;
+}
+
+/**
+ * Moves (sets) the cursor to a new position.
+ *
+ * @returns VBox status code.
+ * @retval  VERR_NO_CHANGE if the cursor wasn't moved (set).
+ * @param   iX                  New X position to set.
+ * @param   iY                  New Y position to set.
+ */
+int RecordingCursorState::Move(int32_t iX, int32_t iY)
+{
+    /* No relative coordinates here. */
+    if (   iX < 0
+        || iY < 0)
+        return VERR_NO_CHANGE;
+
+    if (   m_Shape.Pos.x == (uint32_t)iX
+        && m_Shape.Pos.y == (uint32_t)iY)
+        return VERR_NO_CHANGE;
+
+    m_Shape.Pos.x = (uint16_t)iX;
+    m_Shape.Pos.y = (uint16_t)iY;
+
+    return VINF_SUCCESS;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
 /**
  * Recording context constructor.
@@ -129,11 +234,10 @@
     for (;;)
     {
-        int vrc = RTSemEventWait(pThis->m_WaitEvent, RT_INDEFINITE_WAIT);
-        AssertRCBreak(vrc);
-
-        Log2Func(("Processing %zu streams\n", pThis->m_vecStreams.size()));
+        int vrcWait = RTSemEventWait(pThis->m_WaitEvent, RT_MS_1SEC);
+
+        Log2Func(("Processing %zu streams (wait = %Rrc)\n", pThis->m_vecStreams.size(), vrcWait));
 
         /* Process common raw blocks (data which not has been encoded yet). */
-        vrc = pThis->processCommonData(pThis->m_mapBlocksRaw, 100 /* ms timeout */);
+        int vrc = pThis->processCommonData(pThis->m_mapBlocksRaw, 100 /* ms timeout */);
 
         /** @todo r=andy This is inefficient -- as we already wake up this thread
@@ -146,5 +250,5 @@
 
             /* Hand-in common encoded blocks. */
-            vrc = pStream->Process(pThis->m_mapBlocksEncoded);
+            vrc = pStream->ThreadMain(vrcWait, pThis->m_mapBlocksEncoded);
             if (RT_FAILURE(vrc))
             {
@@ -207,23 +311,19 @@
         {
             RecordingBlock *pBlockCommon = (RecordingBlock *)(*itBlock);
-            switch (pBlockCommon->enmType)
+            PRECORDINGFRAME pFrame = (PRECORDINGFRAME)pBlockCommon->pvData;
+            AssertPtr(pFrame);
+            switch (pFrame->enmType)
             {
 #ifdef VBOX_WITH_AUDIO_RECORDING
-                case RECORDINGBLOCKTYPE_AUDIO:
+                case RECORDINGFRAME_TYPE_AUDIO:
                 {
-                    PRECORDINGAUDIOFRAME pAudioFrame = (PRECORDINGAUDIOFRAME)pBlockCommon->pvData;
-
-                    RECORDINGFRAME Frame;
-                    Frame.msTimestamp = pBlockCommon->msTimestamp;
-                    Frame.Audio.pvBuf = pAudioFrame->pvBuf;
-                    Frame.Audio.cbBuf = pAudioFrame->cbBuf;
-
-                    vrc = recordingCodecEncode(&m_CodecAudio, &Frame, NULL, NULL);
+                    vrc = recordingCodecEncodeFrame(&m_CodecAudio, pFrame, pFrame->msTimestamp, NULL /* pvUser */);
                     break;
                 }
 #endif /* VBOX_WITH_AUDIO_RECORDING */
-                default:
-                    /* Skip unknown stuff. */
-                    break;
+
+            default:
+                /* Skip unknown stuff. */
+                break;
             }
 
@@ -278,33 +378,24 @@
                  pCodec, cbData, msTimestamp, uFlags));
 
-    /** @todo Optimize this! Three allocations in here! */
-
-    RECORDINGBLOCKTYPE const enmType = pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO
-                                     ? RECORDINGBLOCKTYPE_AUDIO : RECORDINGBLOCKTYPE_UNKNOWN;
-
-    AssertReturn(enmType != RECORDINGBLOCKTYPE_UNKNOWN, VERR_NOT_SUPPORTED);
-
-    RecordingBlock *pBlock = new RecordingBlock();
+    RECORDINGFRAME_TYPE const enmType = pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO
+                                      ? RECORDINGFRAME_TYPE_AUDIO : RECORDINGFRAME_TYPE_INVALID;
+
+    AssertReturn(enmType != RECORDINGFRAME_TYPE_INVALID, VERR_NOT_SUPPORTED);
+
+    PRECORDINGFRAME pFrame = NULL;
 
     switch (enmType)
     {
-        case RECORDINGBLOCKTYPE_AUDIO:
-        {
-            PRECORDINGAUDIOFRAME pFrame = (PRECORDINGAUDIOFRAME)RTMemAlloc(sizeof(RECORDINGAUDIOFRAME));
+        case RECORDINGFRAME_TYPE_AUDIO:
+        {
+            pFrame = (PRECORDINGFRAME)RTMemAlloc(sizeof(RECORDINGFRAME));
             AssertPtrReturn(pFrame, VERR_NO_MEMORY);
-
-            pFrame->pvBuf = (uint8_t *)RTMemAlloc(cbData);
-            AssertPtrReturn(pFrame->pvBuf, VERR_NO_MEMORY);
-            pFrame->cbBuf = cbData;
-
-            memcpy(pFrame->pvBuf, pvData, cbData);
-
-            pBlock->enmType     = enmType;
-            pBlock->pvData      = pFrame;
-            pBlock->cbData      = sizeof(RECORDINGAUDIOFRAME) + cbData;
-            pBlock->cRefs       = m_cStreamsEnabled;
-            pBlock->msTimestamp = msTimestamp;
-            pBlock->uFlags      = uFlags;
-
+            pFrame->enmType     = RECORDINGFRAME_TYPE_AUDIO;
+            pFrame->msTimestamp = msTimestamp;
+
+            PRECORDINGAUDIOFRAME pAudioFrame = &pFrame->u.Audio;
+            pAudioFrame->pvBuf = (uint8_t *)RTMemDup(pvData, cbData);
+            AssertPtrReturn(pAudioFrame->pvBuf, VERR_NO_MEMORY);
+            pAudioFrame->cbBuf = cbData;
             break;
         }
@@ -319,6 +410,15 @@
     int vrc;
 
+    RecordingBlock *pBlock = NULL;
     try
     {
+        pBlock = new RecordingBlock();
+
+        pBlock->pvData      = pFrame;
+        pBlock->cbData      = sizeof(RECORDINGFRAME);
+        pBlock->cRefs       = m_cStreamsEnabled;
+        pBlock->msTimestamp = msTimestamp;
+        pBlock->uFlags      = uFlags;
+
         RecordingBlockMap::iterator itBlocks = mapCommon.find(msTimestamp);
         if (itBlocks == mapCommon.end())
@@ -334,7 +434,6 @@
         vrc = VINF_SUCCESS;
     }
-    catch (const std::exception &ex)
-    {
-        RT_NOREF(ex);
+    catch (const std::exception &)
+    {
         vrc = VERR_NO_MEMORY;
     }
@@ -343,5 +442,13 @@
 
     if (RT_SUCCESS(vrc))
+    {
         vrc = threadNotify();
+    }
+    else
+    {
+        if (pBlock)
+            delete pBlock;
+        RecordingFrameFree(pFrame);
+    }
 
     return vrc;
@@ -448,5 +555,5 @@
     if (RT_SUCCESS(vrc))
     {
-        m_tsStartMs = RTTimeMilliTS();
+        m_tsStartMs = 0;
         m_enmState  = RECORDINGSTS_CREATED;
         m_fShutdown = false;
@@ -474,4 +581,6 @@
     Assert(m_enmState == RECORDINGSTS_CREATED);
 
+    m_tsStartMs = RTTimeMilliTS();
+
     int vrc = RTThreadCreate(&m_Thread, RecordingContext::threadMain, (void *)this, 0,
                              RTTHREADTYPE_MAIN_WORKER, RTTHREADFLAGS_WAITABLE, "Record");
@@ -483,5 +592,5 @@
     {
         LogRel(("Recording: Started\n"));
-        m_enmState = RECORDINGSTS_STARTED;
+        m_enmState  = RECORDINGSTS_STARTED;
     }
     else
@@ -516,5 +625,6 @@
     {
         LogRel(("Recording: Stopped\n"));
-        m_enmState = RECORDINGSTS_CREATED;
+        m_tsStartMs = 0;
+        m_enmState  = RECORDINGSTS_CREATED;
     }
     else
@@ -689,4 +799,14 @@
 
 /**
+ * Returns the current PTS (presentation time stamp) for a recording context.
+ *
+ * @returns Current PTS.
+ */
+uint64_t RecordingContext::GetCurrentPTS(void) const
+{
+    return RTTimeMilliTS() - m_tsStartMs;
+}
+
+/**
  * Returns if a specific recoding feature is enabled for at least one of the attached
  * recording streams or not.
@@ -829,5 +949,5 @@
  * @param   msTimestamp         Timestamp (PTS, in ms).
  */
-bool RecordingContext::NeedsUpdate( uint32_t uScreen, uint64_t msTimestamp)
+bool RecordingContext::NeedsUpdate(uint32_t uScreen, uint64_t msTimestamp)
 {
     lock();
@@ -900,23 +1020,21 @@
  *
  * @returns VBox status code.
- * @param   uScreen            Screen number to send video frame to.
- * @param   x                  Starting x coordinate of the video frame.
- * @param   y                  Starting y coordinate of the video frame.
- * @param   uPixelFormat       Pixel format.
- * @param   uBPP               Bits Per Pixel (BPP).
- * @param   uBytesPerLine      Bytes per scanline.
- * @param   uSrcWidth          Width of the video frame.
- * @param   uSrcHeight         Height of the video frame.
- * @param   puSrcData          Pointer to video frame data.
- * @param   msTimestamp        Timestamp (PTS, in ms).
- */
-int RecordingContext::SendVideoFrame(uint32_t uScreen, uint32_t x, uint32_t y,
-                                     uint32_t uPixelFormat, uint32_t uBPP, uint32_t uBytesPerLine,
-                                     uint32_t uSrcWidth, uint32_t uSrcHeight, uint8_t *puSrcData,
-                                     uint64_t msTimestamp)
-{
-    AssertReturn(uSrcWidth,  VERR_INVALID_PARAMETER);
-    AssertReturn(uSrcHeight, VERR_INVALID_PARAMETER);
-    AssertReturn(puSrcData,  VERR_INVALID_POINTER);
+ * @param   uScreen             Screen number to send video frame to.
+ * @param   pFrame              Video frame to send.
+ * @param   msTimestamp         Timestamp (PTS, in ms).
+ */
+int RecordingContext::SendVideoFrame(uint32_t uScreen, PRECORDINGVIDEOFRAME pFrame, uint64_t msTimestamp)
+{
+    AssertPtrReturn(pFrame, VERR_INVALID_POINTER);
+
+    LogFlowFunc(("uScreen=%RU32, offX=%RU32, offY=%RU32, w=%RU32, h=%RU32, msTimestamp=%RU64\n",
+                 uScreen, pFrame->Pos.x, pFrame->Pos.y, pFrame->Info.uWidth, pFrame->Info.uHeight, msTimestamp));
+
+    if (!pFrame->pau8Buf) /* Empty / invalid frame, skip. */
+        return VINF_SUCCESS;
+
+    /* Sanity. */
+    AssertReturn(pFrame->Info.uWidth  * pFrame->Info.uHeight * (pFrame->Info.uBPP / 8) <= pFrame->cbBuf, VERR_INVALID_PARAMETER);
+    AssertReturn(pFrame->Info.uHeight * pFrame->Info.uBytesPerLine <= pFrame->cbBuf, VERR_INVALID_PARAMETER);
 
     lock();
@@ -931,5 +1049,5 @@
     }
 
-    int vrc = pStream->SendVideoFrame(x, y, uPixelFormat, uBPP, uBytesPerLine, uSrcWidth, uSrcHeight, puSrcData, msTimestamp);
+    int vrc = pStream->SendVideoFrame(pFrame, msTimestamp);
 
     unlock();
@@ -944,2 +1062,130 @@
 }
 
+/**
+ * Sends a cursor position change to the recording context.
+ *
+ * @returns VBox status code.
+ * @param   uScreen            Screen number.
+ * @param   x                  X location within the guest.
+ * @param   y                  Y location within the guest.
+ * @param   msTimestamp        Timestamp (PTS, in ms).
+ */
+int RecordingContext::SendCursorPositionChange(uint32_t uScreen, int32_t x, int32_t y, uint64_t msTimestamp)
+{
+    LogFlowFunc(("uScreen=%RU32, x=%RU32, y=%RU32\n", uScreen, x, y));
+
+    /* If no cursor shape is set yet, skip any cursor position changes. */
+    if (!m_Cursor.m_Shape.pau8Buf)
+        return VINF_SUCCESS;
+
+    if (uScreen == 0xFFFFFFFF /* SVGA_ID_INVALID */)
+        uScreen = 0;
+
+    int vrc = m_Cursor.Move(x, y);
+    if (RT_SUCCESS(vrc))
+    {
+        lock();
+
+        RecordingStream *pStream = getStreamInternal(uScreen);
+        if (!pStream)
+        {
+            unlock();
+
+            AssertFailed();
+            return VERR_NOT_FOUND;
+        }
+
+        vrc = pStream->SendCursorPos(0 /* idCursor */, &m_Cursor.m_Shape.Pos, msTimestamp);
+
+        unlock();
+
+        if (   RT_SUCCESS(vrc)
+            && vrc != VINF_RECORDING_THROTTLED) /* Only signal the thread if operation was successful. */
+        {
+            threadNotify();
+        }
+    }
+
+    return vrc;
+}
+
+/**
+ * Sends a cursor shape change to the recording context.
+ *
+ * @returns VBox status code.
+ * @param   fVisible            Whether the mouse cursor actually is visible or not.
+ * @param   fAlpha              Whether the pixel data contains alpha channel information or not.
+ * @param   xHot                X hot position (in pixel) of the new cursor.
+ * @param   yHot                Y hot position (in pixel) of the new cursor.
+ * @param   uWidth              Width (in pixel) of the new cursor.
+ * @param   uHeight             Height (in pixel) of the new cursor.
+ * @param   pu8Shape            Pixel data of the new cursor. Must be 32 BPP RGBA for now.
+ * @param   cbShape             Size of \a pu8Shape (in bytes).
+ * @param   msTimestamp         Timestamp (PTS, in ms).
+ */
+int RecordingContext::SendCursorShapeChange(bool fVisible, bool fAlpha, uint32_t xHot, uint32_t yHot,
+                                            uint32_t uWidth, uint32_t uHeight, const uint8_t *pu8Shape, size_t cbShape,
+                                            uint64_t msTimestamp)
+{
+    RT_NOREF(fAlpha, xHot, yHot);
+
+    LogFlowFunc(("fVisible=%RTbool, fAlpha=%RTbool, uWidth=%RU32, uHeight=%RU32\n", fVisible, fAlpha, uWidth, uHeight));
+
+    if (   !pu8Shape /* Might be NULL on saved state load. */
+        || !fVisible)
+        return VINF_SUCCESS;
+
+    AssertReturn(cbShape, VERR_INVALID_PARAMETER);
+
+    lock();
+
+    int vrc = m_Cursor.CreateOrUpdate(fAlpha, uWidth, uHeight, pu8Shape, cbShape);
+
+    RecordingStreams::iterator it = m_vecStreams.begin();
+    while (it != m_vecStreams.end())
+    {
+        RecordingStream *pStream = (*it);
+
+        int vrc2 = pStream->SendCursorShape(0 /* idCursor */, &m_Cursor.m_Shape, msTimestamp);
+        if (RT_SUCCESS(vrc))
+            vrc = vrc2;
+
+        ++it;
+    }
+
+    unlock();
+
+    if (RT_SUCCESS(vrc))
+        threadNotify();
+
+    return vrc;
+}
+
+/**
+ * Sends a screen change to a recording stream.
+ *
+ * @returns VBox status code.
+ * @param   uScreen             Screen number.
+ * @param   pInfo               Recording screen info to use.
+ * @param   msTimestamp         Timestamp (PTS, in ms).
+ */
+int RecordingContext::SendScreenChange(uint32_t uScreen, PRECORDINGSURFACEINFO pInfo, uint64_t msTimestamp)
+{
+    lock();
+
+    RecordingStream *pStream = getStreamInternal(uScreen);
+    if (!pStream)
+    {
+        unlock();
+
+        AssertFailed();
+        return VERR_NOT_FOUND;
+    }
+
+    int const vrc = pStream->SendScreenChange(pInfo, msTimestamp);
+
+    unlock();
+
+    return vrc;
+}
+
Index: /trunk/src/VBox/Main/src-client/RecordingCodec.cpp
===================================================================
--- /trunk/src/VBox/Main/src-client/RecordingCodec.cpp	(revision 105005)
+++ /trunk/src/VBox/Main/src-client/RecordingCodec.cpp	(revision 105006)
@@ -66,4 +66,5 @@
 #include <VBox/vmm/pdmaudioinline.h>
 
+#include "Recording.h"
 #include "RecordingInternals.h"
 #include "RecordingUtils.h"
@@ -72,4 +73,29 @@
 #include <math.h>
 
+#include <iprt/formats/bmp.h>
+
+
+/*********************************************************************************************************************************
+*   Prototypes                                                                                                                   *
+*********************************************************************************************************************************/
+static int recordingCodecVPXEncodeWorker(PRECORDINGCODEC pCodec, vpx_image_t *pImage, uint64_t msTimestamp);
+
+
+/*********************************************************************************************************************************
+*   Generic inline functions                                                                                                     *
+*********************************************************************************************************************************/
+
+DECLINLINE(void) recordingCodecLock(PRECORDINGCODEC pCodec)
+{
+    int vrc2 = RTCritSectEnter(&pCodec->CritSect);
+    AssertRC(vrc2);
+}
+
+DECLINLINE(void) recordingCodecUnlock(PRECORDINGCODEC pCodec)
+{
+    int vrc2 = RTCritSectLeave(&pCodec->CritSect);
+    AssertRC(vrc2);
+}
+
 
 /*********************************************************************************************************************************
@@ -78,14 +104,27 @@
 
 #ifdef VBOX_WITH_LIBVPX
+/** Prototypes. */
+static DECLCALLBACK(int) recordingCodecVPXScreenChange(PRECORDINGCODEC pCodec, PRECORDINGSURFACEINFO pInfo);
+
+/**
+ * Clears (zeros) the VPX planes.
+ */
+DECLINLINE(void) recordingCodecVPXClearPlanes(PRECORDINGCODEC pCodec)
+{
+    size_t const cbYPlane  = pCodec->Parms.u.Video.uWidth * pCodec->Parms.u.Video.uHeight;
+    memset(pCodec->Video.VPX.RawImage.planes[VPX_PLANE_Y], 0, cbYPlane);
+    size_t const cbUVPlane = (pCodec->Parms.u.Video.uWidth / 2) * (pCodec->Parms.u.Video.uHeight / 2);
+    memset(pCodec->Video.VPX.RawImage.planes[VPX_PLANE_U], 128, cbUVPlane);
+    memset(pCodec->Video.VPX.RawImage.planes[VPX_PLANE_V], 128, cbUVPlane);
+}
+
 /** @copydoc RECORDINGCODECOPS::pfnInit */
 static DECLCALLBACK(int) recordingCodecVPXInit(PRECORDINGCODEC pCodec)
 {
-    pCodec->cbScratch = _4K;
-    pCodec->pvScratch = RTMemAlloc(pCodec->cbScratch);
-    AssertPtrReturn(pCodec->pvScratch, VERR_NO_MEMORY);
-
-    pCodec->Parms.csFrame  = 0;
-    pCodec->Parms.cbFrame  = pCodec->Parms.Video.uWidth * pCodec->Parms.Video.uHeight * 4 /* 32-bit */;
-    pCodec->Parms.msFrame  = 1; /* 1ms per frame. */
+    const unsigned uBPP = 32;
+
+    pCodec->Parms.csFrame = 0;
+    pCodec->Parms.cbFrame = pCodec->Parms.u.Video.uWidth * pCodec->Parms.u.Video.uHeight * (uBPP / 8);
+    pCodec->Parms.msFrame = 1; /* 1ms per frame. */
 
 # ifdef VBOX_WITH_LIBVPX_VP9
@@ -106,7 +145,7 @@
     pVPX->Cfg.rc_target_bitrate = pCodec->Parms.uBitrate;
     /* Frame width. */
-    pVPX->Cfg.g_w = pCodec->Parms.Video.uWidth;
+    pVPX->Cfg.g_w = pCodec->Parms.u.Video.uWidth;
     /* Frame height. */
-    pVPX->Cfg.g_h = pCodec->Parms.Video.uHeight;
+    pVPX->Cfg.g_h = pCodec->Parms.u.Video.uHeight;
     /* ms per frame. */
     pVPX->Cfg.g_timebase.num = pCodec->Parms.msFrame;
@@ -124,14 +163,32 @@
 
     if (!vpx_img_alloc(&pVPX->RawImage, VPX_IMG_FMT_I420,
-                       pCodec->Parms.Video.uWidth, pCodec->Parms.Video.uHeight, 1))
-    {
-        LogRel(("Recording: Failed to allocate image %RU32x%RU32\n", pCodec->Parms.Video.uWidth, pCodec->Parms.Video.uHeight));
+                       pCodec->Parms.u.Video.uWidth, pCodec->Parms.u.Video.uHeight, 1))
+    {
+        LogRel(("Recording: Failed to allocate image %RU32x%RU32\n", pCodec->Parms.u.Video.uWidth, pCodec->Parms.u.Video.uHeight));
         return VERR_RECORDING_CODEC_INIT_FAILED;
     }
 
-    /* Save a pointer to the first raw YUV plane. */
-    pVPX->pu8YuvBuf = pVPX->RawImage.planes[0];
-
-    return VINF_SUCCESS;
+    /* Save a pointer to the Y (Luminance) plane. */
+    pVPX->pu8YuvBuf = pVPX->RawImage.planes[VPX_PLANE_Y];
+
+    /* Initialize front + back buffers. */
+    RT_ZERO(pCodec->Video.VPX.Front);
+    RT_ZERO(pCodec->Video.VPX.Back);
+
+    pCodec->Video.VPX.pCursorShape = NULL;
+
+    RECORDINGSURFACEINFO ScreenInfo;
+    ScreenInfo.uWidth      = pCodec->Parms.u.Video.uWidth;
+    ScreenInfo.uHeight     = pCodec->Parms.u.Video.uHeight;
+    ScreenInfo.uBPP        = uBPP;
+    ScreenInfo.enmPixelFmt = RECORDINGPIXELFMT_BRGA32;
+
+    RT_ZERO(pCodec->Video.VPX.PosCursorOld);
+
+    int vrc = recordingCodecVPXScreenChange(pCodec, &ScreenInfo);
+    if (RT_FAILURE(vrc))
+        LogRel(("Recording: Failed to initialize codec: %Rrc\n", vrc));
+
+    return vrc;
 }
 
@@ -147,5 +204,23 @@
     Assert(rcv == VPX_CODEC_OK); RT_NOREF(rcv);
 
+    RecordingVideoFrameDestroy(&pCodec->Video.VPX.Front);
+    RecordingVideoFrameDestroy(&pCodec->Video.VPX.Back);
+
+    RecordingVideoFrameFree(pCodec->Video.VPX.pCursorShape);
+    pCodec->Video.VPX.pCursorShape = NULL;
+
     return VINF_SUCCESS;
+}
+
+/** @copydoc RECORDINGCODECOPS::pfnFinalize */
+static DECLCALLBACK(int) recordingCodecVPXFinalize(PRECORDINGCODEC pCodec)
+{
+    recordingCodecLock(pCodec);
+
+    int vrc = recordingCodecVPXEncodeWorker(pCodec, NULL /* pImage */, pCodec->State.tsLastWrittenMs + 1);
+
+    recordingCodecUnlock(pCodec);
+
+    return vrc;
 }
 
@@ -165,6 +240,6 @@
             else if (value.compare("good", com::Utf8Str::CaseInsensitive) == 0)
             {
-                AssertStmt(pCodec->Parms.Video.uFPS, pCodec->Parms.Video.uFPS = 25);
-                pVPX->uEncoderDeadline = 1000000 / pCodec->Parms.Video.uFPS;
+                AssertStmt(pCodec->Parms.u.Video.uFPS, pCodec->Parms.u.Video.uFPS = 25);
+                pVPX->uEncoderDeadline = 1000000 / pCodec->Parms.u.Video.uFPS;
             }
             else if (value.compare("best", com::Utf8Str::CaseInsensitive) == 0)
@@ -180,31 +255,29 @@
 }
 
-/** @copydoc RECORDINGCODECOPS::pfnEncode */
-static DECLCALLBACK(int) recordingCodecVPXEncode(PRECORDINGCODEC pCodec, PRECORDINGFRAME pFrame,
-                                                 size_t *pcEncoded, size_t *pcbEncoded)
-{
-    RT_NOREF(pcEncoded, pcbEncoded);
-
-    AssertPtrReturn(pFrame, VERR_INVALID_POINTER);
-
-    PRECORDINGVIDEOFRAME pVideoFrame = pFrame->VideoPtr;
-
-    int vrc = RecordingUtilsRGBToYUV(pVideoFrame->enmPixelFmt,
-                                     /* Destination */
-                                     pCodec->Video.VPX.pu8YuvBuf, pVideoFrame->uWidth, pVideoFrame->uHeight,
-                                     /* Source */
-                                     pVideoFrame->pu8RGBBuf, pCodec->Parms.Video.uWidth, pCodec->Parms.Video.uHeight);
-    AssertRCReturn(vrc, vrc);
+/**
+ * Worker for encoding the last composed image.
+ *
+ * @returns VBox status code.
+ * @param   pCodec              Pointer to codec instance.
+ * @param   pImage              VPX image to encode.
+ *                              Set to NULL to signal the encoder that it has to finish up stuff when ending encoding.
+ * @param   msTimestamp         Timestamp (PTS) to use for encoding.
+ *
+ * @note    Caller must take encoder lock.
+ */
+static int recordingCodecVPXEncodeWorker(PRECORDINGCODEC pCodec, vpx_image_t *pImage, uint64_t msTimestamp)
+{
+    int vrc;
 
     PRECORDINGCODECVPX pVPX = &pCodec->Video.VPX;
 
     /* Presentation TimeStamp (PTS). */
-    vpx_codec_pts_t pts = pFrame->msTimestamp;
-    vpx_codec_err_t rcv = vpx_codec_encode(&pVPX->Ctx,
-                                           &pVPX->RawImage,
-                                           pts                          /* Timestamp */,
-                                           pCodec->Parms.Video.uDelayMs /* How long to show this frame */,
-                                           0                            /* Flags */,
-                                           pVPX->uEncoderDeadline       /* Quality setting */);
+    vpx_codec_pts_t const pts = msTimestamp;
+    vpx_codec_err_t const rcv = vpx_codec_encode(&pVPX->Ctx,
+                                                 pImage,
+                                                 pts                            /* Timestamp */,
+                                                 pCodec->Parms.u.Video.uDelayMs /* How long to show this frame */,
+                                                 0                              /* Flags */,
+                                                 pVPX->uEncoderDeadline         /* Quality setting */);
     if (rcv != VPX_CODEC_OK)
     {
@@ -221,5 +294,5 @@
     {
         const vpx_codec_cx_pkt_t *pPkt = vpx_codec_get_cx_data(&pVPX->Ctx, &iter);
-        if (!pPkt)
+        if (!pPkt) /* End of list */
             break;
 
@@ -240,4 +313,6 @@
                     fFlags |= RECORDINGCODEC_ENC_F_BLOCK_IS_INVISIBLE;
 
+                Log3Func(("msTimestamp=%RU64, fFlags=%#x\n", msTimestamp, fFlags));
+
                 vrc = pCodec->Callbacks.pfnWriteData(pCodec, pPkt->data.frame.buf, pPkt->data.frame.sz,
                                                      tsAbsPTSMs, fFlags, pCodec->Callbacks.pvUser);
@@ -254,4 +329,272 @@
     return vrc;
 }
+
+/** @copydoc RECORDINGCODECOPS::pfnEncode */
+static DECLCALLBACK(int) recordingCodecVPXEncode(PRECORDINGCODEC pCodec, PRECORDINGFRAME pFrame,
+                                                 uint64_t msTimestamp, void *pvUser)
+{
+    recordingCodecLock(pCodec);
+
+    int vrc;
+
+    /* If no frame is given, encode the last composed frame again with the given timestamp. */
+    if (pFrame == NULL)
+    {
+        vrc = recordingCodecVPXEncodeWorker(pCodec, &pCodec->Video.VPX.RawImage, msTimestamp);
+        recordingCodecUnlock(pCodec);
+        return vrc;
+    }
+
+    RecordingContext *pCtx = (RecordingContext *)pvUser;
+    AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
+
+    Assert(pFrame->msTimestamp == msTimestamp);
+
+    /* Note: We get BGRA 32 input here. */
+    PRECORDINGVIDEOFRAME pFront = &pCodec->Video.VPX.Front;
+    PRECORDINGVIDEOFRAME pBack  = &pCodec->Video.VPX.Back;
+
+    int32_t sx = 0; /* X origin within the source frame. */
+    int32_t sy = 0; /* Y origin within the source frame. */
+    int32_t sw = 0; /* Width of the source frame (starting at X origin). */
+    int32_t sh = 0; /* Height of the source frame (starting at Y origin). */
+    int32_t dx = 0; /* X destination of the source frame within the destination frame. */
+    int32_t dy = 0; /* Y destination of the source frame within the destination frame. */
+
+    /*
+     * Note!
+     *
+     * We don't implement any rendering graph or some such here, as we only have two things to render here, namely:
+     *
+     * - the actual framebuffer updates
+     * - if available (through mouse integration via Guest Additions): the guest's mouse cursor via a (software) overlay
+     *
+     * So composing is done the folowing way:
+     *
+     *  - always store the plain framebuffer updates in our back buffer first
+     *  - copy the framebuffer updates to our front buffer
+     *  - restore the area of the old mouse cursor position by copying frame buffer area data from back -> front buffer
+     *  - apply the mouse cursor updates to our front buffer
+     */
+
+    switch (pFrame->enmType)
+    {
+        case RECORDINGFRAME_TYPE_VIDEO:
+        {
+            PRECORDINGVIDEOFRAME pSrc = &pFrame->u.Video;
+
+            vrc = RecordingVideoFrameBlitFrame(pFront, pSrc->Pos.x, pSrc->Pos.y,
+                                               pSrc, 0 /* uSrcX */, 0 /* uSrcY */, pSrc->Info.uWidth, pSrc->Info.uHeight);
+#if 0
+            RecordingUtilsDbgDumpVideoFrameEx(pFront, "/tmp/recording", "encode-front");
+            RecordingUtilsDbgDumpVideoFrameEx(pSrc, "/tmp/recording", "encode-src");
+#endif
+            vrc = RecordingVideoFrameBlitFrame(pBack, pSrc->Pos.x, pSrc->Pos.y,
+                                               pSrc,  0 /* uSrcX */, 0 /* uSrcY */, pSrc->Info.uWidth, pSrc->Info.uHeight);
+            pFront = pBack;
+
+            sw = pSrc->Info.uWidth;
+            sh = pSrc->Info.uHeight;
+            sx = pSrc->Pos.x;
+            sy = pSrc->Pos.y;
+
+            Log3Func(("RECORDINGFRAME_TYPE_VIDEO: sx=%d, sy=%d, sw=%d, sh=%d\n", sx, sy, sw, sh));
+
+            dx = pSrc->Pos.x;
+            dy = pSrc->Pos.y;
+            break;
+        }
+
+        case RECORDINGFRAME_TYPE_CURSOR_SHAPE:
+        {
+            pCodec->Video.VPX.pCursorShape = RecordingVideoFrameDup(&pFrame->u.CursorShape);
+            AssertPtr(pCodec->Video.VPX.pCursorShape);
+
+            RT_FALL_THROUGH(); /* Re-render cursor with new shape below. */
+        }
+
+        case RECORDINGFRAME_TYPE_CURSOR_POS:
+        {
+            const PRECORDINGVIDEOFRAME pCursor = pCodec->Video.VPX.pCursorShape;
+            if (!pCursor) /* No cursor shape set yet. */
+                break;
+
+            PRECORDINGPOS pPosOld = &pCodec->Video.VPX.PosCursorOld;
+            PRECORDINGPOS pPosNew = pFrame->enmType == RECORDINGFRAME_TYPE_CURSOR_POS
+                                  ? &pFrame->u.Cursor.Pos
+                                  : pPosOld;
+
+            Log3Func(("RECORDINGFRAME_TYPE_CURSOR_POS: x=%d, y=%d, oldx=%d, oldy=%d, w=%d, h=%d\n",
+                      pPosNew->x, pPosNew->y,
+                      pPosOld->x, pPosOld->y, pCursor->Info.uWidth, pCursor->Info.uHeight));
+
+            /* Calculate the merged area between the old and the new (current) cursor position
+             * so that we update everything to not create any ghosting. */
+            sx = RT_MIN(pPosNew->x, pPosOld->x);
+            sy = RT_MIN(pPosNew->y, pPosOld->y);
+            sw = (  pPosNew->x > pPosOld->x
+                  ? pPosNew->x - pPosOld->x
+                  : pPosOld->x - pPosNew->x) + pCursor->Info.uWidth;
+            sh = (  pPosNew->y > pPosOld->y
+                  ? pPosNew->y - pPosOld->y
+                  : pPosOld->y - pPosNew->y) + pCursor->Info.uHeight;
+
+            /* Limit the width / height to blit to the front buffer's size. */
+            if (sx + sw >= (int32_t)pFront->Info.uWidth)
+                sw = pFront->Info.uWidth - sx;
+            if (sy + sh >= (int32_t)pFront->Info.uHeight)
+                sh = pFront->Info.uHeight - sy;
+
+            /* Save current cursor position for next iteration. */
+            *pPosOld = *pPosNew;
+
+            dx = sx;
+            dy = sy;
+
+            Log3Func(("RECORDINGFRAME_TYPE_CURSOR_POS: sx=%d, sy=%d, sw=%d, sh=%d\n", sx, sy, sw, sh));
+
+            /* Nothing to encode? Bail out. */
+            if (             sw <= 0
+                ||           sh <= 0
+                || (uint32_t)sx >  pBack->Info.uWidth
+                || (uint32_t)sy >  pBack->Info.uHeight)
+                break;
+
+            /* Restore background of front buffer first. */
+            vrc = RecordingVideoFrameBlitFrame(pFront, dx, dy,
+                                               pBack,  sx, sy, sw, sh);
+
+            /* Blit mouse cursor to front buffer. */
+            if (RT_SUCCESS(vrc))
+                vrc = RecordingVideoFrameBlitRawAlpha(pFront, pPosNew->x, pPosNew->y,
+                                                      pCursor->pau8Buf, pCursor->cbBuf,
+                                                      0 /* uSrcX */, 0 /* uSrcY */, pCursor->Info.uWidth, pCursor->Info.uHeight,
+                                                      pCursor->Info.uBytesPerLine, pCursor->Info.uBPP, pCursor->Info.enmPixelFmt);
+#if 0
+            RecordingUtilsDbgDumpVideoFrameEx(pFront, "/tmp/recording", "cursor-alpha-front");
+#endif
+            break;
+        }
+
+        default:
+            AssertFailed();
+            break;
+
+    }
+
+    /* Nothing to encode? Bail out. */
+    if (   sw == 0
+        || sh == 0)
+    {
+        recordingCodecUnlock(pCodec);
+        return VINF_SUCCESS;
+    }
+
+    Log3Func(("Encoding video parameters: %RU16x%RU16 (%RU8 FPS), originX=%RI32, originY=%RI32\n",
+              pCodec->Parms.u.Video.uWidth, pCodec->Parms.u.Video.uHeight, pCodec->Parms.u.Video.uFPS,
+              pCodec->Parms.u.Video.Scaling.u.Crop.m_iOriginX, pCodec->Parms.u.Video.Scaling.u.Crop.m_iOriginY));
+
+    int32_t sx_b = sx; /* X origin within the source frame. */
+    int32_t sy_b = sy; /* Y origin within the source frame. */
+    int32_t sw_b = sw; /* Width of the source frame (starting at X origin). */
+    int32_t sh_b = sh; /* Height of the source frame (starting at Y origin). */
+    int32_t dx_b = dx; /* X destination of the source frame within the destination frame. */
+    int32_t dy_b = dy; /* Y destination of the source frame within the destination frame. */
+
+    RT_NOREF(sx_b);
+    RT_NOREF(sy_b);
+    RT_NOREF(sw_b);
+    RT_NOREF(sh_b);
+    RT_NOREF(dx_b);
+    RT_NOREF(dy_b);
+
+    vrc = RecordingUtilsCoordsCropCenter(&pCodec->Parms, &sx, &sy, &sw, &sh, &dx, &dy);
+    if (vrc == VINF_SUCCESS) /* vrc might be VWRN_RECORDING_ENCODING_SKIPPED to skip encoding. */
+    {
+        Log3Func(("Encoding source %RI32,%RI32 (%RI32x%RI32) to %RI32,%RI32 (%zu bytes)\n",
+                  sx, sy, sw, sh, dx, dy, sw * sh * (pFront->Info.uBPP / 8)));
+#ifdef DEBUG
+        AssertReturn(sw      <= (int32_t)pFront->Info.uWidth,  VERR_INVALID_PARAMETER);
+        AssertReturn(sh      <= (int32_t)pFront->Info.uHeight, VERR_INVALID_PARAMETER);
+        AssertReturn(sx + sw <= (int32_t)pFront->Info.uWidth , VERR_INVALID_PARAMETER);
+        AssertReturn(sy + sh <= (int32_t)pFront->Info.uHeight, VERR_INVALID_PARAMETER);
+#endif
+
+#if 0
+        RecordingUtilsDbgDumpImageData(&pFront->pau8Buf[(sy * pFront->Info.uBytesPerLine) + (sx * (pFront->Info.uBPP / 8))], pFront->cbBuf,
+                                       "/tmp/recording", "cropped", sw, sh, pFront->Info.uBytesPerLine, pFront->Info.uBPP);
+#endif
+        /* Blit (and convert from BGRA 32) the changed parts of the front buffer to the YUV 420 surface of the codec. */
+        RecordingUtilsConvBGRA32ToYUVI420Ex(/* Destination */
+                                            pCodec->Video.VPX.pu8YuvBuf, dx, dy, pCodec->Parms.u.Video.uWidth, pCodec->Parms.u.Video.uHeight,
+                                            /* Source */
+                                            pFront->pau8Buf, sx, sy, sw, sh, pFront->Info.uBytesPerLine, pFront->Info.uBPP);
+
+        vrc = recordingCodecVPXEncodeWorker(pCodec, &pCodec->Video.VPX.RawImage, msTimestamp);
+    }
+
+    recordingCodecUnlock(pCodec);
+
+    return vrc;
+}
+
+/** @copydoc RECORDINGCODECOPS::pfnScreenChange */
+static DECLCALLBACK(int) recordingCodecVPXScreenChange(PRECORDINGCODEC pCodec, PRECORDINGSURFACEINFO pInfo)
+{
+    LogRel2(("Recording: Codec got screen change notification (%RU16x%RU16, %RU8 BPP)\n",
+             pInfo->uWidth, pInfo->uHeight, pInfo->uBPP));
+
+    /* Fend-off bogus reports. */
+    if (   !pInfo->uWidth
+        || !pInfo->uHeight)
+        return VERR_INVALID_PARAMETER;
+
+     /** @todo BUGBUG Not sure why we sometimes get 0 BPP for a display change from Main.
+      *               For now we ASSUME 32 BPP. */
+    if (!pInfo->uBPP)
+        pInfo->uBPP = 32;
+
+    /* The VPX encoder only understands even frame sizes. */
+    if (   (pInfo->uWidth  % 2) != 0
+        || (pInfo->uHeight % 2) != 0)
+        return VERR_INVALID_PARAMETER;
+
+    PRECORDINGCODECVPX pVPX = &pCodec->Video.VPX;
+
+    recordingCodecLock(pCodec);
+
+    /* Tear down old stuff. */
+    RecordingVideoFrameDestroy(&pVPX->Front);
+    RecordingVideoFrameDestroy(&pVPX->Back);
+
+    /* Initialize front + back buffers. */
+    int vrc = RecordingVideoFrameInit(&pVPX->Front, RECORDINGVIDEOFRAME_F_VISIBLE,
+                                      pInfo->uWidth, pInfo->uHeight, 0, 0,
+                                      pInfo->uBPP, pInfo->enmPixelFmt);
+    if (RT_SUCCESS(vrc))
+        vrc = RecordingVideoFrameInit(&pVPX->Back, RECORDINGVIDEOFRAME_F_VISIBLE,
+                                      pInfo->uWidth, pInfo->uHeight, 0, 0,
+                                      pInfo->uBPP, pInfo->enmPixelFmt);
+    if (RT_SUCCESS(vrc))
+    {
+        RecordingVideoFrameClear(&pVPX->Front);
+        RecordingVideoFrameClear(&pVPX->Back);
+
+        recordingCodecVPXClearPlanes(pCodec);
+
+        /* Calculate the X/Y origins for cropping / centering.
+         * This is needed as the codec's video output size not necessarily matches the VM's frame buffer size. */
+        pCodec->Parms.u.Video.Scaling.u.Crop.m_iOriginX = int32_t(pCodec->Parms.u.Video.uWidth  - pInfo->uWidth) / 2;
+        pCodec->Parms.u.Video.Scaling.u.Crop.m_iOriginY = int32_t(pCodec->Parms.u.Video.uHeight - pInfo->uHeight) / 2;
+    }
+
+    recordingCodecUnlock(pCodec);
+
+    if (RT_FAILURE(vrc))
+        LogRel(("Recording: Codec error handling screen change notification: %Rrc\n", vrc));
+
+    return vrc;
+
+}
 #endif /* VBOX_WITH_LIBVPX */
 
@@ -269,5 +612,5 @@
     AssertPtrReturn(pCodec->pvScratch, VERR_NO_MEMORY);
 
-    const PPDMAUDIOPCMPROPS pPCMProps = &pCodec->Parms.Audio.PCMProps;
+    const PPDMAUDIOPCMPROPS pPCMProps = &pCodec->Parms.u.Audio.PCMProps;
 
     /** @todo BUGBUG When left out this call, vorbis_block_init() does not find oggpack_writeinit and all goes belly up ... */
@@ -336,22 +679,24 @@
 /** @copydoc RECORDINGCODECOPS::pfnEncode */
 static DECLCALLBACK(int) recordingCodecVorbisEncode(PRECORDINGCODEC pCodec,
-                                                    const PRECORDINGFRAME pFrame, size_t *pcEncoded, size_t *pcbEncoded)
-{
-    const PPDMAUDIOPCMPROPS pPCMProps = &pCodec->Parms.Audio.PCMProps;
+                                                    const PRECORDINGFRAME pFrame, uint64_t msTimestamp, void *pvUser)
+{
+    RT_NOREF(msTimestamp, pvUser);
+
+    const PPDMAUDIOPCMPROPS pPCMProps = &pCodec->Parms.u.Audio.PCMProps;
 
     Assert      (pCodec->Parms.cbFrame);
-    AssertReturn(pFrame->Audio.cbBuf % pCodec->Parms.cbFrame == 0, VERR_INVALID_PARAMETER);
-    Assert      (pFrame->Audio.cbBuf);
-    AssertReturn(pFrame->Audio.cbBuf % PDMAudioPropsFrameSize(pPCMProps) == 0, VERR_INVALID_PARAMETER);
-    AssertReturn(pCodec->cbScratch >= pFrame->Audio.cbBuf, VERR_INVALID_PARAMETER);
+    AssertReturn(pFrame->u.Audio.cbBuf % pCodec->Parms.cbFrame == 0, VERR_INVALID_PARAMETER);
+    Assert      (pFrame->u.Audio.cbBuf);
+    AssertReturn(pFrame->u.Audio.cbBuf % PDMAudioPropsFrameSize(pPCMProps) == 0, VERR_INVALID_PARAMETER);
+    AssertReturn(pCodec->cbScratch >= pFrame->u.Audio.cbBuf, VERR_INVALID_PARAMETER);
 
     int vrc = VINF_SUCCESS;
 
     int const cbFrame = PDMAudioPropsFrameSize(pPCMProps);
-    int const cFrames = (int)(pFrame->Audio.cbBuf / cbFrame);
+    int const cFrames = (int)(pFrame->u.Audio.cbBuf / cbFrame);
 
     /* Write non-interleaved frames. */
     float  **buffer = vorbis_analysis_buffer(&pCodec->Audio.Vorbis.dsp_state, cFrames);
-    int16_t *puSrc  = (int16_t *)pFrame->Audio.pvBuf; RT_NOREF(puSrc);
+    int16_t *puSrc  = (int16_t *)pFrame->u.Audio.pvBuf; RT_NOREF(puSrc);
 
     /* Convert samples into floating point. */
@@ -375,9 +720,4 @@
         return VERR_RECORDING_ENCODING_FAILED;
     }
-
-    if (pcEncoded)
-        *pcEncoded = 0;
-    if (pcbEncoded)
-        *pcbEncoded = 0;
 
     size_t cBlocksEncoded = 0;
@@ -439,18 +779,14 @@
     }
 
-    if (pcbEncoded)
-        *pcbEncoded = 0;
-    if (pcEncoded)
-        *pcEncoded  = 0;
-
     if (RT_FAILURE(vrc))
         LogRel(("Recording: Encoding Vorbis audio data failed, vrc=%Rrc\n", vrc));
 
     Log3Func(("cbSrc=%zu, cbDst=%zu, cEncoded=%zu, cbEncoded=%zu, vrc=%Rrc\n",
-              pFrame->Audio.cbBuf, pCodec->cbScratch, cBlocksEncoded, cBytesEncoded, vrc));
-
-    return vrc;
-}
-
+              pFrame->u.Audio.cbBuf, pCodec->cbScratch, cBlocksEncoded, cBytesEncoded, vrc));
+
+    return vrc;
+}
+
+/** @copydoc RECORDINGCODECOPS::pfnFinalize */
 static DECLCALLBACK(int) recordingCodecVorbisFinalize(PRECORDINGCODEC pCodec)
 {
@@ -488,5 +824,5 @@
     LogRel(("Recording: Initializing audio codec '%s'\n", strCodec.c_str()));
 
-    const PPDMAUDIOPCMPROPS pPCMProps = &pCodec->Parms.Audio.PCMProps;
+    const PPDMAUDIOPCMPROPS pPCMProps = &pCodec->Parms.u.Audio.PCMProps;
 
     PDMAudioPropsInit(pPCMProps,
@@ -546,9 +882,9 @@
     LogRel(("Recording: Initializing video codec '%s'\n", strTemp.c_str()));
 
-    pCodec->Parms.uBitrate       = Settings.Video.ulRate;
-    pCodec->Parms.Video.uFPS     = Settings.Video.ulFPS;
-    pCodec->Parms.Video.uWidth   = Settings.Video.ulWidth;
-    pCodec->Parms.Video.uHeight  = Settings.Video.ulHeight;
-    pCodec->Parms.Video.uDelayMs = RT_MS_1SEC / pCodec->Parms.Video.uFPS;
+    pCodec->Parms.uBitrate         = Settings.Video.ulRate;
+    pCodec->Parms.u.Video.uFPS     = Settings.Video.ulFPS;
+    pCodec->Parms.u.Video.uWidth   = Settings.Video.ulWidth;
+    pCodec->Parms.u.Video.uHeight  = Settings.Video.ulHeight;
+    pCodec->Parms.u.Video.uDelayMs = RT_MS_1SEC / pCodec->Parms.u.Video.uFPS;
 
     if (pCallbacks)
@@ -556,9 +892,9 @@
 
     AssertReturn(pCodec->Parms.uBitrate, VERR_INVALID_PARAMETER);        /* Bitrate must be set. */
-    AssertStmt(pCodec->Parms.Video.uFPS, pCodec->Parms.Video.uFPS = 25); /* Prevent division by zero. */
-
-    AssertReturn(pCodec->Parms.Video.uHeight, VERR_INVALID_PARAMETER);
-    AssertReturn(pCodec->Parms.Video.uWidth, VERR_INVALID_PARAMETER);
-    AssertReturn(pCodec->Parms.Video.uDelayMs, VERR_INVALID_PARAMETER);
+    AssertStmt(pCodec->Parms.u.Video.uFPS, pCodec->Parms.u.Video.uFPS = 25); /* Prevent division by zero. */
+
+    AssertReturn(pCodec->Parms.u.Video.uHeight, VERR_INVALID_PARAMETER);
+    AssertReturn(pCodec->Parms.u.Video.uWidth, VERR_INVALID_PARAMETER);
+    AssertReturn(pCodec->Parms.u.Video.uDelayMs, VERR_INVALID_PARAMETER);
 
     int vrc = VINF_SUCCESS;
@@ -602,5 +938,5 @@
             if (value.compare("low", com::Utf8Str::CaseInsensitive) == 0)
             {
-                PDMAudioPropsInit(&pCodec->Parms.Audio.PCMProps, 16, true /* fSigned */, 1 /* Channels */, 8000 /* Hz */);
+                PDMAudioPropsInit(&pCodec->Parms.u.Audio.PCMProps, 16, true /* fSigned */, 1 /* Channels */, 8000 /* Hz */);
             }
             else if (value.startsWith("med" /* "med[ium]" */, com::Utf8Str::CaseInsensitive) == 0)
@@ -610,5 +946,5 @@
             else if (value.compare("high", com::Utf8Str::CaseInsensitive) == 0)
             {
-                PDMAudioPropsInit(&pCodec->Parms.Audio.PCMProps, 16, true /* fSigned */, 2 /* Channels */, 48000 /* Hz */);
+                PDMAudioPropsInit(&pCodec->Parms.u.Audio.PCMProps, 16, true /* fSigned */, 2 /* Channels */, 48000 /* Hz */);
             }
         }
@@ -625,10 +961,5 @@
 {
     pCodec->State.tsLastWrittenMs = 0;
-
     pCodec->State.cEncErrors = 0;
-#ifdef VBOX_WITH_STATISTICS
-    pCodec->STAM.cEncBlocks  = 0;
-    pCodec->STAM.msEncTotal  = 0;
-#endif
 }
 
@@ -708,6 +1039,8 @@
             pCodec->Ops.pfnInit         = recordingCodecVPXInit;
             pCodec->Ops.pfnDestroy      = recordingCodecVPXDestroy;
+            pCodec->Ops.pfnFinalize     = recordingCodecVPXFinalize;
             pCodec->Ops.pfnParseOptions = recordingCodecVPXParseOptions;
             pCodec->Ops.pfnEncode       = recordingCodecVPXEncode;
+            pCodec->Ops.pfnScreenChange = recordingCodecVPXScreenChange;
 
             vrc = VINF_SUCCESS;
@@ -740,7 +1073,12 @@
 int recordingCodecInit(const PRECORDINGCODEC pCodec, const PRECORDINGCODECCALLBACKS pCallbacks, const settings::RecordingScreenSettings &Settings)
 {
+    int vrc = RTCritSectInit(&pCodec->CritSect);
+    AssertRCReturn(vrc, vrc);
+
+    pCodec->cbScratch = 0;
+    pCodec->pvScratch = NULL;
+
     recordingCodecReset(pCodec);
 
-    int vrc;
     if (pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO)
         vrc = recordingCodecInitAudio(pCodec, pCallbacks, Settings);
@@ -811,40 +1149,61 @@
         pCodec->Parms.enmType       = RECORDINGCODECTYPE_INVALID;
         pCodec->Parms.enmVideoCodec = RecordingVideoCodec_None;
-    }
-
-    return vrc;
-}
-
-/**
- * Feeds the codec encoder with data to encode.
+
+        int vrc2 = RTCritSectDelete(&pCodec->CritSect);
+        AssertRC(vrc2);
+    }
+
+    return vrc;
+}
+
+/**
+ * Feeds the codec encoder with frame data to encode.
  *
  * @returns VBox status code.
  * @param   pCodec              Codec to use.
  * @param   pFrame              Pointer to frame data to encode.
- * @param   pcEncoded           Where to return the number of encoded blocks in \a pvDst on success. Optional.
- * @param   pcbEncoded          Where to return the number of encoded bytes in \a pvDst on success. Optional.
- */
-int recordingCodecEncode(PRECORDINGCODEC pCodec,
-                         const PRECORDINGFRAME pFrame, size_t *pcEncoded, size_t *pcbEncoded)
+ * @param   msTimestamp         Timestamp (PTS) to use for encoding.
+ * @param   pvUser              User data pointer. Optional and can be NULL.
+ */
+int recordingCodecEncodeFrame(PRECORDINGCODEC pCodec, const PRECORDINGFRAME pFrame, uint64_t msTimestamp, void *pvUser)
 {
     AssertPtrReturn(pCodec->Ops.pfnEncode, VERR_NOT_SUPPORTED);
 
-    size_t cEncoded, cbEncoded;
-    int vrc = pCodec->Ops.pfnEncode(pCodec, pFrame, &cEncoded, &cbEncoded);
+    int vrc = pCodec->Ops.pfnEncode(pCodec, pFrame, msTimestamp, pvUser);
     if (RT_SUCCESS(vrc))
-    {
         pCodec->State.tsLastWrittenMs = pFrame->msTimestamp;
 
-#ifdef VBOX_WITH_STATISTICS
-        pCodec->STAM.cEncBlocks += cEncoded;
-        pCodec->STAM.msEncTotal += pCodec->Parms.msFrame * cEncoded;
-#endif
-        if (pcEncoded)
-            *pcEncoded = cEncoded;
-        if (pcbEncoded)
-            *pcbEncoded = cbEncoded;
-    }
-
-    return vrc;
+    return vrc;
+}
+
+/**
+ * Feeds the codec encoder with the current composed image.
+ *
+ * @returns VBox status code.
+ * @param   pCodec              Codec to use.
+ * @param   msTimestamp         Timestamp (PTS) to use for encoding.
+ */
+int recordingCodecEncodeCurrent(PRECORDINGCODEC pCodec, uint64_t msTimestamp)
+{
+    int vrc = pCodec->Ops.pfnEncode(pCodec, NULL /* pFrame */, msTimestamp, NULL /* pvUser */);
+    if (RT_SUCCESS(vrc))
+        pCodec->State.tsLastWrittenMs = msTimestamp;
+
+    return vrc;
+}
+
+/**
+ * Lets the codec know that a screen change has happened.
+ *
+ * @returns VBox status code.
+ * @param   pCodec              Codec to use.
+ * @param   pInfo               Screen info to send.
+ */
+int recordingCodecScreenChange(PRECORDINGCODEC pCodec, PRECORDINGSURFACEINFO pInfo)
+{
+    if (!pCodec->Ops.pfnScreenChange)
+        return VINF_SUCCESS;
+
+    return pCodec->Ops.pfnScreenChange(pCodec, pInfo);
 }
 
@@ -885,7 +1244,7 @@
 {
     Log3Func(("%RU64 -- tsLastWrittenMs=%RU64 + uDelayMs=%RU32\n",
-              msTimestamp, pCodec->State.tsLastWrittenMs,pCodec->Parms.Video.uDelayMs));
-
-    if (msTimestamp < pCodec->State.tsLastWrittenMs + pCodec->Parms.Video.uDelayMs)
+              msTimestamp, pCodec->State.tsLastWrittenMs,pCodec->Parms.u.Video.uDelayMs));
+
+    if (msTimestamp < pCodec->State.tsLastWrittenMs + pCodec->Parms.u.Video.uDelayMs)
         return 0; /* Too early for writing (respect set FPS). */
 
@@ -894,2 +1253,3 @@
     return pCodec->Parms.cbFrame;
 }
+
Index: /trunk/src/VBox/Main/src-client/RecordingInternals.cpp
===================================================================
--- /trunk/src/VBox/Main/src-client/RecordingInternals.cpp	(revision 105005)
+++ /trunk/src/VBox/Main/src-client/RecordingInternals.cpp	(revision 105006)
@@ -27,37 +27,129 @@
 
 #include "RecordingInternals.h"
+#include "RecordingUtils.h"
 
 #include <iprt/assert.h>
 #include <iprt/mem.h>
 
-#ifdef VBOX_WITH_AUDIO_RECORDING
-
-/**
- * Initializes a recording frame.
- *
+#ifdef DEBUG
+# include <math.h>
+# include <iprt/file.h>
+# include <iprt/formats/bmp.h>
+#endif
+
+#include "../src-client/Remotery.h"
+
+
+/*********************************************************************************************************************************
+*   Prototypes                                                                                                                   *
+*********************************************************************************************************************************/
+DECLINLINE(int) recordingVideoFrameInit(PRECORDINGVIDEOFRAME pFrame, uint32_t fFlags, uint32_t uWidth, uint32_t uHeight, uint32_t uPosX, uint32_t uPosY,
+                                        uint8_t uBPP, RECORDINGPIXELFMT enmFmt);
+
+
+/**
+ * Allocates an empty video frame, inline version.
+ *
+ * @returns Allocated video frame on success, or NULL on failure.
+ */
+DECLINLINE(PRECORDINGVIDEOFRAME) recordingVideoFrameAlloc(void)
+{
+    return (PRECORDINGVIDEOFRAME)RTMemAlloc(sizeof(RECORDINGVIDEOFRAME));
+}
+
+/**
+ * Allocates an empty video frame.
+ *
+ * @returns Allocated video frame on success, or NULL on failure.
+ */
+PRECORDINGVIDEOFRAME RecordingVideoFrameAlloc(void)
+{
+    PRECORDINGVIDEOFRAME pFrame = recordingVideoFrameAlloc();
+    AssertPtrReturn(pFrame, NULL);
+    RT_BZERO(pFrame, sizeof(RECORDINGVIDEOFRAME));
+    return pFrame;
+}
+
+/**
+ * Returns an allocated video frame from given image data.
+ *
+ * @returns Allocated video frame on success, or NULL on failure.
+ * @param   pvData              Pointer to image data to use.
+ * @param   x                   X location hint (in pixel) to use for allocated frame.
+ *                              This is *not* the offset within \a pvData!
+ * @param   y                   X location hint (in pixel) to use for allocated frame.
+ *                              This is *not* the offset within \a pvData!
+ * @param   w                   Width (in pixel) of \a pvData image data.
+ * @param   h                   Height (in pixel) of \a pvData image data.
+ * @param   uBPP                Bits per pixel) of \a pvData image data.
+ * @param   enmFmt              Pixel format of \a pvData image data.
+ */
+PRECORDINGVIDEOFRAME RecordingVideoFrameAllocEx(const void *pvData, uint32_t x, uint32_t y, uint32_t w, uint32_t h,
+                                                uint8_t uBPP, RECORDINGPIXELFMT enmFmt)
+{
+    PRECORDINGVIDEOFRAME pFrame = recordingVideoFrameAlloc();
+    AssertPtrReturn(pFrame, NULL);
+    int rc = recordingVideoFrameInit(pFrame, RECORDINGVIDEOFRAME_F_VISIBLE, w, h, x, y, uBPP, enmFmt);
+    AssertRCReturn(rc, NULL);
+    memcpy(pFrame->pau8Buf, pvData, pFrame->cbBuf);
+
+    return VINF_SUCCESS;
+}
+
+/**
+ * Frees a recording video frame.
+ *
+ * @param   pFrame              Pointer to video frame to free. The pointer will be invalid after return.
+ */
+void RecordingVideoFrameFree(PRECORDINGVIDEOFRAME pFrame)
+{
+    if (!pFrame)
+        return;
+
+    RecordingVideoFrameDestroy(pFrame);
+
+    RTMemFree(pFrame);
+}
+
+/**
+ * Initializes a recording frame, inline version.
+ *
+ * @returns VBox status code.
  * @param   pFrame              Pointer to video frame to initialize.
- * @param   w                   Width (in pixel) of video frame.
- * @param   h                   Height (in pixel) of video frame.
+ * @param   fFlags              Flags of type RECORDINGVIDEOFRAME_F_XXX.
+ * @param   uWidth              Width (in pixel) of video frame.
+ * @param   uHeight             Height (in pixel) of video frame.
+ * @param   uPosX               X positioning hint.
+ * @param   uPosY               Y positioning hint.
  * @param   uBPP                Bits per pixel (BPP).
- * @param   enmPixelFmt         Pixel format to use.
- */
-int RecordingVideoFrameInit(PRECORDINGVIDEOFRAME pFrame, int w, int h, uint8_t uBPP, RECORDINGPIXELFMT enmPixelFmt)
-{
+ * @param   enmFmt              Pixel format to use.
+ */
+DECLINLINE(int) recordingVideoFrameInit(PRECORDINGVIDEOFRAME pFrame, uint32_t fFlags, uint32_t uWidth, uint32_t uHeight,
+                                        uint32_t uPosX, uint32_t uPosY, uint8_t uBPP, RECORDINGPIXELFMT enmFmt)
+{
+    AssertPtrReturn(pFrame, VERR_INVALID_POINTER);
+    AssertReturn(uWidth, VERR_INVALID_PARAMETER);
+    AssertReturn(uHeight, VERR_INVALID_PARAMETER);
+    AssertReturn(uBPP && uBPP % 8 == 0, VERR_INVALID_PARAMETER);
+
     /* Calculate bytes per pixel and set pixel format. */
     const unsigned uBytesPerPixel = uBPP / 8;
-    const   size_t cbRGBBuf       = w * h * uBytesPerPixel;
+
+    /* Calculate bytes per pixel and set pixel format. */
+    const size_t cbRGBBuf = uWidth * uHeight * uBytesPerPixel;
     AssertReturn(cbRGBBuf, VERR_INVALID_PARAMETER);
 
-    pFrame->pu8RGBBuf = (uint8_t *)RTMemAlloc(cbRGBBuf);
-    AssertPtrReturn(pFrame->pu8RGBBuf, VERR_NO_MEMORY);
-    pFrame->cbRGBBuf    = cbRGBBuf;
-
-    pFrame->uX            = 0;
-    pFrame->uY            = 0;
-    pFrame->uWidth        = w;
-    pFrame->uHeight       = h;
-    pFrame->enmPixelFmt   = enmPixelFmt;
-    pFrame->uBPP          = uBPP;
-    pFrame->uBytesPerLine = w * uBytesPerPixel;
+    pFrame->pau8Buf = (uint8_t *)RTMemAlloc(cbRGBBuf);
+    AssertPtrReturn(pFrame->pau8Buf, VERR_NO_MEMORY);
+    pFrame->cbBuf  = cbRGBBuf;
+
+    pFrame->fFlags             = fFlags;
+    pFrame->Info.uWidth        = uWidth;
+    pFrame->Info.uHeight       = uHeight;
+    pFrame->Info.uBPP          = uBPP;
+    pFrame->Info.enmPixelFmt   = enmFmt;
+    pFrame->Info.uBytesPerLine = uWidth * uBytesPerPixel;
+    pFrame->Pos.x              = uPosX;
+    pFrame->Pos.y              = uPosY;
 
     return VINF_SUCCESS;
@@ -65,14 +157,288 @@
 
 /**
+ * Initializes a recording frame.
+ *
+ * @param   pFrame              Pointer to video frame to initialize.
+ * @param   fFlags              Flags of type RECORDINGVIDEOFRAME_F_XXX.
+ * @param   uWidth              Width (in pixel) of video frame.
+ * @param   uHeight             Height (in pixel) of video frame.
+ * @param   uPosX               X positioning hint.
+ * @param   uPosY               Y positioning hint.
+ * @param   uBPP                Bits per pixel (BPP).
+ * @param   enmFmt              Pixel format to use.
+ */
+int RecordingVideoFrameInit(PRECORDINGVIDEOFRAME pFrame, uint32_t fFlags, uint32_t uWidth, uint32_t uHeight, uint32_t uPosX, uint32_t uPosY,
+                            uint8_t uBPP, RECORDINGPIXELFMT enmFmt)
+{
+    return recordingVideoFrameInit(pFrame, fFlags, uWidth, uHeight, uPosX, uPosY, uBPP, enmFmt);
+}
+
+/**
+ * Destroys a recording video frame.
+ *
+ * @param   pFrame              Pointer to video frame to destroy.
+ */
+void RecordingVideoFrameDestroy(PRECORDINGVIDEOFRAME pFrame)
+{
+    if (!pFrame)
+        return;
+
+    if (pFrame->pau8Buf)
+    {
+        Assert(pFrame->cbBuf);
+        RTMemFree(pFrame->pau8Buf);
+        pFrame->pau8Buf = NULL;
+        pFrame->cbBuf  = 0;
+    }
+}
+
+/**
+ * Duplicates a video frame.
+ *
+ * @returns Pointer to duplicated frame on success, or NULL on failure.
+ * @param   pFrame              Video frame to duplicate.
+ */
+PRECORDINGVIDEOFRAME RecordingVideoFrameDup(PRECORDINGVIDEOFRAME pFrame)
+{
+    PRECORDINGVIDEOFRAME pFrameDup = (PRECORDINGVIDEOFRAME)RTMemDup(pFrame, sizeof(RECORDINGVIDEOFRAME));
+    AssertPtrReturn(pFrameDup, NULL);
+    pFrameDup->pau8Buf = (uint8_t *)RTMemDup(pFrame->pau8Buf, pFrame->cbBuf);
+    AssertPtrReturnStmt(pFrameDup, RTMemFree(pFrameDup), NULL);
+
+    return pFrameDup;
+}
+
+/**
+ * Clears the content of a video recording frame, inlined version.
+ *
+ * @param   pFrame              Video recording frame to clear content for.
+ */
+DECLINLINE(void) recordingVideoFrameClear(PRECORDINGVIDEOFRAME pFrame)
+{
+    RT_BZERO(pFrame->pau8Buf, pFrame->cbBuf);
+}
+
+/**
+ * Clears the content of a video recording frame.
+ *
+ * @param   pFrame              Video recording frame to clear content for.
+ */
+void RecordingVideoFrameClear(PRECORDINGVIDEOFRAME pFrame)
+{
+    recordingVideoFrameClear(pFrame);
+}
+
+/**
+ * Simple blitting function for raw image data, inlined version.
+ *
+ * @returns VBox status code.
+ * @param   pFrame              Destination frame.
+ * @param   uDstX               X destination (in pixel) within destination frame.
+ * @param   uDstY               Y destination (in pixel) within destination frame.
+ * @param   uDstBytesPerLine    Bytes per line in destination buffer.
+ * @param   uDstBPP             BPP of destination buffer.
+ * @param   enmDstFmt           Pixel format of source data. Must match \a pFrame.
+ * @param   pu8Src              Source data to blit. Must be in the same pixel format as \a pFrame.
+ * @param   cbSrc               Size (in bytes) of \a pu8Src.
+ * @param   uSrcX               X start (in pixel) within source data.
+ * @param   uSrcY               Y start (in pixel) within source data.
+ * @param   uSrcWidth           Width (in pixel) to blit from source data.
+ * @param   uSrcHeight          Height (in pixel) to blit from data.
+ * @param   uSrcBytesPerLine    Bytes per line in source data.
+ * @param   uSrcBPP             BPP of source data. Must match \a pFrame.
+ * @param   enmSrcFmt           Pixel format of source data. Must match \a pFrame.
+ */
+DECLINLINE(int) recordingVideoFrameBlitRaw(uint8_t *pu8Dst, size_t cbDst, uint32_t uDstX, uint32_t uDstY,
+                                           uint32_t uDstBytesPerLine, uint8_t uDstBPP, RECORDINGPIXELFMT enmDstFmt,
+                                           const uint8_t *pu8Src, size_t cbSrc, uint32_t uSrcX, uint32_t uSrcY, uint32_t uSrcWidth, uint32_t uSrcHeight,
+                                           uint32_t uSrcBytesPerLine, uint8_t uSrcBPP, RECORDINGPIXELFMT enmSrcFmt)
+{
+    RT_NOREF(enmDstFmt, enmSrcFmt);
+
+    uint8_t const uDstBytesPerPixel = uDstBPP / 8;
+    uint8_t const uSrcBytesPerPixel = uSrcBPP / 8;
+
+    size_t offSrc = RT_MIN(uSrcY * uSrcBytesPerLine + uSrcX * uSrcBytesPerPixel, cbSrc);
+    size_t offDst = RT_MIN(uDstY * uDstBytesPerLine + uDstX * uDstBytesPerPixel, cbDst);
+
+    for (uint32_t y = 0; y < uSrcHeight; y++)
+    {
+        size_t const cbToCopy = RT_MIN(cbDst - offDst,
+                                       RT_MIN(uSrcWidth * uSrcBytesPerPixel, cbSrc - offSrc));
+        if (!cbToCopy)
+            break;
+        memcpy(pu8Dst + offDst, (const uint8_t *)pu8Src + offSrc, cbToCopy);
+        offDst = RT_MIN(offDst + uDstBytesPerLine, cbDst);
+        Assert(offDst <= cbDst);
+        offSrc = RT_MIN(offSrc + uSrcBytesPerLine, cbSrc);
+        Assert(offSrc <= cbSrc);
+    }
+
+    return VINF_SUCCESS;
+}
+
+/**
+ * Simple blitting function for raw image data with alpha channel, inlined version.
+ *
+ * @returns VBox status code.
+ * @param   pFrame              Destination frame.
+ * @param   uDstX               X destination (in pixel) within destination frame.
+ * @param   uDstY               Y destination (in pixel) within destination frame.
+ * @param   pu8Src              Source data to blit. Must be in the same pixel format as \a pFrame.
+ * @param   cbSrc               Size (in bytes) of \a pu8Src.
+ * @param   uSrcX               X start (in pixel) within source data.
+ * @param   uSrcY               Y start (in pixel) within source data.
+ * @param   uSrcWidth           Width (in pixel) to blit from source data.
+ * @param   uSrcHeight          Height (in pixel) to blit from data.
+ * @param   uSrcBytesPerLine    Bytes per line in source data.
+ * @param   uSrcBPP             BPP of source data. Must match \a pFrame.
+ * @param   enmFmt              Pixel format of source data. Must match \a pFrame.
+ */
+DECLINLINE(int) recordingVideoFrameBlitRawAlpha(PRECORDINGVIDEOFRAME pFrame, uint32_t uDstX, uint32_t uDstY,
+                                                const uint8_t *pu8Src, size_t cbSrc, uint32_t uSrcX, uint32_t uSrcY, uint32_t uSrcWidth, uint32_t uSrcHeight,
+                                                uint32_t uSrcBytesPerLine, uint8_t uSrcBPP, RECORDINGPIXELFMT enmFmt)
+{
+    AssertReturn(pFrame->Info.enmPixelFmt == enmFmt, VERR_NOT_SUPPORTED);
+    AssertReturn(pFrame->Info.uBPP == uSrcBPP, VERR_NOT_SUPPORTED);
+
+    RT_NOREF(uDstX, uDstY, cbSrc, uSrcX, uSrcY, uSrcBytesPerLine);
+    uint8_t const uDstBytesPerPixel = pFrame->Info.uBPP / 8;
+    uint8_t const uSrcBytesPerPixel = uSrcBPP / 8;
+
+    for (uint32_t y = 0; y < uSrcHeight; y++)
+    {
+        size_t offSrc = RT_MIN((uSrcY + y) * uSrcBytesPerLine + uSrcX * uSrcBytesPerPixel, cbSrc);
+        size_t offDst = RT_MIN((uDstY + y) * pFrame->Info.uBytesPerLine + uDstX * uDstBytesPerPixel, pFrame->cbBuf);
+
+        for (uint32_t x = 0; x < uSrcWidth; x++)
+        {
+            /* BGRA */
+            int const idx_b = 0;
+            int const idx_g = 1;
+            int const idx_r = 2;
+            int const idx_a = 3;
+
+            unsigned int const alpha = pu8Src[offSrc + idx_a] + 1;
+            unsigned int const inv_alpha = 256 - pu8Src[offSrc + idx_a];
+            if (pu8Src[offSrc + idx_a])
+            {
+                pFrame->pau8Buf[offDst + idx_r] = (unsigned char)((alpha * pu8Src[offSrc + idx_r] + inv_alpha * pFrame->pau8Buf[offDst + idx_r]) >> 8);
+                pFrame->pau8Buf[offDst + idx_g] = (unsigned char)((alpha * pu8Src[offSrc + idx_g] + inv_alpha * pFrame->pau8Buf[offDst + idx_g]) >> 8);
+                pFrame->pau8Buf[offDst + idx_b] = (unsigned char)((alpha * pu8Src[offSrc + idx_b] + inv_alpha * pFrame->pau8Buf[offDst + idx_b]) >> 8);
+                pFrame->pau8Buf[offDst + idx_a] = 0xff;
+            }
+
+            offSrc = RT_MIN(offSrc + uSrcBytesPerPixel, cbSrc);
+            if (offSrc >= cbSrc)
+                break;
+            offDst = RT_MIN(offDst + uDstBytesPerPixel, pFrame->cbBuf);
+            if (offDst >= pFrame->cbBuf)
+                break;
+        }
+    }
+
+#if 0
+    RecordingUtilsDbgDumpImageData(pu8Src, cbSrc, "/tmp", "cursor-src", uSrcWidth, uSrcHeight, uSrcBytesPerLine, 32);
+    RecordingUtilsDbgDumpVideoFrameEx(pFrame, "/tmp", "cursor-dst");
+#endif
+
+    return VINF_SUCCESS;
+}
+
+/**
+ * Simple blitting function for raw image data.
+ *
+ * @returns VBox status code.
+ * @param   pDstFrame           Destination frame.
+ * @param   uDstX               X destination (in pixel) within destination frame.
+ * @param   uDstY               Y destination (in pixel) within destination frame.
+ * @param   pu8Src              Source data to blit. Must be in the same pixel format as \a pFrame.
+ * @param   cbSrc               Size (in bytes) of \a pu8Src.
+ * @param   uSrcX               X start (in pixel) within source data.
+ * @param   uSrcY               Y start (in pixel) within source data.
+ * @param   uSrcWidth           Width (in pixel) to blit from source data.
+ * @param   uSrcHeight          Height (in pixel) to blit from data.
+ * @param   uSrcBytesPerLine    Bytes per line in source data.
+ * @param   uSrcBPP             BPP of source data. Must match \a pFrame.
+ * @param   enmFmt              Pixel format of source data. Must match \a pFrame.
+ */
+int RecordingVideoFrameBlitRaw(PRECORDINGVIDEOFRAME pDstFrame, uint32_t uDstX, uint32_t uDstY,
+                               const uint8_t *pu8Src, size_t cbSrc, uint32_t uSrcX, uint32_t uSrcY, uint32_t uSrcWidth, uint32_t uSrcHeight,
+                               uint32_t uSrcBytesPerLine, uint8_t uSrcBPP, RECORDINGPIXELFMT enmFmt)
+{
+    return recordingVideoFrameBlitRaw(/* Destination */
+                                      pDstFrame->pau8Buf, pDstFrame->cbBuf, uDstX, uDstY,
+                                      pDstFrame->Info.uBytesPerLine, pDstFrame->Info.uBPP, pDstFrame->Info.enmPixelFmt,
+                                      /* Source */
+                                      pu8Src, cbSrc, uSrcX, uSrcY, uSrcWidth, uSrcHeight, uSrcBytesPerLine, uSrcBPP, enmFmt);
+}
+
+/**
+ * Simple blitting function for raw image data with alpha channel.
+ *
+ * @returns VBox status code.
+ * @param   pFrame              Destination frame.
+ * @param   uDstX               X destination (in pixel) within destination frame.
+ * @param   uDstY               Y destination (in pixel) within destination frame.
+ * @param   pu8Src              Source data to blit. Must be in the same pixel format as \a pFrame.
+ * @param   cbSrc               Size (in bytes) of \a pu8Src.
+ * @param   uSrcX               X start (in pixel) within source data.
+ * @param   uSrcY               Y start (in pixel) within source data.
+ * @param   uSrcWidth           Width (in pixel) to blit from source data.
+ * @param   uSrcHeight          Height (in pixel) to blit from data.
+ * @param   uSrcBytesPerLine    Bytes per line in source data.
+ * @param   uSrcBPP             BPP of source data. Must match \a pFrame.
+ * @param   enmFmt              Pixel format of source data. Must match \a pFrame.
+ */
+int RecordingVideoFrameBlitRawAlpha(PRECORDINGVIDEOFRAME pFrame, uint32_t uDstX, uint32_t uDstY,
+                                    const uint8_t *pu8Src, size_t cbSrc, uint32_t uSrcX, uint32_t uSrcY, uint32_t uSrcWidth, uint32_t uSrcHeight,
+                                    uint32_t uSrcBytesPerLine, uint8_t uSrcBPP, RECORDINGPIXELFMT enmFmt)
+{
+    return recordingVideoFrameBlitRawAlpha(pFrame, uDstX, uDstY,
+                                           pu8Src, cbSrc, uSrcX, uSrcY, uSrcWidth, uSrcHeight, uSrcBytesPerLine, uSrcBPP, enmFmt);
+}
+
+/**
+ * Simple blitting function for video frames.
+ *
+ * @returns VBox status code.
+ * @param   pDstFrame           Destination frame.
+ * @param   uDstX               X destination (in pixel) within destination frame.
+ * @param   uDstY               Y destination (in pixel) within destination frame.
+ * @param   pSrcFrame           Source frame.
+ * @param   uSrcX               X start (in pixel) within source frame.
+ * @param   uSrcY               Y start (in pixel) within source frame.
+ * @param   uSrcWidth           Width (in pixel) to blit from source frame.
+ * @param   uSrcHeight          Height (in pixel) to blit from frame.
+ *
+ * @note    Does NOT check for limits, so use with care!
+ */
+int RecordingVideoFrameBlitFrame(PRECORDINGVIDEOFRAME pDstFrame, uint32_t uDstX, uint32_t uDstY,
+                                 PRECORDINGVIDEOFRAME pSrcFrame, uint32_t uSrcX, uint32_t uSrcY, uint32_t uSrcWidth, uint32_t uSrcHeight)
+{
+    return recordingVideoFrameBlitRaw(/* Dest */
+                                      pDstFrame->pau8Buf, pDstFrame->cbBuf, uDstX, uDstY,
+                                      pDstFrame->Info.uBytesPerLine, pDstFrame->Info.uBPP, pDstFrame->Info.enmPixelFmt,
+                                      /* Source */
+                                      pSrcFrame->pau8Buf, pSrcFrame->cbBuf, uSrcX, uSrcY, uSrcWidth, uSrcHeight,
+                                      pSrcFrame->Info.uBytesPerLine, pSrcFrame->Info.uBPP, pSrcFrame->Info.enmPixelFmt);
+}
+
+#ifdef VBOX_WITH_AUDIO_RECORDING
+/**
  * Destroys a recording audio frame.
  *
  * @param   pFrame              Pointer to audio frame to destroy.
  */
-static void recordingAudioFrameDestroy(PRECORDINGAUDIOFRAME pFrame)
-{
+DECLINLINE(void) recordingAudioFrameDestroy(PRECORDINGAUDIOFRAME pFrame)
+{
+    if (!pFrame)
+        return;
+
     if (pFrame->pvBuf)
     {
         Assert(pFrame->cbBuf);
         RTMemFree(pFrame->pvBuf);
+        pFrame->pvBuf = NULL;
         pFrame->cbBuf = 0;
     }
@@ -94,41 +460,11 @@
     pFrame = NULL;
 }
-
 #endif /* VBOX_WITH_AUDIO_RECORDING */
 
 /**
- * Destroys a recording video frame.
- *
- * @param   pFrame              Pointer to video frame to destroy.
- */
-void RecordingVideoFrameDestroy(PRECORDINGVIDEOFRAME pFrame)
-{
-    if (pFrame->pu8RGBBuf)
-    {
-        Assert(pFrame->cbRGBBuf);
-        RTMemFree(pFrame->pu8RGBBuf);
-        pFrame->cbRGBBuf = 0;
-    }
-}
-
-/**
- * Frees a recording video frame.
- *
- * @param   pFrame              Pointer to video frame to free. The pointer will be invalid after return.
- */
-void RecordingVideoFrameFree(PRECORDINGVIDEOFRAME pFrame)
-{
-    if (!pFrame)
-        return;
-
-    RecordingVideoFrameDestroy(pFrame);
-
-    RTMemFree(pFrame);
-}
-
-/**
  * Frees a recording frame.
  *
- * @param   pFrame              Pointer to recording frame to free. The pointer will be invalid after return.
+ * @param   pFrame              Pointer to recording frame to free.
+ *                              The pointer will be invalid after return.
  */
 void RecordingFrameFree(PRECORDINGFRAME pFrame)
@@ -141,13 +477,17 @@
 #ifdef VBOX_WITH_AUDIO_RECORDING
         case RECORDINGFRAME_TYPE_AUDIO:
-            recordingAudioFrameDestroy(&pFrame->Audio);
+            recordingAudioFrameDestroy(&pFrame->u.Audio);
             break;
 #endif
         case RECORDINGFRAME_TYPE_VIDEO:
-            RecordingVideoFrameDestroy(&pFrame->Video);
+            RecordingVideoFrameDestroy(&pFrame->u.Video);
             break;
 
+        case RECORDINGFRAME_TYPE_CURSOR_SHAPE:
+            RecordingVideoFrameDestroy(&pFrame->u.CursorShape);
+            break;
+
         default:
-            AssertFailed();
+            /* Nothing to do here. */
             break;
     }
Index: /trunk/src/VBox/Main/src-client/RecordingStream.cpp
===================================================================
--- /trunk/src/VBox/Main/src-client/RecordingStream.cpp	(revision 105005)
+++ /trunk/src/VBox/Main/src-client/RecordingStream.cpp	(revision 105006)
@@ -259,13 +259,15 @@
 /**
  * Processes a recording stream.
+ *
  * This function takes care of the actual encoding and writing of a certain stream.
  * As this can be very CPU intensive, this function usually is called from a separate thread.
  *
  * @returns VBox status code.
- * @param   mapBlocksCommon     Map of common block to process for this stream.
+ * @param   streamBlocks        Block set of stream to process.
+ * @param   commonBlocks        Block set of common blocks to process for this stream.
  *
  * @note    Runs in recording thread.
  */
-int RecordingStream::Process(RecordingBlockMap &mapBlocksCommon)
+int RecordingStream::process(const RecordingBlockSet &streamBlocks, RecordingBlockMap &commonBlocks)
 {
     LogFlowFuncEnter();
@@ -281,47 +283,48 @@
     int vrc = VINF_SUCCESS;
 
-    RecordingBlockMap::iterator itStreamBlocks = m_Blocks.Map.begin();
-    while (itStreamBlocks != m_Blocks.Map.end())
-    {
-        uint64_t const   msTimestamp = itStreamBlocks->first;
-        RecordingBlocks *pBlocks     = itStreamBlocks->second;
+    RecordingBlockMap::const_iterator itStreamBlock = streamBlocks.Map.begin();
+    while (itStreamBlock != streamBlocks.Map.end())
+    {
+        uint64_t const   msTimestamp = itStreamBlock->first; RT_NOREF(msTimestamp);
+        RecordingBlocks *pBlocks     = itStreamBlock->second;
 
         AssertPtr(pBlocks);
 
-        while (!pBlocks->List.empty())
+        RecordingBlockList::const_iterator itBlockInList = pBlocks->List.cbegin();
+        while (itBlockInList != pBlocks->List.cend())
         {
-            RecordingBlock *pBlock = pBlocks->List.front();
-            AssertPtr(pBlock);
-
-            switch (pBlock->enmType)
-            {
-                case RECORDINGBLOCKTYPE_VIDEO:
-                {
-                    RECORDINGFRAME Frame;
-                    Frame.VideoPtr    = (PRECORDINGVIDEOFRAME)pBlock->pvData;
-                    Frame.msTimestamp = msTimestamp;
-
-                    int vrc2 = recordingCodecEncode(&m_CodecVideo, &Frame, NULL, NULL);
+            PRECORDINGFRAME pFrame = (PRECORDINGFRAME)(*itBlockInList)->pvData;
+            AssertPtr(pFrame);
+            Assert(pFrame->msTimestamp == msTimestamp);
+
+            switch (pFrame->enmType)
+            {
+                case RECORDINGFRAME_TYPE_VIDEO:
+                    RT_FALL_THROUGH();
+                case RECORDINGFRAME_TYPE_CURSOR_SHAPE:
+                    RT_FALL_THROUGH();
+                case RECORDINGFRAME_TYPE_CURSOR_POS:
+                {
+                    int vrc2 = recordingCodecEncodeFrame(&m_CodecVideo, pFrame, pFrame->msTimestamp, m_pCtx /* pvUser */);
                     AssertRC(vrc2);
                     if (RT_SUCCESS(vrc))
                         vrc = vrc2;
-
                     break;
                 }
 
+                case RECORDINGFRAME_TYPE_SCREEN_CHANGE:
+                {
+                    /* ignore rc */ recordingCodecScreenChange(&m_CodecVideo, &pFrame->u.ScreenInfo);
+                    break;
+                }
+
                 default:
-                    /* Note: Audio data already is encoded. */
                     break;
             }
 
-            pBlocks->List.pop_front();
-            delete pBlock;
+            ++itBlockInList;
         }
 
-        Assert(pBlocks->List.empty());
-        delete pBlocks;
-
-        m_Blocks.Map.erase(itStreamBlocks);
-        itStreamBlocks = m_Blocks.Map.begin();
+        ++itStreamBlock;
     }
 
@@ -332,60 +335,49 @@
         /* As each (enabled) screen has to get the same audio data, look for common (audio) data which needs to be
          * written to the screen's assigned recording stream. */
-        RecordingBlockMap::iterator itCommonBlocks = mapBlocksCommon.begin();
-        while (itCommonBlocks != mapBlocksCommon.end())
+        RecordingBlockMap::const_iterator itBlockMap = commonBlocks.begin();
+        while (itBlockMap != commonBlocks.end())
         {
-            RecordingBlockList::iterator itBlock = itCommonBlocks->second->List.begin();
-            while (itBlock != itCommonBlocks->second->List.end())
-            {
-                RecordingBlock *pBlockCommon = (RecordingBlock *)(*itBlock);
-                switch (pBlockCommon->enmType)
-                {
-                    case RECORDINGBLOCKTYPE_AUDIO:
-                    {
-                        PRECORDINGAUDIOFRAME pAudioFrame = (PRECORDINGAUDIOFRAME)pBlockCommon->pvData;
-                        AssertPtr(pAudioFrame);
-                        AssertPtr(pAudioFrame->pvBuf);
-                        Assert(pAudioFrame->cbBuf);
-
-                        AssertPtr(this->File.m_pWEBM);
-                        int vrc2 = this->File.m_pWEBM->WriteBlock(m_uTrackAudio, pAudioFrame->pvBuf, pAudioFrame->cbBuf, pBlockCommon->msTimestamp, pBlockCommon->uFlags);
-                        AssertRC(vrc2);
-                        if (RT_SUCCESS(vrc))
-                            vrc = vrc2;
-                        break;
-                    }
-
-                    default:
-                        AssertFailed();
-                        break;
-                }
-
-                Assert(pBlockCommon->cRefs);
-                pBlockCommon->cRefs--;
-                if (pBlockCommon->cRefs == 0)
-                {
-                    itCommonBlocks->second->List.erase(itBlock);
-                    delete pBlockCommon;
-                    itBlock = itCommonBlocks->second->List.begin();
+            RecordingBlockList &blockList = itBlockMap->second->List;
+
+            RecordingBlockList::iterator itBlockList = blockList.begin();
+            while (itBlockList != blockList.end())
+            {
+                RecordingBlock *pBlock = (RecordingBlock *)(*itBlockList);
+
+                PRECORDINGFRAME      pFrame      = (PRECORDINGFRAME)pBlock->pvData;
+                Assert(pFrame->enmType == RECORDINGFRAME_TYPE_AUDIO);
+                PRECORDINGAUDIOFRAME pAudioFrame = &pFrame->u.Audio;
+
+                int vrc2 = this->File.m_pWEBM->WriteBlock(m_uTrackAudio, pAudioFrame->pvBuf, pAudioFrame->cbBuf, pBlock->msTimestamp, pBlock->uFlags);
+                if (RT_SUCCESS(vrc))
+                    vrc = vrc2;
+
+                Log3Func(("RECORDINGFRAME_TYPE_AUDIO: %zu bytes -> %Rrc\n", pAudioFrame->cbBuf, vrc2));
+
+                Assert(pBlock->cRefs);
+                pBlock->cRefs--;
+                if (pBlock->cRefs == 0)
+                {
+                    blockList.erase(itBlockList);
+                    delete pBlock;
+                    itBlockList = blockList.begin();
                 }
                 else
-                    ++itBlock;
-            }
-
-            /* If no entries are left over in the block map, remove it altogether. */
-            if (itCommonBlocks->second->List.empty())
-            {
-                delete itCommonBlocks->second;
-                mapBlocksCommon.erase(itCommonBlocks);
-                itCommonBlocks = mapBlocksCommon.begin();
+                    ++itBlockList;
+            }
+
+            /* If no entries are left over in the block list, remove it altogether. */
+            if (blockList.empty())
+            {
+                delete itBlockMap->second;
+                commonBlocks.erase(itBlockMap);
+                itBlockMap = commonBlocks.begin();
             }
             else
-                ++itCommonBlocks;
-
-            LogFunc(("Common blocks: %zu\n", mapBlocksCommon.size()));
+                ++itBlockMap;
         }
     }
 #else
-    RT_NOREF(mapBlocksCommon);
+    RT_NOREF(commonBlocks);
 #endif /* VBOX_WITH_AUDIO_RECORDING */
 
@@ -393,4 +385,114 @@
 
     LogFlowFuncLeaveRC(vrc);
+    return vrc;
+}
+
+/**
+ * The stream's main routine called from the encoding thread.
+ *
+ * @returns VBox status code.
+ * @param   rcWait              Result of the encoding thread's wait operation.
+ *                              Can be used for figuring out if the encoder has to perform some
+ *                              worked based on that result.
+ * @param   commonBlocks        Common blocks multiplexed to all recording streams.
+ *
+ * @note    Runs in encoding thread.
+ */
+int RecordingStream::ThreadMain(int rcWait, RecordingBlockMap &commonBlocks)
+{
+    Log3Func(("rcWait=%Rrc\n", rcWait));
+
+    /* No new data arrived within time? Feed the encoder with the last frame we built.
+     *
+     * This is necessary in order to render a video which has a consistent time line,
+     * as we only encode data when something has changed ("dirty areas"). */
+    if (   rcWait == VERR_TIMEOUT
+        && m_ScreenSettings.isFeatureEnabled(RecordingFeature_Video))
+    {
+        return recordingCodecEncodeCurrent(&m_CodecVideo, m_pCtx->GetCurrentPTS());
+    }
+
+    int vrc = process(m_Blocks, commonBlocks);
+
+    /*
+     * Housekeeping.
+     *
+     * Here we delete all processed stream blocks of this stream.
+     * The common blocks will be deleted by the recording context (which owns those).
+     */
+    lock();
+
+    RecordingBlockMap::iterator itStreamBlocks = m_Blocks.Map.begin();
+    while (itStreamBlocks != m_Blocks.Map.end())
+    {
+        RecordingBlocks *pBlocks = itStreamBlocks->second;
+        AssertPtr(pBlocks);
+        pBlocks->Clear();
+        Assert(pBlocks->List.empty());
+        delete pBlocks;
+
+        m_Blocks.Map.erase(itStreamBlocks);
+        itStreamBlocks = m_Blocks.Map.begin();
+    }
+    Assert(m_Blocks.Map.empty());
+
+    unlock();
+
+    return vrc;
+}
+
+/**
+ * Adds a recording frame to be fed to the encoder.
+ *
+ * @returns VBox status code.
+ * @param   pFrame              Recording frame to add.
+ *                              Ownership of the frame will be transferred to the encoder on success then.
+ *                              Must be free'd by the caller on failure.
+ * @param   msTimestamp         Timestamp (PTS, in ms).
+ *
+ * @note    Caller needs to take the stream's lock.
+ */
+int RecordingStream::addFrame(PRECORDINGFRAME pFrame, uint64_t msTimestamp)
+{
+    int vrc;
+
+    Assert(pFrame->msTimestamp == msTimestamp); /* Sanity. */
+
+    try
+    {
+        RecordingBlock *pBlock = new RecordingBlock();
+
+        pBlock->pvData = pFrame;
+        pBlock->cbData = sizeof(RECORDINGFRAME);
+
+        try
+        {
+            RecordingBlocks *pRecordingBlocks;
+            RecordingBlockMap::const_iterator it = m_Blocks.Map.find(msTimestamp);
+            if (it == m_Blocks.Map.end())
+            {
+                pRecordingBlocks = new RecordingBlocks();
+                pRecordingBlocks->List.push_back(pBlock);
+                m_Blocks.Map.insert(std::make_pair(msTimestamp, pRecordingBlocks));
+            }
+            else
+            {
+                pRecordingBlocks = it->second;
+                pRecordingBlocks->List.push_back(pBlock);
+            }
+
+            vrc = VINF_SUCCESS;
+        }
+        catch (const std::exception &)
+        {
+            delete pBlock;
+            vrc = VERR_NO_MEMORY;
+        }
+    }
+    catch (const std::exception &)
+    {
+        vrc = VERR_NO_MEMORY;
+    }
+
     return vrc;
 }
@@ -407,7 +509,4 @@
 {
     AssertPtrReturn(m_pCtx, VERR_WRONG_ORDER);
-    AssertReturn(NeedsUpdate(msTimestamp), VINF_RECORDING_THROTTLED); /* We ASSUME that the caller checked that first. */
-
-    Log3Func(("cbData=%zu, msTimestamp=%RU64\n", cbData, msTimestamp));
 
     /* As audio data is common across all streams, re-route this to the recording context, where
@@ -417,4 +516,92 @@
 
 /**
+ * Sends a cursor position change to the recording stream.
+ *
+ * @returns VBox status code.
+ * @param   idCursor            Cursor ID. Currently unused and always set to 0.
+ * @param   pPos                Cursor information to send.
+ * @param   msTimestamp         Timestamp (PTS, in ms).
+ */
+int RecordingStream::SendCursorPos(uint8_t idCursor, PRECORDINGPOS pPos, uint64_t msTimestamp)
+{
+    RT_NOREF(idCursor);
+    AssertPtrReturn(pPos, VERR_INVALID_POINTER);
+
+    lock();
+
+    int vrc = iterateInternal(msTimestamp);
+    if (vrc != VINF_SUCCESS) /* Can return VINF_RECORDING_LIMIT_REACHED. */
+    {
+        unlock();
+        return vrc;
+    }
+
+    PRECORDINGFRAME pFrame = (PRECORDINGFRAME)RTMemAlloc(sizeof(RECORDINGFRAME));
+    AssertPtrReturn(pFrame, VERR_NO_MEMORY);
+    pFrame->enmType     = RECORDINGFRAME_TYPE_CURSOR_POS;
+    pFrame->msTimestamp = msTimestamp;
+
+    pFrame->u.Cursor.Pos = *pPos;
+
+    vrc = addFrame(pFrame, msTimestamp);
+
+    unlock();
+
+    return vrc;
+}
+
+/**
+ * Sends a cursor shape change to the recording stream.
+ *
+ * @returns VBox status code.
+ * @param   idCursor            Cursor ID. Currently unused and always set to 0.
+ * @param   pShape              Cursor shape to send.
+ * @param   msTimestamp         Timestamp (PTS, in ms).
+ *
+ * @note    Keep it as simple as possible, as this function might run on EMT.
+ * @thread  EMT
+ */
+int RecordingStream::SendCursorShape(uint8_t idCursor, PRECORDINGVIDEOFRAME pShape, uint64_t msTimestamp)
+{
+    RT_NOREF(idCursor);
+    AssertPtrReturn(pShape, VERR_INVALID_POINTER);
+    AssertPtrReturn(m_pCtx, VERR_WRONG_ORDER);
+
+    lock();
+
+    int vrc = iterateInternal(msTimestamp);
+    if (vrc != VINF_SUCCESS) /* Can return VINF_RECORDING_LIMIT_REACHED. */
+    {
+        unlock();
+        return vrc;
+    }
+
+    PRECORDINGFRAME pFrame = (PRECORDINGFRAME)RTMemAlloc(sizeof(RECORDINGFRAME));
+    AssertPtrReturn(pFrame, VERR_NO_MEMORY);
+
+    pFrame->u.Video = *pShape;
+    /* Make a deep copy of the pixel data. */
+    pFrame->u.Video.pau8Buf = (uint8_t *)RTMemDup(pShape->pau8Buf, pShape->cbBuf);
+    AssertPtrReturnStmt(pFrame->u.Video.pau8Buf, RTMemFree(pFrame), VERR_NO_MEMORY);
+    pFrame->u.Video.cbBuf   = pShape->cbBuf;
+
+    pFrame->enmType     = RECORDINGFRAME_TYPE_CURSOR_SHAPE;
+    pFrame->msTimestamp = msTimestamp;
+
+    vrc = addFrame(pFrame, msTimestamp);
+
+    if (RT_FAILURE(vrc))
+    {
+        RecordingVideoFrameDestroy(&pFrame->u.Video);
+        RecordingFrameFree(pFrame);
+    }
+
+    unlock();
+
+    LogFlowFuncLeaveRC(vrc);
+    return vrc;
+}
+
+/**
  * Sends a raw (e.g. not yet encoded) video frame to the recording stream.
  *
@@ -422,27 +609,16 @@
  * @retval  VINF_RECORDING_LIMIT_REACHED if the stream's recording limit has been reached.
  * @retval  VINF_RECORDING_THROTTLED if the frame is too early for the current FPS setting.
- * @param   x                   Upper left (X) coordinate where the video frame starts.
- * @param   y                   Upper left (Y) coordinate where the video frame starts.
- * @param   uPixelFormat        Pixel format of the video frame.
- * @param   uBPP                Bits per pixel (BPP) of the video frame.
- * @param   uBytesPerLine       Bytes per line  of the video frame.
- * @param   uSrcWidth           Width (in pixels) of the video frame.
- * @param   uSrcHeight          Height (in pixels) of the video frame.
- * @param   puSrcData           Actual pixel data of the video frame.
+ * @param   pVideoFrame         Video frame to send.
  * @param   msTimestamp         Timestamp (PTS, in ms).
- */
-int RecordingStream::SendVideoFrame(uint32_t x, uint32_t y, uint32_t uPixelFormat, uint32_t uBPP, uint32_t uBytesPerLine,
-                                    uint32_t uSrcWidth, uint32_t uSrcHeight, uint8_t *puSrcData, uint64_t msTimestamp)
-{
+ *
+ * @note    Keep it as simple as possible, as this function might run on EMT.
+ * @thread  EMT
+ */
+int RecordingStream::SendVideoFrame(PRECORDINGVIDEOFRAME pVideoFrame, uint64_t msTimestamp)
+{
+    AssertPtrReturn(pVideoFrame, VERR_INVALID_POINTER);
     AssertPtrReturn(m_pCtx, VERR_WRONG_ORDER);
 
-    if (RT_UNLIKELY(!NeedsUpdate(msTimestamp)))
-        return VINF_RECORDING_THROTTLED;
-
     lock();
-
-    Log3Func(("[%RU32 %RU32 %RU32 %RU32] msTimestamp=%RU64\n", x , y, uSrcWidth, uSrcHeight, msTimestamp));
-
-    PRECORDINGVIDEOFRAME pFrame = NULL;
 
     int vrc = iterateInternal(msTimestamp);
@@ -453,194 +629,61 @@
     }
 
-    do
-    {
-        int xDiff = ((int)m_ScreenSettings.Video.ulWidth - (int)uSrcWidth) / 2;
-        uint32_t w = uSrcWidth;
-        if ((int)w + xDiff + (int)x <= 0)  /* Nothing visible. */
-        {
-            vrc = VERR_INVALID_PARAMETER;
-            break;
-        }
-
-        uint32_t destX;
-        if ((int)x < -xDiff)
-        {
-            w += xDiff + x;
-            x = -xDiff;
-            destX = 0;
-        }
-        else
-            destX = x + xDiff;
-
-        uint32_t h = uSrcHeight;
-        int yDiff = ((int)m_ScreenSettings.Video.ulHeight - (int)uSrcHeight) / 2;
-        if ((int)h + yDiff + (int)y <= 0)  /* Nothing visible. */
-        {
-            vrc = VERR_INVALID_PARAMETER;
-            break;
-        }
-
-        uint32_t destY;
-        if ((int)y < -yDiff)
-        {
-            h += yDiff + (int)y;
-            y = -yDiff;
-            destY = 0;
-        }
-        else
-            destY = y + yDiff;
-
-        if (   destX > m_ScreenSettings.Video.ulWidth
-            || destY > m_ScreenSettings.Video.ulHeight)
-        {
-            vrc = VERR_INVALID_PARAMETER;  /* Nothing visible. */
-            break;
-        }
-
-        if (destX + w > m_ScreenSettings.Video.ulWidth)
-            w = m_ScreenSettings.Video.ulWidth - destX;
-
-        if (destY + h > m_ScreenSettings.Video.ulHeight)
-            h = m_ScreenSettings.Video.ulHeight - destY;
-
-        pFrame = (PRECORDINGVIDEOFRAME)RTMemAllocZ(sizeof(RECORDINGVIDEOFRAME));
-        AssertBreakStmt(pFrame, vrc = VERR_NO_MEMORY);
-
-        /* Calculate bytes per pixel and set pixel format. */
-        const unsigned uBytesPerPixel = uBPP / 8;
-        if (uPixelFormat == BitmapFormat_BGR)
-        {
-            switch (uBPP)
-            {
-                case 32:
-                    pFrame->enmPixelFmt = RECORDINGPIXELFMT_RGB32;
-                    break;
-                case 24:
-                    pFrame->enmPixelFmt = RECORDINGPIXELFMT_RGB24;
-                    break;
-                case 16:
-                    pFrame->enmPixelFmt = RECORDINGPIXELFMT_RGB565;
-                    break;
-                default:
-                    AssertMsgFailedBreakStmt(("Unknown color depth (%RU32)\n", uBPP), vrc = VERR_NOT_SUPPORTED);
-                    break;
-            }
-        }
-        else
-            AssertMsgFailedBreakStmt(("Unknown pixel format (%RU32)\n", uPixelFormat), vrc = VERR_NOT_SUPPORTED);
-
-        const size_t cbRGBBuf =   m_ScreenSettings.Video.ulWidth
-                                * m_ScreenSettings.Video.ulHeight
-                                * uBytesPerPixel;
-        AssertBreakStmt(cbRGBBuf, vrc = VERR_INVALID_PARAMETER);
-
-        pFrame->pu8RGBBuf = (uint8_t *)RTMemAlloc(cbRGBBuf);
-        AssertBreakStmt(pFrame->pu8RGBBuf, vrc = VERR_NO_MEMORY);
-        pFrame->cbRGBBuf  = cbRGBBuf;
-        pFrame->uWidth    = uSrcWidth;
-        pFrame->uHeight   = uSrcHeight;
-
-        /* If the current video frame is smaller than video resolution we're going to encode,
-         * clear the frame beforehand to prevent artifacts. */
-        if (   uSrcWidth  < m_ScreenSettings.Video.ulWidth
-            || uSrcHeight < m_ScreenSettings.Video.ulHeight)
-        {
-            RT_BZERO(pFrame->pu8RGBBuf, pFrame->cbRGBBuf);
-        }
-
-        /* Calculate start offset in source and destination buffers. */
-        uint32_t offSrc = y * uBytesPerLine + x * uBytesPerPixel;
-        uint32_t offDst = (destY * m_ScreenSettings.Video.ulWidth + destX) * uBytesPerPixel;
-
-#ifdef VBOX_RECORDING_DUMP
-        BMPFILEHDR fileHdr;
-        RT_ZERO(fileHdr);
-
-        BMPWIN3XINFOHDR coreHdr;
-        RT_ZERO(coreHdr);
-
-        fileHdr.uType       = BMP_HDR_MAGIC;
-        fileHdr.cbFileSize = (uint32_t)(sizeof(BMPFILEHDR) + sizeof(BMPWIN3XINFOHDR) + (w * h * uBytesPerPixel));
-        fileHdr.offBits    = (uint32_t)(sizeof(BMPFILEHDR) + sizeof(BMPWIN3XINFOHDR));
-
-        coreHdr.cbSize         = sizeof(BMPWIN3XINFOHDR);
-        coreHdr.uWidth         = w;
-        coreHdr.uHeight        = h;
-        coreHdr.cPlanes        = 1;
-        coreHdr.cBits          = uBPP;
-        coreHdr.uXPelsPerMeter = 5000;
-        coreHdr.uYPelsPerMeter = 5000;
-
-        char szFileName[RTPATH_MAX];
-        RTStrPrintf2(szFileName, sizeof(szFileName), "/tmp/VideoRecFrame-%RU32.bmp", m_uScreenID);
-
-        RTFILE fh;
-        int vrc2 = RTFileOpen(&fh, szFileName,
-                              RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
-        if (RT_SUCCESS(vrc2))
-        {
-            RTFileWrite(fh, &fileHdr,    sizeof(fileHdr),    NULL);
-            RTFileWrite(fh, &coreHdr, sizeof(coreHdr), NULL);
-        }
-#endif
-        Assert(pFrame->cbRGBBuf >= w * h * uBytesPerPixel);
-
-        /* Do the copy. */
-        for (unsigned int i = 0; i < h; i++)
-        {
-            /* Overflow check. */
-            Assert(offSrc + w * uBytesPerPixel <= uSrcHeight * uBytesPerLine);
-            Assert(offDst + w * uBytesPerPixel <= m_ScreenSettings.Video.ulHeight * m_ScreenSettings.Video.ulWidth * uBytesPerPixel);
-
-            memcpy(pFrame->pu8RGBBuf + offDst, puSrcData + offSrc, w * uBytesPerPixel);
-
-#ifdef VBOX_RECORDING_DUMP
-            if (RT_SUCCESS(rc2))
-                RTFileWrite(fh, pFrame->pu8RGBBuf + offDst, w * uBytesPerPixel, NULL);
-#endif
-            offSrc += uBytesPerLine;
-            offDst += m_ScreenSettings.Video.ulWidth * uBytesPerPixel;
-        }
-
-#ifdef VBOX_RECORDING_DUMP
-        if (RT_SUCCESS(vrc2))
-            RTFileClose(fh);
-#endif
-
-    } while (0);
-
-    if (vrc == VINF_SUCCESS) /* Note: Also could be VINF_TRY_AGAIN. */
-    {
-        RecordingBlock *pBlock = new RecordingBlock();
-        if (pBlock)
-        {
-            AssertPtr(pFrame);
-
-            pBlock->enmType = RECORDINGBLOCKTYPE_VIDEO;
-            pBlock->pvData  = pFrame;
-            pBlock->cbData  = sizeof(RECORDINGVIDEOFRAME) + pFrame->cbRGBBuf;
-
-            try
-            {
-                RecordingBlocks *pRecordingBlocks = new RecordingBlocks();
-                pRecordingBlocks->List.push_back(pBlock);
-
-                Assert(m_Blocks.Map.find(msTimestamp) == m_Blocks.Map.end());
-                m_Blocks.Map.insert(std::make_pair(msTimestamp, pRecordingBlocks));
-            }
-            catch (const std::exception &ex)
-            {
-                RT_NOREF(ex);
-
-                delete pBlock;
-                vrc = VERR_NO_MEMORY;
-            }
-        }
-        else
-            vrc = VERR_NO_MEMORY;
-    }
+    PRECORDINGFRAME pFrame = (PRECORDINGFRAME)RTMemAlloc(sizeof(RECORDINGFRAME));
+    AssertPtrReturn(pFrame, VERR_NO_MEMORY);
+
+    pFrame->u.Video = *pVideoFrame;
+    /* Make a deep copy of the pixel data. */
+    pFrame->u.Video.pau8Buf = (uint8_t *)RTMemDup(pVideoFrame->pau8Buf, pVideoFrame->cbBuf);
+    AssertPtrReturnStmt(pFrame->u.Video.pau8Buf, RTMemFree(pFrame), VERR_NO_MEMORY);
+    pFrame->u.Video.cbBuf   = pVideoFrame->cbBuf;
+
+    pFrame->enmType     = RECORDINGFRAME_TYPE_VIDEO;
+    pFrame->msTimestamp = msTimestamp;
+
+    vrc = addFrame(pFrame, msTimestamp);
 
     if (RT_FAILURE(vrc))
-        RecordingVideoFrameFree(pFrame);
+    {
+        RecordingVideoFrameDestroy(&pFrame->u.Video);
+        RecordingFrameFree(pFrame);
+    }
+
+    unlock();
+
+    LogFlowFuncLeaveRC(vrc);
+    return vrc;
+}
+
+/**
+ * Sends a screen size change to a recording stream.
+ *
+ * @returns VBox status code.
+ * @param   pInfo               Recording screen info to use.
+ * @param   msTimestamp         Timestamp (PTS, in ms).
+ * @param   fForce              Set to \c true to force a change, otherwise to \c false.
+ */
+int RecordingStream::SendScreenChange(PRECORDINGSURFACEINFO pInfo, uint64_t msTimestamp, bool fForce /* = false */)
+{
+    AssertPtrReturn(pInfo, VERR_INVALID_POINTER);
+
+    if (   !pInfo->uWidth
+        || !pInfo->uHeight)
+        return VINF_SUCCESS;
+
+    RT_NOREF(fForce);
+
+    LogRel(("Recording: Size of screen #%RU32 changed to %RU32x%RU32 (%RU8 BPP)\n",
+            m_uScreenID, pInfo->uWidth, pInfo->uHeight, pInfo->uBPP));
+
+    lock();
+
+    PRECORDINGFRAME pFrame = (PRECORDINGFRAME)RTMemAlloc(sizeof(RECORDINGFRAME));
+    AssertPtrReturn(pFrame, VERR_NO_MEMORY);
+    pFrame->enmType      = RECORDINGFRAME_TYPE_SCREEN_CHANGE;
+    pFrame->msTimestamp  = msTimestamp;
+
+    pFrame->u.ScreenInfo = *pInfo;
+
+    int vrc = addFrame(pFrame, msTimestamp);
 
     unlock();
@@ -795,5 +838,5 @@
         m_enmState  = RECORDINGSTREAMSTATE_INITIALIZED;
         m_fEnabled  = true;
-        m_tsStartMs = RTTimeProgramMilliTS();
+        m_tsStartMs = RTTimeMilliTS();
 
         return VINF_SUCCESS;
@@ -817,4 +860,6 @@
 {
     int vrc = VINF_SUCCESS;
+
+    /* ignore rc */ recordingCodecFinalize(&m_CodecVideo);
 
     switch (m_ScreenSettings.enmDest)
@@ -918,9 +963,5 @@
 
     if (m_ScreenSettings.isFeatureEnabled(RecordingFeature_Video))
-    {
-        vrc = recordingCodecFinalize(&m_CodecVideo);
-        if (RT_SUCCESS(vrc))
-            vrc = recordingCodecDestroy(&m_CodecVideo);
-    }
+        vrc = recordingCodecDestroy(&m_CodecVideo);
 
     if (RT_SUCCESS(vrc))
@@ -953,5 +994,5 @@
 
     WebMWriter::WebMBlockFlags blockFlags = VBOX_WEBM_BLOCK_FLAG_NONE;
-    if (RT_LIKELY(uFlags != RECORDINGCODEC_ENC_F_NONE))
+    if (RT_LIKELY(uFlags == RECORDINGCODEC_ENC_F_NONE))
     {
         /* All set. */
@@ -966,6 +1007,6 @@
 
     return this->File.m_pWEBM->WriteBlock(  pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO
-                                        ? m_uTrackAudio : m_uTrackVideo,
-                                        pvData, cbData, msAbsPTS, blockFlags);
+                                          ? m_uTrackAudio : m_uTrackVideo,
+                                          pvData, cbData, msAbsPTS, blockFlags);
 }
 
@@ -1012,7 +1053,16 @@
     Callbacks.pfnWriteData = RecordingStream::codecWriteDataCallback;
 
-    int vrc = recordingCodecCreateVideo(pCodec, screenSettings.Video.enmCodec);
+    RECORDINGSURFACEINFO ScreenInfo;
+    ScreenInfo.uWidth  = screenSettings.Video.ulWidth;
+    ScreenInfo.uHeight = screenSettings.Video.ulHeight;
+    ScreenInfo.uBPP    = 32; /* We always start with 32 bit. */
+
+    int vrc = SendScreenChange(&ScreenInfo, true /* fForce */);
     if (RT_SUCCESS(vrc))
-        vrc = recordingCodecInit(pCodec, &Callbacks, screenSettings);
+    {
+        vrc = recordingCodecCreateVideo(pCodec, screenSettings.Video.enmCodec);
+        if (RT_SUCCESS(vrc))
+            vrc = recordingCodecInit(pCodec, &Callbacks, screenSettings);
+    }
 
     if (RT_FAILURE(vrc))
Index: /trunk/src/VBox/Main/src-client/RecordingUtils.cpp
===================================================================
--- /trunk/src/VBox/Main/src-client/RecordingUtils.cpp	(revision 105005)
+++ /trunk/src/VBox/Main/src-client/RecordingUtils.cpp	(revision 105006)
@@ -42,4 +42,6 @@
 #endif
 
+#include <VBox/log.h>
+
 
 /**
@@ -60,56 +62,49 @@
     RT_NOREF(aDstWidth, aDstHeight);
 
-    AssertReturn(!(aSrcWidth & 1),  false);
-    AssertReturn(!(aSrcHeight & 1), false);
-
-    bool fRc = true;
-    T iter1(aSrcWidth, aSrcHeight, aSrcBuf);
-    T iter2 = iter1;
-    iter2.skip(aSrcWidth);
-    unsigned cPixels = aSrcWidth * aSrcHeight;
-    unsigned offY = 0;
-    unsigned offU = cPixels;
-    unsigned offV = cPixels + cPixels / 4;
-    unsigned const cyHalf = aSrcHeight / 2;
-    unsigned const cxHalf = aSrcWidth  / 2;
-    for (unsigned i = 0; i < cyHalf && fRc; ++i)
-    {
-        for (unsigned j = 0; j < cxHalf; ++j)
-        {
-            unsigned red, green, blue;
-            fRc = iter1.getRGB(&red, &green, &blue);
-            AssertReturn(fRc, false);
-            aDstBuf[offY] = ((66 * red + 129 * green + 25 * blue + 128) >> 8) + 16;
-            unsigned u = (((-38 * red - 74 * green + 112 * blue + 128) >> 8) + 128) / 4;
-            unsigned v = (((112 * red - 94 * green -  18 * blue + 128) >> 8) + 128) / 4;
-
-            fRc = iter1.getRGB(&red, &green, &blue);
-            AssertReturn(fRc, false);
-            aDstBuf[offY + 1] = ((66 * red + 129 * green + 25 * blue + 128) >> 8) + 16;
-            u += (((-38 * red - 74 * green + 112 * blue + 128) >> 8) + 128) / 4;
-            v += (((112 * red - 94 * green -  18 * blue + 128) >> 8) + 128) / 4;
-
-            fRc = iter2.getRGB(&red, &green, &blue);
-            AssertReturn(fRc, false);
-            aDstBuf[offY + aSrcWidth] = ((66 * red + 129 * green + 25 * blue + 128) >> 8) + 16;
-            u += (((-38 * red - 74 * green + 112 * blue + 128) >> 8) + 128) / 4;
-            v += (((112 * red - 94 * green -  18 * blue + 128) >> 8) + 128) / 4;
-
-            fRc = iter2.getRGB(&red, &green, &blue);
-            AssertReturn(fRc, false);
-            aDstBuf[offY + aSrcWidth + 1] = ((66 * red + 129 * green + 25 * blue + 128) >> 8) + 16;
-            u += (((-38 * red - 74 * green + 112 * blue + 128) >> 8) + 128) / 4;
-            v += (((112 * red - 94 * green -  18 * blue + 128) >> 8) + 128) / 4;
-
-            aDstBuf[offU] = u;
-            aDstBuf[offV] = v;
-            offY += 2;
-            ++offU;
-            ++offV;
-        }
-
-        iter1.skip(aSrcWidth);
-        iter2.skip(aSrcWidth);
-        offY += aSrcWidth;
+    size_t image_size = aSrcWidth * aSrcHeight;
+    size_t upos = image_size;
+    size_t vpos = upos + upos / 4;
+    size_t i = 0;
+
+#define CALC_Y(r, g, b) \
+    ((66 * r + 129 * g + 25 * b) >> 8) + 16
+#define CALC_U(r, g, b) \
+    ((-38 * r + -74 * g + 112 * b) >> 8) + 128
+#define CALC_V(r, g, b) \
+    ((112 * r + -94 * g + -18 * b) >> 8) + 128
+
+    for (size_t line = 0; line < aSrcHeight; ++line)
+    {
+        if ((line % 2) == 0)
+        {
+            for (size_t x = 0; x < aSrcWidth; x += 2)
+            {
+                uint8_t b = aSrcBuf[4 * i];
+                uint8_t g = aSrcBuf[4 * i + 1];
+                uint8_t r = aSrcBuf[4 * i + 2];
+
+                aDstBuf[i++] = CALC_Y(r, g, b);
+
+                aDstBuf[upos++] = CALC_U(r, g, b);
+                aDstBuf[vpos++] = CALC_V(r, g, b);
+
+                b = aSrcBuf[4 * i];
+                g = aSrcBuf[4 * i + 1];
+                r = aSrcBuf[4 * i + 2];
+
+                aDstBuf[i++] = CALC_Y(r, g, b);
+            }
+        }
+        else
+        {
+            for (size_t x = 0; x < aSrcWidth; x++)
+            {
+                uint8_t b = aSrcBuf[4 * i];
+                uint8_t g = aSrcBuf[4 * i + 1];
+                uint8_t r = aSrcBuf[4 * i + 2];
+
+                aDstBuf[i++] = CALC_Y(r, g, b);
+            }
+        }
     }
 
@@ -152,60 +147,239 @@
 
 /**
- * Converts a RGB to YUV buffer.
- *
- * @returns IPRT status code.
- * @param   enmPixelFormat      Pixel format to use for conversion.
+ * Converts an entire RGB BGRA32 buffer to a YUV I420 buffer.
+ *
  * @param   paDst               Pointer to destination buffer.
- * @param   uDstWidth           Width (X, in pixels) of destination buffer.
- * @param   uDstHeight          Height (Y, in pixels) of destination buffer.
+ * @param   uDstWidth           Width (X, in pixel) of destination buffer.
+ * @param   uDstHeight          Height (Y, in pixel) of destination buffer.
  * @param   paSrc               Pointer to source buffer.
- * @param   uSrcWidth           Width (X, in pixels) of source buffer.
- * @param   uSrcHeight          Height (Y, in pixels) of source buffer.
- */
-int RecordingUtilsRGBToYUV(RECORDINGPIXELFMT enmPixelFormat,
-                           uint8_t *paDst, uint32_t uDstWidth, uint32_t uDstHeight,
-                           uint8_t *paSrc, uint32_t uSrcWidth, uint32_t uSrcHeight)
-{
-    switch (enmPixelFormat)
-    {
-        case RECORDINGPIXELFMT_RGB32:
-            if (!recordingUtilsColorConvWriteYUV420p<ColorConvBGRA32Iter>(paDst, uDstWidth, uDstHeight,
-                                                                          paSrc, uSrcWidth, uSrcHeight))
-                return VERR_INVALID_PARAMETER;
-            break;
-        case RECORDINGPIXELFMT_RGB24:
-            if (!recordingUtilsColorConvWriteYUV420p<ColorConvBGR24Iter>(paDst, uDstWidth, uDstHeight,
-                                                                         paSrc, uSrcWidth, uSrcHeight))
-                return VERR_INVALID_PARAMETER;
-            break;
-        case RECORDINGPIXELFMT_RGB565:
-            if (!recordingUtilsColorConvWriteYUV420p<ColorConvBGR565Iter>(paDst, uDstWidth, uDstHeight,
-                                                                          paSrc, uSrcWidth, uSrcHeight))
-                return VERR_INVALID_PARAMETER;
-            break;
-        default:
-            AssertFailed();
-            return VERR_NOT_SUPPORTED;
-    }
-    return VINF_SUCCESS;
+ * @param   uSrcWidth           Width (X, in pixel) of source buffer.
+ * @param   uSrcHeight          Height (Y, in pixel) of source buffer.
+ */
+void RecordingUtilsConvBGRA32ToYUVI420(uint8_t *paDst, uint32_t uDstWidth, uint32_t uDstHeight,
+                                       uint8_t *paSrc, uint32_t uSrcWidth, uint32_t uSrcHeight)
+{
+    RT_NOREF(uDstWidth, uDstHeight);
+
+    size_t const image_size = uSrcWidth * uSrcHeight;
+    size_t upos = image_size;
+    size_t vpos = upos + upos / 4;
+    size_t i = 0;
+
+#define CALC_Y(r, g, b) \
+    ((66 * r + 129 * g + 25 * b) >> 8) + 16
+#define CALC_U(r, g, b) \
+    ((-38 * r + -74 * g + 112 * b) >> 8) + 128
+#define CALC_V(r, g, b) \
+    ((112 * r + -94 * g + -18 * b) >> 8) + 128
+
+    for (size_t y = 0; y < uSrcHeight; y++)
+    {
+        if ((y % 2) == 0)
+        {
+            for (size_t x = 0; x < uSrcWidth; x += 2)
+            {
+                uint8_t b = paSrc[4 * i];
+                uint8_t g = paSrc[4 * i + 1];
+                uint8_t r = paSrc[4 * i + 2];
+
+                paDst[i++] = CALC_Y(r, g, b);
+
+                paDst[upos++] = CALC_U(r, g, b);
+                paDst[vpos++] = CALC_V(r, g, b);
+
+                b = paSrc[4 * i];
+                g = paSrc[4 * i + 1];
+                r = paSrc[4 * i + 2];
+
+                paDst[i++] = CALC_Y(r, g, b);
+            }
+        }
+        else
+        {
+            for (size_t x = 0; x < uSrcWidth; x++)
+            {
+                uint8_t const b = paSrc[4 * i];
+                uint8_t const g = paSrc[4 * i + 1];
+                uint8_t const r = paSrc[4 * i + 2];
+
+                paDst[i++] = CALC_Y(r, g, b);
+            }
+        }
+    }
+
+#undef CALC_Y
+#undef CALC_U
+#undef CALC_V
+}
+
+/**
+ * Converts a part of a RGB BGRA32 buffer to a YUV I420 buffer.
+ *
+ * @param   paDst               Pointer to destination buffer.
+ * @param   uDstX               X destination position (in pixel).
+ * @param   uDstY               Y destination position (in pixel).
+ * @param   uDstWidth           Width (X, in pixel) of destination buffer.
+ * @param   uDstHeight          Height (Y, in pixel) of destination buffer.
+ * @param   paSrc               Pointer to source buffer.
+ * @param   uSrcX               X source position (in pixel).
+ * @param   uSrcY               Y source position (in pixel).
+ * @param   uSrcWidth           Width (X, in pixel) of source buffer.
+ * @param   uSrcHeight          Height (Y, in pixel) of source buffer.
+ * @param   uSrcStride          Stride (in bytes) of source buffer.
+ * @param   uSrcBPP             Bits per pixel of source buffer.
+ */
+void RecordingUtilsConvBGRA32ToYUVI420Ex(uint8_t *paDst, uint32_t uDstX, uint32_t uDstY, uint32_t uDstWidth, uint32_t uDstHeight,
+                                         uint8_t *paSrc, uint32_t uSrcX, uint32_t uSrcY, uint32_t uSrcWidth, uint32_t uSrcHeight,
+                                         uint32_t uSrcStride, uint8_t uSrcBPP)
+{
+    Assert(uDstX < uDstWidth);
+    Assert(uDstX + uSrcWidth <= uDstWidth);
+    Assert(uDstY < uDstHeight);
+    Assert(uDstY + uSrcHeight <= uDstHeight);
+    Assert(uSrcBPP % 8 == 0);
+
+    RT_NOREF(uSrcHeight, uDstHeight);
+
+#define CALC_Y(r, g, b) \
+    (66 * r + 129 * g + 25 * b) >> 8
+#define CALC_U(r, g, b) \
+    ((-38 * r + -74 * g + 112 * b) >> 8) + 128
+#define CALC_V(r, g, b) \
+    ((112 * r + -94 * g + -18 * b) >> 8) + 128
+
+    uint32_t uDstXCur = uDstX;
+
+    const unsigned uSrcBytesPerPixel = uSrcBPP / 8;
+
+    size_t offSrc = (uSrcY * uSrcStride) + (uSrcX * uSrcBytesPerPixel);
+
+    for (size_t y = 0; y < uSrcHeight; y++)
+    {
+        for (size_t x = 0; x < uSrcWidth; x++)
+        {
+            size_t const offBGR = offSrc + (x * uSrcBytesPerPixel);
+
+            uint8_t const b = paSrc[offBGR];
+            uint8_t const g = paSrc[offBGR + 1];
+            uint8_t const r = paSrc[offBGR + 2];
+
+            size_t const offY  = uDstY * uDstWidth + uDstXCur;
+            size_t const offUV = (uDstY / 2) * (uDstWidth / 2) + (uDstXCur / 2) + uDstWidth * uDstHeight;
+
+            paDst[offY]                               = CALC_Y(r, g, b);
+            paDst[offUV]                              = CALC_U(r, g, b);
+            paDst[offUV + uDstWidth * uDstHeight / 4] = CALC_V(r, g, b);
+
+            uDstXCur++;
+        }
+
+        offSrc += uSrcStride;
+
+        uDstXCur = uDstX;
+        uDstY++;
+    }
+
+#undef CALC_Y
+#undef CALC_U
+#undef CALC_V
+}
+
+/**
+ * Crops (centers) or centers coordinates of a given source.
+ *
+ * @returns VBox status code.
+ * @retval  VWRN_RECORDING_ENCODING_SKIPPED if the source is not visible.
+ * @param   pCodecParms         Video codec parameters to use for cropping / centering.
+ * @param   sx                  Input / output: X location (in pixel) of the source.
+ * @param   sy                  Input / output: Y location (in pixel) of the source.
+ * @param   sw                  Input / output: Width (in pixel) of the source.
+ * @param   sh                  Input / output: Height (in pixel) of the source.
+ * @param   dx                  Input / output: X location (in pixel) of the destination.
+ * @param   dy                  Input / output: Y location (in pixel) of the destination.
+ *
+ * @note    Used when no other scaling algorithm is being selected / available. See testcase.
+ */
+int RecordingUtilsCoordsCropCenter(PRECORDINGCODECPARMS pCodecParms,
+                                   int32_t *sx, int32_t *sy, int32_t *sw, int32_t *sh, int32_t *dx, int32_t *dy)
+{
+    int vrc = VINF_SUCCESS;
+
+    Log3Func(("Original: sx=%RI32 sy=%RI32 sw=%RI32 sh=%RI32 dx=%RI32 dy=%RI32\n",
+              *sx, *sy, *sw, *sh, *dx, *dy));
+
+    /*
+     * Do centered cropping or centering.
+     */
+
+    int32_t const uOriginX = (int32_t)pCodecParms->u.Video.Scaling.u.Crop.m_iOriginX;
+    int32_t const uOriginY = (int32_t)pCodecParms->u.Video.Scaling.u.Crop.m_iOriginY;
+
+    /* Apply cropping / centering values. */
+    *dx += uOriginX;
+    *dy += uOriginY;
+
+    if (*dx < 0)
+    {
+        *dx  = 0;
+        *sx += RT_ABS(uOriginX);
+        *sw -= RT_ABS(uOriginX);
+    }
+
+    if (*dy < 0)
+    {
+        *dy  = 0;
+        *sy += RT_ABS(uOriginY);
+        *sh -= RT_ABS(uOriginY);
+    }
+
+    Log3Func(("Crop0: sx=%RI32 sy=%RI32 sw=%RI32 sh=%RI32 dx=%RI32 dy=%RI32\n",
+              *sx, *sy, *sw, *sh, *dx, *dy));
+
+    if (*sw > (int32_t)pCodecParms->u.Video.uWidth)
+        *sw = (int32_t)pCodecParms->u.Video.uWidth;
+
+    if (*sh > (int32_t)pCodecParms->u.Video.uHeight)
+        *sh = pCodecParms->u.Video.uHeight;
+
+    if (*dx + *sw >= (int32_t)pCodecParms->u.Video.uWidth)
+        *sw = pCodecParms->u.Video.uWidth - *dx;
+
+    if (*dy + *sh >= (int32_t)pCodecParms->u.Video.uHeight)
+        *sh = pCodecParms->u.Video.uHeight - *dy;
+
+    if (   *dx + *sw < 1
+        || *dy + *sh < 1
+        || *dx > (int32_t)pCodecParms->u.Video.uWidth
+        || *dy > (int32_t)pCodecParms->u.Video.uHeight
+        || *sw < 1
+        || *sh < 1)
+    {
+        vrc = VWRN_RECORDING_ENCODING_SKIPPED; /* Not visible, skip encoding. */
+    }
+
+    Log3Func(("Crop1: sx=%RI32 sy=%RI32 sw=%RI32 sh=%RI32 dx=%RI32 dy=%RI32 -> vrc=%Rrc\n",
+              *sx, *sy, *sw, *sh, *dx, *dy, vrc));
+
+    return vrc;
 }
 
 #ifdef DEBUG
 /**
- * Dumps a video recording frame to a bitmap (BMP) file, extended version.
+ * Dumps image data to a bitmap (BMP) file.
  *
  * @returns VBox status code.
- * @param   pu8RGBBuf           Pointer to actual RGB frame data.
+ * @param   pu8RGBBuf           Pointer to actual RGB image data.
  * @param   cbRGBBuf            Size (in bytes) of \a pu8RGBBuf.
  * @param   pszPath             Absolute path to dump file to. Must exist.
- *                              Specify NULL to use the system's temp directory.
- *                              Existing frame files will be overwritten.
- * @param   pszPrefx            Naming prefix to use. Optional and can be NULL.
+ *                              Specify NULL to use the system's temp directory as output directory.
+ *                              Existing files will be overwritten.
+ * @param   pszWhat             Hint of what to dump. Optional and can be NULL.
  * @param   uWidth              Width (in pixel) to write.
  * @param   uHeight             Height (in pixel) to write.
+ * @param   uBytsPerLine        Bytes per line.
  * @param   uBPP                Bits in pixel.
  */
-int RecordingUtilsDbgDumpFrameEx(const uint8_t *pu8RGBBuf, size_t cbRGBBuf, const char *pszPath, const char *pszPrefx,
-                                 uint16_t uWidth, uint32_t uHeight, uint8_t uBPP)
+int RecordingUtilsDbgDumpImageData(const uint8_t *pu8RGBBuf, size_t cbRGBBuf, const char *pszPath, const char *pszWhat,
+                                   uint32_t uWidth, uint32_t uHeight, uint32_t uBytesPerLine, uint8_t uBPP)
 {
     RT_NOREF(cbRGBBuf);
@@ -228,5 +402,5 @@
 
     coreHdr.cbSize         = sizeof(BMPWIN3XINFOHDR);
-    coreHdr.uWidth         = uWidth ;
+    coreHdr.uWidth         = uWidth;
     coreHdr.uHeight        = uHeight;
     coreHdr.cPlanes        = 1;
@@ -237,14 +411,17 @@
     static uint64_t s_iCount = 0;
 
+    /* Hardcoded path for now. */
     char szPath[RTPATH_MAX];
     if (!pszPath)
-        RTPathTemp(szPath, sizeof(szPath));
+    {
+        int rc2 = RTPathTemp(szPath, sizeof(szPath));
+        if (RT_FAILURE(rc2))
+            return rc2;
+    }
 
     char szFileName[RTPATH_MAX];
     if (RTStrPrintf2(szFileName, sizeof(szFileName), "%s/RecDump-%04RU64-%s-w%RU16h%RU16.bmp",
-                     pszPath ? pszPath : szPath, s_iCount, pszPrefx ? pszPrefx : "Frame", uWidth, uHeight) <= 0)
-    {
+                     pszPath ? pszPath : szPath, s_iCount, pszWhat ? pszWhat : "Frame", uWidth, uHeight) <= 0)
         return VERR_BUFFER_OVERFLOW;
-    }
 
     s_iCount++;
@@ -259,11 +436,11 @@
 
         /* Bitmaps (DIBs) are stored upside-down (thanks, OS/2), so work from the bottom up. */
-        uint32_t offSrc = (uHeight * uWidth * uBytesPerPixel) - uWidth * uBytesPerPixel;
+        uint32_t offSrc = cbRGBBuf - uBytesPerLine;
 
         /* Do the copy. */
         for (unsigned int i = 0; i < uHeight; i++)
         {
-            RTFileWrite(fh, pu8RGBBuf + offSrc, uWidth * uBytesPerPixel, NULL);
-            offSrc -= uWidth * uBytesPerPixel;
+            RTFileWrite(fh, pu8RGBBuf + offSrc, uBytesPerLine, NULL);
+            offSrc -= uBytesPerLine;
         }
 
@@ -275,15 +452,28 @@
 
 /**
- * Dumps a video recording frame to a bitmap (BMP) file.
+ * Dumps a video recording frame to a bitmap (BMP) file, extended version.
  *
  * @returns VBox status code.
  * @param   pFrame              Video frame to dump.
- */
-int RecordingUtilsDbgDumpFrame(const PRECORDINGFRAME pFrame)
-{
-    AssertReturn(pFrame->enmType == RECORDINGFRAME_TYPE_VIDEO, VERR_INVALID_PARAMETER);
-    return RecordingUtilsDbgDumpFrameEx(pFrame->Video.pu8RGBBuf, pFrame->Video.cbRGBBuf,
-                                        NULL /*  Use temp directory */, NULL /* pszPrefix */,
-                                        pFrame->Video.uWidth, pFrame->Video.uHeight, pFrame->Video.uBPP);
+ * @param   pszPath             Output directory.
+ * @param   pszWhat             Hint of what to dump. Optional and can be NULL.
+ */
+int RecordingUtilsDbgDumpVideoFrameEx(const PRECORDINGVIDEOFRAME pFrame, const char *pszPath, const char *pszWhat)
+{
+    return RecordingUtilsDbgDumpImageData(pFrame->pau8Buf, pFrame->cbBuf,
+                                          pszPath, pszWhat,
+                                          pFrame->Info.uWidth, pFrame->Info.uHeight, pFrame->Info.uBytesPerLine, pFrame->Info.uBPP);
+}
+
+/**
+ * Dumps a video recording frame to a bitmap (BMP) file.
+ *
+ * @returns VBox status code.
+ * @param   pFrame              Video frame to dump.
+ * @param   pszWhat             Hint of what to dump. Optional and can be NULL.
+ */
+int RecordingUtilsDbgDumpVideoFrame(const PRECORDINGVIDEOFRAME pFrame, const char *pszWhat)
+{
+    return RecordingUtilsDbgDumpVideoFrameEx(pFrame, NULL /* Use temp directory */, pszWhat);
 }
 #endif /* DEBUG */
Index: /trunk/src/VBox/Main/testcase/Makefile.kmk
===================================================================
--- /trunk/src/VBox/Main/testcase/Makefile.kmk	(revision 105005)
+++ /trunk/src/VBox/Main/testcase/Makefile.kmk	(revision 105006)
@@ -48,4 +48,5 @@
   	tstMediumLock \
   	tstSafeArray \
+  	tstRecording \
   	tstSettings \
   	tstBstr \
@@ -294,4 +295,15 @@
 tstSafeArray_TEMPLATE = VBoxMainClientTstExe
 tstSafeArray_SOURCES  = tstSafeArray.cpp
+
+
+#
+# tstRecording
+#
+tstRecording_TEMPLATE = VBoxMainClientTstExe
+tstRecording_SOURCES  = \
+	tstRecording.cpp \
+	../src-client/RecordingUtils.cpp
+tstRecording_INCS     = \
+	../include
 
 
Index: /trunk/src/VBox/Main/testcase/tstRecording.cpp
===================================================================
--- /trunk/src/VBox/Main/testcase/tstRecording.cpp	(revision 105006)
+++ /trunk/src/VBox/Main/testcase/tstRecording.cpp	(revision 105006)
@@ -0,0 +1,158 @@
+/* $Id$ */
+/** @file
+ * Recording testcases.
+ */
+
+/*
+ * Copyright (C) 2024 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * 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, in version 3 of the
+ * License.
+ *
+ * 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, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+*   Header Files                                                                                                                 *
+*********************************************************************************************************************************/
+#include <iprt/test.h>
+#include <VBox/err.h>
+
+#include "RecordingUtils.h"
+
+
+/** Tests centering / cropped centering of coordinates. */
+static void testCenteredCrop(RTTEST hTest)
+{
+    RTTestSub(hTest, "testCenteredCrop");
+
+    RECORDINGCODECPARMS Parms;
+
+    #define INIT_CROP(/* Framebuffer width / height */   a_fw, a_fh, \
+                      /* Video (codec) width / height */ a_vw, a_vh) \
+        Parms.u.Video.uWidth  = a_vw; \
+        Parms.u.Video.uHeight = a_vh; \
+        Parms.u.Video.Scaling.u.Crop.m_iOriginX = int32_t(Parms.u.Video.uWidth  - a_fw) / 2; \
+        Parms.u.Video.Scaling.u.Crop.m_iOriginY = int32_t(Parms.u.Video.uHeight - a_fh) / 2;
+
+    #define TEST_CROP(/* Source In  */ a_in_sx, a_in_sy, a_in_sw, a_in_sh, \
+                      /* Dest In    */ a_in_dx, a_in_dy, \
+                      /* Source Out */ a_out_sx, a_out_sy, a_out_sw, a_out_sh, \
+                      /* Dest Out   */ a_out_dx, a_out_dy, out_rc) \
+        { \
+            int32_t sx = a_in_sx; int32_t sy = a_in_sy; int32_t sw = a_in_sw; int32_t sh = a_in_sh; \
+            int32_t dx = a_in_dx; int32_t dy = a_in_dy; \
+            RTTEST_CHECK_RC (hTest, RecordingUtilsCoordsCropCenter(&Parms, &sx, &sy, &sw, &sh, &dx, &dy), out_rc); \
+            RTTEST_CHECK_MSG(hTest, sx  == a_out_sx, (hTest, "Expected a_out_sx == %RI16, but got %RI16\n", a_out_sx, sx)); \
+            RTTEST_CHECK_MSG(hTest, sy  == a_out_sy, (hTest, "Expected a_out_sy == %RI16, but got %RI16\n", a_out_sy, sy)); \
+            RTTEST_CHECK_MSG(hTest, sw  == a_out_sw, (hTest, "Expected a_out_sw == %RI16, but got %RI16\n", a_out_sw, sw)); \
+            RTTEST_CHECK_MSG(hTest, sh  == a_out_sh, (hTest, "Expected a_out_sh == %RI16, but got %RI16\n", a_out_sh, sh)); \
+            RTTEST_CHECK_MSG(hTest, dx  == a_out_dx, (hTest, "Expected a_out_dx == %RI16, but got %RI16\n", a_out_dx, dx)); \
+            RTTEST_CHECK_MSG(hTest, dy  == a_out_dy, (hTest, "Expected a_out_dy == %RI16, but got %RI16\n", a_out_dy, dy)); \
+        }
+
+    /*
+     * No center / cropping performed (framebuffer and video codec are of the same size).
+     */
+    INIT_CROP(1024, 768, 1024, 768);
+    TEST_CROP(0, 0, 1024, 768,
+              0, 0,
+              0, 0, 1024, 768,
+              0, 0, VINF_SUCCESS);
+    /* Source is bigger than allowed. */
+    TEST_CROP(0, 0, 2048, 1536,
+              0, 0,
+              0, 0, 1024, 768,
+              0, 0, VINF_SUCCESS);
+    /* Source is bigger than allowed. */
+    TEST_CROP(1024, 768, 2048, 1536,
+              0, 0,
+              1024, 768, 1024, 768,
+              0, 0, VINF_SUCCESS);
+    /* Check limits with custom destination. */
+    TEST_CROP(0, 0, 1024, 768,
+              512, 512,
+              0, 0, 512, 256,
+              512, 512, VINF_SUCCESS);
+    TEST_CROP(512, 512, 1024, 768,
+              512, 512,
+              512, 512, 512, 256,
+              512, 512, VINF_SUCCESS);
+    TEST_CROP(512, 512, 1024, 768,
+              1024, 768,
+              512, 512, 0, 0,
+              1024, 768, VWRN_RECORDING_ENCODING_SKIPPED);
+    TEST_CROP(1024, 768, 1024, 768,
+              1024, 768,
+              1024, 768, 0, 0,
+              1024, 768, VWRN_RECORDING_ENCODING_SKIPPED);
+
+    /*
+     * Framebuffer is twice the size of the video codec -- center crop the framebuffer.
+     */
+    INIT_CROP(2048, 1536, 1024, 768);
+    TEST_CROP(0, 0, 2048, 1536,
+              0, 0,
+              512, 384, 1024, 768,
+              0, 0, VINF_SUCCESS);
+
+    TEST_CROP(1024, 768, 1024, 768,
+              0, 0,
+              1536, 1152, 512, 384,
+              0, 0, VINF_SUCCESS);
+    /* Check limits with custom destination. */
+    TEST_CROP(1024, 768, 1024, 768,
+              512, 384,
+              1024, 768, 1024, 768,
+              0, 0, VINF_SUCCESS);
+    TEST_CROP(1024, 768, 1024, 768,
+              512 + 42, 384 + 42,
+              1024, 768, 1024 - 42, 768 - 42,
+              42, 42, VINF_SUCCESS);
+    TEST_CROP(1024, 768, 1024 * 2, 768 * 2,
+              512, 384,
+              1024, 768, 1024, 768,
+              0, 0, VINF_SUCCESS);
+
+    /*
+     * Framebuffer is half the size of the video codec -- center (but not crop) the framebuffer within the video output.
+     */
+    INIT_CROP(1024, 768, 2048, 1536);
+    TEST_CROP(0, 0, 1024, 768,
+              0, 0,
+              0, 0, 1024, 768,
+              512, 384, VINF_SUCCESS);
+
+#undef INIT_CROP
+#undef TEST_CROP
+}
+
+int main()
+{
+    RTTEST     hTest;
+    RTEXITCODE rcExit = RTTestInitAndCreate("tstRecording", &hTest);
+    if (rcExit == RTEXITCODE_SUCCESS)
+    {
+        RTTestBanner(hTest);
+
+        testCenteredCrop(hTest);
+
+        rcExit = RTTestSummaryAndDestroy(hTest);
+    }
+    return rcExit;
+}
+
