VirtualBox

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

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

Recording: Implemented support for Vorbis codec (provided by libvorbis, not enabled by default yet). This also makes all the codec handling more abstract by using a simple codec wrapper, to keep other places free from codec-specific as much as possible. Initial implementation works and output files are being recognized by media players, but there still are some timing bugs to resolve, as well as optimizing the performance [build fix]. bugref:10275

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