summaryrefslogtreecommitdiffstats
path: root/src/VBox/Main/src-client/RecordingCodec.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Main/src-client/RecordingCodec.cpp')
-rw-r--r--src/VBox/Main/src-client/RecordingCodec.cpp894
1 files changed, 894 insertions, 0 deletions
diff --git a/src/VBox/Main/src-client/RecordingCodec.cpp b/src/VBox/Main/src-client/RecordingCodec.cpp
new file mode 100644
index 00000000..2de401c3
--- /dev/null
+++ b/src/VBox/Main/src-client/RecordingCodec.cpp
@@ -0,0 +1,894 @@
+/* $Id: RecordingCodec.cpp $ */
+/** @file
+ * Recording codec wrapper.
+ */
+
+/*
+ * Copyright (C) 2022-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/* This code makes use of Vorbis (libvorbis):
+ *
+ * Copyright (c) 2002-2020 Xiph.org Foundation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of the Xiph.org Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION
+ * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define LOG_GROUP LOG_GROUP_RECORDING
+#include "LoggingNew.h"
+
+#include <VBox/com/string.h>
+#include <VBox/err.h>
+#include <VBox/vmm/pdmaudioifs.h>
+#include <VBox/vmm/pdmaudioinline.h>
+
+#include "RecordingInternals.h"
+#include "RecordingUtils.h"
+#include "WebMWriter.h"
+
+#include <math.h>
+
+
+/*********************************************************************************************************************************
+* VPX (VP8 / VP9) codec *
+*********************************************************************************************************************************/
+
+#ifdef VBOX_WITH_LIBVPX
+/** @copydoc RECORDINGCODECOPS::pfnInit */
+static DECLCALLBACK(int) recordingCodecVPXInit(PRECORDINGCODEC pCodec)
+{
+ pCodec->cbScratch = _4K;
+ pCodec->pvScratch = RTMemAlloc(pCodec->cbScratch);
+ AssertPtrReturn(pCodec->pvScratch, VERR_NO_MEMORY);
+
+ pCodec->Parms.csFrame = 0;
+ pCodec->Parms.cbFrame = pCodec->Parms.Video.uWidth * pCodec->Parms.Video.uHeight * 4 /* 32-bit */;
+ pCodec->Parms.msFrame = 1; /* 1ms per frame. */
+
+# ifdef VBOX_WITH_LIBVPX_VP9
+ vpx_codec_iface_t *pCodecIface = vpx_codec_vp9_cx();
+# else /* Default is using VP8. */
+ vpx_codec_iface_t *pCodecIface = vpx_codec_vp8_cx();
+# endif
+ PRECORDINGCODECVPX pVPX = &pCodec->Video.VPX;
+
+ vpx_codec_err_t rcv = vpx_codec_enc_config_default(pCodecIface, &pVPX->Cfg, 0 /* Reserved */);
+ if (rcv != VPX_CODEC_OK)
+ {
+ LogRel(("Recording: Failed to get default config for VPX encoder: %s\n", vpx_codec_err_to_string(rcv)));
+ return VERR_RECORDING_CODEC_INIT_FAILED;
+ }
+
+ /* Target bitrate in kilobits per second. */
+ pVPX->Cfg.rc_target_bitrate = pCodec->Parms.uBitrate;
+ /* Frame width. */
+ pVPX->Cfg.g_w = pCodec->Parms.Video.uWidth;
+ /* Frame height. */
+ pVPX->Cfg.g_h = pCodec->Parms.Video.uHeight;
+ /* ms per frame. */
+ pVPX->Cfg.g_timebase.num = pCodec->Parms.msFrame;
+ pVPX->Cfg.g_timebase.den = 1000;
+ /* Disable multithreading. */
+ pVPX->Cfg.g_threads = 0;
+
+ /* Initialize codec. */
+ rcv = vpx_codec_enc_init(&pVPX->Ctx, pCodecIface, &pVPX->Cfg, 0 /* Flags */);
+ if (rcv != VPX_CODEC_OK)
+ {
+ LogRel(("Recording: Failed to initialize VPX encoder: %s\n", vpx_codec_err_to_string(rcv)));
+ return VERR_RECORDING_CODEC_INIT_FAILED;
+ }
+
+ if (!vpx_img_alloc(&pVPX->RawImage, VPX_IMG_FMT_I420,
+ pCodec->Parms.Video.uWidth, pCodec->Parms.Video.uHeight, 1))
+ {
+ LogRel(("Recording: Failed to allocate image %RU32x%RU32\n", pCodec->Parms.Video.uWidth, pCodec->Parms.Video.uHeight));
+ return VERR_RECORDING_CODEC_INIT_FAILED;
+ }
+
+ /* Save a pointer to the first raw YUV plane. */
+ pVPX->pu8YuvBuf = pVPX->RawImage.planes[0];
+
+ return VINF_SUCCESS;
+}
+
+/** @copydoc RECORDINGCODECOPS::pfnDestroy */
+static DECLCALLBACK(int) recordingCodecVPXDestroy(PRECORDINGCODEC pCodec)
+{
+ PRECORDINGCODECVPX pVPX = &pCodec->Video.VPX;
+
+ vpx_img_free(&pVPX->RawImage);
+ pVPX->pu8YuvBuf = NULL; /* Was pointing to VPX.RawImage. */
+
+ vpx_codec_err_t rcv = vpx_codec_destroy(&pVPX->Ctx);
+ Assert(rcv == VPX_CODEC_OK); RT_NOREF(rcv);
+
+ return VINF_SUCCESS;
+}
+
+/** @copydoc RECORDINGCODECOPS::pfnParseOptions */
+static DECLCALLBACK(int) recordingCodecVPXParseOptions(PRECORDINGCODEC pCodec, const com::Utf8Str &strOptions)
+{
+ size_t pos = 0;
+ com::Utf8Str key, value;
+ while ((pos = strOptions.parseKeyValue(key, value, pos)) != com::Utf8Str::npos)
+ {
+ if (key.compare("vc_quality", com::Utf8Str::CaseInsensitive) == 0)
+ {
+ const PRECORDINGCODECVPX pVPX = &pCodec->Video.VPX;
+
+ if (value.compare("realtime", com::Utf8Str::CaseInsensitive) == 0)
+ pVPX->uEncoderDeadline = VPX_DL_REALTIME;
+ else if (value.compare("good", com::Utf8Str::CaseInsensitive) == 0)
+ {
+ AssertStmt(pCodec->Parms.Video.uFPS, pCodec->Parms.Video.uFPS = 25);
+ pVPX->uEncoderDeadline = 1000000 / pCodec->Parms.Video.uFPS;
+ }
+ else if (value.compare("best", com::Utf8Str::CaseInsensitive) == 0)
+ pVPX->uEncoderDeadline = VPX_DL_BEST_QUALITY;
+ else
+ pVPX->uEncoderDeadline = value.toUInt32();
+ }
+ else
+ LogRel2(("Recording: Unknown option '%s' (value '%s'), skipping\n", key.c_str(), value.c_str()));
+ } /* while */
+
+ return VINF_SUCCESS;
+}
+
+/** @copydoc RECORDINGCODECOPS::pfnEncode */
+static DECLCALLBACK(int) recordingCodecVPXEncode(PRECORDINGCODEC pCodec, PRECORDINGFRAME pFrame,
+ size_t *pcEncoded, size_t *pcbEncoded)
+{
+ RT_NOREF(pcEncoded, pcbEncoded);
+
+ AssertPtrReturn(pFrame, VERR_INVALID_POINTER);
+
+ PRECORDINGVIDEOFRAME pVideoFrame = pFrame->VideoPtr;
+
+ int vrc = RecordingUtilsRGBToYUV(pVideoFrame->enmPixelFmt,
+ /* Destination */
+ pCodec->Video.VPX.pu8YuvBuf, pVideoFrame->uWidth, pVideoFrame->uHeight,
+ /* Source */
+ pVideoFrame->pu8RGBBuf, pCodec->Parms.Video.uWidth, pCodec->Parms.Video.uHeight);
+
+ PRECORDINGCODECVPX pVPX = &pCodec->Video.VPX;
+
+ /* Presentation TimeStamp (PTS). */
+ vpx_codec_pts_t pts = pFrame->msTimestamp;
+ vpx_codec_err_t rcv = vpx_codec_encode(&pVPX->Ctx,
+ &pVPX->RawImage,
+ pts /* Timestamp */,
+ pCodec->Parms.Video.uDelayMs /* How long to show this frame */,
+ 0 /* Flags */,
+ pVPX->uEncoderDeadline /* Quality setting */);
+ if (rcv != VPX_CODEC_OK)
+ {
+ if (pCodec->State.cEncErrors++ < 64) /** @todo Make this configurable. */
+ LogRel(("Recording: Failed to encode video frame: %s\n", vpx_codec_err_to_string(rcv)));
+ return VERR_RECORDING_ENCODING_FAILED;
+ }
+
+ pCodec->State.cEncErrors = 0;
+
+ vpx_codec_iter_t iter = NULL;
+ vrc = VERR_NO_DATA;
+ for (;;)
+ {
+ const vpx_codec_cx_pkt_t *pPkt = vpx_codec_get_cx_data(&pVPX->Ctx, &iter);
+ if (!pPkt)
+ break;
+
+ switch (pPkt->kind)
+ {
+ case VPX_CODEC_CX_FRAME_PKT:
+ {
+ /* Calculate the absolute PTS of this frame (in ms). */
+ uint64_t tsAbsPTSMs = pPkt->data.frame.pts * 1000
+ * (uint64_t)pCodec->Video.VPX.Cfg.g_timebase.num / pCodec->Video.VPX.Cfg.g_timebase.den;
+
+ const bool fKeyframe = RT_BOOL(pPkt->data.frame.flags & VPX_FRAME_IS_KEY);
+
+ uint32_t fFlags = RECORDINGCODEC_ENC_F_NONE;
+ if (fKeyframe)
+ fFlags |= RECORDINGCODEC_ENC_F_BLOCK_IS_KEY;
+ if (pPkt->data.frame.flags & VPX_FRAME_IS_INVISIBLE)
+ fFlags |= RECORDINGCODEC_ENC_F_BLOCK_IS_INVISIBLE;
+
+ vrc = pCodec->Callbacks.pfnWriteData(pCodec, pPkt->data.frame.buf, pPkt->data.frame.sz,
+ tsAbsPTSMs, fFlags, pCodec->Callbacks.pvUser);
+ break;
+ }
+
+ default:
+ AssertFailed();
+ LogFunc(("Unexpected video packet type %ld\n", pPkt->kind));
+ break;
+ }
+ }
+
+ return vrc;
+}
+#endif /* VBOX_WITH_LIBVPX */
+
+
+/*********************************************************************************************************************************
+* Ogg Vorbis codec *
+*********************************************************************************************************************************/
+
+#ifdef VBOX_WITH_LIBVORBIS
+/** @copydoc RECORDINGCODECOPS::pfnInit */
+static DECLCALLBACK(int) recordingCodecVorbisInit(PRECORDINGCODEC pCodec)
+{
+ pCodec->cbScratch = _4K;
+ pCodec->pvScratch = RTMemAlloc(pCodec->cbScratch);
+ AssertPtrReturn(pCodec->pvScratch, VERR_NO_MEMORY);
+
+ const PPDMAUDIOPCMPROPS pPCMProps = &pCodec->Parms.Audio.PCMProps;
+
+ /** @todo BUGBUG When left out this call, vorbis_block_init() does not find oggpack_writeinit and all goes belly up ... */
+ oggpack_buffer b;
+ oggpack_writeinit(&b);
+
+ vorbis_info_init(&pCodec->Audio.Vorbis.info);
+
+ int vorbis_rc;
+ if (pCodec->Parms.uBitrate == 0) /* No bitrate management? Then go for ABR (Average Bit Rate) only. */
+ vorbis_rc = vorbis_encode_init_vbr(&pCodec->Audio.Vorbis.info,
+ PDMAudioPropsChannels(pPCMProps), PDMAudioPropsHz(pPCMProps),
+ (float).4 /* Quality, from -.1 (lowest) to 1 (highest) */);
+ else
+ vorbis_rc = vorbis_encode_setup_managed(&pCodec->Audio.Vorbis.info, PDMAudioPropsChannels(pPCMProps), PDMAudioPropsHz(pPCMProps),
+ -1 /* max bitrate (unset) */, pCodec->Parms.uBitrate /* kbps, nominal */, -1 /* min bitrate (unset) */);
+ if (vorbis_rc)
+ {
+ LogRel(("Recording: Audio codec failed to setup %s mode (bitrate %RU32): %d\n",
+ pCodec->Parms.uBitrate == 0 ? "VBR" : "bitrate management", pCodec->Parms.uBitrate, vorbis_rc));
+ return VERR_RECORDING_CODEC_INIT_FAILED;
+ }
+
+ vorbis_rc = vorbis_encode_setup_init(&pCodec->Audio.Vorbis.info);
+ if (vorbis_rc)
+ {
+ LogRel(("Recording: vorbis_encode_setup_init() failed (%d)\n", vorbis_rc));
+ return VERR_RECORDING_CODEC_INIT_FAILED;
+ }
+
+ /* Initialize the analysis state and encoding storage. */
+ vorbis_rc = vorbis_analysis_init(&pCodec->Audio.Vorbis.dsp_state, &pCodec->Audio.Vorbis.info);
+ if (vorbis_rc)
+ {
+ vorbis_info_clear(&pCodec->Audio.Vorbis.info);
+ LogRel(("Recording: vorbis_analysis_init() failed (%d)\n", vorbis_rc));
+ return VERR_RECORDING_CODEC_INIT_FAILED;
+ }
+
+ vorbis_rc = vorbis_block_init(&pCodec->Audio.Vorbis.dsp_state, &pCodec->Audio.Vorbis.block_cur);
+ if (vorbis_rc)
+ {
+ vorbis_info_clear(&pCodec->Audio.Vorbis.info);
+ LogRel(("Recording: vorbis_block_init() failed (%d)\n", vorbis_rc));
+ return VERR_RECORDING_CODEC_INIT_FAILED;
+ }
+
+ if (!pCodec->Parms.msFrame) /* No ms per frame defined? Use default. */
+ pCodec->Parms.msFrame = VBOX_RECORDING_VORBIS_FRAME_MS_DEFAULT;
+
+ return VINF_SUCCESS;
+}
+
+/** @copydoc RECORDINGCODECOPS::pfnDestroy */
+static DECLCALLBACK(int) recordingCodecVorbisDestroy(PRECORDINGCODEC pCodec)
+{
+ PRECORDINGCODECVORBIS pVorbis = &pCodec->Audio.Vorbis;
+
+ vorbis_block_clear(&pVorbis->block_cur);
+ vorbis_dsp_clear (&pVorbis->dsp_state);
+ vorbis_info_clear (&pVorbis->info);
+
+ return VINF_SUCCESS;
+}
+
+/** @copydoc RECORDINGCODECOPS::pfnEncode */
+static DECLCALLBACK(int) recordingCodecVorbisEncode(PRECORDINGCODEC pCodec,
+ const PRECORDINGFRAME pFrame, size_t *pcEncoded, size_t *pcbEncoded)
+{
+ const PPDMAUDIOPCMPROPS pPCMProps = &pCodec->Parms.Audio.PCMProps;
+
+ Assert (pCodec->Parms.cbFrame);
+ AssertReturn(pFrame->Audio.cbBuf % pCodec->Parms.cbFrame == 0, VERR_INVALID_PARAMETER);
+ Assert (pFrame->Audio.cbBuf);
+ AssertReturn(pFrame->Audio.cbBuf % PDMAudioPropsFrameSize(pPCMProps) == 0, VERR_INVALID_PARAMETER);
+ AssertReturn(pCodec->cbScratch >= pFrame->Audio.cbBuf, VERR_INVALID_PARAMETER);
+
+ int vrc = VINF_SUCCESS;
+
+ int const cbFrame = PDMAudioPropsFrameSize(pPCMProps);
+ int const cFrames = (int)(pFrame->Audio.cbBuf / cbFrame);
+
+ /* Write non-interleaved frames. */
+ float **buffer = vorbis_analysis_buffer(&pCodec->Audio.Vorbis.dsp_state, cFrames);
+ int16_t *puSrc = (int16_t *)pFrame->Audio.pvBuf; RT_NOREF(puSrc);
+
+ /* Convert samples into floating point. */
+ /** @todo This is sloooooooooooow! Optimize this! */
+ uint8_t const cChannels = PDMAudioPropsChannels(pPCMProps);
+ AssertReturn(cChannels == 2, VERR_NOT_SUPPORTED);
+
+ float const div = 1.0f / 32768.0f;
+
+ for(int f = 0; f < cFrames; f++)
+ {
+ buffer[0][f] = (float)puSrc[0] * div;
+ buffer[1][f] = (float)puSrc[1] * div;
+ puSrc += cChannels;
+ }
+
+ int vorbis_rc = vorbis_analysis_wrote(&pCodec->Audio.Vorbis.dsp_state, cFrames);
+ if (vorbis_rc)
+ {
+ LogRel(("Recording: vorbis_analysis_wrote() failed (%d)\n", vorbis_rc));
+ return VERR_RECORDING_ENCODING_FAILED;
+ }
+
+ if (pcEncoded)
+ *pcEncoded = 0;
+ if (pcbEncoded)
+ *pcbEncoded = 0;
+
+ size_t cBlocksEncoded = 0;
+ size_t cBytesEncoded = 0;
+
+ uint8_t *puDst = (uint8_t *)pCodec->pvScratch;
+
+ while (vorbis_analysis_blockout(&pCodec->Audio.Vorbis.dsp_state, &pCodec->Audio.Vorbis.block_cur) == 1 /* More available? */)
+ {
+ vorbis_rc = vorbis_analysis(&pCodec->Audio.Vorbis.block_cur, NULL);
+ if (vorbis_rc < 0)
+ {
+ LogRel(("Recording: vorbis_analysis() failed (%d)\n", vorbis_rc));
+ vorbis_rc = 0; /* Reset */
+ vrc = VERR_RECORDING_ENCODING_FAILED;
+ break;
+ }
+
+ vorbis_rc = vorbis_bitrate_addblock(&pCodec->Audio.Vorbis.block_cur);
+ if (vorbis_rc < 0)
+ {
+ LogRel(("Recording: vorbis_bitrate_addblock() failed (%d)\n", vorbis_rc));
+ vorbis_rc = 0; /* Reset */
+ vrc = VERR_RECORDING_ENCODING_FAILED;
+ break;
+ }
+
+ /* Vorbis expects us to flush packets one at a time directly to the container.
+ *
+ * If we flush more than one packet in a row, players can't decode this then. */
+ ogg_packet op;
+ while ((vorbis_rc = vorbis_bitrate_flushpacket(&pCodec->Audio.Vorbis.dsp_state, &op)) > 0)
+ {
+ cBytesEncoded += op.bytes;
+ AssertBreakStmt(cBytesEncoded <= pCodec->cbScratch, vrc = VERR_BUFFER_OVERFLOW);
+ cBlocksEncoded++;
+
+ vrc = pCodec->Callbacks.pfnWriteData(pCodec, op.packet, (size_t)op.bytes, pCodec->State.tsLastWrittenMs,
+ RECORDINGCODEC_ENC_F_BLOCK_IS_KEY /* Every Vorbis frame is a key frame */,
+ pCodec->Callbacks.pvUser);
+ }
+
+ RT_NOREF(puDst);
+
+ /* Note: When vorbis_rc is 0, this marks the last packet, a negative values means error. */
+ if (vorbis_rc < 0)
+ {
+ LogRel(("Recording: vorbis_bitrate_flushpacket() failed (%d)\n", vorbis_rc));
+ vorbis_rc = 0; /* Reset */
+ vrc = VERR_RECORDING_ENCODING_FAILED;
+ break;
+ }
+ }
+
+ if (vorbis_rc < 0)
+ {
+ LogRel(("Recording: vorbis_analysis_blockout() failed (%d)\n", vorbis_rc));
+ return VERR_RECORDING_ENCODING_FAILED;
+ }
+
+ if (pcbEncoded)
+ *pcbEncoded = 0;
+ if (pcEncoded)
+ *pcEncoded = 0;
+
+ if (RT_FAILURE(vrc))
+ LogRel(("Recording: Encoding Vorbis audio data failed, vrc=%Rrc\n", vrc));
+
+ Log3Func(("cbSrc=%zu, cbDst=%zu, cEncoded=%zu, cbEncoded=%zu, vrc=%Rrc\n",
+ pFrame->Audio.cbBuf, pCodec->cbScratch, cBlocksEncoded, cBytesEncoded, vrc));
+
+ return vrc;
+}
+
+static DECLCALLBACK(int) recordingCodecVorbisFinalize(PRECORDINGCODEC pCodec)
+{
+ int vorbis_rc = vorbis_analysis_wrote(&pCodec->Audio.Vorbis.dsp_state, 0 /* Means finalize */);
+ if (vorbis_rc)
+ {
+ LogRel(("Recording: vorbis_analysis_wrote() failed for finalizing stream (%d)\n", vorbis_rc));
+ return VERR_RECORDING_ENCODING_FAILED;
+ }
+
+ return VINF_SUCCESS;
+}
+#endif /* VBOX_WITH_LIBVORBIS */
+
+
+/*********************************************************************************************************************************
+* Codec API *
+*********************************************************************************************************************************/
+
+/**
+ * Initializes an audio codec.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec instance to initialize.
+ * @param pCallbacks Codec callback table to use for the codec.
+ * @param Settings Screen settings to use for initialization.
+ */
+static int recordingCodecInitAudio(const PRECORDINGCODEC pCodec,
+ const PRECORDINGCODECCALLBACKS pCallbacks, const settings::RecordingScreenSettings &Settings)
+{
+ AssertReturn(pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO, VERR_INVALID_PARAMETER);
+
+ com::Utf8Str strCodec;
+ settings::RecordingScreenSettings::audioCodecToString(pCodec->Parms.enmAudioCodec, strCodec);
+ LogRel(("Recording: Initializing audio codec '%s'\n", strCodec.c_str()));
+
+ const PPDMAUDIOPCMPROPS pPCMProps = &pCodec->Parms.Audio.PCMProps;
+
+ PDMAudioPropsInit(pPCMProps,
+ Settings.Audio.cBits / 8,
+ true /* fSigned */, Settings.Audio.cChannels, Settings.Audio.uHz);
+ pCodec->Parms.uBitrate = 0; /** @todo No bitrate management for audio yet. */
+
+ if (pCallbacks)
+ memcpy(&pCodec->Callbacks, pCallbacks, sizeof(RECORDINGCODECCALLBACKS));
+
+ int vrc = VINF_SUCCESS;
+
+ if (pCodec->Ops.pfnParseOptions)
+ vrc = pCodec->Ops.pfnParseOptions(pCodec, Settings.strOptions);
+
+ if (RT_SUCCESS(vrc))
+ vrc = pCodec->Ops.pfnInit(pCodec);
+
+ if (RT_SUCCESS(vrc))
+ {
+ Assert(PDMAudioPropsAreValid(pPCMProps));
+
+ uint32_t uBitrate = pCodec->Parms.uBitrate; /* Bitrate management could have been changed by pfnInit(). */
+
+ LogRel2(("Recording: Audio codec is initialized with %RU32Hz, %RU8 channel(s), %RU8 bits per sample\n",
+ PDMAudioPropsHz(pPCMProps), PDMAudioPropsChannels(pPCMProps), PDMAudioPropsSampleBits(pPCMProps)));
+ LogRel2(("Recording: Audio codec's bitrate management is %s (%RU32 kbps)\n", uBitrate ? "enabled" : "disabled", uBitrate));
+
+ if (!pCodec->Parms.msFrame || pCodec->Parms.msFrame >= RT_MS_1SEC) /* Not set yet by codec stuff above? */
+ pCodec->Parms.msFrame = 20; /* 20ms by default should be a sensible value; to prevent division by zero. */
+
+ pCodec->Parms.csFrame = PDMAudioPropsHz(pPCMProps) / (RT_MS_1SEC / pCodec->Parms.msFrame);
+ pCodec->Parms.cbFrame = PDMAudioPropsFramesToBytes(pPCMProps, pCodec->Parms.csFrame);
+
+ LogFlowFunc(("cbSample=%RU32, msFrame=%RU32 -> csFrame=%RU32, cbFrame=%RU32, uBitrate=%RU32\n",
+ PDMAudioPropsSampleSize(pPCMProps), pCodec->Parms.msFrame, pCodec->Parms.csFrame, pCodec->Parms.cbFrame, pCodec->Parms.uBitrate));
+ }
+ else
+ LogRel(("Recording: Error initializing audio codec (%Rrc)\n", vrc));
+
+ return vrc;
+}
+
+/**
+ * Initializes a video codec.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec instance to initialize.
+ * @param pCallbacks Codec callback table to use for the codec.
+ * @param Settings Screen settings to use for initialization.
+ */
+static int recordingCodecInitVideo(const PRECORDINGCODEC pCodec,
+ const PRECORDINGCODECCALLBACKS pCallbacks, const settings::RecordingScreenSettings &Settings)
+{
+ com::Utf8Str strTemp;
+ settings::RecordingScreenSettings::videoCodecToString(pCodec->Parms.enmVideoCodec, strTemp);
+ LogRel(("Recording: Initializing video codec '%s'\n", strTemp.c_str()));
+
+ pCodec->Parms.uBitrate = Settings.Video.ulRate;
+ pCodec->Parms.Video.uFPS = Settings.Video.ulFPS;
+ pCodec->Parms.Video.uWidth = Settings.Video.ulWidth;
+ pCodec->Parms.Video.uHeight = Settings.Video.ulHeight;
+ pCodec->Parms.Video.uDelayMs = RT_MS_1SEC / pCodec->Parms.Video.uFPS;
+
+ if (pCallbacks)
+ memcpy(&pCodec->Callbacks, pCallbacks, sizeof(RECORDINGCODECCALLBACKS));
+
+ AssertReturn(pCodec->Parms.uBitrate, VERR_INVALID_PARAMETER); /* Bitrate must be set. */
+ AssertStmt(pCodec->Parms.Video.uFPS, pCodec->Parms.Video.uFPS = 25); /* Prevent division by zero. */
+
+ AssertReturn(pCodec->Parms.Video.uHeight, VERR_INVALID_PARAMETER);
+ AssertReturn(pCodec->Parms.Video.uWidth, VERR_INVALID_PARAMETER);
+ AssertReturn(pCodec->Parms.Video.uDelayMs, VERR_INVALID_PARAMETER);
+
+ int vrc = VINF_SUCCESS;
+
+ if (pCodec->Ops.pfnParseOptions)
+ vrc = pCodec->Ops.pfnParseOptions(pCodec, Settings.strOptions);
+
+ if ( RT_SUCCESS(vrc)
+ && pCodec->Ops.pfnInit)
+ vrc = pCodec->Ops.pfnInit(pCodec);
+
+ if (RT_SUCCESS(vrc))
+ {
+ pCodec->Parms.enmType = RECORDINGCODECTYPE_VIDEO;
+ pCodec->Parms.enmVideoCodec = RecordingVideoCodec_VP8; /** @todo No VP9 yet. */
+ }
+ else
+ LogRel(("Recording: Error initializing video codec (%Rrc)\n", vrc));
+
+ return vrc;
+}
+
+#ifdef VBOX_WITH_AUDIO_RECORDING
+/**
+ * Lets an audio codec parse advanced options given from a string.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec instance to parse options for.
+ * @param strOptions Options string to parse.
+ */
+static DECLCALLBACK(int) recordingCodecAudioParseOptions(PRECORDINGCODEC pCodec, const com::Utf8Str &strOptions)
+{
+ AssertReturn(pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO, VERR_INVALID_PARAMETER);
+
+ size_t pos = 0;
+ com::Utf8Str key, value;
+ while ((pos = strOptions.parseKeyValue(key, value, pos)) != com::Utf8Str::npos)
+ {
+ if (key.compare("ac_profile", com::Utf8Str::CaseInsensitive) == 0)
+ {
+ if (value.compare("low", com::Utf8Str::CaseInsensitive) == 0)
+ {
+ PDMAudioPropsInit(&pCodec->Parms.Audio.PCMProps, 16, true /* fSigned */, 1 /* Channels */, 8000 /* Hz */);
+ }
+ else if (value.startsWith("med" /* "med[ium]" */, com::Utf8Str::CaseInsensitive) == 0)
+ {
+ /* Stay with the defaults. */
+ }
+ else if (value.compare("high", com::Utf8Str::CaseInsensitive) == 0)
+ {
+ PDMAudioPropsInit(&pCodec->Parms.Audio.PCMProps, 16, true /* fSigned */, 2 /* Channels */, 48000 /* Hz */);
+ }
+ }
+ else
+ LogRel(("Recording: Unknown option '%s' (value '%s'), skipping\n", key.c_str(), value.c_str()));
+
+ } /* while */
+
+ return VINF_SUCCESS;
+}
+#endif
+
+static void recordingCodecReset(PRECORDINGCODEC pCodec)
+{
+ pCodec->State.tsLastWrittenMs = 0;
+
+ pCodec->State.cEncErrors = 0;
+#ifdef VBOX_WITH_STATISTICS
+ pCodec->STAM.cEncBlocks = 0;
+ pCodec->STAM.msEncTotal = 0;
+#endif
+}
+
+/**
+ * Common code for codec creation.
+ *
+ * @param pCodec Codec instance to create.
+ */
+static void recordingCodecCreateCommon(PRECORDINGCODEC pCodec)
+{
+ RT_ZERO(pCodec->Ops);
+ RT_ZERO(pCodec->Callbacks);
+}
+
+/**
+ * Creates an audio codec.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec instance to create.
+ * @param enmAudioCodec Audio codec to create.
+ */
+int recordingCodecCreateAudio(PRECORDINGCODEC pCodec, RecordingAudioCodec_T enmAudioCodec)
+{
+ int vrc;
+
+ recordingCodecCreateCommon(pCodec);
+
+ switch (enmAudioCodec)
+ {
+# ifdef VBOX_WITH_LIBVORBIS
+ case RecordingAudioCodec_OggVorbis:
+ {
+ pCodec->Ops.pfnInit = recordingCodecVorbisInit;
+ pCodec->Ops.pfnDestroy = recordingCodecVorbisDestroy;
+ pCodec->Ops.pfnParseOptions = recordingCodecAudioParseOptions;
+ pCodec->Ops.pfnEncode = recordingCodecVorbisEncode;
+ pCodec->Ops.pfnFinalize = recordingCodecVorbisFinalize;
+
+ vrc = VINF_SUCCESS;
+ break;
+ }
+# endif /* VBOX_WITH_LIBVORBIS */
+
+ default:
+ LogRel(("Recording: Selected codec is not supported!\n"));
+ vrc = VERR_RECORDING_CODEC_NOT_SUPPORTED;
+ break;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ pCodec->Parms.enmType = RECORDINGCODECTYPE_AUDIO;
+ pCodec->Parms.enmAudioCodec = enmAudioCodec;
+ }
+
+ return vrc;
+}
+
+/**
+ * Creates a video codec.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec instance to create.
+ * @param enmVideoCodec Video codec to create.
+ */
+int recordingCodecCreateVideo(PRECORDINGCODEC pCodec, RecordingVideoCodec_T enmVideoCodec)
+{
+ int vrc;
+
+ recordingCodecCreateCommon(pCodec);
+
+ switch (enmVideoCodec)
+ {
+# ifdef VBOX_WITH_LIBVPX
+ case RecordingVideoCodec_VP8:
+ {
+ pCodec->Ops.pfnInit = recordingCodecVPXInit;
+ pCodec->Ops.pfnDestroy = recordingCodecVPXDestroy;
+ pCodec->Ops.pfnParseOptions = recordingCodecVPXParseOptions;
+ pCodec->Ops.pfnEncode = recordingCodecVPXEncode;
+
+ vrc = VINF_SUCCESS;
+ break;
+ }
+# endif /* VBOX_WITH_LIBVPX */
+
+ default:
+ vrc = VERR_RECORDING_CODEC_NOT_SUPPORTED;
+ break;
+ }
+
+ if (RT_SUCCESS(vrc))
+ {
+ pCodec->Parms.enmType = RECORDINGCODECTYPE_VIDEO;
+ pCodec->Parms.enmVideoCodec = enmVideoCodec;
+ }
+
+ return vrc;
+}
+
+/**
+ * Initializes a codec.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec to initialize.
+ * @param pCallbacks Codec callback table to use. Optional and may be NULL.
+ * @param Settings Settings to use for initializing the codec.
+ */
+int recordingCodecInit(const PRECORDINGCODEC pCodec, const PRECORDINGCODECCALLBACKS pCallbacks, const settings::RecordingScreenSettings &Settings)
+{
+ recordingCodecReset(pCodec);
+
+ int vrc;
+ if (pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO)
+ vrc = recordingCodecInitAudio(pCodec, pCallbacks, Settings);
+ else if (pCodec->Parms.enmType == RECORDINGCODECTYPE_VIDEO)
+ vrc = recordingCodecInitVideo(pCodec, pCallbacks, Settings);
+ else
+ AssertFailedStmt(vrc = VERR_NOT_SUPPORTED);
+
+ return vrc;
+}
+
+/**
+ * Destroys an audio codec.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec to destroy.
+ */
+static int recordingCodecDestroyAudio(PRECORDINGCODEC pCodec)
+{
+ AssertReturn(pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO, VERR_INVALID_PARAMETER);
+
+ return pCodec->Ops.pfnDestroy(pCodec);
+}
+
+/**
+ * Destroys a video codec.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec to destroy.
+ */
+static int recordingCodecDestroyVideo(PRECORDINGCODEC pCodec)
+{
+ AssertReturn(pCodec->Parms.enmType == RECORDINGCODECTYPE_VIDEO, VERR_INVALID_PARAMETER);
+
+ return pCodec->Ops.pfnDestroy(pCodec);
+}
+
+/**
+ * Destroys the codec.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec to destroy.
+ */
+int recordingCodecDestroy(PRECORDINGCODEC pCodec)
+{
+ if (pCodec->Parms.enmType == RECORDINGCODECTYPE_INVALID)
+ return VINF_SUCCESS;
+
+ int vrc;
+
+ if (pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO)
+ vrc = recordingCodecDestroyAudio(pCodec);
+ else if (pCodec->Parms.enmType == RECORDINGCODECTYPE_VIDEO)
+ vrc =recordingCodecDestroyVideo(pCodec);
+ else
+ AssertFailedReturn(VERR_NOT_SUPPORTED);
+
+ if (RT_SUCCESS(vrc))
+ {
+ if (pCodec->pvScratch)
+ {
+ Assert(pCodec->cbScratch);
+ RTMemFree(pCodec->pvScratch);
+ pCodec->pvScratch = NULL;
+ pCodec->cbScratch = 0;
+ }
+
+ pCodec->Parms.enmType = RECORDINGCODECTYPE_INVALID;
+ pCodec->Parms.enmVideoCodec = RecordingVideoCodec_None;
+ }
+
+ return vrc;
+}
+
+/**
+ * Feeds the codec encoder with data to encode.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec to use.
+ * @param pFrame Pointer to frame data to encode.
+ * @param pcEncoded Where to return the number of encoded blocks in \a pvDst on success. Optional.
+ * @param pcbEncoded Where to return the number of encoded bytes in \a pvDst on success. Optional.
+ */
+int recordingCodecEncode(PRECORDINGCODEC pCodec,
+ const PRECORDINGFRAME pFrame, size_t *pcEncoded, size_t *pcbEncoded)
+{
+ AssertPtrReturn(pCodec->Ops.pfnEncode, VERR_NOT_SUPPORTED);
+
+ size_t cEncoded, cbEncoded;
+ int vrc = pCodec->Ops.pfnEncode(pCodec, pFrame, &cEncoded, &cbEncoded);
+ if (RT_SUCCESS(vrc))
+ {
+ pCodec->State.tsLastWrittenMs = pFrame->msTimestamp;
+
+#ifdef VBOX_WITH_STATISTICS
+ pCodec->STAM.cEncBlocks += cEncoded;
+ pCodec->STAM.msEncTotal += pCodec->Parms.msFrame * cEncoded;
+#endif
+ if (pcEncoded)
+ *pcEncoded = cEncoded;
+ if (pcbEncoded)
+ *pcbEncoded = cbEncoded;
+ }
+
+ return vrc;
+}
+
+/**
+ * Tells the codec that has to finalize the stream.
+ *
+ * @returns VBox status code.
+ * @param pCodec Codec to finalize stream for.
+ */
+int recordingCodecFinalize(PRECORDINGCODEC pCodec)
+{
+ if (pCodec->Ops.pfnFinalize)
+ return pCodec->Ops.pfnFinalize(pCodec);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Returns whether the codec has been initialized or not.
+ *
+ * @returns @c true if initialized, or @c false if not.
+ * @param pCodec Codec to return initialization status for.
+ */
+bool recordingCodecIsInitialized(const PRECORDINGCODEC pCodec)
+{
+ return pCodec->Ops.pfnInit != NULL; /* pfnInit acts as a beacon for initialization status. */
+}
+
+/**
+ * Returns the number of writable bytes for a given timestamp.
+ *
+ * This basically is a helper function to respect the set frames per second (FPS).
+ *
+ * @returns Number of writable bytes.
+ * @param pCodec Codec to return number of writable bytes for.
+ * @param msTimestamp Timestamp (PTS, in ms) return number of writable bytes for.
+ */
+uint32_t recordingCodecGetWritable(const PRECORDINGCODEC pCodec, uint64_t msTimestamp)
+{
+ Log3Func(("%RU64 -- tsLastWrittenMs=%RU64 + uDelayMs=%RU32\n",
+ msTimestamp, pCodec->State.tsLastWrittenMs,pCodec->Parms.Video.uDelayMs));
+
+ if (msTimestamp < pCodec->State.tsLastWrittenMs + pCodec->Parms.Video.uDelayMs)
+ return 0; /* Too early for writing (respect set FPS). */
+
+ /* For now we just return the complete frame space. */
+ AssertMsg(pCodec->Parms.cbFrame, ("Codec not initialized yet\n"));
+ return pCodec->Parms.cbFrame;
+}