VirtualBox

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

Last change on this file since 104635 was 104635, checked in by vboxsync, 7 months ago

Main: Fixed warnings. ​bugref:3409

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