diff options
Diffstat (limited to 'src/VBox/Main/src-client/VideoRec.cpp')
-rw-r--r-- | src/VBox/Main/src-client/VideoRec.cpp | 885 |
1 files changed, 885 insertions, 0 deletions
diff --git a/src/VBox/Main/src-client/VideoRec.cpp b/src/VBox/Main/src-client/VideoRec.cpp new file mode 100644 index 00000000..f47527e4 --- /dev/null +++ b/src/VBox/Main/src-client/VideoRec.cpp @@ -0,0 +1,885 @@ +/* $Id: VideoRec.cpp $ */ +/** @file + * Encodes the screen content in VPX format. + */ + +/* + * Copyright (C) 2012-2013 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#define LOG_GROUP LOG_GROUP_MAIN +#include <VBox/log.h> +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/semaphore.h> +#include <iprt/thread.h> + +#include <VBox/com/VirtualBox.h> +#include <VBox/com/com.h> +#include <VBox/com/string.h> + +#include "EbmlWriter.h" +#include "VideoRec.h" + +#define VPX_CODEC_DISABLE_COMPAT 1 +#include <vpx/vp8cx.h> +#include <vpx/vpx_image.h> + +/** Default VPX codec to use */ +#define DEFAULTCODEC (vpx_codec_vp8_cx()) + +static int videoRecEncodeAndWrite(PVIDEORECSTREAM pStrm); +static int videoRecRGBToYUV(PVIDEORECSTREAM pStrm); + +/* state to synchronized between threads */ +enum +{ + VIDREC_UNINITIALIZED = 0, + /* initialized, idle */ + VIDREC_IDLE = 1, + /* currently in VideoRecCopyToIntBuf(), delay termination */ + VIDREC_COPYING = 2, + /* signal that we are terminating */ + VIDREC_TERMINATING = 3 +}; + +/* Must be always accessible and therefore cannot be part of VIDEORECCONTEXT */ +static uint32_t g_enmState = VIDREC_UNINITIALIZED; + + +typedef struct VIDEORECSTREAM +{ + /* container context */ + EbmlGlobal Ebml; + /* VPX codec context */ + vpx_codec_ctx_t VpxCodec; + /* VPX configuration */ + vpx_codec_enc_cfg_t VpxConfig; + /* X resolution */ + uint32_t uTargetWidth; + /* Y resolution */ + uint32_t uTargetHeight; + /* X resolution of the last encoded picture */ + uint32_t uLastSourceWidth; + /* Y resolution of the last encoded picture */ + uint32_t uLastSourceHeight; + /* current frame number */ + uint32_t cFrame; + /* RGB buffer containing the most recent frame of the framebuffer */ + uint8_t *pu8RgbBuf; + /* YUV buffer the encode function fetches the frame from */ + uint8_t *pu8YuvBuf; + /* VPX image context */ + vpx_image_t VpxRawImage; + /* true if video recording is enabled */ + bool fEnabled; + /* true if the RGB buffer is filled */ + bool fRgbFilled; + /* pixel format of the current frame */ + uint32_t u32PixelFormat; + /* minimal delay between two frames */ + uint32_t uDelay; + /* time stamp of the last frame we encoded */ + uint64_t u64LastTimeStamp; + /* time stamp of the current frame */ + uint64_t u64TimeStamp; +} VIDEORECSTREAM; + +typedef struct VIDEORECCONTEXT +{ + /* semaphore to signal the encoding worker thread */ + RTSEMEVENT WaitEvent; + /* semaphore required during termination */ + RTSEMEVENT TermEvent; + /* true if video recording is enabled */ + bool fEnabled; + /* worker thread */ + RTTHREAD Thread; + /* number of stream contexts */ + uint32_t cScreens; + /* video recording stream contexts */ + VIDEORECSTREAM Strm[1]; +} VIDEORECCONTEXT; + + +/** + * 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) + { + LogFlow(("width = %d height=%d aBuf=%lx\n", aWidth, aHeight, 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; +}; + +/** + * Convert an image to YUV420p format + * @returns true on success, false on failure + * @param aWidth width of image + * @param aHeight height of image + * @param aDestBuf an allocated memory buffer large enough to hold the + * destination image (i.e. width * height * 12bits) + * @param aSrcBuf the source image as an array of bytes + */ +template <class T> +inline bool colorConvWriteYUV420p(unsigned aWidth, unsigned aHeight, + uint8_t *aDestBuf, uint8_t *aSrcBuf) +{ + AssertReturn(0 == (aWidth & 1), false); + AssertReturn(0 == (aHeight & 1), false); + bool rc = true; + T iter1(aWidth, aHeight, aSrcBuf); + T iter2 = iter1; + iter2.skip(aWidth); + unsigned cPixels = aWidth * aHeight; + unsigned offY = 0; + unsigned offU = cPixels; + unsigned offV = cPixels + cPixels / 4; + for (unsigned i = 0; (i < aHeight / 2) && rc; ++i) + { + for (unsigned j = 0; (j < aWidth / 2) && rc; ++j) + { + unsigned red, green, blue, u, v; + rc = iter1.getRGB(&red, &green, &blue); + if (rc) + { + aDestBuf[offY] = ((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; + rc = iter1.getRGB(&red, &green, &blue); + } + if (rc) + { + aDestBuf[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; + rc = iter2.getRGB(&red, &green, &blue); + } + if (rc) + { + aDestBuf[offY + aWidth] = ((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; + rc = iter2.getRGB(&red, &green, &blue); + } + if (rc) + { + aDestBuf[offY + aWidth + 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; + aDestBuf[offU] = u; + aDestBuf[offV] = v; + offY += 2; + ++offU; + ++offV; + } + } + if (rc) + { + iter1.skip(aWidth); + iter2.skip(aWidth); + offY += aWidth; + } + } + return rc; +} + +/** + * Convert an image to RGB24 format + * @returns true on success, false on failure + * @param aWidth width of image + * @param aHeight height of image + * @param aDestBuf an allocated memory buffer large enough to hold the + * destination image (i.e. width * height * 12bits) + * @param aSrcBuf the source image as an array of bytes + */ +template <class T> +inline bool colorConvWriteRGB24(unsigned aWidth, unsigned aHeight, + uint8_t *aDestBuf, uint8_t *aSrcBuf) +{ + enum { PIX_SIZE = 3 }; + bool rc = true; + AssertReturn(0 == (aWidth & 1), false); + AssertReturn(0 == (aHeight & 1), false); + T iter(aWidth, aHeight, aSrcBuf); + unsigned cPixels = aWidth * aHeight; + for (unsigned i = 0; i < cPixels && rc; ++i) + { + unsigned red, green, blue; + rc = iter.getRGB(&red, &green, &blue); + if (rc) + { + aDestBuf[i * PIX_SIZE ] = red; + aDestBuf[i * PIX_SIZE + 1] = green; + aDestBuf[i * PIX_SIZE + 2] = blue; + } + } + return rc; +} + +/** + * Worker thread for all streams. + * + * RGB/YUV conversion and encoding. + */ +static DECLCALLBACK(int) videoRecThread(RTTHREAD Thread, void *pvUser) +{ + PVIDEORECCONTEXT pCtx = (PVIDEORECCONTEXT)pvUser; + for (;;) + { + int rc = RTSemEventWait(pCtx->WaitEvent, RT_INDEFINITE_WAIT); + AssertRCBreak(rc); + + if (ASMAtomicReadU32(&g_enmState) == VIDREC_TERMINATING) + break; + for (unsigned uScreen = 0; uScreen < pCtx->cScreens; uScreen++) + { + PVIDEORECSTREAM pStrm = &pCtx->Strm[uScreen]; + if ( pStrm->fEnabled + && ASMAtomicReadBool(&pStrm->fRgbFilled)) + { + rc = videoRecRGBToYUV(pStrm); + ASMAtomicWriteBool(&pStrm->fRgbFilled, false); + if (RT_SUCCESS(rc)) + rc = videoRecEncodeAndWrite(pStrm); + if (RT_FAILURE(rc)) + { + static unsigned cErrors = 100; + if (cErrors > 0) + { + LogRel(("Error %Rrc encoding / writing video frame\n", rc)); + cErrors--; + } + } + } + } + } + + return VINF_SUCCESS; +} + +/** + * VideoRec utility function to create video recording context. + * + * @returns IPRT status code. + * @param ppCtx Video recording context + * @param cScreens Number of screens. + */ +int VideoRecContextCreate(PVIDEORECCONTEXT *ppCtx, uint32_t cScreens) +{ + Assert(ASMAtomicReadU32(&g_enmState) == VIDREC_UNINITIALIZED); + + PVIDEORECCONTEXT pCtx = (PVIDEORECCONTEXT)RTMemAllocZ(RT_OFFSETOF(VIDEORECCONTEXT, Strm[cScreens])); + *ppCtx = pCtx; + AssertPtrReturn(pCtx, VERR_NO_MEMORY); + + pCtx->cScreens = cScreens; + for (unsigned uScreen = 0; uScreen < cScreens; uScreen++) + pCtx->Strm[uScreen].Ebml.last_pts_ms = -1; + + int rc = RTSemEventCreate(&pCtx->WaitEvent); + AssertRCReturn(rc, rc); + + rc = RTSemEventCreate(&pCtx->TermEvent); + AssertRCReturn(rc, rc); + + rc = RTThreadCreate(&pCtx->Thread, videoRecThread, (void*)pCtx, 0, + RTTHREADTYPE_MAIN_WORKER, RTTHREADFLAGS_WAITABLE, "VideoRec"); + AssertRCReturn(rc, rc); + + ASMAtomicWriteU32(&g_enmState, VIDREC_IDLE); + return VINF_SUCCESS; +} + +/** + * VideoRec utility function to initialize video recording context. + * + * @returns IPRT status code. + * @param pCtx Pointer to video recording context to initialize Framebuffer width. + * @param uScreeen Screen number. + * @param strFile File to save the recorded data + * @param uTargetWidth Width of the target image in the video recoriding file (movie) + * @param uTargetHeight Height of the target image in video recording file. + */ +int VideoRecStrmInit(PVIDEORECCONTEXT pCtx, uint32_t uScreen, const char *pszFile, + uint32_t uWidth, uint32_t uHeight, uint32_t uRate, uint32_t uFps) +{ + AssertPtrReturn(pCtx, VERR_INVALID_PARAMETER); + AssertReturn(uScreen < pCtx->cScreens, VERR_INVALID_PARAMETER); + + PVIDEORECSTREAM pStrm = &pCtx->Strm[uScreen]; + pStrm->uTargetWidth = uWidth; + pStrm->uTargetHeight = uHeight; + pStrm->pu8RgbBuf = (uint8_t *)RTMemAllocZ(uWidth * uHeight * 4); + AssertReturn(pStrm->pu8RgbBuf, VERR_NO_MEMORY); + + /* Play safe: the file must not exist, overwriting is potentially + * hazardous as nothing prevents the user from picking a file name of some + * other important file, causing unintentional data loss. */ + int rc = RTFileOpen(&pStrm->Ebml.file, pszFile, + RTFILE_O_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); + if (RT_FAILURE(rc)) + { + LogRel(("Failed to create the video capture output file \"%s\" (%Rrc)\n", pszFile, rc)); + return rc; + } + + vpx_codec_err_t rcv = vpx_codec_enc_config_default(DEFAULTCODEC, &pStrm->VpxConfig, 0); + if (rcv != VPX_CODEC_OK) + { + LogFlow(("Failed to configure codec\n", vpx_codec_err_to_string(rcv))); + return VERR_INVALID_PARAMETER; + } + + /* target bitrate in kilobits per second */ + pStrm->VpxConfig.rc_target_bitrate = uRate; + /* frame width */ + pStrm->VpxConfig.g_w = uWidth; + /* frame height */ + pStrm->VpxConfig.g_h = uHeight; + /* 1ms per frame */ + pStrm->VpxConfig.g_timebase.num = 1; + pStrm->VpxConfig.g_timebase.den = 1000; + /* disable multithreading */ + pStrm->VpxConfig.g_threads = 0; + + pStrm->uDelay = 1000 / uFps; + + struct vpx_rational arg_framerate = { 30, 1 }; + rc = Ebml_WriteWebMFileHeader(&pStrm->Ebml, &pStrm->VpxConfig, &arg_framerate); + AssertRCReturn(rc, rc); + + /* Initialize codec */ + rcv = vpx_codec_enc_init(&pStrm->VpxCodec, DEFAULTCODEC, &pStrm->VpxConfig, 0); + if (rcv != VPX_CODEC_OK) + { + LogFlow(("Failed to initialize VP8 encoder %s", vpx_codec_err_to_string(rcv))); + return VERR_INVALID_PARAMETER; + } + + if (!vpx_img_alloc(&pStrm->VpxRawImage, VPX_IMG_FMT_I420, uWidth, uHeight, 1)) + { + LogFlow(("Failed to allocate image %dx%d", uWidth, uHeight)); + return VERR_NO_MEMORY; + } + pStrm->pu8YuvBuf = pStrm->VpxRawImage.planes[0]; + + pCtx->fEnabled = true; + pStrm->fEnabled = true; + return VINF_SUCCESS; +} + +/** + * VideoRec utility function to close the video recording context. + * + * @param pCtx Pointer to video recording context. + */ +void VideoRecContextClose(PVIDEORECCONTEXT pCtx) +{ + if (!pCtx) + return; + + uint32_t enmState = VIDREC_IDLE; + for (;;) + { + if (ASMAtomicCmpXchgExU32(&g_enmState, VIDREC_TERMINATING, enmState, &enmState)) + break; + if (enmState == VIDREC_UNINITIALIZED) + return; + } + if (enmState == VIDREC_COPYING) + { + int rc = RTSemEventWait(pCtx->TermEvent, RT_INDEFINITE_WAIT); + AssertRC(rc); + } + + RTSemEventSignal(pCtx->WaitEvent); + RTThreadWait(pCtx->Thread, 10000, NULL); + RTSemEventDestroy(pCtx->WaitEvent); + RTSemEventDestroy(pCtx->TermEvent); + + for (unsigned uScreen = 0; uScreen < pCtx->cScreens; uScreen++) + { + PVIDEORECSTREAM pStrm = &pCtx->Strm[uScreen]; + if (pStrm->fEnabled) + { + if (pStrm->Ebml.file != NIL_RTFILE) + { + int rc = Ebml_WriteWebMFileFooter(&pStrm->Ebml, 0); + AssertRC(rc); + RTFileClose(pStrm->Ebml.file); + pStrm->Ebml.file = NIL_RTFILE; + } + if (pStrm->Ebml.cue_list) + { + RTMemFree(pStrm->Ebml.cue_list); + pStrm->Ebml.cue_list = NULL; + } + vpx_img_free(&pStrm->VpxRawImage); + vpx_codec_err_t rcv = vpx_codec_destroy(&pStrm->VpxCodec); + Assert(rcv == VPX_CODEC_OK); + RTMemFree(pStrm->pu8RgbBuf); + pStrm->pu8RgbBuf = NULL; + } + } + + RTMemFree(pCtx); + + ASMAtomicWriteU32(&g_enmState, VIDREC_UNINITIALIZED); +} + +/** + * VideoRec utility function to check if recording is enabled. + * + * @returns true if recording is enabled + * @param pCtx Pointer to video recording context. + */ +bool VideoRecIsEnabled(PVIDEORECCONTEXT pCtx) +{ + uint32_t enmState = ASMAtomicReadU32(&g_enmState); + return ( enmState == VIDREC_IDLE + || enmState == VIDREC_COPYING); +} + +/** + * VideoRec utility function to check if recording engine is ready to accept a new frame + * for the given screen. + * + * @returns true if recording engine is ready + * @param pCtx Pointer to video recording context. + * @param uScreen screen id. + * @param u64TimeStamp current time stamp + */ +bool VideoRecIsReady(PVIDEORECCONTEXT pCtx, uint32_t uScreen, uint64_t u64TimeStamp) +{ + uint32_t enmState = ASMAtomicReadU32(&g_enmState); + if (enmState != VIDREC_IDLE) + return false; + + PVIDEORECSTREAM pStrm = &pCtx->Strm[uScreen]; + if (!pStrm->fEnabled) + return false; + + if (u64TimeStamp < pStrm->u64LastTimeStamp + pStrm->uDelay) + return false; + + if (ASMAtomicReadBool(&pStrm->fRgbFilled)) + return false; + + return true; +} + +/** + * VideoRec utility function to encode the source image and write the encoded + * image to target file. + * + * @returns IPRT status code. + * @param pCtx Pointer to video recording context. + * @param uSourceWidth Width of the source image. + * @param uSourceHeight Height of the source image. + */ +static int videoRecEncodeAndWrite(PVIDEORECSTREAM pStrm) +{ + /* presentation time stamp */ + vpx_codec_pts_t pts = pStrm->u64TimeStamp; + vpx_codec_err_t rcv = vpx_codec_encode(&pStrm->VpxCodec, + &pStrm->VpxRawImage, + pts /* time stamp */, + 10 /* how long to show this frame */, + 0 /* flags */, + VPX_DL_REALTIME /* deadline */); + if (rcv != VPX_CODEC_OK) + { + LogFlow(("Failed to encode:%s\n", vpx_codec_err_to_string(rcv))); + return VERR_GENERAL_FAILURE; + } + + vpx_codec_iter_t iter = NULL; + int rc = VERR_NO_DATA; + for (;;) + { + const vpx_codec_cx_pkt_t *pkt = vpx_codec_get_cx_data(&pStrm->VpxCodec, &iter); + if (!pkt) + break; + switch (pkt->kind) + { + case VPX_CODEC_CX_FRAME_PKT: + rc = Ebml_WriteWebMBlock(&pStrm->Ebml, &pStrm->VpxConfig, pkt); + break; + default: + LogFlow(("Unexpected CODEC Packet.\n")); + break; + } + } + + pStrm->cFrame++; + return rc; +} + +/** + * VideoRec utility function to convert RGB to YUV. + * + * @returns IPRT status code. + * @param pCtx Pointer to video recording context. + */ +static int videoRecRGBToYUV(PVIDEORECSTREAM pStrm) +{ + switch (pStrm->u32PixelFormat) + { + case VPX_IMG_FMT_RGB32: + LogFlow(("32 bit\n")); + if (!colorConvWriteYUV420p<ColorConvBGRA32Iter>(pStrm->uTargetWidth, + pStrm->uTargetHeight, + pStrm->pu8YuvBuf, + pStrm->pu8RgbBuf)) + return VERR_GENERAL_FAILURE; + break; + case VPX_IMG_FMT_RGB24: + LogFlow(("24 bit\n")); + if (!colorConvWriteYUV420p<ColorConvBGR24Iter>(pStrm->uTargetWidth, + pStrm->uTargetHeight, + pStrm->pu8YuvBuf, + pStrm->pu8RgbBuf)) + return VERR_GENERAL_FAILURE; + break; + case VPX_IMG_FMT_RGB565: + LogFlow(("565 bit\n")); + if (!colorConvWriteYUV420p<ColorConvBGR565Iter>(pStrm->uTargetWidth, + pStrm->uTargetHeight, + pStrm->pu8YuvBuf, + pStrm->pu8RgbBuf)) + return VERR_GENERAL_FAILURE; + break; + default: + return VERR_GENERAL_FAILURE; + } + return VINF_SUCCESS; +} + +/** + * VideoRec utility function to copy a source image (FrameBuf) to the intermediate + * RGB buffer. This function is executed only once per time. + * + * @thread EMT + * + * @returns IPRT status code. + * @param pCtx Pointer to the video recording context. + * @param uScreen Screen number. + * @param x Starting x coordinate of the source buffer (Framebuffer). + * @param y Starting y coordinate of the source buffer (Framebuffer). + * @param uPixelFormat Pixel Format. + * @param uBitsPerPixel Bits Per Pixel + * @param uBytesPerLine Bytes per source scanlineName. + * @param uSourceWidth Width of the source image (framebuffer). + * @param uSourceHeight Height of the source image (framebuffer). + * @param pu8BufAddr Pointer to source image(framebuffer). + * @param u64TimeStamp Time stamp (milliseconds). + */ +int VideoRecCopyToIntBuf(PVIDEORECCONTEXT pCtx, uint32_t uScreen, uint32_t x, uint32_t y, + uint32_t uPixelFormat, uint32_t uBitsPerPixel, uint32_t uBytesPerLine, + uint32_t uSourceWidth, uint32_t uSourceHeight, uint8_t *pu8BufAddr, + uint64_t u64TimeStamp) +{ + /* Do not execute during termination and guard against termination */ + if (!ASMAtomicCmpXchgU32(&g_enmState, VIDREC_COPYING, VIDREC_IDLE)) + return VINF_TRY_AGAIN; + + int rc = VINF_SUCCESS; + do + { + AssertPtrBreakStmt(pu8BufAddr, rc = VERR_INVALID_PARAMETER); + AssertBreakStmt(uSourceWidth, rc = VERR_INVALID_PARAMETER); + AssertBreakStmt(uSourceHeight, rc = VERR_INVALID_PARAMETER); + AssertBreakStmt(uScreen < pCtx->cScreens, rc = VERR_INVALID_PARAMETER); + + PVIDEORECSTREAM pStrm = &pCtx->Strm[uScreen]; + if (!pStrm->fEnabled) + { + rc = VINF_TRY_AGAIN; /* not (yet) enabled */ + break; + } + if (u64TimeStamp < pStrm->u64LastTimeStamp + pStrm->uDelay) + { + rc = VINF_TRY_AGAIN; /* respect maximum frames per second */ + break; + } + if (ASMAtomicReadBool(&pStrm->fRgbFilled)) + { + rc = VERR_TRY_AGAIN; /* previous frame not yet encoded */ + break; + } + + pStrm->u64LastTimeStamp = u64TimeStamp; + + int xDiff = ((int)pStrm->uTargetWidth - (int)uSourceWidth) / 2; + uint32_t w = uSourceWidth; + if ((int)w + xDiff + (int)x <= 0) /* nothing visible */ + { + rc = 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 = uSourceHeight; + int yDiff = ((int)pStrm->uTargetHeight - (int)uSourceHeight) / 2; + if ((int)h + yDiff + (int)y <= 0) /* nothing visible */ + { + rc = 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 > pStrm->uTargetWidth + || destY > pStrm->uTargetHeight) + { + rc = VERR_INVALID_PARAMETER; /* nothing visible */ + break; + } + + if (destX + w > pStrm->uTargetWidth) + w = pStrm->uTargetWidth - destX; + + if (destY + h > pStrm->uTargetHeight) + h = pStrm->uTargetHeight - destY; + + /* Calculate bytes per pixel */ + uint32_t bpp = 1; + if (uPixelFormat == FramebufferPixelFormat_FOURCC_RGB) + { + switch (uBitsPerPixel) + { + case 32: + pStrm->u32PixelFormat = VPX_IMG_FMT_RGB32; + bpp = 4; + break; + case 24: + pStrm->u32PixelFormat = VPX_IMG_FMT_RGB24; + bpp = 3; + break; + case 16: + pStrm->u32PixelFormat = VPX_IMG_FMT_RGB565; + bpp = 2; + break; + default: + AssertMsgFailed(("Unknown color depth! mBitsPerPixel=%d\n", uBitsPerPixel)); + break; + } + } + else + AssertMsgFailed(("Unknown pixel format! mPixelFormat=%d\n", uPixelFormat)); + + /* One of the dimensions of the current frame is smaller than before so + * clear the entire buffer to prevent artifacts from the previous frame */ + if ( uSourceWidth < pStrm->uLastSourceWidth + || uSourceHeight < pStrm->uLastSourceHeight) + memset(pStrm->pu8RgbBuf, 0, pStrm->uTargetWidth * pStrm->uTargetHeight * 4); + + pStrm->uLastSourceWidth = uSourceWidth; + pStrm->uLastSourceHeight = uSourceHeight; + + /* Calculate start offset in source and destination buffers */ + uint32_t offSrc = y * uBytesPerLine + x * bpp; + uint32_t offDst = (destY * pStrm->uTargetWidth + destX) * bpp; + /* do the copy */ + for (unsigned int i = 0; i < h; i++) + { + /* Overflow check */ + Assert(offSrc + w * bpp <= uSourceHeight * uBytesPerLine); + Assert(offDst + w * bpp <= pStrm->uTargetHeight * pStrm->uTargetWidth * bpp); + memcpy(pStrm->pu8RgbBuf + offDst, pu8BufAddr + offSrc, w * bpp); + offSrc += uBytesPerLine; + offDst += pStrm->uTargetWidth * bpp; + } + + pStrm->u64TimeStamp = u64TimeStamp; + + ASMAtomicWriteBool(&pStrm->fRgbFilled, true); + RTSemEventSignal(pCtx->WaitEvent); + } while (0); + + if (!ASMAtomicCmpXchgU32(&g_enmState, VIDREC_IDLE, VIDREC_COPYING)) + { + rc = RTSemEventSignal(pCtx->TermEvent); + AssertRC(rc); + } + + return rc; +} |