VirtualBox

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

Last change on this file since 96229 was 96229, checked in by vboxsync, 3 years ago

Recording/Main: Decoupled the WebM writer class from codec dependencies. Various bugfixes. bugref:10275

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette