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