VirtualBox

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

Last change on this file since 107770 was 107646, checked in by vboxsync, 5 weeks ago

Main/src-client/RecordingCodec.cpp: Enclose debug log only code in #ifdef LOG_ENABLED #endif, bugref:3409

  • 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 107646 2025-01-10 13:18:16Z vboxsync $ */
2/** @file
3 * Recording codec wrapper.
4 */
5
6/*
7 * Copyright (C) 2022-2024 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#ifdef LOG_ENABLED
705 size_t cBlocksEncoded = 0;
706#endif
707 size_t cBytesEncoded = 0;
708
709 uint8_t *puDst = (uint8_t *)pCodec->pvScratch;
710
711 while (vorbis_analysis_blockout(&pCodec->Audio.Vorbis.dsp_state, &pCodec->Audio.Vorbis.block_cur) == 1 /* More available? */)
712 {
713 vorbis_rc = vorbis_analysis(&pCodec->Audio.Vorbis.block_cur, NULL);
714 if (vorbis_rc < 0)
715 {
716 LogRel(("Recording: vorbis_analysis() failed (%d)\n", vorbis_rc));
717 vorbis_rc = 0; /* Reset */
718 vrc = VERR_RECORDING_ENCODING_FAILED;
719 break;
720 }
721
722 vorbis_rc = vorbis_bitrate_addblock(&pCodec->Audio.Vorbis.block_cur);
723 if (vorbis_rc < 0)
724 {
725 LogRel(("Recording: vorbis_bitrate_addblock() failed (%d)\n", vorbis_rc));
726 vorbis_rc = 0; /* Reset */
727 vrc = VERR_RECORDING_ENCODING_FAILED;
728 break;
729 }
730
731 /* Vorbis expects us to flush packets one at a time directly to the container.
732 *
733 * If we flush more than one packet in a row, players can't decode this then. */
734 ogg_packet op;
735 while ((vorbis_rc = vorbis_bitrate_flushpacket(&pCodec->Audio.Vorbis.dsp_state, &op)) > 0)
736 {
737 cBytesEncoded += op.bytes;
738 AssertBreakStmt(cBytesEncoded <= pCodec->cbScratch, vrc = VERR_BUFFER_OVERFLOW);
739#ifdef LOG_ENABLED
740 cBlocksEncoded++;
741#endif
742
743 vrc = pCodec->Callbacks.pfnWriteData(pCodec, op.packet, (size_t)op.bytes, pCodec->State.tsLastWrittenMs,
744 RECORDINGCODEC_ENC_F_BLOCK_IS_KEY /* Every Vorbis frame is a key frame */,
745 pCodec->Callbacks.pvUser);
746 }
747
748 RT_NOREF(puDst);
749
750 /* Note: When vorbis_rc is 0, this marks the last packet, a negative values means error. */
751 if (vorbis_rc < 0)
752 {
753 LogRel(("Recording: vorbis_bitrate_flushpacket() failed (%d)\n", vorbis_rc));
754 vorbis_rc = 0; /* Reset */
755 vrc = VERR_RECORDING_ENCODING_FAILED;
756 break;
757 }
758 }
759
760 if (vorbis_rc < 0)
761 {
762 LogRel(("Recording: vorbis_analysis_blockout() failed (%d)\n", vorbis_rc));
763 return VERR_RECORDING_ENCODING_FAILED;
764 }
765
766 if (RT_FAILURE(vrc))
767 LogRel(("Recording: Encoding Vorbis audio data failed, vrc=%Rrc\n", vrc));
768
769 Log3Func(("cbSrc=%zu, cbDst=%zu, cEncoded=%zu, cbEncoded=%zu, vrc=%Rrc\n",
770 pFrame->u.Audio.cbBuf, pCodec->cbScratch, cBlocksEncoded, cBytesEncoded, vrc));
771
772 return vrc;
773}
774
775/** @copydoc RECORDINGCODECOPS::pfnFinalize */
776static DECLCALLBACK(int) recordingCodecVorbisFinalize(PRECORDINGCODEC pCodec)
777{
778 int vorbis_rc = vorbis_analysis_wrote(&pCodec->Audio.Vorbis.dsp_state, 0 /* Means finalize */);
779 if (vorbis_rc)
780 {
781 LogRel(("Recording: vorbis_analysis_wrote() failed for finalizing stream (%d)\n", vorbis_rc));
782 return VERR_RECORDING_ENCODING_FAILED;
783 }
784
785 return VINF_SUCCESS;
786}
787#endif /* VBOX_WITH_LIBVORBIS */
788
789
790/*********************************************************************************************************************************
791* Codec API *
792*********************************************************************************************************************************/
793
794/**
795 * Initializes an audio codec.
796 *
797 * @returns VBox status code.
798 * @param pCodec Codec instance to initialize.
799 * @param pCallbacks Codec callback table to use for the codec.
800 * @param Settings Screen settings to use for initialization.
801 */
802static int recordingCodecInitAudio(const PRECORDINGCODEC pCodec,
803 const PRECORDINGCODECCALLBACKS pCallbacks, const settings::RecordingScreen &Settings)
804{
805 AssertReturn(pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO, VERR_INVALID_PARAMETER);
806
807 com::Utf8Str strCodec;
808 settings::RecordingScreen::audioCodecToString(pCodec->Parms.enmAudioCodec, strCodec);
809 LogRel(("Recording: Initializing audio codec '%s'\n", strCodec.c_str()));
810
811 const PPDMAUDIOPCMPROPS pPCMProps = &pCodec->Parms.u.Audio.PCMProps;
812
813 PDMAudioPropsInit(pPCMProps,
814 Settings.Audio.cBits / 8,
815 true /* fSigned */, Settings.Audio.cChannels, Settings.Audio.uHz);
816 pCodec->Parms.uBitrate = 0; /** @todo No bitrate management for audio yet. */
817
818 if (pCallbacks)
819 memcpy(&pCodec->Callbacks, pCallbacks, sizeof(RECORDINGCODECCALLBACKS));
820
821 int vrc = VINF_SUCCESS;
822
823 if (pCodec->Ops.pfnParseOptions)
824 vrc = pCodec->Ops.pfnParseOptions(pCodec, Settings.strOptions);
825
826 if (RT_SUCCESS(vrc))
827 vrc = pCodec->Ops.pfnInit(pCodec);
828
829 if (RT_SUCCESS(vrc))
830 {
831 Assert(PDMAudioPropsAreValid(pPCMProps));
832
833 uint32_t uBitrate = pCodec->Parms.uBitrate; /* Bitrate management could have been changed by pfnInit(). */
834
835 LogRel2(("Recording: Audio codec is initialized with %RU32Hz, %RU8 channel(s), %RU8 bits per sample\n",
836 PDMAudioPropsHz(pPCMProps), PDMAudioPropsChannels(pPCMProps), PDMAudioPropsSampleBits(pPCMProps)));
837 LogRel2(("Recording: Audio codec's bitrate management is %s (%RU32 kbps)\n", uBitrate ? "enabled" : "disabled", uBitrate));
838
839 if (!pCodec->Parms.msFrame || pCodec->Parms.msFrame >= RT_MS_1SEC) /* Not set yet by codec stuff above? */
840 pCodec->Parms.msFrame = 20; /* 20ms by default should be a sensible value; to prevent division by zero. */
841
842 pCodec->Parms.csFrame = PDMAudioPropsHz(pPCMProps) / (RT_MS_1SEC / pCodec->Parms.msFrame);
843 pCodec->Parms.cbFrame = PDMAudioPropsFramesToBytes(pPCMProps, pCodec->Parms.csFrame);
844
845 LogFlowFunc(("cbSample=%RU32, msFrame=%RU32 -> csFrame=%RU32, cbFrame=%RU32, uBitrate=%RU32\n",
846 PDMAudioPropsSampleSize(pPCMProps), pCodec->Parms.msFrame, pCodec->Parms.csFrame, pCodec->Parms.cbFrame, pCodec->Parms.uBitrate));
847 }
848 else
849 LogRel(("Recording: Error initializing audio codec (%Rrc)\n", vrc));
850
851 return vrc;
852}
853
854/**
855 * Initializes a video codec.
856 *
857 * @returns VBox status code.
858 * @param pCodec Codec instance to initialize.
859 * @param pCallbacks Codec callback table to use for the codec.
860 * @param Settings Screen settings to use for initialization.
861 */
862static int recordingCodecInitVideo(const PRECORDINGCODEC pCodec,
863 const PRECORDINGCODECCALLBACKS pCallbacks, const settings::RecordingScreen &Settings)
864{
865 com::Utf8Str strTemp;
866 settings::RecordingScreen::videoCodecToString(pCodec->Parms.enmVideoCodec, strTemp);
867 LogRel(("Recording: Initializing video codec '%s'\n", strTemp.c_str()));
868
869 pCodec->Parms.uBitrate = Settings.Video.ulRate;
870 pCodec->Parms.u.Video.uFPS = Settings.Video.ulFPS;
871 pCodec->Parms.u.Video.uWidth = Settings.Video.ulWidth;
872 pCodec->Parms.u.Video.uHeight = Settings.Video.ulHeight;
873 pCodec->Parms.u.Video.uDelayMs = RT_MS_1SEC / pCodec->Parms.u.Video.uFPS;
874
875 if (pCallbacks)
876 memcpy(&pCodec->Callbacks, pCallbacks, sizeof(RECORDINGCODECCALLBACKS));
877
878 AssertReturn(pCodec->Parms.uBitrate, VERR_INVALID_PARAMETER); /* Bitrate must be set. */
879 AssertStmt(pCodec->Parms.u.Video.uFPS, pCodec->Parms.u.Video.uFPS = 25); /* Prevent division by zero. */
880
881 AssertReturn(pCodec->Parms.u.Video.uHeight, VERR_INVALID_PARAMETER);
882 AssertReturn(pCodec->Parms.u.Video.uWidth, VERR_INVALID_PARAMETER);
883 AssertReturn(pCodec->Parms.u.Video.uDelayMs, VERR_INVALID_PARAMETER);
884
885 int vrc = VINF_SUCCESS;
886
887 if (pCodec->Ops.pfnParseOptions)
888 vrc = pCodec->Ops.pfnParseOptions(pCodec, Settings.strOptions);
889
890 if ( RT_SUCCESS(vrc)
891 && pCodec->Ops.pfnInit)
892 vrc = pCodec->Ops.pfnInit(pCodec);
893
894 if (RT_SUCCESS(vrc))
895 {
896 pCodec->Parms.enmType = RECORDINGCODECTYPE_VIDEO;
897 pCodec->Parms.enmVideoCodec = RecordingVideoCodec_VP8; /** @todo No VP9 yet. */
898 }
899 else
900 LogRel(("Recording: Error initializing video codec (%Rrc)\n", vrc));
901
902 return vrc;
903}
904
905#ifdef VBOX_WITH_AUDIO_RECORDING
906/**
907 * Lets an audio codec parse advanced options given from a string.
908 *
909 * @returns VBox status code.
910 * @param pCodec Codec instance to parse options for.
911 * @param strOptions Options string to parse.
912 */
913static DECLCALLBACK(int) recordingCodecAudioParseOptions(PRECORDINGCODEC pCodec, const com::Utf8Str &strOptions)
914{
915 AssertReturn(pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO, VERR_INVALID_PARAMETER);
916
917 size_t pos = 0;
918 com::Utf8Str key, value;
919 while ((pos = strOptions.parseKeyValue(key, value, pos)) != com::Utf8Str::npos)
920 {
921 if (key.compare("ac_profile", com::Utf8Str::CaseInsensitive) == 0)
922 {
923 if (value.compare("low", com::Utf8Str::CaseInsensitive) == 0)
924 {
925 PDMAudioPropsInit(&pCodec->Parms.u.Audio.PCMProps, 16, true /* fSigned */, 1 /* Channels */, 8000 /* Hz */);
926 }
927 else if (value.startsWith("med" /* "med[ium]" */, com::Utf8Str::CaseInsensitive) == 0)
928 {
929 /* Stay with the defaults. */
930 }
931 else if (value.compare("high", com::Utf8Str::CaseInsensitive) == 0)
932 {
933 PDMAudioPropsInit(&pCodec->Parms.u.Audio.PCMProps, 16, true /* fSigned */, 2 /* Channels */, 48000 /* Hz */);
934 }
935 }
936 else
937 LogRel(("Recording: Unknown option '%s' (value '%s'), skipping\n", key.c_str(), value.c_str()));
938
939 } /* while */
940
941 return VINF_SUCCESS;
942}
943#endif
944
945static void recordingCodecReset(PRECORDINGCODEC pCodec)
946{
947 pCodec->State.tsLastWrittenMs = 0;
948 pCodec->State.cEncErrors = 0;
949}
950
951/**
952 * Common code for codec creation.
953 *
954 * @param pCodec Codec instance to create.
955 */
956static void recordingCodecCreateCommon(PRECORDINGCODEC pCodec)
957{
958 RT_ZERO(pCodec->Ops);
959 RT_ZERO(pCodec->Callbacks);
960}
961
962/**
963 * Creates an audio codec.
964 *
965 * @returns VBox status code.
966 * @param pCodec Codec instance to create.
967 * @param enmAudioCodec Audio codec to create.
968 */
969int recordingCodecCreateAudio(PRECORDINGCODEC pCodec, RecordingAudioCodec_T enmAudioCodec)
970{
971 int vrc;
972
973 recordingCodecCreateCommon(pCodec);
974
975 switch (enmAudioCodec)
976 {
977# ifdef VBOX_WITH_LIBVORBIS
978 case RecordingAudioCodec_OggVorbis:
979 {
980 pCodec->Ops.pfnInit = recordingCodecVorbisInit;
981 pCodec->Ops.pfnDestroy = recordingCodecVorbisDestroy;
982 pCodec->Ops.pfnParseOptions = recordingCodecAudioParseOptions;
983 pCodec->Ops.pfnEncode = recordingCodecVorbisEncode;
984 pCodec->Ops.pfnFinalize = recordingCodecVorbisFinalize;
985
986 vrc = VINF_SUCCESS;
987 break;
988 }
989# endif /* VBOX_WITH_LIBVORBIS */
990
991 default:
992 LogRel(("Recording: Selected codec is not supported!\n"));
993 vrc = VERR_RECORDING_CODEC_NOT_SUPPORTED;
994 break;
995 }
996
997 if (RT_SUCCESS(vrc))
998 {
999 pCodec->Parms.enmType = RECORDINGCODECTYPE_AUDIO;
1000 pCodec->Parms.enmAudioCodec = enmAudioCodec;
1001 }
1002
1003 return vrc;
1004}
1005
1006/**
1007 * Creates a video codec.
1008 *
1009 * @returns VBox status code.
1010 * @param pCodec Codec instance to create.
1011 * @param enmVideoCodec Video codec to create.
1012 */
1013int recordingCodecCreateVideo(PRECORDINGCODEC pCodec, RecordingVideoCodec_T enmVideoCodec)
1014{
1015 int vrc;
1016
1017 recordingCodecCreateCommon(pCodec);
1018
1019 switch (enmVideoCodec)
1020 {
1021# ifdef VBOX_WITH_LIBVPX
1022 case RecordingVideoCodec_VP8:
1023 {
1024 pCodec->Ops.pfnInit = recordingCodecVPXInit;
1025 pCodec->Ops.pfnDestroy = recordingCodecVPXDestroy;
1026 pCodec->Ops.pfnFinalize = recordingCodecVPXFinalize;
1027 pCodec->Ops.pfnParseOptions = recordingCodecVPXParseOptions;
1028 pCodec->Ops.pfnEncode = recordingCodecVPXEncode;
1029 pCodec->Ops.pfnScreenChange = recordingCodecVPXScreenChange;
1030
1031 vrc = VINF_SUCCESS;
1032 break;
1033 }
1034# endif /* VBOX_WITH_LIBVPX */
1035
1036 default:
1037 vrc = VERR_RECORDING_CODEC_NOT_SUPPORTED;
1038 break;
1039 }
1040
1041 if (RT_SUCCESS(vrc))
1042 {
1043 pCodec->Parms.enmType = RECORDINGCODECTYPE_VIDEO;
1044 pCodec->Parms.enmVideoCodec = enmVideoCodec;
1045 }
1046
1047 return vrc;
1048}
1049
1050/**
1051 * Initializes a codec.
1052 *
1053 * @returns VBox status code.
1054 * @param pCodec Codec to initialize.
1055 * @param pCallbacks Codec callback table to use. Optional and may be NULL.
1056 * @param Settings Settings to use for initializing the codec.
1057 */
1058int recordingCodecInit(const PRECORDINGCODEC pCodec, const PRECORDINGCODECCALLBACKS pCallbacks, const settings::RecordingScreen &Settings)
1059{
1060 int vrc = RTCritSectInit(&pCodec->CritSect);
1061 AssertRCReturn(vrc, vrc);
1062
1063 pCodec->cbScratch = 0;
1064 pCodec->pvScratch = NULL;
1065
1066 recordingCodecReset(pCodec);
1067
1068 if (pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO)
1069 vrc = recordingCodecInitAudio(pCodec, pCallbacks, Settings);
1070 else if (pCodec->Parms.enmType == RECORDINGCODECTYPE_VIDEO)
1071 vrc = recordingCodecInitVideo(pCodec, pCallbacks, Settings);
1072 else
1073 AssertFailedStmt(vrc = VERR_NOT_SUPPORTED);
1074
1075 return vrc;
1076}
1077
1078/**
1079 * Destroys an audio codec.
1080 *
1081 * @returns VBox status code.
1082 * @param pCodec Codec to destroy.
1083 */
1084static int recordingCodecDestroyAudio(PRECORDINGCODEC pCodec)
1085{
1086 AssertReturn(pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO, VERR_INVALID_PARAMETER);
1087
1088 return pCodec->Ops.pfnDestroy(pCodec);
1089}
1090
1091/**
1092 * Destroys a video codec.
1093 *
1094 * @returns VBox status code.
1095 * @param pCodec Codec to destroy.
1096 */
1097static int recordingCodecDestroyVideo(PRECORDINGCODEC pCodec)
1098{
1099 AssertReturn(pCodec->Parms.enmType == RECORDINGCODECTYPE_VIDEO, VERR_INVALID_PARAMETER);
1100
1101 return pCodec->Ops.pfnDestroy(pCodec);
1102}
1103
1104/**
1105 * Destroys the codec.
1106 *
1107 * @returns VBox status code.
1108 * @param pCodec Codec to destroy.
1109 */
1110int recordingCodecDestroy(PRECORDINGCODEC pCodec)
1111{
1112 if (pCodec->Parms.enmType == RECORDINGCODECTYPE_INVALID)
1113 return VINF_SUCCESS;
1114
1115 int vrc;
1116
1117 if (pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO)
1118 vrc = recordingCodecDestroyAudio(pCodec);
1119 else if (pCodec->Parms.enmType == RECORDINGCODECTYPE_VIDEO)
1120 vrc =recordingCodecDestroyVideo(pCodec);
1121 else
1122 AssertFailedReturn(VERR_NOT_SUPPORTED);
1123
1124 if (RT_SUCCESS(vrc))
1125 {
1126 if (pCodec->pvScratch)
1127 {
1128 Assert(pCodec->cbScratch);
1129 RTMemFree(pCodec->pvScratch);
1130 pCodec->pvScratch = NULL;
1131 pCodec->cbScratch = 0;
1132 }
1133
1134 pCodec->Parms.enmType = RECORDINGCODECTYPE_INVALID;
1135 pCodec->Parms.enmVideoCodec = RecordingVideoCodec_None;
1136
1137 int vrc2 = RTCritSectDelete(&pCodec->CritSect);
1138 AssertRC(vrc2);
1139 }
1140
1141 return vrc;
1142}
1143
1144/**
1145 * Feeds the codec encoder with frame data to encode.
1146 *
1147 * @returns VBox status code.
1148 * @param pCodec Codec to use.
1149 * @param pFrame Pointer to frame data to encode.
1150 * @param msTimestamp Timestamp (PTS) to use for encoding.
1151 * @param pvUser User data pointer. Optional and can be NULL.
1152 */
1153int recordingCodecEncodeFrame(PRECORDINGCODEC pCodec, const PRECORDINGFRAME pFrame, uint64_t msTimestamp, void *pvUser)
1154{
1155 AssertPtrReturn(pCodec->Ops.pfnEncode, VERR_NOT_SUPPORTED);
1156
1157 int vrc = pCodec->Ops.pfnEncode(pCodec, pFrame, msTimestamp, pvUser);
1158 if (RT_SUCCESS(vrc))
1159 pCodec->State.tsLastWrittenMs = pFrame->msTimestamp;
1160
1161 return vrc;
1162}
1163
1164/**
1165 * Feeds the codec encoder with the current composed image.
1166 *
1167 * @returns VBox status code.
1168 * @param pCodec Codec to use.
1169 * @param msTimestamp Timestamp (PTS) to use for encoding.
1170 */
1171int recordingCodecEncodeCurrent(PRECORDINGCODEC pCodec, uint64_t msTimestamp)
1172{
1173 int vrc = pCodec->Ops.pfnEncode(pCodec, NULL /* pFrame */, msTimestamp, NULL /* pvUser */);
1174 if (RT_SUCCESS(vrc))
1175 pCodec->State.tsLastWrittenMs = msTimestamp;
1176
1177 return vrc;
1178}
1179
1180/**
1181 * Lets the codec know that a screen change has happened.
1182 *
1183 * @returns VBox status code.
1184 * @param pCodec Codec to use.
1185 * @param pInfo Screen info to send.
1186 */
1187int recordingCodecScreenChange(PRECORDINGCODEC pCodec, PRECORDINGSURFACEINFO pInfo)
1188{
1189 LogRel2(("Recording: Codec got screen change notification (%RU16x%RU16, %RU8 BPP)\n",
1190 pInfo->uWidth, pInfo->uHeight, pInfo->uBPP));
1191
1192 if (!pCodec->Ops.pfnScreenChange)
1193 return VINF_SUCCESS;
1194
1195 /* Fend-off bogus reports. */
1196 if ( !pInfo->uWidth
1197 || !pInfo->uHeight)
1198 return VERR_INVALID_PARAMETER;
1199 AssertReturn(pInfo->uBPP % 8 == 0, VERR_INVALID_PARAMETER);
1200
1201 return pCodec->Ops.pfnScreenChange(pCodec, pInfo);
1202}
1203
1204/**
1205 * Tells the codec that has to finalize the stream.
1206 *
1207 * @returns VBox status code.
1208 * @param pCodec Codec to finalize stream for.
1209 */
1210int recordingCodecFinalize(PRECORDINGCODEC pCodec)
1211{
1212 if (pCodec->Ops.pfnFinalize)
1213 return pCodec->Ops.pfnFinalize(pCodec);
1214 return VINF_SUCCESS;
1215}
1216
1217/**
1218 * Returns whether the codec has been initialized or not.
1219 *
1220 * @returns @c true if initialized, or @c false if not.
1221 * @param pCodec Codec to return initialization status for.
1222 */
1223bool recordingCodecIsInitialized(const PRECORDINGCODEC pCodec)
1224{
1225 return pCodec->Ops.pfnInit != NULL; /* pfnInit acts as a beacon for initialization status. */
1226}
1227
1228/**
1229 * Returns the number of writable bytes for a given timestamp.
1230 *
1231 * This basically is a helper function to respect the set frames per second (FPS).
1232 *
1233 * @returns Number of writable bytes.
1234 * @param pCodec Codec to return number of writable bytes for.
1235 * @param msTimestamp Timestamp (PTS, in ms) return number of writable bytes for.
1236 */
1237uint32_t recordingCodecGetWritable(const PRECORDINGCODEC pCodec, uint64_t msTimestamp)
1238{
1239 Log3Func(("%RU64 -- tsLastWrittenMs=%RU64 + uDelayMs=%RU32\n",
1240 msTimestamp, pCodec->State.tsLastWrittenMs,pCodec->Parms.u.Video.uDelayMs));
1241
1242 if (msTimestamp < pCodec->State.tsLastWrittenMs + pCodec->Parms.u.Video.uDelayMs)
1243 return 0; /* Too early for writing (respect set FPS). */
1244
1245 /* For now we just return the complete frame space. */
1246 AssertMsg(pCodec->Parms.cbFrame, ("Codec not initialized yet\n"));
1247 return pCodec->Parms.cbFrame;
1248}
1249
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