VirtualBox

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

Last change on this file was 106061, checked in by vboxsync, 8 weeks ago

Copyright year updates by scm.

  • 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 106061 2024-09-16 14:03:52Z 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 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