VirtualBox

source: vbox/trunk/src/VBox/Main/src-client/RecordingCodec.cpp@ 105006

Last change on this file since 105006 was 105006, checked in by vboxsync, 11 months ago

Video Recording: Big revamp to improve overall performance. We now don't rely on the periodic display refresh callback anymore to render the entire framebuffer but now rely on delta updates ("dirty rectangles"). Also, we now only encode new frames when an area has changed. This also needed cursor position + change change notifications, as we render the cursor on the host side if mouse integration is enabled (requires 7.1 Guest Additions as of now). Optimized the BGRA32->YUV IV420 color space conversion as well as the overall amount of pixel data shuffled forth and back. Added a new testcase for the cropping/centering code. bugref:10650

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 47.0 KB
Line 
1/* $Id: RecordingCodec.cpp 105006 2024-06-24 17:43:00Z vboxsync $ */
2/** @file
3 * Recording codec wrapper.
4 */
5
6/*
7 * Copyright (C) 2022-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.215389.xyz.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28/* This code makes use of Vorbis (libvorbis):
29 *
30 * Copyright (c) 2002-2020 Xiph.org Foundation
31 *
32 * Redistribution and use in source and binary forms, with or without
33 * modification, are permitted provided that the following conditions
34 * are met:
35 *
36 * - Redistributions of source code must retain the above copyright
37 * notice, this list of conditions and the following disclaimer.
38 *
39 * - Redistributions in binary form must reproduce the above copyright
40 * notice, this list of conditions and the following disclaimer in the
41 * documentation and/or other materials provided with the distribution.
42 *
43 * - Neither the name of the Xiph.org Foundation nor the names of its
44 * contributors may be used to endorse or promote products derived from
45 * this software without specific prior written permission.
46 *
47 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
48 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
49 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
50 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION
51 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
52 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
53 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
54 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
55 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
56 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
57 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
58 */
59
60#define LOG_GROUP LOG_GROUP_RECORDING
61#include "LoggingNew.h"
62
63#include <VBox/com/string.h>
64#include <VBox/err.h>
65#include <VBox/vmm/pdmaudioifs.h>
66#include <VBox/vmm/pdmaudioinline.h>
67
68#include "Recording.h"
69#include "RecordingInternals.h"
70#include "RecordingUtils.h"
71#include "WebMWriter.h"
72
73#include <math.h>
74
75#include <iprt/formats/bmp.h>
76
77
78/*********************************************************************************************************************************
79* Prototypes *
80*********************************************************************************************************************************/
81static int recordingCodecVPXEncodeWorker(PRECORDINGCODEC pCodec, vpx_image_t *pImage, uint64_t msTimestamp);
82
83
84/*********************************************************************************************************************************
85* Generic inline functions *
86*********************************************************************************************************************************/
87
88DECLINLINE(void) recordingCodecLock(PRECORDINGCODEC pCodec)
89{
90 int vrc2 = RTCritSectEnter(&pCodec->CritSect);
91 AssertRC(vrc2);
92}
93
94DECLINLINE(void) recordingCodecUnlock(PRECORDINGCODEC pCodec)
95{
96 int vrc2 = RTCritSectLeave(&pCodec->CritSect);
97 AssertRC(vrc2);
98}
99
100
101/*********************************************************************************************************************************
102* VPX (VP8 / VP9) codec *
103*********************************************************************************************************************************/
104
105#ifdef VBOX_WITH_LIBVPX
106/** Prototypes. */
107static DECLCALLBACK(int) recordingCodecVPXScreenChange(PRECORDINGCODEC pCodec, PRECORDINGSURFACEINFO pInfo);
108
109/**
110 * Clears (zeros) the VPX planes.
111 */
112DECLINLINE(void) recordingCodecVPXClearPlanes(PRECORDINGCODEC pCodec)
113{
114 size_t const cbYPlane = pCodec->Parms.u.Video.uWidth * pCodec->Parms.u.Video.uHeight;
115 memset(pCodec->Video.VPX.RawImage.planes[VPX_PLANE_Y], 0, cbYPlane);
116 size_t const cbUVPlane = (pCodec->Parms.u.Video.uWidth / 2) * (pCodec->Parms.u.Video.uHeight / 2);
117 memset(pCodec->Video.VPX.RawImage.planes[VPX_PLANE_U], 128, cbUVPlane);
118 memset(pCodec->Video.VPX.RawImage.planes[VPX_PLANE_V], 128, cbUVPlane);
119}
120
121/** @copydoc RECORDINGCODECOPS::pfnInit */
122static DECLCALLBACK(int) recordingCodecVPXInit(PRECORDINGCODEC pCodec)
123{
124 const unsigned uBPP = 32;
125
126 pCodec->Parms.csFrame = 0;
127 pCodec->Parms.cbFrame = pCodec->Parms.u.Video.uWidth * pCodec->Parms.u.Video.uHeight * (uBPP / 8);
128 pCodec->Parms.msFrame = 1; /* 1ms per frame. */
129
130# ifdef VBOX_WITH_LIBVPX_VP9
131 vpx_codec_iface_t *pCodecIface = vpx_codec_vp9_cx();
132# else /* Default is using VP8. */
133 vpx_codec_iface_t *pCodecIface = vpx_codec_vp8_cx();
134# endif
135 PRECORDINGCODECVPX pVPX = &pCodec->Video.VPX;
136
137 vpx_codec_err_t rcv = vpx_codec_enc_config_default(pCodecIface, &pVPX->Cfg, 0 /* Reserved */);
138 if (rcv != VPX_CODEC_OK)
139 {
140 LogRel(("Recording: Failed to get default config for VPX encoder: %s\n", vpx_codec_err_to_string(rcv)));
141 return VERR_RECORDING_CODEC_INIT_FAILED;
142 }
143
144 /* Target bitrate in kilobits per second. */
145 pVPX->Cfg.rc_target_bitrate = pCodec->Parms.uBitrate;
146 /* Frame width. */
147 pVPX->Cfg.g_w = pCodec->Parms.u.Video.uWidth;
148 /* Frame height. */
149 pVPX->Cfg.g_h = pCodec->Parms.u.Video.uHeight;
150 /* ms per frame. */
151 pVPX->Cfg.g_timebase.num = pCodec->Parms.msFrame;
152 pVPX->Cfg.g_timebase.den = 1000;
153 /* Disable multithreading. */
154 pVPX->Cfg.g_threads = 0;
155
156 /* Initialize codec. */
157 rcv = vpx_codec_enc_init(&pVPX->Ctx, pCodecIface, &pVPX->Cfg, 0 /* Flags */);
158 if (rcv != VPX_CODEC_OK)
159 {
160 LogRel(("Recording: Failed to initialize VPX encoder: %s\n", vpx_codec_err_to_string(rcv)));
161 return VERR_RECORDING_CODEC_INIT_FAILED;
162 }
163
164 if (!vpx_img_alloc(&pVPX->RawImage, VPX_IMG_FMT_I420,
165 pCodec->Parms.u.Video.uWidth, pCodec->Parms.u.Video.uHeight, 1))
166 {
167 LogRel(("Recording: Failed to allocate image %RU32x%RU32\n", pCodec->Parms.u.Video.uWidth, pCodec->Parms.u.Video.uHeight));
168 return VERR_RECORDING_CODEC_INIT_FAILED;
169 }
170
171 /* Save a pointer to the Y (Luminance) plane. */
172 pVPX->pu8YuvBuf = pVPX->RawImage.planes[VPX_PLANE_Y];
173
174 /* Initialize front + back buffers. */
175 RT_ZERO(pCodec->Video.VPX.Front);
176 RT_ZERO(pCodec->Video.VPX.Back);
177
178 pCodec->Video.VPX.pCursorShape = NULL;
179
180 RECORDINGSURFACEINFO ScreenInfo;
181 ScreenInfo.uWidth = pCodec->Parms.u.Video.uWidth;
182 ScreenInfo.uHeight = pCodec->Parms.u.Video.uHeight;
183 ScreenInfo.uBPP = uBPP;
184 ScreenInfo.enmPixelFmt = RECORDINGPIXELFMT_BRGA32;
185
186 RT_ZERO(pCodec->Video.VPX.PosCursorOld);
187
188 int vrc = recordingCodecVPXScreenChange(pCodec, &ScreenInfo);
189 if (RT_FAILURE(vrc))
190 LogRel(("Recording: Failed to initialize codec: %Rrc\n", vrc));
191
192 return vrc;
193}
194
195/** @copydoc RECORDINGCODECOPS::pfnDestroy */
196static DECLCALLBACK(int) recordingCodecVPXDestroy(PRECORDINGCODEC pCodec)
197{
198 PRECORDINGCODECVPX pVPX = &pCodec->Video.VPX;
199
200 vpx_img_free(&pVPX->RawImage);
201 pVPX->pu8YuvBuf = NULL; /* Was pointing to VPX.RawImage. */
202
203 vpx_codec_err_t rcv = vpx_codec_destroy(&pVPX->Ctx);
204 Assert(rcv == VPX_CODEC_OK); RT_NOREF(rcv);
205
206 RecordingVideoFrameDestroy(&pCodec->Video.VPX.Front);
207 RecordingVideoFrameDestroy(&pCodec->Video.VPX.Back);
208
209 RecordingVideoFrameFree(pCodec->Video.VPX.pCursorShape);
210 pCodec->Video.VPX.pCursorShape = NULL;
211
212 return VINF_SUCCESS;
213}
214
215/** @copydoc RECORDINGCODECOPS::pfnFinalize */
216static DECLCALLBACK(int) recordingCodecVPXFinalize(PRECORDINGCODEC pCodec)
217{
218 recordingCodecLock(pCodec);
219
220 int vrc = recordingCodecVPXEncodeWorker(pCodec, NULL /* pImage */, pCodec->State.tsLastWrittenMs + 1);
221
222 recordingCodecUnlock(pCodec);
223
224 return vrc;
225}
226
227/** @copydoc RECORDINGCODECOPS::pfnParseOptions */
228static DECLCALLBACK(int) recordingCodecVPXParseOptions(PRECORDINGCODEC pCodec, const com::Utf8Str &strOptions)
229{
230 size_t pos = 0;
231 com::Utf8Str key, value;
232 while ((pos = strOptions.parseKeyValue(key, value, pos)) != com::Utf8Str::npos)
233 {
234 if (key.compare("vc_quality", com::Utf8Str::CaseInsensitive) == 0)
235 {
236 const PRECORDINGCODECVPX pVPX = &pCodec->Video.VPX;
237
238 if (value.compare("realtime", com::Utf8Str::CaseInsensitive) == 0)
239 pVPX->uEncoderDeadline = VPX_DL_REALTIME;
240 else if (value.compare("good", com::Utf8Str::CaseInsensitive) == 0)
241 {
242 AssertStmt(pCodec->Parms.u.Video.uFPS, pCodec->Parms.u.Video.uFPS = 25);
243 pVPX->uEncoderDeadline = 1000000 / pCodec->Parms.u.Video.uFPS;
244 }
245 else if (value.compare("best", com::Utf8Str::CaseInsensitive) == 0)
246 pVPX->uEncoderDeadline = VPX_DL_BEST_QUALITY;
247 else
248 pVPX->uEncoderDeadline = value.toUInt32();
249 }
250 else
251 LogRel2(("Recording: Unknown option '%s' (value '%s'), skipping\n", key.c_str(), value.c_str()));
252 } /* while */
253
254 return VINF_SUCCESS;
255}
256
257/**
258 * Worker for encoding the last composed image.
259 *
260 * @returns VBox status code.
261 * @param pCodec Pointer to codec instance.
262 * @param pImage VPX image to encode.
263 * Set to NULL to signal the encoder that it has to finish up stuff when ending encoding.
264 * @param msTimestamp Timestamp (PTS) to use for encoding.
265 *
266 * @note Caller must take encoder lock.
267 */
268static int recordingCodecVPXEncodeWorker(PRECORDINGCODEC pCodec, vpx_image_t *pImage, uint64_t msTimestamp)
269{
270 int vrc;
271
272 PRECORDINGCODECVPX pVPX = &pCodec->Video.VPX;
273
274 /* Presentation TimeStamp (PTS). */
275 vpx_codec_pts_t const pts = msTimestamp;
276 vpx_codec_err_t const rcv = vpx_codec_encode(&pVPX->Ctx,
277 pImage,
278 pts /* Timestamp */,
279 pCodec->Parms.u.Video.uDelayMs /* How long to show this frame */,
280 0 /* Flags */,
281 pVPX->uEncoderDeadline /* Quality setting */);
282 if (rcv != VPX_CODEC_OK)
283 {
284 if (pCodec->State.cEncErrors++ < 64) /** @todo Make this configurable. */
285 LogRel(("Recording: Failed to encode video frame: %s\n", vpx_codec_err_to_string(rcv)));
286 return VERR_RECORDING_ENCODING_FAILED;
287 }
288
289 pCodec->State.cEncErrors = 0;
290
291 vpx_codec_iter_t iter = NULL;
292 vrc = VERR_NO_DATA;
293 for (;;)
294 {
295 const vpx_codec_cx_pkt_t *pPkt = vpx_codec_get_cx_data(&pVPX->Ctx, &iter);
296 if (!pPkt) /* End of list */
297 break;
298
299 switch (pPkt->kind)
300 {
301 case VPX_CODEC_CX_FRAME_PKT:
302 {
303 /* Calculate the absolute PTS of this frame (in ms). */
304 uint64_t tsAbsPTSMs = pPkt->data.frame.pts * 1000
305 * (uint64_t)pCodec->Video.VPX.Cfg.g_timebase.num / pCodec->Video.VPX.Cfg.g_timebase.den;
306
307 const bool fKeyframe = RT_BOOL(pPkt->data.frame.flags & VPX_FRAME_IS_KEY);
308
309 uint32_t fFlags = RECORDINGCODEC_ENC_F_NONE;
310 if (fKeyframe)
311 fFlags |= RECORDINGCODEC_ENC_F_BLOCK_IS_KEY;
312 if (pPkt->data.frame.flags & VPX_FRAME_IS_INVISIBLE)
313 fFlags |= RECORDINGCODEC_ENC_F_BLOCK_IS_INVISIBLE;
314
315 Log3Func(("msTimestamp=%RU64, fFlags=%#x\n", msTimestamp, fFlags));
316
317 vrc = pCodec->Callbacks.pfnWriteData(pCodec, pPkt->data.frame.buf, pPkt->data.frame.sz,
318 tsAbsPTSMs, fFlags, pCodec->Callbacks.pvUser);
319 break;
320 }
321
322 default:
323 AssertFailed();
324 LogFunc(("Unexpected video packet type %ld\n", pPkt->kind));
325 break;
326 }
327 }
328
329 return vrc;
330}
331
332/** @copydoc RECORDINGCODECOPS::pfnEncode */
333static DECLCALLBACK(int) recordingCodecVPXEncode(PRECORDINGCODEC pCodec, PRECORDINGFRAME pFrame,
334 uint64_t msTimestamp, void *pvUser)
335{
336 recordingCodecLock(pCodec);
337
338 int vrc;
339
340 /* If no frame is given, encode the last composed frame again with the given timestamp. */
341 if (pFrame == NULL)
342 {
343 vrc = recordingCodecVPXEncodeWorker(pCodec, &pCodec->Video.VPX.RawImage, msTimestamp);
344 recordingCodecUnlock(pCodec);
345 return vrc;
346 }
347
348 RecordingContext *pCtx = (RecordingContext *)pvUser;
349 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
350
351 Assert(pFrame->msTimestamp == msTimestamp);
352
353 /* Note: We get BGRA 32 input here. */
354 PRECORDINGVIDEOFRAME pFront = &pCodec->Video.VPX.Front;
355 PRECORDINGVIDEOFRAME pBack = &pCodec->Video.VPX.Back;
356
357 int32_t sx = 0; /* X origin within the source frame. */
358 int32_t sy = 0; /* Y origin within the source frame. */
359 int32_t sw = 0; /* Width of the source frame (starting at X origin). */
360 int32_t sh = 0; /* Height of the source frame (starting at Y origin). */
361 int32_t dx = 0; /* X destination of the source frame within the destination frame. */
362 int32_t dy = 0; /* Y destination of the source frame within the destination frame. */
363
364 /*
365 * Note!
366 *
367 * We don't implement any rendering graph or some such here, as we only have two things to render here, namely:
368 *
369 * - the actual framebuffer updates
370 * - if available (through mouse integration via Guest Additions): the guest's mouse cursor via a (software) overlay
371 *
372 * So composing is done the folowing way:
373 *
374 * - always store the plain framebuffer updates in our back buffer first
375 * - copy the framebuffer updates to our front buffer
376 * - restore the area of the old mouse cursor position by copying frame buffer area data from back -> front buffer
377 * - apply the mouse cursor updates to our front buffer
378 */
379
380 switch (pFrame->enmType)
381 {
382 case RECORDINGFRAME_TYPE_VIDEO:
383 {
384 PRECORDINGVIDEOFRAME pSrc = &pFrame->u.Video;
385
386 vrc = RecordingVideoFrameBlitFrame(pFront, pSrc->Pos.x, pSrc->Pos.y,
387 pSrc, 0 /* uSrcX */, 0 /* uSrcY */, pSrc->Info.uWidth, pSrc->Info.uHeight);
388#if 0
389 RecordingUtilsDbgDumpVideoFrameEx(pFront, "/tmp/recording", "encode-front");
390 RecordingUtilsDbgDumpVideoFrameEx(pSrc, "/tmp/recording", "encode-src");
391#endif
392 vrc = RecordingVideoFrameBlitFrame(pBack, pSrc->Pos.x, pSrc->Pos.y,
393 pSrc, 0 /* uSrcX */, 0 /* uSrcY */, pSrc->Info.uWidth, pSrc->Info.uHeight);
394 pFront = pBack;
395
396 sw = pSrc->Info.uWidth;
397 sh = pSrc->Info.uHeight;
398 sx = pSrc->Pos.x;
399 sy = pSrc->Pos.y;
400
401 Log3Func(("RECORDINGFRAME_TYPE_VIDEO: sx=%d, sy=%d, sw=%d, sh=%d\n", sx, sy, sw, sh));
402
403 dx = pSrc->Pos.x;
404 dy = pSrc->Pos.y;
405 break;
406 }
407
408 case RECORDINGFRAME_TYPE_CURSOR_SHAPE:
409 {
410 pCodec->Video.VPX.pCursorShape = RecordingVideoFrameDup(&pFrame->u.CursorShape);
411 AssertPtr(pCodec->Video.VPX.pCursorShape);
412
413 RT_FALL_THROUGH(); /* Re-render cursor with new shape below. */
414 }
415
416 case RECORDINGFRAME_TYPE_CURSOR_POS:
417 {
418 const PRECORDINGVIDEOFRAME pCursor = pCodec->Video.VPX.pCursorShape;
419 if (!pCursor) /* No cursor shape set yet. */
420 break;
421
422 PRECORDINGPOS pPosOld = &pCodec->Video.VPX.PosCursorOld;
423 PRECORDINGPOS pPosNew = pFrame->enmType == RECORDINGFRAME_TYPE_CURSOR_POS
424 ? &pFrame->u.Cursor.Pos
425 : pPosOld;
426
427 Log3Func(("RECORDINGFRAME_TYPE_CURSOR_POS: x=%d, y=%d, oldx=%d, oldy=%d, w=%d, h=%d\n",
428 pPosNew->x, pPosNew->y,
429 pPosOld->x, pPosOld->y, pCursor->Info.uWidth, pCursor->Info.uHeight));
430
431 /* Calculate the merged area between the old and the new (current) cursor position
432 * so that we update everything to not create any ghosting. */
433 sx = RT_MIN(pPosNew->x, pPosOld->x);
434 sy = RT_MIN(pPosNew->y, pPosOld->y);
435 sw = ( pPosNew->x > pPosOld->x
436 ? pPosNew->x - pPosOld->x
437 : pPosOld->x - pPosNew->x) + pCursor->Info.uWidth;
438 sh = ( pPosNew->y > pPosOld->y
439 ? pPosNew->y - pPosOld->y
440 : pPosOld->y - pPosNew->y) + pCursor->Info.uHeight;
441
442 /* Limit the width / height to blit to the front buffer's size. */
443 if (sx + sw >= (int32_t)pFront->Info.uWidth)
444 sw = pFront->Info.uWidth - sx;
445 if (sy + sh >= (int32_t)pFront->Info.uHeight)
446 sh = pFront->Info.uHeight - sy;
447
448 /* Save current cursor position for next iteration. */
449 *pPosOld = *pPosNew;
450
451 dx = sx;
452 dy = sy;
453
454 Log3Func(("RECORDINGFRAME_TYPE_CURSOR_POS: sx=%d, sy=%d, sw=%d, sh=%d\n", sx, sy, sw, sh));
455
456 /* Nothing to encode? Bail out. */
457 if ( sw <= 0
458 || sh <= 0
459 || (uint32_t)sx > pBack->Info.uWidth
460 || (uint32_t)sy > pBack->Info.uHeight)
461 break;
462
463 /* Restore background of front buffer first. */
464 vrc = RecordingVideoFrameBlitFrame(pFront, dx, dy,
465 pBack, sx, sy, sw, sh);
466
467 /* Blit mouse cursor to front buffer. */
468 if (RT_SUCCESS(vrc))
469 vrc = RecordingVideoFrameBlitRawAlpha(pFront, pPosNew->x, pPosNew->y,
470 pCursor->pau8Buf, pCursor->cbBuf,
471 0 /* uSrcX */, 0 /* uSrcY */, pCursor->Info.uWidth, pCursor->Info.uHeight,
472 pCursor->Info.uBytesPerLine, pCursor->Info.uBPP, pCursor->Info.enmPixelFmt);
473#if 0
474 RecordingUtilsDbgDumpVideoFrameEx(pFront, "/tmp/recording", "cursor-alpha-front");
475#endif
476 break;
477 }
478
479 default:
480 AssertFailed();
481 break;
482
483 }
484
485 /* Nothing to encode? Bail out. */
486 if ( sw == 0
487 || sh == 0)
488 {
489 recordingCodecUnlock(pCodec);
490 return VINF_SUCCESS;
491 }
492
493 Log3Func(("Encoding video parameters: %RU16x%RU16 (%RU8 FPS), originX=%RI32, originY=%RI32\n",
494 pCodec->Parms.u.Video.uWidth, pCodec->Parms.u.Video.uHeight, pCodec->Parms.u.Video.uFPS,
495 pCodec->Parms.u.Video.Scaling.u.Crop.m_iOriginX, pCodec->Parms.u.Video.Scaling.u.Crop.m_iOriginY));
496
497 int32_t sx_b = sx; /* X origin within the source frame. */
498 int32_t sy_b = sy; /* Y origin within the source frame. */
499 int32_t sw_b = sw; /* Width of the source frame (starting at X origin). */
500 int32_t sh_b = sh; /* Height of the source frame (starting at Y origin). */
501 int32_t dx_b = dx; /* X destination of the source frame within the destination frame. */
502 int32_t dy_b = dy; /* Y destination of the source frame within the destination frame. */
503
504 RT_NOREF(sx_b);
505 RT_NOREF(sy_b);
506 RT_NOREF(sw_b);
507 RT_NOREF(sh_b);
508 RT_NOREF(dx_b);
509 RT_NOREF(dy_b);
510
511 vrc = RecordingUtilsCoordsCropCenter(&pCodec->Parms, &sx, &sy, &sw, &sh, &dx, &dy);
512 if (vrc == VINF_SUCCESS) /* vrc might be VWRN_RECORDING_ENCODING_SKIPPED to skip encoding. */
513 {
514 Log3Func(("Encoding source %RI32,%RI32 (%RI32x%RI32) to %RI32,%RI32 (%zu bytes)\n",
515 sx, sy, sw, sh, dx, dy, sw * sh * (pFront->Info.uBPP / 8)));
516#ifdef DEBUG
517 AssertReturn(sw <= (int32_t)pFront->Info.uWidth, VERR_INVALID_PARAMETER);
518 AssertReturn(sh <= (int32_t)pFront->Info.uHeight, VERR_INVALID_PARAMETER);
519 AssertReturn(sx + sw <= (int32_t)pFront->Info.uWidth , VERR_INVALID_PARAMETER);
520 AssertReturn(sy + sh <= (int32_t)pFront->Info.uHeight, VERR_INVALID_PARAMETER);
521#endif
522
523#if 0
524 RecordingUtilsDbgDumpImageData(&pFront->pau8Buf[(sy * pFront->Info.uBytesPerLine) + (sx * (pFront->Info.uBPP / 8))], pFront->cbBuf,
525 "/tmp/recording", "cropped", sw, sh, pFront->Info.uBytesPerLine, pFront->Info.uBPP);
526#endif
527 /* Blit (and convert from BGRA 32) the changed parts of the front buffer to the YUV 420 surface of the codec. */
528 RecordingUtilsConvBGRA32ToYUVI420Ex(/* Destination */
529 pCodec->Video.VPX.pu8YuvBuf, dx, dy, pCodec->Parms.u.Video.uWidth, pCodec->Parms.u.Video.uHeight,
530 /* Source */
531 pFront->pau8Buf, sx, sy, sw, sh, pFront->Info.uBytesPerLine, pFront->Info.uBPP);
532
533 vrc = recordingCodecVPXEncodeWorker(pCodec, &pCodec->Video.VPX.RawImage, msTimestamp);
534 }
535
536 recordingCodecUnlock(pCodec);
537
538 return vrc;
539}
540
541/** @copydoc RECORDINGCODECOPS::pfnScreenChange */
542static DECLCALLBACK(int) recordingCodecVPXScreenChange(PRECORDINGCODEC pCodec, PRECORDINGSURFACEINFO pInfo)
543{
544 LogRel2(("Recording: Codec got screen change notification (%RU16x%RU16, %RU8 BPP)\n",
545 pInfo->uWidth, pInfo->uHeight, pInfo->uBPP));
546
547 /* Fend-off bogus reports. */
548 if ( !pInfo->uWidth
549 || !pInfo->uHeight)
550 return VERR_INVALID_PARAMETER;
551
552 /** @todo BUGBUG Not sure why we sometimes get 0 BPP for a display change from Main.
553 * For now we ASSUME 32 BPP. */
554 if (!pInfo->uBPP)
555 pInfo->uBPP = 32;
556
557 /* The VPX encoder only understands even frame sizes. */
558 if ( (pInfo->uWidth % 2) != 0
559 || (pInfo->uHeight % 2) != 0)
560 return VERR_INVALID_PARAMETER;
561
562 PRECORDINGCODECVPX pVPX = &pCodec->Video.VPX;
563
564 recordingCodecLock(pCodec);
565
566 /* Tear down old stuff. */
567 RecordingVideoFrameDestroy(&pVPX->Front);
568 RecordingVideoFrameDestroy(&pVPX->Back);
569
570 /* Initialize front + back buffers. */
571 int vrc = RecordingVideoFrameInit(&pVPX->Front, RECORDINGVIDEOFRAME_F_VISIBLE,
572 pInfo->uWidth, pInfo->uHeight, 0, 0,
573 pInfo->uBPP, pInfo->enmPixelFmt);
574 if (RT_SUCCESS(vrc))
575 vrc = RecordingVideoFrameInit(&pVPX->Back, RECORDINGVIDEOFRAME_F_VISIBLE,
576 pInfo->uWidth, pInfo->uHeight, 0, 0,
577 pInfo->uBPP, pInfo->enmPixelFmt);
578 if (RT_SUCCESS(vrc))
579 {
580 RecordingVideoFrameClear(&pVPX->Front);
581 RecordingVideoFrameClear(&pVPX->Back);
582
583 recordingCodecVPXClearPlanes(pCodec);
584
585 /* Calculate the X/Y origins for cropping / centering.
586 * This is needed as the codec's video output size not necessarily matches the VM's frame buffer size. */
587 pCodec->Parms.u.Video.Scaling.u.Crop.m_iOriginX = int32_t(pCodec->Parms.u.Video.uWidth - pInfo->uWidth) / 2;
588 pCodec->Parms.u.Video.Scaling.u.Crop.m_iOriginY = int32_t(pCodec->Parms.u.Video.uHeight - pInfo->uHeight) / 2;
589 }
590
591 recordingCodecUnlock(pCodec);
592
593 if (RT_FAILURE(vrc))
594 LogRel(("Recording: Codec error handling screen change notification: %Rrc\n", vrc));
595
596 return vrc;
597
598}
599#endif /* VBOX_WITH_LIBVPX */
600
601
602/*********************************************************************************************************************************
603* Ogg Vorbis codec *
604*********************************************************************************************************************************/
605
606#ifdef VBOX_WITH_LIBVORBIS
607/** @copydoc RECORDINGCODECOPS::pfnInit */
608static DECLCALLBACK(int) recordingCodecVorbisInit(PRECORDINGCODEC pCodec)
609{
610 pCodec->cbScratch = _4K;
611 pCodec->pvScratch = RTMemAlloc(pCodec->cbScratch);
612 AssertPtrReturn(pCodec->pvScratch, VERR_NO_MEMORY);
613
614 const PPDMAUDIOPCMPROPS pPCMProps = &pCodec->Parms.u.Audio.PCMProps;
615
616 /** @todo BUGBUG When left out this call, vorbis_block_init() does not find oggpack_writeinit and all goes belly up ... */
617 oggpack_buffer b;
618 oggpack_writeinit(&b);
619
620 vorbis_info_init(&pCodec->Audio.Vorbis.info);
621
622 int vorbis_rc;
623 if (pCodec->Parms.uBitrate == 0) /* No bitrate management? Then go for ABR (Average Bit Rate) only. */
624 vorbis_rc = vorbis_encode_init_vbr(&pCodec->Audio.Vorbis.info,
625 PDMAudioPropsChannels(pPCMProps), PDMAudioPropsHz(pPCMProps),
626 (float).4 /* Quality, from -.1 (lowest) to 1 (highest) */);
627 else
628 vorbis_rc = vorbis_encode_setup_managed(&pCodec->Audio.Vorbis.info, PDMAudioPropsChannels(pPCMProps), PDMAudioPropsHz(pPCMProps),
629 -1 /* max bitrate (unset) */, pCodec->Parms.uBitrate /* kbps, nominal */, -1 /* min bitrate (unset) */);
630 if (vorbis_rc)
631 {
632 LogRel(("Recording: Audio codec failed to setup %s mode (bitrate %RU32): %d\n",
633 pCodec->Parms.uBitrate == 0 ? "VBR" : "bitrate management", pCodec->Parms.uBitrate, vorbis_rc));
634 return VERR_RECORDING_CODEC_INIT_FAILED;
635 }
636
637 vorbis_rc = vorbis_encode_setup_init(&pCodec->Audio.Vorbis.info);
638 if (vorbis_rc)
639 {
640 LogRel(("Recording: vorbis_encode_setup_init() failed (%d)\n", vorbis_rc));
641 return VERR_RECORDING_CODEC_INIT_FAILED;
642 }
643
644 /* Initialize the analysis state and encoding storage. */
645 vorbis_rc = vorbis_analysis_init(&pCodec->Audio.Vorbis.dsp_state, &pCodec->Audio.Vorbis.info);
646 if (vorbis_rc)
647 {
648 vorbis_info_clear(&pCodec->Audio.Vorbis.info);
649 LogRel(("Recording: vorbis_analysis_init() failed (%d)\n", vorbis_rc));
650 return VERR_RECORDING_CODEC_INIT_FAILED;
651 }
652
653 vorbis_rc = vorbis_block_init(&pCodec->Audio.Vorbis.dsp_state, &pCodec->Audio.Vorbis.block_cur);
654 if (vorbis_rc)
655 {
656 vorbis_info_clear(&pCodec->Audio.Vorbis.info);
657 LogRel(("Recording: vorbis_block_init() failed (%d)\n", vorbis_rc));
658 return VERR_RECORDING_CODEC_INIT_FAILED;
659 }
660
661 if (!pCodec->Parms.msFrame) /* No ms per frame defined? Use default. */
662 pCodec->Parms.msFrame = VBOX_RECORDING_VORBIS_FRAME_MS_DEFAULT;
663
664 return VINF_SUCCESS;
665}
666
667/** @copydoc RECORDINGCODECOPS::pfnDestroy */
668static DECLCALLBACK(int) recordingCodecVorbisDestroy(PRECORDINGCODEC pCodec)
669{
670 PRECORDINGCODECVORBIS pVorbis = &pCodec->Audio.Vorbis;
671
672 vorbis_block_clear(&pVorbis->block_cur);
673 vorbis_dsp_clear (&pVorbis->dsp_state);
674 vorbis_info_clear (&pVorbis->info);
675
676 return VINF_SUCCESS;
677}
678
679/** @copydoc RECORDINGCODECOPS::pfnEncode */
680static DECLCALLBACK(int) recordingCodecVorbisEncode(PRECORDINGCODEC pCodec,
681 const PRECORDINGFRAME pFrame, uint64_t msTimestamp, void *pvUser)
682{
683 RT_NOREF(msTimestamp, pvUser);
684
685 const PPDMAUDIOPCMPROPS pPCMProps = &pCodec->Parms.u.Audio.PCMProps;
686
687 Assert (pCodec->Parms.cbFrame);
688 AssertReturn(pFrame->u.Audio.cbBuf % pCodec->Parms.cbFrame == 0, VERR_INVALID_PARAMETER);
689 Assert (pFrame->u.Audio.cbBuf);
690 AssertReturn(pFrame->u.Audio.cbBuf % PDMAudioPropsFrameSize(pPCMProps) == 0, VERR_INVALID_PARAMETER);
691 AssertReturn(pCodec->cbScratch >= pFrame->u.Audio.cbBuf, VERR_INVALID_PARAMETER);
692
693 int vrc = VINF_SUCCESS;
694
695 int const cbFrame = PDMAudioPropsFrameSize(pPCMProps);
696 int const cFrames = (int)(pFrame->u.Audio.cbBuf / cbFrame);
697
698 /* Write non-interleaved frames. */
699 float **buffer = vorbis_analysis_buffer(&pCodec->Audio.Vorbis.dsp_state, cFrames);
700 int16_t *puSrc = (int16_t *)pFrame->u.Audio.pvBuf; RT_NOREF(puSrc);
701
702 /* Convert samples into floating point. */
703 /** @todo This is sloooooooooooow! Optimize this! */
704 uint8_t const cChannels = PDMAudioPropsChannels(pPCMProps);
705 AssertReturn(cChannels == 2, VERR_NOT_SUPPORTED);
706
707 float const div = 1.0f / 32768.0f;
708
709 for(int f = 0; f < cFrames; f++)
710 {
711 buffer[0][f] = (float)puSrc[0] * div;
712 buffer[1][f] = (float)puSrc[1] * div;
713 puSrc += cChannels;
714 }
715
716 int vorbis_rc = vorbis_analysis_wrote(&pCodec->Audio.Vorbis.dsp_state, cFrames);
717 if (vorbis_rc)
718 {
719 LogRel(("Recording: vorbis_analysis_wrote() failed (%d)\n", vorbis_rc));
720 return VERR_RECORDING_ENCODING_FAILED;
721 }
722
723 size_t cBlocksEncoded = 0;
724 size_t cBytesEncoded = 0;
725
726 uint8_t *puDst = (uint8_t *)pCodec->pvScratch;
727
728 while (vorbis_analysis_blockout(&pCodec->Audio.Vorbis.dsp_state, &pCodec->Audio.Vorbis.block_cur) == 1 /* More available? */)
729 {
730 vorbis_rc = vorbis_analysis(&pCodec->Audio.Vorbis.block_cur, NULL);
731 if (vorbis_rc < 0)
732 {
733 LogRel(("Recording: vorbis_analysis() failed (%d)\n", vorbis_rc));
734 vorbis_rc = 0; /* Reset */
735 vrc = VERR_RECORDING_ENCODING_FAILED;
736 break;
737 }
738
739 vorbis_rc = vorbis_bitrate_addblock(&pCodec->Audio.Vorbis.block_cur);
740 if (vorbis_rc < 0)
741 {
742 LogRel(("Recording: vorbis_bitrate_addblock() failed (%d)\n", vorbis_rc));
743 vorbis_rc = 0; /* Reset */
744 vrc = VERR_RECORDING_ENCODING_FAILED;
745 break;
746 }
747
748 /* Vorbis expects us to flush packets one at a time directly to the container.
749 *
750 * If we flush more than one packet in a row, players can't decode this then. */
751 ogg_packet op;
752 while ((vorbis_rc = vorbis_bitrate_flushpacket(&pCodec->Audio.Vorbis.dsp_state, &op)) > 0)
753 {
754 cBytesEncoded += op.bytes;
755 AssertBreakStmt(cBytesEncoded <= pCodec->cbScratch, vrc = VERR_BUFFER_OVERFLOW);
756 cBlocksEncoded++;
757
758 vrc = pCodec->Callbacks.pfnWriteData(pCodec, op.packet, (size_t)op.bytes, pCodec->State.tsLastWrittenMs,
759 RECORDINGCODEC_ENC_F_BLOCK_IS_KEY /* Every Vorbis frame is a key frame */,
760 pCodec->Callbacks.pvUser);
761 }
762
763 RT_NOREF(puDst);
764
765 /* Note: When vorbis_rc is 0, this marks the last packet, a negative values means error. */
766 if (vorbis_rc < 0)
767 {
768 LogRel(("Recording: vorbis_bitrate_flushpacket() failed (%d)\n", vorbis_rc));
769 vorbis_rc = 0; /* Reset */
770 vrc = VERR_RECORDING_ENCODING_FAILED;
771 break;
772 }
773 }
774
775 if (vorbis_rc < 0)
776 {
777 LogRel(("Recording: vorbis_analysis_blockout() failed (%d)\n", vorbis_rc));
778 return VERR_RECORDING_ENCODING_FAILED;
779 }
780
781 if (RT_FAILURE(vrc))
782 LogRel(("Recording: Encoding Vorbis audio data failed, vrc=%Rrc\n", vrc));
783
784 Log3Func(("cbSrc=%zu, cbDst=%zu, cEncoded=%zu, cbEncoded=%zu, vrc=%Rrc\n",
785 pFrame->u.Audio.cbBuf, pCodec->cbScratch, cBlocksEncoded, cBytesEncoded, vrc));
786
787 return vrc;
788}
789
790/** @copydoc RECORDINGCODECOPS::pfnFinalize */
791static DECLCALLBACK(int) recordingCodecVorbisFinalize(PRECORDINGCODEC pCodec)
792{
793 int vorbis_rc = vorbis_analysis_wrote(&pCodec->Audio.Vorbis.dsp_state, 0 /* Means finalize */);
794 if (vorbis_rc)
795 {
796 LogRel(("Recording: vorbis_analysis_wrote() failed for finalizing stream (%d)\n", vorbis_rc));
797 return VERR_RECORDING_ENCODING_FAILED;
798 }
799
800 return VINF_SUCCESS;
801}
802#endif /* VBOX_WITH_LIBVORBIS */
803
804
805/*********************************************************************************************************************************
806* Codec API *
807*********************************************************************************************************************************/
808
809/**
810 * Initializes an audio codec.
811 *
812 * @returns VBox status code.
813 * @param pCodec Codec instance to initialize.
814 * @param pCallbacks Codec callback table to use for the codec.
815 * @param Settings Screen settings to use for initialization.
816 */
817static int recordingCodecInitAudio(const PRECORDINGCODEC pCodec,
818 const PRECORDINGCODECCALLBACKS pCallbacks, const settings::RecordingScreenSettings &Settings)
819{
820 AssertReturn(pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO, VERR_INVALID_PARAMETER);
821
822 com::Utf8Str strCodec;
823 settings::RecordingScreenSettings::audioCodecToString(pCodec->Parms.enmAudioCodec, strCodec);
824 LogRel(("Recording: Initializing audio codec '%s'\n", strCodec.c_str()));
825
826 const PPDMAUDIOPCMPROPS pPCMProps = &pCodec->Parms.u.Audio.PCMProps;
827
828 PDMAudioPropsInit(pPCMProps,
829 Settings.Audio.cBits / 8,
830 true /* fSigned */, Settings.Audio.cChannels, Settings.Audio.uHz);
831 pCodec->Parms.uBitrate = 0; /** @todo No bitrate management for audio yet. */
832
833 if (pCallbacks)
834 memcpy(&pCodec->Callbacks, pCallbacks, sizeof(RECORDINGCODECCALLBACKS));
835
836 int vrc = VINF_SUCCESS;
837
838 if (pCodec->Ops.pfnParseOptions)
839 vrc = pCodec->Ops.pfnParseOptions(pCodec, Settings.strOptions);
840
841 if (RT_SUCCESS(vrc))
842 vrc = pCodec->Ops.pfnInit(pCodec);
843
844 if (RT_SUCCESS(vrc))
845 {
846 Assert(PDMAudioPropsAreValid(pPCMProps));
847
848 uint32_t uBitrate = pCodec->Parms.uBitrate; /* Bitrate management could have been changed by pfnInit(). */
849
850 LogRel2(("Recording: Audio codec is initialized with %RU32Hz, %RU8 channel(s), %RU8 bits per sample\n",
851 PDMAudioPropsHz(pPCMProps), PDMAudioPropsChannels(pPCMProps), PDMAudioPropsSampleBits(pPCMProps)));
852 LogRel2(("Recording: Audio codec's bitrate management is %s (%RU32 kbps)\n", uBitrate ? "enabled" : "disabled", uBitrate));
853
854 if (!pCodec->Parms.msFrame || pCodec->Parms.msFrame >= RT_MS_1SEC) /* Not set yet by codec stuff above? */
855 pCodec->Parms.msFrame = 20; /* 20ms by default should be a sensible value; to prevent division by zero. */
856
857 pCodec->Parms.csFrame = PDMAudioPropsHz(pPCMProps) / (RT_MS_1SEC / pCodec->Parms.msFrame);
858 pCodec->Parms.cbFrame = PDMAudioPropsFramesToBytes(pPCMProps, pCodec->Parms.csFrame);
859
860 LogFlowFunc(("cbSample=%RU32, msFrame=%RU32 -> csFrame=%RU32, cbFrame=%RU32, uBitrate=%RU32\n",
861 PDMAudioPropsSampleSize(pPCMProps), pCodec->Parms.msFrame, pCodec->Parms.csFrame, pCodec->Parms.cbFrame, pCodec->Parms.uBitrate));
862 }
863 else
864 LogRel(("Recording: Error initializing audio codec (%Rrc)\n", vrc));
865
866 return vrc;
867}
868
869/**
870 * Initializes a video codec.
871 *
872 * @returns VBox status code.
873 * @param pCodec Codec instance to initialize.
874 * @param pCallbacks Codec callback table to use for the codec.
875 * @param Settings Screen settings to use for initialization.
876 */
877static int recordingCodecInitVideo(const PRECORDINGCODEC pCodec,
878 const PRECORDINGCODECCALLBACKS pCallbacks, const settings::RecordingScreenSettings &Settings)
879{
880 com::Utf8Str strTemp;
881 settings::RecordingScreenSettings::videoCodecToString(pCodec->Parms.enmVideoCodec, strTemp);
882 LogRel(("Recording: Initializing video codec '%s'\n", strTemp.c_str()));
883
884 pCodec->Parms.uBitrate = Settings.Video.ulRate;
885 pCodec->Parms.u.Video.uFPS = Settings.Video.ulFPS;
886 pCodec->Parms.u.Video.uWidth = Settings.Video.ulWidth;
887 pCodec->Parms.u.Video.uHeight = Settings.Video.ulHeight;
888 pCodec->Parms.u.Video.uDelayMs = RT_MS_1SEC / pCodec->Parms.u.Video.uFPS;
889
890 if (pCallbacks)
891 memcpy(&pCodec->Callbacks, pCallbacks, sizeof(RECORDINGCODECCALLBACKS));
892
893 AssertReturn(pCodec->Parms.uBitrate, VERR_INVALID_PARAMETER); /* Bitrate must be set. */
894 AssertStmt(pCodec->Parms.u.Video.uFPS, pCodec->Parms.u.Video.uFPS = 25); /* Prevent division by zero. */
895
896 AssertReturn(pCodec->Parms.u.Video.uHeight, VERR_INVALID_PARAMETER);
897 AssertReturn(pCodec->Parms.u.Video.uWidth, VERR_INVALID_PARAMETER);
898 AssertReturn(pCodec->Parms.u.Video.uDelayMs, VERR_INVALID_PARAMETER);
899
900 int vrc = VINF_SUCCESS;
901
902 if (pCodec->Ops.pfnParseOptions)
903 vrc = pCodec->Ops.pfnParseOptions(pCodec, Settings.strOptions);
904
905 if ( RT_SUCCESS(vrc)
906 && pCodec->Ops.pfnInit)
907 vrc = pCodec->Ops.pfnInit(pCodec);
908
909 if (RT_SUCCESS(vrc))
910 {
911 pCodec->Parms.enmType = RECORDINGCODECTYPE_VIDEO;
912 pCodec->Parms.enmVideoCodec = RecordingVideoCodec_VP8; /** @todo No VP9 yet. */
913 }
914 else
915 LogRel(("Recording: Error initializing video codec (%Rrc)\n", vrc));
916
917 return vrc;
918}
919
920#ifdef VBOX_WITH_AUDIO_RECORDING
921/**
922 * Lets an audio codec parse advanced options given from a string.
923 *
924 * @returns VBox status code.
925 * @param pCodec Codec instance to parse options for.
926 * @param strOptions Options string to parse.
927 */
928static DECLCALLBACK(int) recordingCodecAudioParseOptions(PRECORDINGCODEC pCodec, const com::Utf8Str &strOptions)
929{
930 AssertReturn(pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO, VERR_INVALID_PARAMETER);
931
932 size_t pos = 0;
933 com::Utf8Str key, value;
934 while ((pos = strOptions.parseKeyValue(key, value, pos)) != com::Utf8Str::npos)
935 {
936 if (key.compare("ac_profile", com::Utf8Str::CaseInsensitive) == 0)
937 {
938 if (value.compare("low", com::Utf8Str::CaseInsensitive) == 0)
939 {
940 PDMAudioPropsInit(&pCodec->Parms.u.Audio.PCMProps, 16, true /* fSigned */, 1 /* Channels */, 8000 /* Hz */);
941 }
942 else if (value.startsWith("med" /* "med[ium]" */, com::Utf8Str::CaseInsensitive) == 0)
943 {
944 /* Stay with the defaults. */
945 }
946 else if (value.compare("high", com::Utf8Str::CaseInsensitive) == 0)
947 {
948 PDMAudioPropsInit(&pCodec->Parms.u.Audio.PCMProps, 16, true /* fSigned */, 2 /* Channels */, 48000 /* Hz */);
949 }
950 }
951 else
952 LogRel(("Recording: Unknown option '%s' (value '%s'), skipping\n", key.c_str(), value.c_str()));
953
954 } /* while */
955
956 return VINF_SUCCESS;
957}
958#endif
959
960static void recordingCodecReset(PRECORDINGCODEC pCodec)
961{
962 pCodec->State.tsLastWrittenMs = 0;
963 pCodec->State.cEncErrors = 0;
964}
965
966/**
967 * Common code for codec creation.
968 *
969 * @param pCodec Codec instance to create.
970 */
971static void recordingCodecCreateCommon(PRECORDINGCODEC pCodec)
972{
973 RT_ZERO(pCodec->Ops);
974 RT_ZERO(pCodec->Callbacks);
975}
976
977/**
978 * Creates an audio codec.
979 *
980 * @returns VBox status code.
981 * @param pCodec Codec instance to create.
982 * @param enmAudioCodec Audio codec to create.
983 */
984int recordingCodecCreateAudio(PRECORDINGCODEC pCodec, RecordingAudioCodec_T enmAudioCodec)
985{
986 int vrc;
987
988 recordingCodecCreateCommon(pCodec);
989
990 switch (enmAudioCodec)
991 {
992# ifdef VBOX_WITH_LIBVORBIS
993 case RecordingAudioCodec_OggVorbis:
994 {
995 pCodec->Ops.pfnInit = recordingCodecVorbisInit;
996 pCodec->Ops.pfnDestroy = recordingCodecVorbisDestroy;
997 pCodec->Ops.pfnParseOptions = recordingCodecAudioParseOptions;
998 pCodec->Ops.pfnEncode = recordingCodecVorbisEncode;
999 pCodec->Ops.pfnFinalize = recordingCodecVorbisFinalize;
1000
1001 vrc = VINF_SUCCESS;
1002 break;
1003 }
1004# endif /* VBOX_WITH_LIBVORBIS */
1005
1006 default:
1007 LogRel(("Recording: Selected codec is not supported!\n"));
1008 vrc = VERR_RECORDING_CODEC_NOT_SUPPORTED;
1009 break;
1010 }
1011
1012 if (RT_SUCCESS(vrc))
1013 {
1014 pCodec->Parms.enmType = RECORDINGCODECTYPE_AUDIO;
1015 pCodec->Parms.enmAudioCodec = enmAudioCodec;
1016 }
1017
1018 return vrc;
1019}
1020
1021/**
1022 * Creates a video codec.
1023 *
1024 * @returns VBox status code.
1025 * @param pCodec Codec instance to create.
1026 * @param enmVideoCodec Video codec to create.
1027 */
1028int recordingCodecCreateVideo(PRECORDINGCODEC pCodec, RecordingVideoCodec_T enmVideoCodec)
1029{
1030 int vrc;
1031
1032 recordingCodecCreateCommon(pCodec);
1033
1034 switch (enmVideoCodec)
1035 {
1036# ifdef VBOX_WITH_LIBVPX
1037 case RecordingVideoCodec_VP8:
1038 {
1039 pCodec->Ops.pfnInit = recordingCodecVPXInit;
1040 pCodec->Ops.pfnDestroy = recordingCodecVPXDestroy;
1041 pCodec->Ops.pfnFinalize = recordingCodecVPXFinalize;
1042 pCodec->Ops.pfnParseOptions = recordingCodecVPXParseOptions;
1043 pCodec->Ops.pfnEncode = recordingCodecVPXEncode;
1044 pCodec->Ops.pfnScreenChange = recordingCodecVPXScreenChange;
1045
1046 vrc = VINF_SUCCESS;
1047 break;
1048 }
1049# endif /* VBOX_WITH_LIBVPX */
1050
1051 default:
1052 vrc = VERR_RECORDING_CODEC_NOT_SUPPORTED;
1053 break;
1054 }
1055
1056 if (RT_SUCCESS(vrc))
1057 {
1058 pCodec->Parms.enmType = RECORDINGCODECTYPE_VIDEO;
1059 pCodec->Parms.enmVideoCodec = enmVideoCodec;
1060 }
1061
1062 return vrc;
1063}
1064
1065/**
1066 * Initializes a codec.
1067 *
1068 * @returns VBox status code.
1069 * @param pCodec Codec to initialize.
1070 * @param pCallbacks Codec callback table to use. Optional and may be NULL.
1071 * @param Settings Settings to use for initializing the codec.
1072 */
1073int recordingCodecInit(const PRECORDINGCODEC pCodec, const PRECORDINGCODECCALLBACKS pCallbacks, const settings::RecordingScreenSettings &Settings)
1074{
1075 int vrc = RTCritSectInit(&pCodec->CritSect);
1076 AssertRCReturn(vrc, vrc);
1077
1078 pCodec->cbScratch = 0;
1079 pCodec->pvScratch = NULL;
1080
1081 recordingCodecReset(pCodec);
1082
1083 if (pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO)
1084 vrc = recordingCodecInitAudio(pCodec, pCallbacks, Settings);
1085 else if (pCodec->Parms.enmType == RECORDINGCODECTYPE_VIDEO)
1086 vrc = recordingCodecInitVideo(pCodec, pCallbacks, Settings);
1087 else
1088 AssertFailedStmt(vrc = VERR_NOT_SUPPORTED);
1089
1090 return vrc;
1091}
1092
1093/**
1094 * Destroys an audio codec.
1095 *
1096 * @returns VBox status code.
1097 * @param pCodec Codec to destroy.
1098 */
1099static int recordingCodecDestroyAudio(PRECORDINGCODEC pCodec)
1100{
1101 AssertReturn(pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO, VERR_INVALID_PARAMETER);
1102
1103 return pCodec->Ops.pfnDestroy(pCodec);
1104}
1105
1106/**
1107 * Destroys a video codec.
1108 *
1109 * @returns VBox status code.
1110 * @param pCodec Codec to destroy.
1111 */
1112static int recordingCodecDestroyVideo(PRECORDINGCODEC pCodec)
1113{
1114 AssertReturn(pCodec->Parms.enmType == RECORDINGCODECTYPE_VIDEO, VERR_INVALID_PARAMETER);
1115
1116 return pCodec->Ops.pfnDestroy(pCodec);
1117}
1118
1119/**
1120 * Destroys the codec.
1121 *
1122 * @returns VBox status code.
1123 * @param pCodec Codec to destroy.
1124 */
1125int recordingCodecDestroy(PRECORDINGCODEC pCodec)
1126{
1127 if (pCodec->Parms.enmType == RECORDINGCODECTYPE_INVALID)
1128 return VINF_SUCCESS;
1129
1130 int vrc;
1131
1132 if (pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO)
1133 vrc = recordingCodecDestroyAudio(pCodec);
1134 else if (pCodec->Parms.enmType == RECORDINGCODECTYPE_VIDEO)
1135 vrc =recordingCodecDestroyVideo(pCodec);
1136 else
1137 AssertFailedReturn(VERR_NOT_SUPPORTED);
1138
1139 if (RT_SUCCESS(vrc))
1140 {
1141 if (pCodec->pvScratch)
1142 {
1143 Assert(pCodec->cbScratch);
1144 RTMemFree(pCodec->pvScratch);
1145 pCodec->pvScratch = NULL;
1146 pCodec->cbScratch = 0;
1147 }
1148
1149 pCodec->Parms.enmType = RECORDINGCODECTYPE_INVALID;
1150 pCodec->Parms.enmVideoCodec = RecordingVideoCodec_None;
1151
1152 int vrc2 = RTCritSectDelete(&pCodec->CritSect);
1153 AssertRC(vrc2);
1154 }
1155
1156 return vrc;
1157}
1158
1159/**
1160 * Feeds the codec encoder with frame data to encode.
1161 *
1162 * @returns VBox status code.
1163 * @param pCodec Codec to use.
1164 * @param pFrame Pointer to frame data to encode.
1165 * @param msTimestamp Timestamp (PTS) to use for encoding.
1166 * @param pvUser User data pointer. Optional and can be NULL.
1167 */
1168int recordingCodecEncodeFrame(PRECORDINGCODEC pCodec, const PRECORDINGFRAME pFrame, uint64_t msTimestamp, void *pvUser)
1169{
1170 AssertPtrReturn(pCodec->Ops.pfnEncode, VERR_NOT_SUPPORTED);
1171
1172 int vrc = pCodec->Ops.pfnEncode(pCodec, pFrame, msTimestamp, pvUser);
1173 if (RT_SUCCESS(vrc))
1174 pCodec->State.tsLastWrittenMs = pFrame->msTimestamp;
1175
1176 return vrc;
1177}
1178
1179/**
1180 * Feeds the codec encoder with the current composed image.
1181 *
1182 * @returns VBox status code.
1183 * @param pCodec Codec to use.
1184 * @param msTimestamp Timestamp (PTS) to use for encoding.
1185 */
1186int recordingCodecEncodeCurrent(PRECORDINGCODEC pCodec, uint64_t msTimestamp)
1187{
1188 int vrc = pCodec->Ops.pfnEncode(pCodec, NULL /* pFrame */, msTimestamp, NULL /* pvUser */);
1189 if (RT_SUCCESS(vrc))
1190 pCodec->State.tsLastWrittenMs = msTimestamp;
1191
1192 return vrc;
1193}
1194
1195/**
1196 * Lets the codec know that a screen change has happened.
1197 *
1198 * @returns VBox status code.
1199 * @param pCodec Codec to use.
1200 * @param pInfo Screen info to send.
1201 */
1202int recordingCodecScreenChange(PRECORDINGCODEC pCodec, PRECORDINGSURFACEINFO pInfo)
1203{
1204 if (!pCodec->Ops.pfnScreenChange)
1205 return VINF_SUCCESS;
1206
1207 return pCodec->Ops.pfnScreenChange(pCodec, pInfo);
1208}
1209
1210/**
1211 * Tells the codec that has to finalize the stream.
1212 *
1213 * @returns VBox status code.
1214 * @param pCodec Codec to finalize stream for.
1215 */
1216int recordingCodecFinalize(PRECORDINGCODEC pCodec)
1217{
1218 if (pCodec->Ops.pfnFinalize)
1219 return pCodec->Ops.pfnFinalize(pCodec);
1220 return VINF_SUCCESS;
1221}
1222
1223/**
1224 * Returns whether the codec has been initialized or not.
1225 *
1226 * @returns @c true if initialized, or @c false if not.
1227 * @param pCodec Codec to return initialization status for.
1228 */
1229bool recordingCodecIsInitialized(const PRECORDINGCODEC pCodec)
1230{
1231 return pCodec->Ops.pfnInit != NULL; /* pfnInit acts as a beacon for initialization status. */
1232}
1233
1234/**
1235 * Returns the number of writable bytes for a given timestamp.
1236 *
1237 * This basically is a helper function to respect the set frames per second (FPS).
1238 *
1239 * @returns Number of writable bytes.
1240 * @param pCodec Codec to return number of writable bytes for.
1241 * @param msTimestamp Timestamp (PTS, in ms) return number of writable bytes for.
1242 */
1243uint32_t recordingCodecGetWritable(const PRECORDINGCODEC pCodec, uint64_t msTimestamp)
1244{
1245 Log3Func(("%RU64 -- tsLastWrittenMs=%RU64 + uDelayMs=%RU32\n",
1246 msTimestamp, pCodec->State.tsLastWrittenMs,pCodec->Parms.u.Video.uDelayMs));
1247
1248 if (msTimestamp < pCodec->State.tsLastWrittenMs + pCodec->Parms.u.Video.uDelayMs)
1249 return 0; /* Too early for writing (respect set FPS). */
1250
1251 /* For now we just return the complete frame space. */
1252 AssertMsg(pCodec->Parms.cbFrame, ("Codec not initialized yet\n"));
1253 return pCodec->Parms.cbFrame;
1254}
1255
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette