VirtualBox

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

Last change on this file since 96391 was 96323, checked in by vboxsync, 2 years ago

Recording/Main: Docs nits (use @c instead of \c).

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