VirtualBox

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

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