VirtualBox

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

Last change on this file since 98123 was 98103, checked in by vboxsync, 23 months ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 32.3 KB
Line 
1/* $Id: RecordingCodec.cpp 98103 2023-01-17 14:15:46Z 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 "RecordingInternals.h"
69#include "RecordingUtils.h"
70#include "WebMWriter.h"
71
72#include <math.h>
73
74
75/*********************************************************************************************************************************
76* VPX (VP8 / VP9) codec *
77*********************************************************************************************************************************/
78
79#ifdef VBOX_WITH_LIBVPX
80/** @copydoc RECORDINGCODECOPS::pfnInit */
81static DECLCALLBACK(int) recordingCodecVPXInit(PRECORDINGCODEC pCodec)
82{
83 pCodec->cbScratch = _4K;
84 pCodec->pvScratch = RTMemAlloc(pCodec->cbScratch);
85 AssertPtrReturn(pCodec->pvScratch, VERR_NO_MEMORY);
86
87 pCodec->Parms.csFrame = 0;
88 pCodec->Parms.cbFrame = pCodec->Parms.Video.uWidth * pCodec->Parms.Video.uHeight * 4 /* 32-bit */;
89 pCodec->Parms.msFrame = 1; /* 1ms per frame. */
90
91# ifdef VBOX_WITH_LIBVPX_VP9
92 vpx_codec_iface_t *pCodecIface = vpx_codec_vp9_cx();
93# else /* Default is using VP8. */
94 vpx_codec_iface_t *pCodecIface = vpx_codec_vp8_cx();
95# endif
96 PRECORDINGCODECVPX pVPX = &pCodec->Video.VPX;
97
98 vpx_codec_err_t rcv = vpx_codec_enc_config_default(pCodecIface, &pVPX->Cfg, 0 /* Reserved */);
99 if (rcv != VPX_CODEC_OK)
100 {
101 LogRel(("Recording: Failed to get default config for VPX encoder: %s\n", vpx_codec_err_to_string(rcv)));
102 return VERR_RECORDING_CODEC_INIT_FAILED;
103 }
104
105 /* Target bitrate in kilobits per second. */
106 pVPX->Cfg.rc_target_bitrate = pCodec->Parms.uBitrate;
107 /* Frame width. */
108 pVPX->Cfg.g_w = pCodec->Parms.Video.uWidth;
109 /* Frame height. */
110 pVPX->Cfg.g_h = pCodec->Parms.Video.uHeight;
111 /* ms per frame. */
112 pVPX->Cfg.g_timebase.num = pCodec->Parms.msFrame;
113 pVPX->Cfg.g_timebase.den = 1000;
114 /* Disable multithreading. */
115 pVPX->Cfg.g_threads = 0;
116
117 /* Initialize codec. */
118 rcv = vpx_codec_enc_init(&pVPX->Ctx, pCodecIface, &pVPX->Cfg, 0 /* Flags */);
119 if (rcv != VPX_CODEC_OK)
120 {
121 LogRel(("Recording: Failed to initialize VPX encoder: %s\n", vpx_codec_err_to_string(rcv)));
122 return VERR_RECORDING_CODEC_INIT_FAILED;
123 }
124
125 if (!vpx_img_alloc(&pVPX->RawImage, VPX_IMG_FMT_I420,
126 pCodec->Parms.Video.uWidth, pCodec->Parms.Video.uHeight, 1))
127 {
128 LogRel(("Recording: Failed to allocate image %RU32x%RU32\n", pCodec->Parms.Video.uWidth, pCodec->Parms.Video.uHeight));
129 return VERR_RECORDING_CODEC_INIT_FAILED;
130 }
131
132 /* Save a pointer to the first raw YUV plane. */
133 pVPX->pu8YuvBuf = pVPX->RawImage.planes[0];
134
135 return VINF_SUCCESS;
136}
137
138/** @copydoc RECORDINGCODECOPS::pfnDestroy */
139static DECLCALLBACK(int) recordingCodecVPXDestroy(PRECORDINGCODEC pCodec)
140{
141 PRECORDINGCODECVPX pVPX = &pCodec->Video.VPX;
142
143 vpx_img_free(&pVPX->RawImage);
144 pVPX->pu8YuvBuf = NULL; /* Was pointing to VPX.RawImage. */
145
146 vpx_codec_err_t rcv = vpx_codec_destroy(&pVPX->Ctx);
147 Assert(rcv == VPX_CODEC_OK); RT_NOREF(rcv);
148
149 return VINF_SUCCESS;
150}
151
152/** @copydoc RECORDINGCODECOPS::pfnParseOptions */
153static DECLCALLBACK(int) recordingCodecVPXParseOptions(PRECORDINGCODEC pCodec, const com::Utf8Str &strOptions)
154{
155 size_t pos = 0;
156 com::Utf8Str key, value;
157 while ((pos = strOptions.parseKeyValue(key, value, pos)) != com::Utf8Str::npos)
158 {
159 if (key.compare("vc_quality", com::Utf8Str::CaseInsensitive) == 0)
160 {
161 const PRECORDINGCODECVPX pVPX = &pCodec->Video.VPX;
162
163 if (value.compare("realtime", com::Utf8Str::CaseInsensitive) == 0)
164 pVPX->uEncoderDeadline = VPX_DL_REALTIME;
165 else if (value.compare("good", com::Utf8Str::CaseInsensitive) == 0)
166 {
167 AssertStmt(pCodec->Parms.Video.uFPS, pCodec->Parms.Video.uFPS = 25);
168 pVPX->uEncoderDeadline = 1000000 / pCodec->Parms.Video.uFPS;
169 }
170 else if (value.compare("best", com::Utf8Str::CaseInsensitive) == 0)
171 pVPX->uEncoderDeadline = VPX_DL_BEST_QUALITY;
172 else
173 pVPX->uEncoderDeadline = value.toUInt32();
174 }
175 else
176 LogRel2(("Recording: Unknown option '%s' (value '%s'), skipping\n", key.c_str(), value.c_str()));
177 } /* while */
178
179 return VINF_SUCCESS;
180}
181
182/** @copydoc RECORDINGCODECOPS::pfnEncode */
183static DECLCALLBACK(int) recordingCodecVPXEncode(PRECORDINGCODEC pCodec, PRECORDINGFRAME pFrame,
184 size_t *pcEncoded, size_t *pcbEncoded)
185{
186 RT_NOREF(pcEncoded, pcbEncoded);
187
188 AssertPtrReturn(pFrame, VERR_INVALID_POINTER);
189
190 PRECORDINGVIDEOFRAME pVideoFrame = pFrame->VideoPtr;
191
192 int vrc = RecordingUtilsRGBToYUV(pVideoFrame->enmPixelFmt,
193 /* Destination */
194 pCodec->Video.VPX.pu8YuvBuf, pVideoFrame->uWidth, pVideoFrame->uHeight,
195 /* Source */
196 pVideoFrame->pu8RGBBuf, pCodec->Parms.Video.uWidth, pCodec->Parms.Video.uHeight);
197
198 PRECORDINGCODECVPX pVPX = &pCodec->Video.VPX;
199
200 /* Presentation TimeStamp (PTS). */
201 vpx_codec_pts_t pts = pFrame->msTimestamp;
202 vpx_codec_err_t rcv = vpx_codec_encode(&pVPX->Ctx,
203 &pVPX->RawImage,
204 pts /* Timestamp */,
205 pCodec->Parms.Video.uDelayMs /* How long to show this frame */,
206 0 /* Flags */,
207 pVPX->uEncoderDeadline /* Quality setting */);
208 if (rcv != VPX_CODEC_OK)
209 {
210 if (pCodec->State.cEncErrors++ < 64) /** @todo Make this configurable. */
211 LogRel(("Recording: Failed to encode video frame: %s\n", vpx_codec_err_to_string(rcv)));
212 return VERR_RECORDING_ENCODING_FAILED;
213 }
214
215 pCodec->State.cEncErrors = 0;
216
217 vpx_codec_iter_t iter = NULL;
218 vrc = VERR_NO_DATA;
219 for (;;)
220 {
221 const vpx_codec_cx_pkt_t *pPkt = vpx_codec_get_cx_data(&pVPX->Ctx, &iter);
222 if (!pPkt)
223 break;
224
225 switch (pPkt->kind)
226 {
227 case VPX_CODEC_CX_FRAME_PKT:
228 {
229 /* Calculate the absolute PTS of this frame (in ms). */
230 uint64_t tsAbsPTSMs = pPkt->data.frame.pts * 1000
231 * (uint64_t)pCodec->Video.VPX.Cfg.g_timebase.num / pCodec->Video.VPX.Cfg.g_timebase.den;
232
233 const bool fKeyframe = RT_BOOL(pPkt->data.frame.flags & VPX_FRAME_IS_KEY);
234
235 uint32_t fFlags = RECORDINGCODEC_ENC_F_NONE;
236 if (fKeyframe)
237 fFlags |= RECORDINGCODEC_ENC_F_BLOCK_IS_KEY;
238 if (pPkt->data.frame.flags & VPX_FRAME_IS_INVISIBLE)
239 fFlags |= RECORDINGCODEC_ENC_F_BLOCK_IS_INVISIBLE;
240
241 vrc = pCodec->Callbacks.pfnWriteData(pCodec, pPkt->data.frame.buf, pPkt->data.frame.sz,
242 tsAbsPTSMs, fFlags, pCodec->Callbacks.pvUser);
243 break;
244 }
245
246 default:
247 AssertFailed();
248 LogFunc(("Unexpected video packet type %ld\n", pPkt->kind));
249 break;
250 }
251 }
252
253 return vrc;
254}
255#endif /* VBOX_WITH_LIBVPX */
256
257
258/*********************************************************************************************************************************
259* Ogg Vorbis codec *
260*********************************************************************************************************************************/
261
262#ifdef VBOX_WITH_LIBVORBIS
263/** @copydoc RECORDINGCODECOPS::pfnInit */
264static DECLCALLBACK(int) recordingCodecVorbisInit(PRECORDINGCODEC pCodec)
265{
266 pCodec->cbScratch = _4K;
267 pCodec->pvScratch = RTMemAlloc(pCodec->cbScratch);
268 AssertPtrReturn(pCodec->pvScratch, VERR_NO_MEMORY);
269
270 const PPDMAUDIOPCMPROPS pPCMProps = &pCodec->Parms.Audio.PCMProps;
271
272 /** @todo BUGBUG When left out this call, vorbis_block_init() does not find oggpack_writeinit and all goes belly up ... */
273 oggpack_buffer b;
274 oggpack_writeinit(&b);
275
276 vorbis_info_init(&pCodec->Audio.Vorbis.info);
277
278 int vorbis_rc;
279 if (pCodec->Parms.uBitrate == 0) /* No bitrate management? Then go for ABR (Average Bit Rate) only. */
280 vorbis_rc = vorbis_encode_init_vbr(&pCodec->Audio.Vorbis.info,
281 PDMAudioPropsChannels(pPCMProps), PDMAudioPropsHz(pPCMProps),
282 (float).4 /* Quality, from -.1 (lowest) to 1 (highest) */);
283 else
284 vorbis_rc = vorbis_encode_setup_managed(&pCodec->Audio.Vorbis.info, PDMAudioPropsChannels(pPCMProps), PDMAudioPropsHz(pPCMProps),
285 -1 /* max bitrate (unset) */, pCodec->Parms.uBitrate /* kbps, nominal */, -1 /* min bitrate (unset) */);
286 if (vorbis_rc)
287 {
288 LogRel(("Recording: Audio codec failed to setup %s mode (bitrate %RU32): %d\n",
289 pCodec->Parms.uBitrate == 0 ? "VBR" : "bitrate management", pCodec->Parms.uBitrate, vorbis_rc));
290 return VERR_RECORDING_CODEC_INIT_FAILED;
291 }
292
293 vorbis_rc = vorbis_encode_setup_init(&pCodec->Audio.Vorbis.info);
294 if (vorbis_rc)
295 {
296 LogRel(("Recording: vorbis_encode_setup_init() failed (%d)\n", vorbis_rc));
297 return VERR_RECORDING_CODEC_INIT_FAILED;
298 }
299
300 /* Initialize the analysis state and encoding storage. */
301 vorbis_rc = vorbis_analysis_init(&pCodec->Audio.Vorbis.dsp_state, &pCodec->Audio.Vorbis.info);
302 if (vorbis_rc)
303 {
304 vorbis_info_clear(&pCodec->Audio.Vorbis.info);
305 LogRel(("Recording: vorbis_analysis_init() failed (%d)\n", vorbis_rc));
306 return VERR_RECORDING_CODEC_INIT_FAILED;
307 }
308
309 vorbis_rc = vorbis_block_init(&pCodec->Audio.Vorbis.dsp_state, &pCodec->Audio.Vorbis.block_cur);
310 if (vorbis_rc)
311 {
312 vorbis_info_clear(&pCodec->Audio.Vorbis.info);
313 LogRel(("Recording: vorbis_block_init() failed (%d)\n", vorbis_rc));
314 return VERR_RECORDING_CODEC_INIT_FAILED;
315 }
316
317 if (!pCodec->Parms.msFrame) /* No ms per frame defined? Use default. */
318 pCodec->Parms.msFrame = VBOX_RECORDING_VORBIS_FRAME_MS_DEFAULT;
319
320 return VINF_SUCCESS;
321}
322
323/** @copydoc RECORDINGCODECOPS::pfnDestroy */
324static DECLCALLBACK(int) recordingCodecVorbisDestroy(PRECORDINGCODEC pCodec)
325{
326 PRECORDINGCODECVORBIS pVorbis = &pCodec->Audio.Vorbis;
327
328 vorbis_block_clear(&pVorbis->block_cur);
329 vorbis_dsp_clear (&pVorbis->dsp_state);
330 vorbis_info_clear (&pVorbis->info);
331
332 return VINF_SUCCESS;
333}
334
335/** @copydoc RECORDINGCODECOPS::pfnEncode */
336static DECLCALLBACK(int) recordingCodecVorbisEncode(PRECORDINGCODEC pCodec,
337 const PRECORDINGFRAME pFrame, size_t *pcEncoded, size_t *pcbEncoded)
338{
339 const PPDMAUDIOPCMPROPS pPCMProps = &pCodec->Parms.Audio.PCMProps;
340
341 Assert (pCodec->Parms.cbFrame);
342 AssertReturn(pFrame->Audio.cbBuf % pCodec->Parms.cbFrame == 0, VERR_INVALID_PARAMETER);
343 Assert (pFrame->Audio.cbBuf);
344 AssertReturn(pFrame->Audio.cbBuf % PDMAudioPropsFrameSize(pPCMProps) == 0, VERR_INVALID_PARAMETER);
345 AssertReturn(pCodec->cbScratch >= pFrame->Audio.cbBuf, VERR_INVALID_PARAMETER);
346
347 int vrc = VINF_SUCCESS;
348
349 int const cbFrame = PDMAudioPropsFrameSize(pPCMProps);
350 int const cFrames = (int)(pFrame->Audio.cbBuf / cbFrame);
351
352 /* Write non-interleaved frames. */
353 float **buffer = vorbis_analysis_buffer(&pCodec->Audio.Vorbis.dsp_state, cFrames);
354 int16_t *puSrc = (int16_t *)pFrame->Audio.pvBuf; RT_NOREF(puSrc);
355
356 /* Convert samples into floating point. */
357 /** @todo This is sloooooooooooow! Optimize this! */
358 uint8_t const cChannels = PDMAudioPropsChannels(pPCMProps);
359 AssertReturn(cChannels == 2, VERR_NOT_SUPPORTED);
360
361 float const div = 1.0f / 32768.0f;
362
363 for(int f = 0; f < cFrames; f++)
364 {
365 buffer[0][f] = (float)puSrc[0] * div;
366 buffer[1][f] = (float)puSrc[1] * div;
367 puSrc += cChannels;
368 }
369
370 int vorbis_rc = vorbis_analysis_wrote(&pCodec->Audio.Vorbis.dsp_state, cFrames);
371 if (vorbis_rc)
372 {
373 LogRel(("Recording: vorbis_analysis_wrote() failed (%d)\n", vorbis_rc));
374 return VERR_RECORDING_ENCODING_FAILED;
375 }
376
377 if (pcEncoded)
378 *pcEncoded = 0;
379 if (pcbEncoded)
380 *pcbEncoded = 0;
381
382 size_t cBlocksEncoded = 0;
383 size_t cBytesEncoded = 0;
384
385 uint8_t *puDst = (uint8_t *)pCodec->pvScratch;
386
387 while (vorbis_analysis_blockout(&pCodec->Audio.Vorbis.dsp_state, &pCodec->Audio.Vorbis.block_cur) == 1 /* More available? */)
388 {
389 vorbis_rc = vorbis_analysis(&pCodec->Audio.Vorbis.block_cur, NULL);
390 if (vorbis_rc < 0)
391 {
392 LogRel(("Recording: vorbis_analysis() failed (%d)\n", vorbis_rc));
393 vorbis_rc = 0; /* Reset */
394 vrc = VERR_RECORDING_ENCODING_FAILED;
395 break;
396 }
397
398 vorbis_rc = vorbis_bitrate_addblock(&pCodec->Audio.Vorbis.block_cur);
399 if (vorbis_rc < 0)
400 {
401 LogRel(("Recording: vorbis_bitrate_addblock() failed (%d)\n", vorbis_rc));
402 vorbis_rc = 0; /* Reset */
403 vrc = VERR_RECORDING_ENCODING_FAILED;
404 break;
405 }
406
407 /* Vorbis expects us to flush packets one at a time directly to the container.
408 *
409 * If we flush more than one packet in a row, players can't decode this then. */
410 ogg_packet op;
411 while ((vorbis_rc = vorbis_bitrate_flushpacket(&pCodec->Audio.Vorbis.dsp_state, &op)) > 0)
412 {
413 cBytesEncoded += op.bytes;
414 AssertBreakStmt(cBytesEncoded <= pCodec->cbScratch, vrc = VERR_BUFFER_OVERFLOW);
415 cBlocksEncoded++;
416
417 vrc = pCodec->Callbacks.pfnWriteData(pCodec, op.packet, (size_t)op.bytes, pCodec->State.tsLastWrittenMs,
418 RECORDINGCODEC_ENC_F_BLOCK_IS_KEY /* Every Vorbis frame is a key frame */,
419 pCodec->Callbacks.pvUser);
420 }
421
422 RT_NOREF(puDst);
423
424 /* Note: When vorbis_rc is 0, this marks the last packet, a negative values means error. */
425 if (vorbis_rc < 0)
426 {
427 LogRel(("Recording: vorbis_bitrate_flushpacket() failed (%d)\n", vorbis_rc));
428 vorbis_rc = 0; /* Reset */
429 vrc = VERR_RECORDING_ENCODING_FAILED;
430 break;
431 }
432 }
433
434 if (vorbis_rc < 0)
435 {
436 LogRel(("Recording: vorbis_analysis_blockout() failed (%d)\n", vorbis_rc));
437 return VERR_RECORDING_ENCODING_FAILED;
438 }
439
440 if (pcbEncoded)
441 *pcbEncoded = 0;
442 if (pcEncoded)
443 *pcEncoded = 0;
444
445 if (RT_FAILURE(vrc))
446 LogRel(("Recording: Encoding Vorbis audio data failed, rc=%Rrc\n", vrc));
447
448 Log3Func(("cbSrc=%zu, cbDst=%zu, cEncoded=%zu, cbEncoded=%zu, vrc=%Rrc\n",
449 pFrame->Audio.cbBuf, pCodec->cbScratch, cBlocksEncoded, cBytesEncoded, vrc));
450
451 return vrc;
452}
453
454static DECLCALLBACK(int) recordingCodecVorbisFinalize(PRECORDINGCODEC pCodec)
455{
456 int vorbis_rc = vorbis_analysis_wrote(&pCodec->Audio.Vorbis.dsp_state, 0 /* Means finalize */);
457 if (vorbis_rc)
458 {
459 LogRel(("Recording: vorbis_analysis_wrote() failed for finalizing stream (%d)\n", vorbis_rc));
460 return VERR_RECORDING_ENCODING_FAILED;
461 }
462
463 return VINF_SUCCESS;
464}
465#endif /* VBOX_WITH_LIBVORBIS */
466
467
468/*********************************************************************************************************************************
469* Codec API *
470*********************************************************************************************************************************/
471
472/**
473 * Initializes an audio codec.
474 *
475 * @returns VBox status code.
476 * @param pCodec Codec instance to initialize.
477 * @param pCallbacks Codec callback table to use for the codec.
478 * @param Settings Screen settings to use for initialization.
479 */
480static int recordingCodecInitAudio(const PRECORDINGCODEC pCodec,
481 const PRECORDINGCODECCALLBACKS pCallbacks, const settings::RecordingScreenSettings &Settings)
482{
483 AssertReturn(pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO, VERR_INVALID_PARAMETER);
484
485 com::Utf8Str strCodec;
486 settings::RecordingScreenSettings::audioCodecToString(pCodec->Parms.enmAudioCodec, strCodec);
487 LogRel(("Recording: Initializing audio codec '%s'\n", strCodec.c_str()));
488
489 const PPDMAUDIOPCMPROPS pPCMProps = &pCodec->Parms.Audio.PCMProps;
490
491 PDMAudioPropsInit(pPCMProps,
492 Settings.Audio.cBits / 8,
493 true /* fSigned */, Settings.Audio.cChannels, Settings.Audio.uHz);
494 pCodec->Parms.uBitrate = 0; /** @todo No bitrate management for audio yet. */
495
496 if (pCallbacks)
497 memcpy(&pCodec->Callbacks, pCallbacks, sizeof(RECORDINGCODECCALLBACKS));
498
499 int vrc = VINF_SUCCESS;
500
501 if (pCodec->Ops.pfnParseOptions)
502 vrc = pCodec->Ops.pfnParseOptions(pCodec, Settings.strOptions);
503
504 if (RT_SUCCESS(vrc))
505 vrc = pCodec->Ops.pfnInit(pCodec);
506
507 if (RT_SUCCESS(vrc))
508 {
509 Assert(PDMAudioPropsAreValid(pPCMProps));
510
511 uint32_t uBitrate = pCodec->Parms.uBitrate; /* Bitrate management could have been changed by pfnInit(). */
512
513 LogRel2(("Recording: Audio codec is initialized with %RU32Hz, %RU8 channel(s), %RU8 bits per sample\n",
514 PDMAudioPropsHz(pPCMProps), PDMAudioPropsChannels(pPCMProps), PDMAudioPropsSampleBits(pPCMProps)));
515 LogRel2(("Recording: Audio codec's bitrate management is %s (%RU32 kbps)\n", uBitrate ? "enabled" : "disabled", uBitrate));
516
517 if (!pCodec->Parms.msFrame || pCodec->Parms.msFrame >= RT_MS_1SEC) /* Not set yet by codec stuff above? */
518 pCodec->Parms.msFrame = 20; /* 20ms by default should be a sensible value; to prevent division by zero. */
519
520 pCodec->Parms.csFrame = PDMAudioPropsHz(pPCMProps) / (RT_MS_1SEC / pCodec->Parms.msFrame);
521 pCodec->Parms.cbFrame = PDMAudioPropsFramesToBytes(pPCMProps, pCodec->Parms.csFrame);
522
523 LogFlowFunc(("cbSample=%RU32, msFrame=%RU32 -> csFrame=%RU32, cbFrame=%RU32, uBitrate=%RU32\n",
524 PDMAudioPropsSampleSize(pPCMProps), pCodec->Parms.msFrame, pCodec->Parms.csFrame, pCodec->Parms.cbFrame, pCodec->Parms.uBitrate));
525 }
526 else
527 LogRel(("Recording: Error initializing audio codec (%Rrc)\n", vrc));
528
529 return vrc;
530}
531
532/**
533 * Initializes a video codec.
534 *
535 * @returns VBox status code.
536 * @param pCodec Codec instance to initialize.
537 * @param pCallbacks Codec callback table to use for the codec.
538 * @param Settings Screen settings to use for initialization.
539 */
540static int recordingCodecInitVideo(const PRECORDINGCODEC pCodec,
541 const PRECORDINGCODECCALLBACKS pCallbacks, const settings::RecordingScreenSettings &Settings)
542{
543 com::Utf8Str strTemp;
544 settings::RecordingScreenSettings::videoCodecToString(pCodec->Parms.enmVideoCodec, strTemp);
545 LogRel(("Recording: Initializing video codec '%s'\n", strTemp.c_str()));
546
547 pCodec->Parms.uBitrate = Settings.Video.ulRate;
548 pCodec->Parms.Video.uFPS = Settings.Video.ulFPS;
549 pCodec->Parms.Video.uWidth = Settings.Video.ulWidth;
550 pCodec->Parms.Video.uHeight = Settings.Video.ulHeight;
551 pCodec->Parms.Video.uDelayMs = RT_MS_1SEC / pCodec->Parms.Video.uFPS;
552
553 if (pCallbacks)
554 memcpy(&pCodec->Callbacks, pCallbacks, sizeof(RECORDINGCODECCALLBACKS));
555
556 AssertReturn(pCodec->Parms.uBitrate, VERR_INVALID_PARAMETER); /* Bitrate must be set. */
557 AssertStmt(pCodec->Parms.Video.uFPS, pCodec->Parms.Video.uFPS = 25); /* Prevent division by zero. */
558
559 AssertReturn(pCodec->Parms.Video.uHeight, VERR_INVALID_PARAMETER);
560 AssertReturn(pCodec->Parms.Video.uWidth, VERR_INVALID_PARAMETER);
561 AssertReturn(pCodec->Parms.Video.uDelayMs, VERR_INVALID_PARAMETER);
562
563 int vrc = VINF_SUCCESS;
564
565 if (pCodec->Ops.pfnParseOptions)
566 vrc = pCodec->Ops.pfnParseOptions(pCodec, Settings.strOptions);
567
568 if ( RT_SUCCESS(vrc)
569 && pCodec->Ops.pfnInit)
570 vrc = pCodec->Ops.pfnInit(pCodec);
571
572 if (RT_SUCCESS(vrc))
573 {
574 pCodec->Parms.enmType = RECORDINGCODECTYPE_VIDEO;
575 pCodec->Parms.enmVideoCodec = RecordingVideoCodec_VP8; /** @todo No VP9 yet. */
576 }
577 else
578 LogRel(("Recording: Error initializing video codec (%Rrc)\n", vrc));
579
580 return vrc;
581}
582
583#ifdef VBOX_WITH_AUDIO_RECORDING
584/**
585 * Lets an audio codec parse advanced options given from a string.
586 *
587 * @returns VBox status code.
588 * @param pCodec Codec instance to parse options for.
589 * @param strOptions Options string to parse.
590 */
591static DECLCALLBACK(int) recordingCodecAudioParseOptions(PRECORDINGCODEC pCodec, const com::Utf8Str &strOptions)
592{
593 AssertReturn(pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO, VERR_INVALID_PARAMETER);
594
595 size_t pos = 0;
596 com::Utf8Str key, value;
597 while ((pos = strOptions.parseKeyValue(key, value, pos)) != com::Utf8Str::npos)
598 {
599 if (key.compare("ac_profile", com::Utf8Str::CaseInsensitive) == 0)
600 {
601 if (value.compare("low", com::Utf8Str::CaseInsensitive) == 0)
602 {
603 PDMAudioPropsInit(&pCodec->Parms.Audio.PCMProps, 16, true /* fSigned */, 1 /* Channels */, 8000 /* Hz */);
604 }
605 else if (value.startsWith("med" /* "med[ium]" */, com::Utf8Str::CaseInsensitive) == 0)
606 {
607 /* Stay with the defaults. */
608 }
609 else if (value.compare("high", com::Utf8Str::CaseInsensitive) == 0)
610 {
611 PDMAudioPropsInit(&pCodec->Parms.Audio.PCMProps, 16, true /* fSigned */, 2 /* Channels */, 48000 /* Hz */);
612 }
613 }
614 else
615 LogRel(("Recording: Unknown option '%s' (value '%s'), skipping\n", key.c_str(), value.c_str()));
616
617 } /* while */
618
619 return VINF_SUCCESS;
620}
621#endif
622
623static void recordingCodecReset(PRECORDINGCODEC pCodec)
624{
625 pCodec->State.tsLastWrittenMs = 0;
626
627 pCodec->State.cEncErrors = 0;
628#ifdef VBOX_WITH_STATISTICS
629 pCodec->STAM.cEncBlocks = 0;
630 pCodec->STAM.msEncTotal = 0;
631#endif
632}
633
634/**
635 * Common code for codec creation.
636 *
637 * @param pCodec Codec instance to create.
638 */
639static void recordingCodecCreateCommon(PRECORDINGCODEC pCodec)
640{
641 RT_ZERO(pCodec->Ops);
642 RT_ZERO(pCodec->Callbacks);
643}
644
645/**
646 * Creates an audio codec.
647 *
648 * @returns VBox status code.
649 * @param pCodec Codec instance to create.
650 * @param enmAudioCodec Audio codec to create.
651 */
652int recordingCodecCreateAudio(PRECORDINGCODEC pCodec, RecordingAudioCodec_T enmAudioCodec)
653{
654 int vrc;
655
656 recordingCodecCreateCommon(pCodec);
657
658 switch (enmAudioCodec)
659 {
660# ifdef VBOX_WITH_LIBVORBIS
661 case RecordingAudioCodec_OggVorbis:
662 {
663 pCodec->Ops.pfnInit = recordingCodecVorbisInit;
664 pCodec->Ops.pfnDestroy = recordingCodecVorbisDestroy;
665 pCodec->Ops.pfnParseOptions = recordingCodecAudioParseOptions;
666 pCodec->Ops.pfnEncode = recordingCodecVorbisEncode;
667 pCodec->Ops.pfnFinalize = recordingCodecVorbisFinalize;
668
669 vrc = VINF_SUCCESS;
670 break;
671 }
672# endif /* VBOX_WITH_LIBVORBIS */
673
674 default:
675 LogRel(("Recording: Selected codec is not supported!\n"));
676 vrc = VERR_RECORDING_CODEC_NOT_SUPPORTED;
677 break;
678 }
679
680 if (RT_SUCCESS(vrc))
681 {
682 pCodec->Parms.enmType = RECORDINGCODECTYPE_AUDIO;
683 pCodec->Parms.enmAudioCodec = enmAudioCodec;
684 }
685
686 return vrc;
687}
688
689/**
690 * Creates a video codec.
691 *
692 * @returns VBox status code.
693 * @param pCodec Codec instance to create.
694 * @param enmVideoCodec Video codec to create.
695 */
696int recordingCodecCreateVideo(PRECORDINGCODEC pCodec, RecordingVideoCodec_T enmVideoCodec)
697{
698 int vrc;
699
700 recordingCodecCreateCommon(pCodec);
701
702 switch (enmVideoCodec)
703 {
704# ifdef VBOX_WITH_LIBVPX
705 case RecordingVideoCodec_VP8:
706 {
707 pCodec->Ops.pfnInit = recordingCodecVPXInit;
708 pCodec->Ops.pfnDestroy = recordingCodecVPXDestroy;
709 pCodec->Ops.pfnParseOptions = recordingCodecVPXParseOptions;
710 pCodec->Ops.pfnEncode = recordingCodecVPXEncode;
711
712 vrc = VINF_SUCCESS;
713 break;
714 }
715# endif /* VBOX_WITH_LIBVPX */
716
717 default:
718 vrc = VERR_RECORDING_CODEC_NOT_SUPPORTED;
719 break;
720 }
721
722 if (RT_SUCCESS(vrc))
723 {
724 pCodec->Parms.enmType = RECORDINGCODECTYPE_VIDEO;
725 pCodec->Parms.enmVideoCodec = enmVideoCodec;
726 }
727
728 return vrc;
729}
730
731/**
732 * Initializes a codec.
733 *
734 * @returns VBox status code.
735 * @param pCodec Codec to initialize.
736 * @param pCallbacks Codec callback table to use. Optional and may be NULL.
737 * @param Settings Settings to use for initializing the codec.
738 */
739int recordingCodecInit(const PRECORDINGCODEC pCodec, const PRECORDINGCODECCALLBACKS pCallbacks, const settings::RecordingScreenSettings &Settings)
740{
741 recordingCodecReset(pCodec);
742
743 int vrc;
744 if (pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO)
745 vrc = recordingCodecInitAudio(pCodec, pCallbacks, Settings);
746 else if (pCodec->Parms.enmType == RECORDINGCODECTYPE_VIDEO)
747 vrc = recordingCodecInitVideo(pCodec, pCallbacks, Settings);
748 else
749 AssertFailedStmt(vrc = VERR_NOT_SUPPORTED);
750
751 return vrc;
752}
753
754/**
755 * Destroys an audio codec.
756 *
757 * @returns VBox status code.
758 * @param pCodec Codec to destroy.
759 */
760static int recordingCodecDestroyAudio(PRECORDINGCODEC pCodec)
761{
762 AssertReturn(pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO, VERR_INVALID_PARAMETER);
763
764 return pCodec->Ops.pfnDestroy(pCodec);
765}
766
767/**
768 * Destroys a video codec.
769 *
770 * @returns VBox status code.
771 * @param pCodec Codec to destroy.
772 */
773static int recordingCodecDestroyVideo(PRECORDINGCODEC pCodec)
774{
775 AssertReturn(pCodec->Parms.enmType == RECORDINGCODECTYPE_VIDEO, VERR_INVALID_PARAMETER);
776
777 return pCodec->Ops.pfnDestroy(pCodec);
778}
779
780/**
781 * Destroys the codec.
782 *
783 * @returns VBox status code.
784 * @param pCodec Codec to destroy.
785 */
786int recordingCodecDestroy(PRECORDINGCODEC pCodec)
787{
788 if (pCodec->Parms.enmType == RECORDINGCODECTYPE_INVALID)
789 return VINF_SUCCESS;
790
791 int vrc;
792
793 if (pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO)
794 vrc = recordingCodecDestroyAudio(pCodec);
795 else if (pCodec->Parms.enmType == RECORDINGCODECTYPE_VIDEO)
796 vrc =recordingCodecDestroyVideo(pCodec);
797 else
798 AssertFailedReturn(VERR_NOT_SUPPORTED);
799
800 if (RT_SUCCESS(vrc))
801 {
802 if (pCodec->pvScratch)
803 {
804 Assert(pCodec->cbScratch);
805 RTMemFree(pCodec->pvScratch);
806 pCodec->pvScratch = NULL;
807 pCodec->cbScratch = 0;
808 }
809
810 pCodec->Parms.enmType = RECORDINGCODECTYPE_INVALID;
811 pCodec->Parms.enmVideoCodec = RecordingVideoCodec_None;
812 }
813
814 return vrc;
815}
816
817/**
818 * Feeds the codec encoder with data to encode.
819 *
820 * @returns VBox status code.
821 * @param pCodec Codec to use.
822 * @param pFrame Pointer to frame data to encode.
823 * @param pcEncoded Where to return the number of encoded blocks in \a pvDst on success. Optional.
824 * @param pcbEncoded Where to return the number of encoded bytes in \a pvDst on success. Optional.
825 */
826int recordingCodecEncode(PRECORDINGCODEC pCodec,
827 const PRECORDINGFRAME pFrame, size_t *pcEncoded, size_t *pcbEncoded)
828{
829 AssertPtrReturn(pCodec->Ops.pfnEncode, VERR_NOT_SUPPORTED);
830
831 size_t cEncoded, cbEncoded;
832 int vrc = pCodec->Ops.pfnEncode(pCodec, pFrame, &cEncoded, &cbEncoded);
833 if (RT_SUCCESS(vrc))
834 {
835 pCodec->State.tsLastWrittenMs = pFrame->msTimestamp;
836
837#ifdef VBOX_WITH_STATISTICS
838 pCodec->STAM.cEncBlocks += cEncoded;
839 pCodec->STAM.msEncTotal += pCodec->Parms.msFrame * cEncoded;
840#endif
841 if (pcEncoded)
842 *pcEncoded = cEncoded;
843 if (pcbEncoded)
844 *pcbEncoded = cbEncoded;
845 }
846
847 return vrc;
848}
849
850/**
851 * Tells the codec that has to finalize the stream.
852 *
853 * @returns VBox status code.
854 * @param pCodec Codec to finalize stream for.
855 */
856int recordingCodecFinalize(PRECORDINGCODEC pCodec)
857{
858 if (pCodec->Ops.pfnFinalize)
859 return pCodec->Ops.pfnFinalize(pCodec);
860 return VINF_SUCCESS;
861}
862
863/**
864 * Returns whether the codec has been initialized or not.
865 *
866 * @returns @c true if initialized, or @c false if not.
867 * @param pCodec Codec to return initialization status for.
868 */
869bool recordingCodecIsInitialized(const PRECORDINGCODEC pCodec)
870{
871 return pCodec->Ops.pfnInit != NULL; /* pfnInit acts as a beacon for initialization status. */
872}
873
874/**
875 * Returns the number of writable bytes for a given timestamp.
876 *
877 * This basically is a helper function to respect the set frames per second (FPS).
878 *
879 * @returns Number of writable bytes.
880 * @param pCodec Codec to return number of writable bytes for.
881 * @param msTimestamp Timestamp (PTS, in ms) return number of writable bytes for.
882 */
883uint32_t recordingCodecGetWritable(const PRECORDINGCODEC pCodec, uint64_t msTimestamp)
884{
885 Log3Func(("%RU64 -- tsLastWrittenMs=%RU64 + uDelayMs=%RU32\n",
886 msTimestamp, pCodec->State.tsLastWrittenMs,pCodec->Parms.Video.uDelayMs));
887
888 if (msTimestamp < pCodec->State.tsLastWrittenMs + pCodec->Parms.Video.uDelayMs)
889 return 0; /* Too early for writing (respect set FPS). */
890
891 /* For now we just return the complete frame space. */
892 AssertMsg(pCodec->Parms.cbFrame, ("Codec not initialized yet\n"));
893 return pCodec->Parms.cbFrame;
894}
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