VirtualBox

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

Last change on this file since 105605 was 105605, checked in by vboxsync, 6 months ago

Recording/Main: Renaming: Dropped the superfluous "Settings" suffix of the settings namespace recording classes. This makes those classes easier to find, also for editors which don't support namespace handling, as the same class names were used for the actual implementation classes (i.e. RecordingSettingsImpl.cpp -> RecordingSettings). No functional changes.

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

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