[53442] | 1 | /* $Id: DrvAudioRec.cpp 94326 2022-03-22 15:14:54Z vboxsync $ */
|
---|
| 2 | /** @file
|
---|
| 3 | * Video recording audio backend for Main.
|
---|
[88030] | 4 | *
|
---|
| 5 | * This driver is part of Main and is responsible for providing audio
|
---|
| 6 | * data to Main's video capturing feature.
|
---|
| 7 | *
|
---|
| 8 | * The driver itself implements a PDM host audio backend, which in turn
|
---|
| 9 | * provides the driver with the required audio data and audio events.
|
---|
| 10 | *
|
---|
| 11 | * For now there is support for the following destinations (called "sinks"):
|
---|
| 12 | *
|
---|
| 13 | * - Direct writing of .webm files to the host.
|
---|
| 14 | * - Communicating with Main via the Console object to send the encoded audio data to.
|
---|
| 15 | * The Console object in turn then will route the data to the Display / video capturing interface then.
|
---|
[53442] | 16 | */
|
---|
| 17 |
|
---|
| 18 | /*
|
---|
[93115] | 19 | * Copyright (C) 2016-2022 Oracle Corporation
|
---|
[53442] | 20 | *
|
---|
| 21 | * This file is part of VirtualBox Open Source Edition (OSE), as
|
---|
| 22 | * available from http://www.virtualbox.org. This file is free software;
|
---|
| 23 | * you can redistribute it and/or modify it under the terms of the GNU
|
---|
| 24 | * General Public License (GPL) as published by the Free Software
|
---|
| 25 | * Foundation, in version 2 as it comes in the "COPYING" file of the
|
---|
| 26 | * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
|
---|
| 27 | * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
|
---|
| 28 | */
|
---|
[65162] | 29 |
|
---|
[68715] | 30 | /* This code makes use of the Opus codec (libopus):
|
---|
| 31 | *
|
---|
| 32 | * Copyright 2001-2011 Xiph.Org, Skype Limited, Octasic,
|
---|
| 33 | * Jean-Marc Valin, Timothy B. Terriberry,
|
---|
| 34 | * CSIRO, Gregory Maxwell, Mark Borgerding,
|
---|
| 35 | * Erik de Castro Lopo
|
---|
| 36 | *
|
---|
| 37 | * Redistribution and use in source and binary forms, with or without
|
---|
| 38 | * modification, are permitted provided that the following conditions
|
---|
| 39 | * are met:
|
---|
| 40 | *
|
---|
| 41 | * - Redistributions of source code must retain the above copyright
|
---|
| 42 | * notice, this list of conditions and the following disclaimer.
|
---|
| 43 | *
|
---|
| 44 | * - Redistributions in binary form must reproduce the above copyright
|
---|
| 45 | * notice, this list of conditions and the following disclaimer in the
|
---|
| 46 | * documentation and/or other materials provided with the distribution.
|
---|
| 47 | *
|
---|
| 48 | * - Neither the name of Internet Society, IETF or IETF Trust, nor the
|
---|
| 49 | * names of specific contributors, may be used to endorse or promote
|
---|
| 50 | * products derived from this software without specific prior written
|
---|
| 51 | * permission.
|
---|
| 52 | *
|
---|
| 53 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
---|
| 54 | * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
---|
| 55 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
---|
| 56 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
---|
| 57 | * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
---|
| 58 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
---|
| 59 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
---|
| 60 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
---|
| 61 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
---|
| 62 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
---|
| 63 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
---|
| 64 | *
|
---|
| 65 | * Opus is subject to the royalty-free patent licenses which are
|
---|
| 66 | * specified at:
|
---|
| 67 | *
|
---|
| 68 | * Xiph.Org Foundation:
|
---|
| 69 | * https://datatracker.ietf.org/ipr/1524/
|
---|
| 70 | *
|
---|
| 71 | * Microsoft Corporation:
|
---|
| 72 | * https://datatracker.ietf.org/ipr/1914/
|
---|
| 73 | *
|
---|
| 74 | * Broadcom Corporation:
|
---|
| 75 | * https://datatracker.ietf.org/ipr/1526/
|
---|
| 76 | *
|
---|
| 77 | */
|
---|
| 78 |
|
---|
[65162] | 79 |
|
---|
| 80 | /*********************************************************************************************************************************
|
---|
| 81 | * Header Files *
|
---|
| 82 | *********************************************************************************************************************************/
|
---|
[65694] | 83 | #define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
|
---|
[67914] | 84 | #include "LoggingNew.h"
|
---|
| 85 |
|
---|
[75344] | 86 | #include "DrvAudioRec.h"
|
---|
[53442] | 87 | #include "ConsoleImpl.h"
|
---|
| 88 |
|
---|
[69683] | 89 | #include "WebMWriter.h"
|
---|
[65162] | 90 |
|
---|
[53442] | 91 | #include <iprt/mem.h>
|
---|
| 92 | #include <iprt/cdefs.h>
|
---|
| 93 |
|
---|
[88028] | 94 | #include <VBox/vmm/cfgm.h>
|
---|
| 95 | #include <VBox/vmm/pdmdrv.h>
|
---|
[53442] | 96 | #include <VBox/vmm/pdmaudioifs.h>
|
---|
[88028] | 97 | #include <VBox/vmm/pdmaudioinline.h>
|
---|
[93444] | 98 | #include <VBox/vmm/vmmr3vtable.h>
|
---|
[53442] | 99 | #include <VBox/err.h>
|
---|
| 100 |
|
---|
[65263] | 101 | #ifdef VBOX_WITH_LIBOPUS
|
---|
| 102 | # include <opus.h>
|
---|
| 103 | #endif
|
---|
[53442] | 104 |
|
---|
[68323] | 105 |
|
---|
[65162] | 106 | /*********************************************************************************************************************************
|
---|
[68323] | 107 | * Defines *
|
---|
| 108 | *********************************************************************************************************************************/
|
---|
[87995] | 109 | #define AVREC_OPUS_HZ_MAX 48000 /**< Maximum sample rate (in Hz) Opus can handle. */
|
---|
| 110 | #define AVREC_OPUS_FRAME_MS_DEFAULT 20 /**< Default Opus frame size (in ms). */
|
---|
[68323] | 111 |
|
---|
| 112 |
|
---|
| 113 | /*********************************************************************************************************************************
|
---|
[65162] | 114 | * Structures and Typedefs *
|
---|
| 115 | *********************************************************************************************************************************/
|
---|
[53442] | 116 | /**
|
---|
[65410] | 117 | * Enumeration for specifying the recording container type.
|
---|
[65197] | 118 | */
|
---|
[65410] | 119 | typedef enum AVRECCONTAINERTYPE
|
---|
[65197] | 120 | {
|
---|
[65410] | 121 | /** Unknown / invalid container type. */
|
---|
| 122 | AVRECCONTAINERTYPE_UNKNOWN = 0,
|
---|
| 123 | /** Recorded data goes to Main / Console. */
|
---|
| 124 | AVRECCONTAINERTYPE_MAIN_CONSOLE = 1,
|
---|
[68321] | 125 | /** Recorded data will be written to a .webm file. */
|
---|
[65410] | 126 | AVRECCONTAINERTYPE_WEBM = 2
|
---|
| 127 | } AVRECCONTAINERTYPE;
|
---|
[65197] | 128 |
|
---|
| 129 | /**
|
---|
[65410] | 130 | * Structure for keeping generic container parameters.
|
---|
[65197] | 131 | */
|
---|
[65410] | 132 | typedef struct AVRECCONTAINERPARMS
|
---|
| 133 | {
|
---|
| 134 | /** The container's type. */
|
---|
| 135 | AVRECCONTAINERTYPE enmType;
|
---|
[74955] | 136 | union
|
---|
| 137 | {
|
---|
| 138 | /** WebM file specifics. */
|
---|
| 139 | struct
|
---|
| 140 | {
|
---|
| 141 | /** Allocated file name to write .webm file to. Must be free'd. */
|
---|
| 142 | char *pszFile;
|
---|
| 143 | } WebM;
|
---|
| 144 | };
|
---|
[65410] | 145 |
|
---|
| 146 | } AVRECCONTAINERPARMS, *PAVRECCONTAINERPARMS;
|
---|
| 147 |
|
---|
| 148 | /**
|
---|
| 149 | * Structure for keeping container-specific data.
|
---|
| 150 | */
|
---|
| 151 | typedef struct AVRECCONTAINER
|
---|
| 152 | {
|
---|
| 153 | /** Generic container parameters. */
|
---|
| 154 | AVRECCONTAINERPARMS Parms;
|
---|
| 155 |
|
---|
| 156 | union
|
---|
| 157 | {
|
---|
| 158 | struct
|
---|
| 159 | {
|
---|
| 160 | /** Pointer to Console. */
|
---|
| 161 | Console *pConsole;
|
---|
| 162 | } Main;
|
---|
| 163 |
|
---|
| 164 | struct
|
---|
| 165 | {
|
---|
| 166 | /** Pointer to WebM container to write recorded audio data to.
|
---|
| 167 | * See the AVRECMODE enumeration for more information. */
|
---|
| 168 | WebMWriter *pWebM;
|
---|
| 169 | /** Assigned track number from WebM container. */
|
---|
| 170 | uint8_t uTrack;
|
---|
| 171 | } WebM;
|
---|
| 172 | };
|
---|
| 173 | } AVRECCONTAINER, *PAVRECCONTAINER;
|
---|
| 174 |
|
---|
| 175 | /**
|
---|
| 176 | * Structure for keeping generic codec parameters.
|
---|
| 177 | */
|
---|
| 178 | typedef struct AVRECCODECPARMS
|
---|
| 179 | {
|
---|
[74850] | 180 | /** The codec's used PCM properties. */
|
---|
| 181 | PDMAUDIOPCMPROPS PCMProps;
|
---|
[65410] | 182 | /** The codec's bitrate. 0 if not used / cannot be specified. */
|
---|
| 183 | uint32_t uBitrate;
|
---|
| 184 |
|
---|
| 185 | } AVRECCODECPARMS, *PAVRECCODECPARMS;
|
---|
| 186 |
|
---|
| 187 | /**
|
---|
| 188 | * Structure for keeping codec-specific data.
|
---|
| 189 | */
|
---|
[65197] | 190 | typedef struct AVRECCODEC
|
---|
| 191 | {
|
---|
[65410] | 192 | /** Generic codec parameters. */
|
---|
| 193 | AVRECCODECPARMS Parms;
|
---|
[65197] | 194 | union
|
---|
| 195 | {
|
---|
[65263] | 196 | #ifdef VBOX_WITH_LIBOPUS
|
---|
[65197] | 197 | struct
|
---|
| 198 | {
|
---|
| 199 | /** Encoder we're going to use. */
|
---|
[65389] | 200 | OpusEncoder *pEnc;
|
---|
[68338] | 201 | /** Time (in ms) an (encoded) frame takes.
|
---|
| 202 | *
|
---|
[68452] | 203 | * For Opus, valid frame sizes are:
|
---|
[68338] | 204 | * ms Frame size
|
---|
| 205 | * 2.5 120
|
---|
| 206 | * 5 240
|
---|
| 207 | * 10 480
|
---|
| 208 | * 20 (Default) 960
|
---|
| 209 | * 40 1920
|
---|
| 210 | * 60 2880
|
---|
| 211 | */
|
---|
| 212 | uint32_t msFrame;
|
---|
[75082] | 213 | /** The frame size in bytes (based on msFrame). */
|
---|
| 214 | uint32_t cbFrame;
|
---|
| 215 | /** The frame size in samples per frame (based on msFrame). */
|
---|
| 216 | uint32_t csFrame;
|
---|
[65197] | 217 | } Opus;
|
---|
[65263] | 218 | #endif /* VBOX_WITH_LIBOPUS */
|
---|
[65197] | 219 | };
|
---|
[65410] | 220 |
|
---|
[88459] | 221 | #ifdef VBOX_WITH_STATISTICS /** @todo Register these values. */
|
---|
[65410] | 222 | struct
|
---|
| 223 | {
|
---|
| 224 | /** Number of frames encoded. */
|
---|
| 225 | uint64_t cEncFrames;
|
---|
| 226 | /** Total time (in ms) of already encoded audio data. */
|
---|
| 227 | uint64_t msEncTotal;
|
---|
[88459] | 228 | } Stats;
|
---|
| 229 | #endif
|
---|
[65197] | 230 | } AVRECCODEC, *PAVRECCODEC;
|
---|
| 231 |
|
---|
[65410] | 232 | typedef struct AVRECSINK
|
---|
| 233 | {
|
---|
| 234 | /** @todo Add types for container / codec as soon as we implement more stuff. */
|
---|
| 235 |
|
---|
| 236 | /** Container data to use for data processing. */
|
---|
| 237 | AVRECCONTAINER Con;
|
---|
[68338] | 238 | /** Codec data this sink uses for encoding. */
|
---|
[65410] | 239 | AVRECCODEC Codec;
|
---|
[69188] | 240 | /** Timestamp (in ms) of when the sink was created. */
|
---|
| 241 | uint64_t tsStartMs;
|
---|
[65410] | 242 | } AVRECSINK, *PAVRECSINK;
|
---|
| 243 |
|
---|
[65197] | 244 | /**
|
---|
[65624] | 245 | * Audio video recording (output) stream.
|
---|
[65197] | 246 | */
|
---|
[65624] | 247 | typedef struct AVRECSTREAM
|
---|
[65197] | 248 | {
|
---|
[88718] | 249 | /** Common part. */
|
---|
| 250 | PDMAUDIOBACKENDSTREAM Core;
|
---|
[65624] | 251 | /** The stream's acquired configuration. */
|
---|
[88718] | 252 | PDMAUDIOSTREAMCFG Cfg;
|
---|
[65256] | 253 | /** (Audio) frame buffer. */
|
---|
[88718] | 254 | PRTCIRCBUF pCircBuf;
|
---|
[65410] | 255 | /** Pointer to sink to use for writing. */
|
---|
[88718] | 256 | PAVRECSINK pSink;
|
---|
[69188] | 257 | /** Last encoded PTS (in ms). */
|
---|
[88718] | 258 | uint64_t uLastPTSMs;
|
---|
[75082] | 259 | /** Temporary buffer for the input (source) data to encode. */
|
---|
[88718] | 260 | void *pvSrcBuf;
|
---|
[75082] | 261 | /** Size (in bytes) of the temporary buffer holding the input (source) data to encode. */
|
---|
[88718] | 262 | size_t cbSrcBuf;
|
---|
[75082] | 263 | /** Temporary buffer for the encoded output (destination) data. */
|
---|
[88718] | 264 | void *pvDstBuf;
|
---|
[75082] | 265 | /** Size (in bytes) of the temporary buffer holding the encoded output (destination) data. */
|
---|
[88718] | 266 | size_t cbDstBuf;
|
---|
[65624] | 267 | } AVRECSTREAM, *PAVRECSTREAM;
|
---|
[65197] | 268 |
|
---|
| 269 | /**
|
---|
[65162] | 270 | * Video recording audio driver instance data.
|
---|
[53442] | 271 | */
|
---|
[75354] | 272 | typedef struct DRVAUDIORECORDING
|
---|
[53442] | 273 | {
|
---|
| 274 | /** Pointer to audio video recording object. */
|
---|
[65162] | 275 | AudioVideoRec *pAudioVideoRec;
|
---|
[53442] | 276 | /** Pointer to the driver instance structure. */
|
---|
[65162] | 277 | PPDMDRVINS pDrvIns;
|
---|
| 278 | /** Pointer to host audio interface. */
|
---|
| 279 | PDMIHOSTAUDIO IHostAudio;
|
---|
[65410] | 280 | /** Pointer to the console object. */
|
---|
[73104] | 281 | ComPtr<Console> pConsole;
|
---|
[65162] | 282 | /** Pointer to the DrvAudio port interface that is above us. */
|
---|
| 283 | PPDMIAUDIOCONNECTOR pDrvAudio;
|
---|
[74955] | 284 | /** The driver's configured container parameters. */
|
---|
| 285 | AVRECCONTAINERPARMS ContainerParms;
|
---|
| 286 | /** The driver's configured codec parameters. */
|
---|
| 287 | AVRECCODECPARMS CodecParms;
|
---|
[65410] | 288 | /** The driver's sink for writing output to. */
|
---|
| 289 | AVRECSINK Sink;
|
---|
[75354] | 290 | } DRVAUDIORECORDING, *PDRVAUDIORECORDING;
|
---|
[53442] | 291 |
|
---|
| 292 |
|
---|
[88459] | 293 | AudioVideoRec::AudioVideoRec(Console *pConsole)
|
---|
| 294 | : AudioDriver(pConsole)
|
---|
| 295 | , mpDrv(NULL)
|
---|
[53442] | 296 | {
|
---|
[88459] | 297 | }
|
---|
[53442] | 298 |
|
---|
[65389] | 299 |
|
---|
[88459] | 300 | AudioVideoRec::~AudioVideoRec(void)
|
---|
| 301 | {
|
---|
| 302 | if (mpDrv)
|
---|
[68338] | 303 | {
|
---|
[88459] | 304 | mpDrv->pAudioVideoRec = NULL;
|
---|
| 305 | mpDrv = NULL;
|
---|
[68338] | 306 | }
|
---|
[88459] | 307 | }
|
---|
[65389] | 308 |
|
---|
[65410] | 309 |
|
---|
[88459] | 310 | /**
|
---|
| 311 | * Applies a video recording configuration to this driver instance.
|
---|
| 312 | *
|
---|
| 313 | * @returns VBox status code.
|
---|
| 314 | * @param Settings Capturing configuration to apply.
|
---|
| 315 | */
|
---|
| 316 | int AudioVideoRec::applyConfiguration(const settings::RecordingSettings &Settings)
|
---|
| 317 | {
|
---|
| 318 | /** @todo Do some validation here. */
|
---|
| 319 | mVideoRecCfg = Settings; /* Note: Does have an own copy operator. */
|
---|
| 320 | return VINF_SUCCESS;
|
---|
| 321 | }
|
---|
[65410] | 322 |
|
---|
[68338] | 323 |
|
---|
[93444] | 324 | int AudioVideoRec::configureDriver(PCFGMNODE pLunCfg, PCVMMR3VTABLE pVMM)
|
---|
[88459] | 325 | {
|
---|
| 326 | /** @todo For now we're using the configuration of the first screen here audio-wise. */
|
---|
| 327 | Assert(mVideoRecCfg.mapScreens.size() >= 1);
|
---|
| 328 | const settings::RecordingScreenSettings &Screen0Settings = mVideoRecCfg.mapScreens[0];
|
---|
[74848] | 329 |
|
---|
[94326] | 330 | int rc = pVMM->pfnCFGMR3InsertInteger(pLunCfg, "ContainerType", (uint64_t)Screen0Settings.enmDest);
|
---|
[88459] | 331 | AssertRCReturn(rc, rc);
|
---|
| 332 | if (Screen0Settings.enmDest == RecordingDestination_File)
|
---|
[74848] | 333 | {
|
---|
[93444] | 334 | rc = pVMM->pfnCFGMR3InsertString(pLunCfg, "ContainerFileName", Utf8Str(Screen0Settings.File.strName).c_str());
|
---|
[88459] | 335 | AssertRCReturn(rc, rc);
|
---|
[74848] | 336 | }
|
---|
[93444] | 337 | rc = pVMM->pfnCFGMR3InsertInteger(pLunCfg, "CodecHz", Screen0Settings.Audio.uHz);
|
---|
[88459] | 338 | AssertRCReturn(rc, rc);
|
---|
[93444] | 339 | rc = pVMM->pfnCFGMR3InsertInteger(pLunCfg, "CodecBits", Screen0Settings.Audio.cBits);
|
---|
[88459] | 340 | AssertRCReturn(rc, rc);
|
---|
[93444] | 341 | rc = pVMM->pfnCFGMR3InsertInteger(pLunCfg, "CodecChannels", Screen0Settings.Audio.cChannels);
|
---|
[88459] | 342 | AssertRCReturn(rc, rc);
|
---|
[93444] | 343 | rc = pVMM->pfnCFGMR3InsertInteger(pLunCfg, "CodecBitrate", 0); /* Let Opus decide for now. */
|
---|
[88459] | 344 | AssertRCReturn(rc, rc);
|
---|
[74848] | 345 |
|
---|
[93444] | 346 | return AudioDriver::configureDriver(pLunCfg, pVMM);
|
---|
[88459] | 347 | }
|
---|
[65410] | 348 |
|
---|
| 349 |
|
---|
[88459] | 350 | /*********************************************************************************************************************************
|
---|
| 351 | * PDMIHOSTAUDIO *
|
---|
| 352 | *********************************************************************************************************************************/
|
---|
[74955] | 353 |
|
---|
[88459] | 354 | /**
|
---|
| 355 | * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
|
---|
| 356 | */
|
---|
| 357 | static DECLCALLBACK(int) drvAudioVideoRecHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
|
---|
| 358 | {
|
---|
| 359 | RT_NOREF(pInterface);
|
---|
| 360 | AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
|
---|
[65410] | 361 |
|
---|
[88534] | 362 | /*
|
---|
| 363 | * Fill in the config structure.
|
---|
| 364 | */
|
---|
| 365 | RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "VideoRec");
|
---|
| 366 | pBackendCfg->cbStream = sizeof(AVRECSTREAM);
|
---|
| 367 | pBackendCfg->fFlags = 0;
|
---|
[88459] | 368 | pBackendCfg->cMaxStreamsIn = 0;
|
---|
| 369 | pBackendCfg->cMaxStreamsOut = UINT32_MAX;
|
---|
[65416] | 370 |
|
---|
[88459] | 371 | return VINF_SUCCESS;
|
---|
[65410] | 372 | }
|
---|
| 373 |
|
---|
| 374 |
|
---|
| 375 | /**
|
---|
[88459] | 376 | * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
|
---|
[65410] | 377 | */
|
---|
[88459] | 378 | static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvAudioVideoRecHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
|
---|
[65410] | 379 | {
|
---|
[88459] | 380 | RT_NOREF(pInterface, enmDir);
|
---|
| 381 | return PDMAUDIOBACKENDSTS_RUNNING;
|
---|
[65410] | 382 | }
|
---|
| 383 |
|
---|
| 384 |
|
---|
| 385 | /**
|
---|
| 386 | * Creates an audio output stream and associates it with the specified recording sink.
|
---|
| 387 | *
|
---|
[88300] | 388 | * @returns VBox status code.
|
---|
[65410] | 389 | * @param pThis Driver instance.
|
---|
[65624] | 390 | * @param pStreamAV Audio output stream to create.
|
---|
[65410] | 391 | * @param pSink Recording sink to associate audio output stream to.
|
---|
| 392 | * @param pCfgReq Requested configuration by the audio backend.
|
---|
| 393 | * @param pCfgAcq Acquired configuration by the audio output stream.
|
---|
| 394 | */
|
---|
[75354] | 395 | static int avRecCreateStreamOut(PDRVAUDIORECORDING pThis, PAVRECSTREAM pStreamAV,
|
---|
[89487] | 396 | PAVRECSINK pSink, PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
|
---|
[65410] | 397 | {
|
---|
[65624] | 398 | AssertPtrReturn(pThis, VERR_INVALID_POINTER);
|
---|
| 399 | AssertPtrReturn(pStreamAV, VERR_INVALID_POINTER);
|
---|
| 400 | AssertPtrReturn(pSink, VERR_INVALID_POINTER);
|
---|
| 401 | AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
|
---|
| 402 | AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
|
---|
[65410] | 403 |
|
---|
[89218] | 404 | if (pCfgReq->enmPath != PDMAUDIOPATH_OUT_FRONT)
|
---|
[65410] | 405 | {
|
---|
[82252] | 406 | LogRel2(("Recording: Support for surround audio not implemented yet\n"));
|
---|
[65410] | 407 | AssertFailed();
|
---|
| 408 | return VERR_NOT_SUPPORTED;
|
---|
| 409 | }
|
---|
| 410 |
|
---|
| 411 | #ifdef VBOX_WITH_LIBOPUS
|
---|
[82252] | 412 | int rc = RTCircBufCreate(&pStreamAV->pCircBuf, pSink->Codec.Opus.cbFrame * 2 /* Use "double buffering" */);
|
---|
[65162] | 413 | if (RT_SUCCESS(rc))
|
---|
[53442] | 414 | {
|
---|
[75082] | 415 | size_t cbScratchBuf = pSink->Codec.Opus.cbFrame;
|
---|
| 416 | pStreamAV->pvSrcBuf = RTMemAlloc(cbScratchBuf);
|
---|
| 417 | if (pStreamAV->pvSrcBuf)
|
---|
[65197] | 418 | {
|
---|
[75082] | 419 | pStreamAV->cbSrcBuf = cbScratchBuf;
|
---|
| 420 | pStreamAV->pvDstBuf = RTMemAlloc(cbScratchBuf);
|
---|
| 421 | if (pStreamAV->pvDstBuf)
|
---|
| 422 | {
|
---|
| 423 | pStreamAV->cbDstBuf = cbScratchBuf;
|
---|
[73370] | 424 |
|
---|
[75082] | 425 | pStreamAV->pSink = pSink; /* Assign sink to stream. */
|
---|
| 426 | pStreamAV->uLastPTSMs = 0;
|
---|
| 427 |
|
---|
[88269] | 428 | /* Make sure to let the driver backend know that we need the audio data in
|
---|
| 429 | * a specific sampling rate Opus is optimized for. */
|
---|
| 430 | /** @todo r=bird: pCfgAcq->Props isn't initialized at all, except for uHz... */
|
---|
| 431 | pCfgAcq->Props.uHz = pSink->Codec.Parms.PCMProps.uHz;
|
---|
| 432 | // pCfgAcq->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfgAcq->Props.cbSample, pCfgAcq->Props.cChannels);
|
---|
[75082] | 433 |
|
---|
[88269] | 434 | /* Every Opus frame marks a period for now. Optimize this later. */
|
---|
| 435 | pCfgAcq->Backend.cFramesPeriod = PDMAudioPropsMilliToFrames(&pCfgAcq->Props, pSink->Codec.Opus.msFrame);
|
---|
| 436 | pCfgAcq->Backend.cFramesBufferSize = PDMAudioPropsMilliToFrames(&pCfgAcq->Props, 100 /*ms*/); /** @todo Make this configurable. */
|
---|
| 437 | pCfgAcq->Backend.cFramesPreBuffering = pCfgAcq->Backend.cFramesPeriod * 2;
|
---|
[75082] | 438 | }
|
---|
| 439 | else
|
---|
| 440 | rc = VERR_NO_MEMORY;
|
---|
[65353] | 441 | }
|
---|
[75082] | 442 | else
|
---|
| 443 | rc = VERR_NO_MEMORY;
|
---|
[53442] | 444 | }
|
---|
[65263] | 445 | #else
|
---|
[65624] | 446 | RT_NOREF(pThis, pSink, pStreamAV, pCfgReq, pCfgAcq);
|
---|
[82252] | 447 | int rc = VERR_NOT_SUPPORTED;
|
---|
[65263] | 448 | #endif /* VBOX_WITH_LIBOPUS */
|
---|
[53442] | 449 |
|
---|
[65263] | 450 | LogFlowFuncLeaveRC(rc);
|
---|
| 451 | return rc;
|
---|
[53442] | 452 | }
|
---|
| 453 |
|
---|
[65162] | 454 |
|
---|
[65410] | 455 | /**
|
---|
[88459] | 456 | * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
|
---|
| 457 | */
|
---|
| 458 | static DECLCALLBACK(int) drvAudioVideoRecHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
|
---|
[89487] | 459 | PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
|
---|
[88459] | 460 | {
|
---|
| 461 | PDRVAUDIORECORDING pThis = RT_FROM_CPP_MEMBER(pInterface, DRVAUDIORECORDING, IHostAudio);
|
---|
| 462 | PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
|
---|
| 463 | AssertPtrReturn(pStreamAV, VERR_INVALID_POINTER);
|
---|
| 464 | AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
|
---|
| 465 | AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
|
---|
| 466 |
|
---|
| 467 | if (pCfgReq->enmDir == PDMAUDIODIR_IN)
|
---|
| 468 | return VERR_NOT_SUPPORTED;
|
---|
| 469 |
|
---|
| 470 | /* For now we only have one sink, namely the driver's one.
|
---|
| 471 | * Later each stream could have its own one, to e.g. router different stream to different sinks .*/
|
---|
| 472 | PAVRECSINK pSink = &pThis->Sink;
|
---|
| 473 |
|
---|
| 474 | int rc = avRecCreateStreamOut(pThis, pStreamAV, pSink, pCfgReq, pCfgAcq);
|
---|
| 475 | PDMAudioStrmCfgCopy(&pStreamAV->Cfg, pCfgAcq);
|
---|
| 476 |
|
---|
| 477 | return rc;
|
---|
| 478 | }
|
---|
| 479 |
|
---|
| 480 |
|
---|
| 481 | /**
|
---|
[65410] | 482 | * Destroys (closes) an audio output stream.
|
---|
| 483 | *
|
---|
[88300] | 484 | * @returns VBox status code.
|
---|
[65410] | 485 | * @param pThis Driver instance.
|
---|
[65624] | 486 | * @param pStreamAV Audio output stream to destroy.
|
---|
[65410] | 487 | */
|
---|
[75354] | 488 | static int avRecDestroyStreamOut(PDRVAUDIORECORDING pThis, PAVRECSTREAM pStreamAV)
|
---|
[65410] | 489 | {
|
---|
| 490 | RT_NOREF(pThis);
|
---|
| 491 |
|
---|
[65624] | 492 | if (pStreamAV->pCircBuf)
|
---|
[65410] | 493 | {
|
---|
[65624] | 494 | RTCircBufDestroy(pStreamAV->pCircBuf);
|
---|
| 495 | pStreamAV->pCircBuf = NULL;
|
---|
[65410] | 496 | }
|
---|
| 497 |
|
---|
[75082] | 498 | if (pStreamAV->pvSrcBuf)
|
---|
| 499 | {
|
---|
| 500 | Assert(pStreamAV->cbSrcBuf);
|
---|
| 501 | RTMemFree(pStreamAV->pvSrcBuf);
|
---|
[82252] | 502 | pStreamAV->pvSrcBuf = NULL;
|
---|
[75082] | 503 | pStreamAV->cbSrcBuf = 0;
|
---|
| 504 | }
|
---|
| 505 |
|
---|
| 506 | if (pStreamAV->pvDstBuf)
|
---|
| 507 | {
|
---|
| 508 | Assert(pStreamAV->cbDstBuf);
|
---|
| 509 | RTMemFree(pStreamAV->pvDstBuf);
|
---|
[82252] | 510 | pStreamAV->pvDstBuf = NULL;
|
---|
[75082] | 511 | pStreamAV->cbDstBuf = 0;
|
---|
| 512 | }
|
---|
| 513 |
|
---|
[65410] | 514 | return VINF_SUCCESS;
|
---|
| 515 | }
|
---|
| 516 |
|
---|
| 517 |
|
---|
| 518 | /**
|
---|
[88459] | 519 | * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
|
---|
| 520 | */
|
---|
[89213] | 521 | static DECLCALLBACK(int) drvAudioVideoRecHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
|
---|
| 522 | bool fImmediate)
|
---|
[88459] | 523 | {
|
---|
| 524 | PDRVAUDIORECORDING pThis = RT_FROM_CPP_MEMBER(pInterface, DRVAUDIORECORDING, IHostAudio);
|
---|
| 525 | PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
|
---|
| 526 | AssertPtrReturn(pStream, VERR_INVALID_POINTER);
|
---|
[89213] | 527 | RT_NOREF(fImmediate);
|
---|
[88459] | 528 |
|
---|
| 529 | int rc = VINF_SUCCESS;
|
---|
| 530 | if (pStreamAV->Cfg.enmDir == PDMAUDIODIR_OUT)
|
---|
| 531 | rc = avRecDestroyStreamOut(pThis, pStreamAV);
|
---|
| 532 |
|
---|
| 533 | return rc;
|
---|
| 534 | }
|
---|
| 535 |
|
---|
| 536 |
|
---|
| 537 | /**
|
---|
[89510] | 538 | * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable}
|
---|
[65410] | 539 | */
|
---|
[89510] | 540 | static DECLCALLBACK(int) drvAudioVideoRecHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
|
---|
[53442] | 541 | {
|
---|
[89510] | 542 | RT_NOREF(pInterface, pStream);
|
---|
| 543 | return VINF_SUCCESS;
|
---|
| 544 | }
|
---|
[53442] | 545 |
|
---|
[65353] | 546 |
|
---|
[89510] | 547 | /**
|
---|
| 548 | * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable}
|
---|
| 549 | */
|
---|
| 550 | static DECLCALLBACK(int) drvAudioVideoRecHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
|
---|
| 551 | {
|
---|
| 552 | RT_NOREF(pInterface, pStream);
|
---|
| 553 | return VINF_SUCCESS;
|
---|
| 554 | }
|
---|
[65353] | 555 |
|
---|
[89510] | 556 |
|
---|
| 557 | /**
|
---|
| 558 | * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause}
|
---|
| 559 | */
|
---|
| 560 | static DECLCALLBACK(int) drvAudioVideoRecHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
|
---|
| 561 | {
|
---|
| 562 | RT_NOREF(pInterface, pStream);
|
---|
| 563 | return VINF_SUCCESS;
|
---|
[53442] | 564 | }
|
---|
| 565 |
|
---|
| 566 |
|
---|
[65162] | 567 | /**
|
---|
[89510] | 568 | * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume}
|
---|
[53442] | 569 | */
|
---|
[89510] | 570 | static DECLCALLBACK(int) drvAudioVideoRecHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
|
---|
[53442] | 571 | {
|
---|
[89510] | 572 | RT_NOREF(pInterface, pStream);
|
---|
| 573 | return VINF_SUCCESS;
|
---|
| 574 | }
|
---|
[53442] | 575 |
|
---|
| 576 |
|
---|
[89510] | 577 | /**
|
---|
| 578 | * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain}
|
---|
| 579 | */
|
---|
| 580 | static DECLCALLBACK(int) drvAudioVideoRecHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
|
---|
| 581 | {
|
---|
| 582 | RT_NOREF(pInterface, pStream);
|
---|
[65162] | 583 | return VINF_SUCCESS;
|
---|
[53442] | 584 | }
|
---|
| 585 |
|
---|
| 586 |
|
---|
[65162] | 587 | /**
|
---|
[89504] | 588 | * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState}
|
---|
[88459] | 589 | */
|
---|
[89504] | 590 | static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvAudioVideoRecHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
|
---|
| 591 | PPDMAUDIOBACKENDSTREAM pStream)
|
---|
[88459] | 592 | {
|
---|
[89504] | 593 | RT_NOREF(pInterface);
|
---|
| 594 | AssertPtrReturn(pStream, PDMHOSTAUDIOSTREAMSTATE_INVALID);
|
---|
| 595 | return PDMHOSTAUDIOSTREAMSTATE_OKAY;
|
---|
[88459] | 596 | }
|
---|
| 597 |
|
---|
| 598 |
|
---|
| 599 | /**
|
---|
| 600 | * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
|
---|
| 601 | */
|
---|
| 602 | static DECLCALLBACK(uint32_t) drvAudioVideoRecHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
|
---|
| 603 | {
|
---|
| 604 | RT_NOREF(pInterface, pStream);
|
---|
| 605 | return UINT32_MAX;
|
---|
| 606 | }
|
---|
| 607 |
|
---|
| 608 |
|
---|
| 609 | /**
|
---|
[65162] | 610 | * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
|
---|
| 611 | */
|
---|
[82255] | 612 | static DECLCALLBACK(int) drvAudioVideoRecHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
|
---|
[88394] | 613 | const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
|
---|
[53442] | 614 | {
|
---|
[88395] | 615 | RT_NOREF(pInterface);
|
---|
[88394] | 616 | PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
|
---|
| 617 | AssertPtrReturn(pStreamAV, VERR_INVALID_POINTER);
|
---|
[88991] | 618 | if (cbBuf)
|
---|
| 619 | AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
|
---|
[88394] | 620 | AssertReturn(pcbWritten, VERR_INVALID_PARAMETER);
|
---|
[53442] | 621 |
|
---|
[65565] | 622 | int rc = VINF_SUCCESS;
|
---|
[65429] | 623 |
|
---|
[65565] | 624 | uint32_t cbWrittenTotal = 0;
|
---|
[53442] | 625 |
|
---|
[65162] | 626 | /*
|
---|
[65256] | 627 | * Call the encoder with the data.
|
---|
[65162] | 628 | */
|
---|
[65263] | 629 | #ifdef VBOX_WITH_LIBOPUS
|
---|
[65624] | 630 | PAVRECSINK pSink = pStreamAV->pSink;
|
---|
[65410] | 631 | AssertPtr(pSink);
|
---|
[75082] | 632 | PAVRECCODEC pCodec = &pSink->Codec;
|
---|
| 633 | AssertPtr(pCodec);
|
---|
[65624] | 634 | PRTCIRCBUF pCircBuf = pStreamAV->pCircBuf;
|
---|
[65410] | 635 | AssertPtr(pCircBuf);
|
---|
[53442] | 636 |
|
---|
[88394] | 637 | uint32_t cbToWrite = cbBuf;
|
---|
[65565] | 638 |
|
---|
[65389] | 639 | /*
|
---|
[88459] | 640 | * Write as much as we can into our internal ring buffer.
|
---|
[65389] | 641 | */
|
---|
[88991] | 642 | while ( cbToWrite > 0
|
---|
[65565] | 643 | && RTCircBufFree(pCircBuf))
|
---|
[53442] | 644 | {
|
---|
[88459] | 645 | void *pvCircBuf = NULL;
|
---|
| 646 | size_t cbCircBuf = 0;
|
---|
[65565] | 647 | RTCircBufAcquireWriteBlock(pCircBuf, cbToWrite, &pvCircBuf, &cbCircBuf);
|
---|
[53442] | 648 |
|
---|
[65429] | 649 | if (cbCircBuf)
|
---|
[65162] | 650 | {
|
---|
[65565] | 651 | memcpy(pvCircBuf, (uint8_t *)pvBuf + cbWrittenTotal, cbCircBuf),
|
---|
[68720] | 652 | cbWrittenTotal += (uint32_t)cbCircBuf;
|
---|
[65699] | 653 | Assert(cbToWrite >= cbCircBuf);
|
---|
[68720] | 654 | cbToWrite -= (uint32_t)cbCircBuf;
|
---|
[65330] | 655 | }
|
---|
| 656 |
|
---|
[65565] | 657 | RTCircBufReleaseWriteBlock(pCircBuf, cbCircBuf);
|
---|
[88459] | 658 | AssertBreak(cbCircBuf);
|
---|
[65389] | 659 | }
|
---|
[65330] | 660 |
|
---|
[65389] | 661 | /*
|
---|
| 662 | * Process our internal ring buffer and encode the data.
|
---|
| 663 | */
|
---|
[65330] | 664 |
|
---|
[68452] | 665 | /* Only encode data if we have data for the given time period (or more). */
|
---|
[75082] | 666 | while (RTCircBufUsed(pCircBuf) >= pCodec->Opus.cbFrame)
|
---|
[65389] | 667 | {
|
---|
[75082] | 668 | LogFunc(("cbAvail=%zu, csFrame=%RU32, cbFrame=%RU32\n",
|
---|
| 669 | RTCircBufUsed(pCircBuf), pCodec->Opus.csFrame, pCodec->Opus.cbFrame));
|
---|
| 670 |
|
---|
[88459] | 671 | uint32_t cbSrc = 0;
|
---|
[75082] | 672 | while (cbSrc < pCodec->Opus.cbFrame)
|
---|
[65389] | 673 | {
|
---|
[88459] | 674 | void *pvCircBuf = NULL;
|
---|
| 675 | size_t cbCircBuf = 0;
|
---|
[75082] | 676 | RTCircBufAcquireReadBlock(pCircBuf, pCodec->Opus.cbFrame - cbSrc, &pvCircBuf, &cbCircBuf);
|
---|
[65330] | 677 |
|
---|
[65429] | 678 | if (cbCircBuf)
|
---|
[65256] | 679 | {
|
---|
[75082] | 680 | memcpy((uint8_t *)pStreamAV->pvSrcBuf + cbSrc, pvCircBuf, cbCircBuf);
|
---|
[65353] | 681 |
|
---|
[75083] | 682 | cbSrc += (uint32_t)cbCircBuf;
|
---|
[75082] | 683 | Assert(cbSrc <= pStreamAV->cbSrcBuf);
|
---|
[65256] | 684 | }
|
---|
[65330] | 685 |
|
---|
[65429] | 686 | RTCircBufReleaseReadBlock(pCircBuf, cbCircBuf);
|
---|
[88459] | 687 | AssertBreak(cbCircBuf);
|
---|
[53442] | 688 | }
|
---|
[65389] | 689 |
|
---|
[88459] | 690 | Assert(cbSrc == pCodec->Opus.cbFrame);
|
---|
| 691 |
|
---|
[89344] | 692 | # ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH
|
---|
[65389] | 693 | RTFILE fh;
|
---|
[68320] | 694 | RTFileOpen(&fh, VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "DrvAudioVideoRec.pcm",
|
---|
| 695 | RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
|
---|
[75082] | 696 | RTFileWrite(fh, pStreamAV->pvSrcBuf, cbSrc, NULL);
|
---|
[65389] | 697 | RTFileClose(fh);
|
---|
[65410] | 698 | # endif
|
---|
[65389] | 699 |
|
---|
| 700 | /*
|
---|
[75082] | 701 | * Opus always encodes PER "OPUS FRAME", that is, exactly 2.5, 5, 10, 20, 40 or 60 ms of audio data.
|
---|
[65389] | 702 | *
|
---|
| 703 | * A packet can have up to 120ms worth of audio data.
|
---|
| 704 | * Anything > 120ms of data will result in a "corrupted package" error message by
|
---|
| 705 | * by decoding application.
|
---|
| 706 | */
|
---|
| 707 |
|
---|
[75082] | 708 | /* Call the encoder to encode one "Opus frame" per iteration. */
|
---|
[65410] | 709 | opus_int32 cbWritten = opus_encode(pSink->Codec.Opus.pEnc,
|
---|
[75082] | 710 | (opus_int16 *)pStreamAV->pvSrcBuf, pCodec->Opus.csFrame,
|
---|
| 711 | (uint8_t *)pStreamAV->pvDstBuf, (opus_int32)pStreamAV->cbDstBuf);
|
---|
[65389] | 712 | if (cbWritten > 0)
|
---|
| 713 | {
|
---|
| 714 | /* Get overall frames encoded. */
|
---|
[75082] | 715 | const uint32_t cEncFrames = opus_packet_get_nb_frames((uint8_t *)pStreamAV->pvDstBuf, cbWritten);
|
---|
[65389] | 716 |
|
---|
[70038] | 717 | # ifdef VBOX_WITH_STATISTICS
|
---|
[88459] | 718 | pSink->Codec.Stats.cEncFrames += cEncFrames;
|
---|
| 719 | pSink->Codec.Stats.msEncTotal += pSink->Codec.Opus.msFrame * cEncFrames;
|
---|
[65410] | 720 | # endif
|
---|
[75082] | 721 | Assert((uint32_t)cbWritten <= (uint32_t)pStreamAV->cbDstBuf);
|
---|
[75083] | 722 | const uint32_t cbDst = RT_MIN((uint32_t)cbWritten, (uint32_t)pStreamAV->cbDstBuf);
|
---|
[65389] | 723 |
|
---|
[75082] | 724 | Assert(cEncFrames == 1);
|
---|
[68850] | 725 |
|
---|
[69188] | 726 | if (pStreamAV->uLastPTSMs == 0)
|
---|
[75077] | 727 | pStreamAV->uLastPTSMs = RTTimeProgramMilliTS(); /* We want the absolute time (in ms) since program start. */
|
---|
[69188] | 728 |
|
---|
[68850] | 729 | const uint64_t uDurationMs = pSink->Codec.Opus.msFrame * cEncFrames;
|
---|
[75077] | 730 | const uint64_t uPTSMs = pStreamAV->uLastPTSMs;
|
---|
[68850] | 731 |
|
---|
[69188] | 732 | pStreamAV->uLastPTSMs += uDurationMs;
|
---|
| 733 |
|
---|
[65410] | 734 | switch (pSink->Con.Parms.enmType)
|
---|
| 735 | {
|
---|
| 736 | case AVRECCONTAINERTYPE_MAIN_CONSOLE:
|
---|
| 737 | {
|
---|
[75352] | 738 | HRESULT hr = pSink->Con.Main.pConsole->i_recordingSendAudio(pStreamAV->pvDstBuf, cbDst, uPTSMs);
|
---|
[65410] | 739 | Assert(hr == S_OK);
|
---|
[66266] | 740 | RT_NOREF(hr);
|
---|
[65410] | 741 | break;
|
---|
| 742 | }
|
---|
| 743 |
|
---|
| 744 | case AVRECCONTAINERTYPE_WEBM:
|
---|
| 745 | {
|
---|
[75082] | 746 | WebMWriter::BlockData_Opus blockData = { pStreamAV->pvDstBuf, cbDst, uPTSMs };
|
---|
[65410] | 747 | rc = pSink->Con.WebM.pWebM->WriteBlock(pSink->Con.WebM.uTrack, &blockData, sizeof(blockData));
|
---|
| 748 | AssertRC(rc);
|
---|
| 749 | break;
|
---|
| 750 | }
|
---|
| 751 |
|
---|
| 752 | default:
|
---|
| 753 | AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
|
---|
| 754 | break;
|
---|
| 755 | }
|
---|
[65389] | 756 | }
|
---|
| 757 | else if (cbWritten < 0)
|
---|
| 758 | {
|
---|
| 759 | AssertMsgFailed(("Encoding failed: %s\n", opus_strerror(cbWritten)));
|
---|
| 760 | rc = VERR_INVALID_PARAMETER;
|
---|
| 761 | }
|
---|
| 762 |
|
---|
| 763 | if (RT_FAILURE(rc))
|
---|
| 764 | break;
|
---|
[65162] | 765 | }
|
---|
[68772] | 766 |
|
---|
[88394] | 767 | *pcbWritten = cbWrittenTotal;
|
---|
[65263] | 768 | #else
|
---|
[68772] | 769 | /* Report back all data as being processed. */
|
---|
[88394] | 770 | *pcbWritten = cbBuf;
|
---|
[68772] | 771 |
|
---|
[65263] | 772 | rc = VERR_NOT_SUPPORTED;
|
---|
| 773 | #endif /* VBOX_WITH_LIBOPUS */
|
---|
[53442] | 774 |
|
---|
[65565] | 775 | LogFlowFunc(("csReadTotal=%RU32, rc=%Rrc\n", cbWrittenTotal, rc));
|
---|
[53442] | 776 | return rc;
|
---|
| 777 | }
|
---|
| 778 |
|
---|
[65162] | 779 |
|
---|
| 780 | /**
|
---|
[89504] | 781 | * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
|
---|
| 782 | */
|
---|
| 783 | static DECLCALLBACK(uint32_t) drvAudioVideoRecHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
|
---|
| 784 | {
|
---|
| 785 | RT_NOREF(pInterface, pStream);
|
---|
| 786 | return 0; /* Video capturing does not provide any input. */
|
---|
| 787 | }
|
---|
| 788 |
|
---|
| 789 |
|
---|
| 790 | /**
|
---|
[88459] | 791 | * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
|
---|
[65162] | 792 | */
|
---|
[88459] | 793 | static DECLCALLBACK(int) drvAudioVideoRecHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
|
---|
| 794 | void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
|
---|
[53442] | 795 | {
|
---|
[88459] | 796 | RT_NOREF(pInterface, pStream, pvBuf, cbBuf);
|
---|
| 797 | *pcbRead = 0;
|
---|
[65162] | 798 | return VINF_SUCCESS;
|
---|
| 799 | }
|
---|
[53442] | 800 |
|
---|
| 801 |
|
---|
[88459] | 802 | /*********************************************************************************************************************************
|
---|
| 803 | * PDMIBASE *
|
---|
| 804 | *********************************************************************************************************************************/
|
---|
[53442] | 805 |
|
---|
[65162] | 806 | /**
|
---|
[53442] | 807 | * @interface_method_impl{PDMIBASE,pfnQueryInterface}
|
---|
| 808 | */
|
---|
| 809 | static DECLCALLBACK(void *) drvAudioVideoRecQueryInterface(PPDMIBASE pInterface, const char *pszIID)
|
---|
| 810 | {
|
---|
| 811 | PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
|
---|
[75354] | 812 | PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
|
---|
[65162] | 813 |
|
---|
[53442] | 814 | PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
|
---|
[54368] | 815 | PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
|
---|
[53442] | 816 | return NULL;
|
---|
| 817 | }
|
---|
| 818 |
|
---|
[65162] | 819 |
|
---|
[88459] | 820 | /*********************************************************************************************************************************
|
---|
| 821 | * PDMDRVREG *
|
---|
| 822 | *********************************************************************************************************************************/
|
---|
[53442] | 823 |
|
---|
[70644] | 824 | /**
|
---|
[88459] | 825 | * Shuts down (closes) a recording sink,
|
---|
[74955] | 826 | *
|
---|
[88300] | 827 | * @returns VBox status code.
|
---|
[88459] | 828 | * @param pSink Recording sink to shut down.
|
---|
[74955] | 829 | */
|
---|
[88459] | 830 | static void avRecSinkShutdown(PAVRECSINK pSink)
|
---|
[74955] | 831 | {
|
---|
[88459] | 832 | AssertPtrReturnVoid(pSink);
|
---|
[74955] | 833 |
|
---|
[88459] | 834 | #ifdef VBOX_WITH_LIBOPUS
|
---|
| 835 | if (pSink->Codec.Opus.pEnc)
|
---|
| 836 | {
|
---|
| 837 | opus_encoder_destroy(pSink->Codec.Opus.pEnc);
|
---|
| 838 | pSink->Codec.Opus.pEnc = NULL;
|
---|
| 839 | }
|
---|
| 840 | #endif
|
---|
| 841 | switch (pSink->Con.Parms.enmType)
|
---|
| 842 | {
|
---|
| 843 | case AVRECCONTAINERTYPE_WEBM:
|
---|
| 844 | {
|
---|
| 845 | if (pSink->Con.WebM.pWebM)
|
---|
| 846 | {
|
---|
| 847 | LogRel2(("Recording: Finished recording audio to file '%s' (%zu bytes)\n",
|
---|
| 848 | pSink->Con.WebM.pWebM->GetFileName().c_str(), pSink->Con.WebM.pWebM->GetFileSize()));
|
---|
[74955] | 849 |
|
---|
[88459] | 850 | int rc2 = pSink->Con.WebM.pWebM->Close();
|
---|
| 851 | AssertRC(rc2);
|
---|
[70644] | 852 |
|
---|
[88459] | 853 | delete pSink->Con.WebM.pWebM;
|
---|
| 854 | pSink->Con.WebM.pWebM = NULL;
|
---|
| 855 | }
|
---|
| 856 | break;
|
---|
| 857 | }
|
---|
[75251] | 858 |
|
---|
[88459] | 859 | case AVRECCONTAINERTYPE_MAIN_CONSOLE:
|
---|
| 860 | default:
|
---|
| 861 | break;
|
---|
[74955] | 862 | }
|
---|
[70563] | 863 | }
|
---|
| 864 |
|
---|
| 865 |
|
---|
[53442] | 866 | /**
|
---|
[88382] | 867 | * @interface_method_impl{PDMDRVREG,pfnPowerOff}
|
---|
| 868 | */
|
---|
| 869 | /*static*/ DECLCALLBACK(void) AudioVideoRec::drvPowerOff(PPDMDRVINS pDrvIns)
|
---|
| 870 | {
|
---|
| 871 | PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
|
---|
| 872 | LogFlowFuncEnter();
|
---|
| 873 | avRecSinkShutdown(&pThis->Sink);
|
---|
| 874 | }
|
---|
| 875 |
|
---|
| 876 |
|
---|
| 877 | /**
|
---|
| 878 | * @interface_method_impl{PDMDRVREG,pfnDestruct}
|
---|
| 879 | */
|
---|
| 880 | /*static*/ DECLCALLBACK(void) AudioVideoRec::drvDestruct(PPDMDRVINS pDrvIns)
|
---|
| 881 | {
|
---|
| 882 | PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
|
---|
| 883 | PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
|
---|
| 884 |
|
---|
| 885 | LogFlowFuncEnter();
|
---|
| 886 |
|
---|
| 887 | switch (pThis->ContainerParms.enmType)
|
---|
| 888 | {
|
---|
| 889 | case AVRECCONTAINERTYPE_WEBM:
|
---|
| 890 | {
|
---|
| 891 | avRecSinkShutdown(&pThis->Sink);
|
---|
| 892 | RTStrFree(pThis->ContainerParms.WebM.pszFile);
|
---|
| 893 | break;
|
---|
| 894 | }
|
---|
| 895 |
|
---|
| 896 | default:
|
---|
| 897 | break;
|
---|
| 898 | }
|
---|
| 899 |
|
---|
| 900 | /*
|
---|
| 901 | * If the AudioVideoRec object is still alive, we must clear it's reference to
|
---|
| 902 | * us since we'll be invalid when we return from this method.
|
---|
| 903 | */
|
---|
| 904 | if (pThis->pAudioVideoRec)
|
---|
| 905 | {
|
---|
| 906 | pThis->pAudioVideoRec->mpDrv = NULL;
|
---|
| 907 | pThis->pAudioVideoRec = NULL;
|
---|
| 908 | }
|
---|
| 909 |
|
---|
| 910 | LogFlowFuncLeave();
|
---|
| 911 | }
|
---|
| 912 |
|
---|
| 913 |
|
---|
| 914 | /**
|
---|
[88459] | 915 | * Initializes a recording sink.
|
---|
| 916 | *
|
---|
| 917 | * @returns VBox status code.
|
---|
| 918 | * @param pThis Driver instance.
|
---|
| 919 | * @param pSink Sink to initialize.
|
---|
| 920 | * @param pConParms Container parameters to set.
|
---|
| 921 | * @param pCodecParms Codec parameters to set.
|
---|
| 922 | */
|
---|
| 923 | static int avRecSinkInit(PDRVAUDIORECORDING pThis, PAVRECSINK pSink, PAVRECCONTAINERPARMS pConParms, PAVRECCODECPARMS pCodecParms)
|
---|
| 924 | {
|
---|
| 925 | uint32_t uHz = PDMAudioPropsHz(&pCodecParms->PCMProps);
|
---|
| 926 | uint8_t const cbSample = PDMAudioPropsSampleSize(&pCodecParms->PCMProps);
|
---|
| 927 | uint8_t cChannels = PDMAudioPropsChannels(&pCodecParms->PCMProps);
|
---|
| 928 | uint32_t uBitrate = pCodecParms->uBitrate;
|
---|
| 929 |
|
---|
| 930 | /* Opus only supports certain input sample rates in an efficient manner.
|
---|
| 931 | * So make sure that we use those by resampling the data to the requested rate. */
|
---|
| 932 | if (uHz > 24000) uHz = AVREC_OPUS_HZ_MAX;
|
---|
| 933 | else if (uHz > 16000) uHz = 24000;
|
---|
| 934 | else if (uHz > 12000) uHz = 16000;
|
---|
| 935 | else if (uHz > 8000 ) uHz = 12000;
|
---|
| 936 | else uHz = 8000;
|
---|
| 937 |
|
---|
| 938 | if (cChannels > 2)
|
---|
| 939 | {
|
---|
| 940 | LogRel(("Recording: Warning: More than 2 (stereo) channels are not supported at the moment\n"));
|
---|
| 941 | cChannels = 2;
|
---|
| 942 | }
|
---|
| 943 |
|
---|
| 944 | int orc;
|
---|
| 945 | OpusEncoder *pEnc = opus_encoder_create(uHz, cChannels, OPUS_APPLICATION_AUDIO, &orc);
|
---|
| 946 | if (orc != OPUS_OK)
|
---|
| 947 | {
|
---|
| 948 | LogRel(("Recording: Audio codec failed to initialize: %s\n", opus_strerror(orc)));
|
---|
| 949 | return VERR_AUDIO_BACKEND_INIT_FAILED;
|
---|
| 950 | }
|
---|
| 951 |
|
---|
| 952 | AssertPtr(pEnc);
|
---|
| 953 |
|
---|
| 954 | if (uBitrate) /* Only explicitly set the bitrate if we specified one. Otherwise let Opus decide. */
|
---|
| 955 | {
|
---|
| 956 | opus_encoder_ctl(pEnc, OPUS_SET_BITRATE(uBitrate));
|
---|
| 957 | if (orc != OPUS_OK)
|
---|
| 958 | {
|
---|
| 959 | opus_encoder_destroy(pEnc);
|
---|
| 960 | pEnc = NULL;
|
---|
| 961 |
|
---|
| 962 | LogRel(("Recording: Audio codec failed to set bitrate (%RU32): %s\n", uBitrate, opus_strerror(orc)));
|
---|
| 963 | return VERR_AUDIO_BACKEND_INIT_FAILED;
|
---|
| 964 | }
|
---|
| 965 | }
|
---|
| 966 |
|
---|
| 967 | const bool fUseVBR = true; /** Use Variable Bit Rate (VBR) by default. @todo Make this configurable? */
|
---|
| 968 |
|
---|
| 969 | orc = opus_encoder_ctl(pEnc, OPUS_SET_VBR(fUseVBR ? 1 : 0));
|
---|
| 970 | if (orc != OPUS_OK)
|
---|
| 971 | {
|
---|
| 972 | opus_encoder_destroy(pEnc);
|
---|
| 973 | pEnc = NULL;
|
---|
| 974 |
|
---|
| 975 | LogRel(("Recording: Audio codec failed to %s VBR mode: %s\n", fUseVBR ? "enable" : "disable", opus_strerror(orc)));
|
---|
| 976 | return VERR_AUDIO_BACKEND_INIT_FAILED;
|
---|
| 977 | }
|
---|
| 978 |
|
---|
| 979 | int rc = VINF_SUCCESS;
|
---|
| 980 |
|
---|
| 981 | try
|
---|
| 982 | {
|
---|
| 983 | switch (pConParms->enmType)
|
---|
| 984 | {
|
---|
| 985 | case AVRECCONTAINERTYPE_MAIN_CONSOLE:
|
---|
| 986 | {
|
---|
| 987 | if (pThis->pConsole)
|
---|
| 988 | {
|
---|
| 989 | pSink->Con.Main.pConsole = pThis->pConsole;
|
---|
| 990 | }
|
---|
| 991 | else
|
---|
| 992 | rc = VERR_NOT_SUPPORTED;
|
---|
| 993 | break;
|
---|
| 994 | }
|
---|
| 995 |
|
---|
| 996 | case AVRECCONTAINERTYPE_WEBM:
|
---|
| 997 | {
|
---|
| 998 | /* If we only record audio, create our own WebM writer instance here. */
|
---|
| 999 | if (!pSink->Con.WebM.pWebM) /* Do we already have our WebM writer instance? */
|
---|
| 1000 | {
|
---|
| 1001 | /** @todo Add sink name / number to file name. */
|
---|
| 1002 | const char *pszFile = pSink->Con.Parms.WebM.pszFile;
|
---|
| 1003 | AssertPtr(pszFile);
|
---|
| 1004 |
|
---|
| 1005 | pSink->Con.WebM.pWebM = new WebMWriter();
|
---|
| 1006 | rc = pSink->Con.WebM.pWebM->Open(pszFile,
|
---|
| 1007 | /** @todo Add option to add some suffix if file exists instead of overwriting? */
|
---|
| 1008 | RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE,
|
---|
| 1009 | WebMWriter::AudioCodec_Opus, WebMWriter::VideoCodec_None);
|
---|
| 1010 | if (RT_SUCCESS(rc))
|
---|
| 1011 | {
|
---|
| 1012 | rc = pSink->Con.WebM.pWebM->AddAudioTrack(uHz, cChannels, cbSample * 8 /* Bits */,
|
---|
| 1013 | &pSink->Con.WebM.uTrack);
|
---|
| 1014 | if (RT_SUCCESS(rc))
|
---|
| 1015 | {
|
---|
| 1016 | LogRel(("Recording: Recording audio to audio file '%s'\n", pszFile));
|
---|
| 1017 | }
|
---|
| 1018 | else
|
---|
| 1019 | LogRel(("Recording: Error creating audio track for audio file '%s' (%Rrc)\n", pszFile, rc));
|
---|
| 1020 | }
|
---|
| 1021 | else
|
---|
| 1022 | LogRel(("Recording: Error creating audio file '%s' (%Rrc)\n", pszFile, rc));
|
---|
| 1023 | }
|
---|
| 1024 | break;
|
---|
| 1025 | }
|
---|
| 1026 |
|
---|
| 1027 | default:
|
---|
| 1028 | rc = VERR_NOT_SUPPORTED;
|
---|
| 1029 | break;
|
---|
| 1030 | }
|
---|
| 1031 | }
|
---|
| 1032 | catch (std::bad_alloc &)
|
---|
| 1033 | {
|
---|
| 1034 | rc = VERR_NO_MEMORY;
|
---|
| 1035 | }
|
---|
| 1036 |
|
---|
| 1037 | if (RT_SUCCESS(rc))
|
---|
| 1038 | {
|
---|
| 1039 | pSink->Con.Parms.enmType = pConParms->enmType;
|
---|
| 1040 |
|
---|
| 1041 | PAVRECCODEC pCodec = &pSink->Codec;
|
---|
| 1042 |
|
---|
| 1043 | PDMAudioPropsInit(&pCodec->Parms.PCMProps, cbSample, pCodecParms->PCMProps.fSigned, cChannels, uHz);
|
---|
| 1044 | pCodec->Parms.uBitrate = uBitrate;
|
---|
| 1045 |
|
---|
| 1046 | pCodec->Opus.pEnc = pEnc;
|
---|
| 1047 | pCodec->Opus.msFrame = AVREC_OPUS_FRAME_MS_DEFAULT;
|
---|
| 1048 |
|
---|
| 1049 | if (!pCodec->Opus.msFrame)
|
---|
| 1050 | pCodec->Opus.msFrame = AVREC_OPUS_FRAME_MS_DEFAULT; /* 20ms by default; to prevent division by zero. */
|
---|
| 1051 | pCodec->Opus.csFrame = pSink->Codec.Parms.PCMProps.uHz / (1000 /* s in ms */ / pSink->Codec.Opus.msFrame);
|
---|
| 1052 | pCodec->Opus.cbFrame = PDMAudioPropsFramesToBytes(&pSink->Codec.Parms.PCMProps, pCodec->Opus.csFrame);
|
---|
| 1053 |
|
---|
| 1054 | #ifdef VBOX_WITH_STATISTICS
|
---|
| 1055 | pSink->Codec.Stats.cEncFrames = 0;
|
---|
| 1056 | pSink->Codec.Stats.msEncTotal = 0;
|
---|
| 1057 | #endif
|
---|
| 1058 | pSink->tsStartMs = RTTimeMilliTS();
|
---|
| 1059 | }
|
---|
| 1060 | else
|
---|
| 1061 | {
|
---|
| 1062 | if (pEnc)
|
---|
| 1063 | {
|
---|
| 1064 | opus_encoder_destroy(pEnc);
|
---|
| 1065 | pEnc = NULL;
|
---|
| 1066 | }
|
---|
| 1067 |
|
---|
| 1068 | LogRel(("Recording: Error creating sink (%Rrc)\n", rc));
|
---|
| 1069 | }
|
---|
| 1070 |
|
---|
| 1071 | return rc;
|
---|
| 1072 | }
|
---|
| 1073 |
|
---|
| 1074 |
|
---|
| 1075 | /**
|
---|
[65162] | 1076 | * Construct a audio video recording driver instance.
|
---|
[53442] | 1077 | *
|
---|
| 1078 | * @copydoc FNPDMDRVCONSTRUCT
|
---|
| 1079 | */
|
---|
[88382] | 1080 | /*static*/ DECLCALLBACK(int) AudioVideoRec::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
|
---|
[53442] | 1081 | {
|
---|
[65162] | 1082 | PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
|
---|
[75354] | 1083 | PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
|
---|
[68882] | 1084 | RT_NOREF(fFlags);
|
---|
[65162] | 1085 |
|
---|
[65197] | 1086 | LogRel(("Audio: Initializing video recording audio driver\n"));
|
---|
[53442] | 1087 | LogFlowFunc(("fFlags=0x%x\n", fFlags));
|
---|
| 1088 |
|
---|
| 1089 | AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
|
---|
| 1090 | ("Configuration error: Not possible to attach anything to this driver!\n"),
|
---|
| 1091 | VERR_PDM_DRVINS_NO_ATTACH);
|
---|
| 1092 |
|
---|
| 1093 | /*
|
---|
| 1094 | * Init the static parts.
|
---|
| 1095 | */
|
---|
[54230] | 1096 | pThis->pDrvIns = pDrvIns;
|
---|
[53442] | 1097 | /* IBase */
|
---|
[54230] | 1098 | pDrvIns->IBase.pfnQueryInterface = drvAudioVideoRecQueryInterface;
|
---|
[54368] | 1099 | /* IHostAudio */
|
---|
[88761] | 1100 | pThis->IHostAudio.pfnGetConfig = drvAudioVideoRecHA_GetConfig;
|
---|
| 1101 | pThis->IHostAudio.pfnGetDevices = NULL;
|
---|
[89258] | 1102 | pThis->IHostAudio.pfnSetDevice = NULL;
|
---|
[88761] | 1103 | pThis->IHostAudio.pfnGetStatus = drvAudioVideoRecHA_GetStatus;
|
---|
[88819] | 1104 | pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
|
---|
| 1105 | pThis->IHostAudio.pfnStreamConfigHint = NULL;
|
---|
[88761] | 1106 | pThis->IHostAudio.pfnStreamCreate = drvAudioVideoRecHA_StreamCreate;
|
---|
[88819] | 1107 | pThis->IHostAudio.pfnStreamInitAsync = NULL;
|
---|
[88761] | 1108 | pThis->IHostAudio.pfnStreamDestroy = drvAudioVideoRecHA_StreamDestroy;
|
---|
| 1109 | pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
|
---|
[89510] | 1110 | pThis->IHostAudio.pfnStreamEnable = drvAudioVideoRecHA_StreamEnable;
|
---|
| 1111 | pThis->IHostAudio.pfnStreamDisable = drvAudioVideoRecHA_StreamDisable;
|
---|
| 1112 | pThis->IHostAudio.pfnStreamPause = drvAudioVideoRecHA_StreamPause;
|
---|
| 1113 | pThis->IHostAudio.pfnStreamResume = drvAudioVideoRecHA_StreamResume;
|
---|
| 1114 | pThis->IHostAudio.pfnStreamDrain = drvAudioVideoRecHA_StreamDrain;
|
---|
[89504] | 1115 | pThis->IHostAudio.pfnStreamGetState = drvAudioVideoRecHA_StreamGetState;
|
---|
| 1116 | pThis->IHostAudio.pfnStreamGetPending = NULL;
|
---|
[88761] | 1117 | pThis->IHostAudio.pfnStreamGetWritable = drvAudioVideoRecHA_StreamGetWritable;
|
---|
| 1118 | pThis->IHostAudio.pfnStreamPlay = drvAudioVideoRecHA_StreamPlay;
|
---|
[89504] | 1119 | pThis->IHostAudio.pfnStreamGetReadable = drvAudioVideoRecHA_StreamGetReadable;
|
---|
[88761] | 1120 | pThis->IHostAudio.pfnStreamCapture = drvAudioVideoRecHA_StreamCapture;
|
---|
[53442] | 1121 |
|
---|
[65162] | 1122 | /*
|
---|
[93444] | 1123 | * Read configuration.
|
---|
| 1124 | */
|
---|
| 1125 | PCPDMDRVHLPR3 const pHlp = pDrvIns->pHlpR3;
|
---|
| 1126 | /** @todo validate it. */
|
---|
| 1127 |
|
---|
| 1128 | /*
|
---|
[65410] | 1129 | * Get the Console object pointer.
|
---|
[65162] | 1130 | */
|
---|
[94326] | 1131 | com::Guid ConsoleUuid(COM_IIDOF(IConsole));
|
---|
| 1132 | IConsole *pIConsole = (IConsole *)PDMDrvHlpQueryGenericUserObject(pDrvIns, ConsoleUuid.raw());
|
---|
| 1133 | AssertLogRelReturn(pIConsole, VERR_INTERNAL_ERROR_3);
|
---|
| 1134 | Console *pConsole = static_cast<Console *>(pIConsole);
|
---|
| 1135 | AssertLogRelReturn(pConsole, VERR_INTERNAL_ERROR_3);
|
---|
[65410] | 1136 |
|
---|
[94326] | 1137 | pThis->pConsole = pConsole;
|
---|
[68797] | 1138 | AssertReturn(!pThis->pConsole.isNull(), VERR_INVALID_POINTER);
|
---|
[94326] | 1139 | pThis->pAudioVideoRec = pConsole->i_recordingGetAudioDrv();
|
---|
| 1140 | AssertPtrReturn(pThis->pAudioVideoRec, VERR_INVALID_POINTER);
|
---|
[65410] | 1141 |
|
---|
[94326] | 1142 | pThis->pAudioVideoRec->mpDrv = pThis;
|
---|
[53442] | 1143 |
|
---|
[68797] | 1144 |
|
---|
[74955] | 1145 | /*
|
---|
| 1146 | * Get the recording container and codec parameters from the audio driver instance.
|
---|
| 1147 | */
|
---|
| 1148 | PAVRECCONTAINERPARMS pConParams = &pThis->ContainerParms;
|
---|
| 1149 | PAVRECCODECPARMS pCodecParms = &pThis->CodecParms;
|
---|
| 1150 |
|
---|
| 1151 | RT_ZERO(pThis->ContainerParms);
|
---|
| 1152 | RT_ZERO(pThis->CodecParms);
|
---|
| 1153 |
|
---|
[94326] | 1154 | int rc = pHlp->pfnCFGMQueryU32(pCfg, "ContainerType", (uint32_t *)&pConParams->enmType);
|
---|
[74955] | 1155 | AssertRCReturn(rc, rc);
|
---|
| 1156 |
|
---|
| 1157 | switch (pConParams->enmType)
|
---|
| 1158 | {
|
---|
| 1159 | case AVRECCONTAINERTYPE_WEBM:
|
---|
[93444] | 1160 | rc = pHlp->pfnCFGMQueryStringAlloc(pCfg, "ContainerFileName", &pConParams->WebM.pszFile);
|
---|
[74955] | 1161 | AssertRCReturn(rc, rc);
|
---|
| 1162 | break;
|
---|
| 1163 |
|
---|
| 1164 | default:
|
---|
| 1165 | break;
|
---|
| 1166 | }
|
---|
| 1167 |
|
---|
[88269] | 1168 | uint32_t uHz = 0;
|
---|
[93444] | 1169 | rc = pHlp->pfnCFGMQueryU32(pCfg, "CodecHz", &uHz);
|
---|
[74955] | 1170 | AssertRCReturn(rc, rc);
|
---|
[88269] | 1171 |
|
---|
| 1172 | uint8_t cSampleBits = 0;
|
---|
[93444] | 1173 | rc = pHlp->pfnCFGMQueryU8(pCfg, "CodecBits", &cSampleBits); /** @todo CodecBits != CodecBytes */
|
---|
[74955] | 1174 | AssertRCReturn(rc, rc);
|
---|
[88269] | 1175 |
|
---|
| 1176 | uint8_t cChannels = 0;
|
---|
[93444] | 1177 | rc = pHlp->pfnCFGMQueryU8(pCfg, "CodecChannels", &cChannels);
|
---|
[74955] | 1178 | AssertRCReturn(rc, rc);
|
---|
[88269] | 1179 |
|
---|
| 1180 | PDMAudioPropsInit(&pCodecParms->PCMProps, cSampleBits / 8, true /*fSigned*/, cChannels, uHz);
|
---|
| 1181 | AssertMsgReturn(PDMAudioPropsAreValid(&pCodecParms->PCMProps),
|
---|
| 1182 | ("Configuration error: Audio configuration is invalid!\n"), VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES); /** @todo wrong status code. */
|
---|
| 1183 |
|
---|
[93444] | 1184 | rc = pHlp->pfnCFGMQueryU32(pCfg, "CodecBitrate", &pCodecParms->uBitrate);
|
---|
[74955] | 1185 | AssertRCReturn(rc, rc);
|
---|
| 1186 |
|
---|
[53442] | 1187 | /*
|
---|
| 1188 | * Get the interface for the above driver (DrvAudio) to make mixer/conversion calls.
|
---|
| 1189 | * Described in CFGM tree.
|
---|
| 1190 | */
|
---|
[88381] | 1191 | /** @todo r=bird: What on earth do you think you need this for?!? It's not an
|
---|
| 1192 | * interface lower drivers are supposed to be messing with! */
|
---|
[65162] | 1193 | pThis->pDrvAudio = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIAUDIOCONNECTOR);
|
---|
| 1194 | AssertMsgReturn(pThis->pDrvAudio, ("Configuration error: No upper interface specified!\n"), VERR_PDM_MISSING_INTERFACE_ABOVE);
|
---|
[53442] | 1195 |
|
---|
[89344] | 1196 | #ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH
|
---|
[68725] | 1197 | RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "DrvAudioVideoRec.webm");
|
---|
| 1198 | RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "DrvAudioVideoRec.pcm");
|
---|
| 1199 | #endif
|
---|
| 1200 |
|
---|
[88381] | 1201 | /*
|
---|
| 1202 | * Init the recording sink.
|
---|
| 1203 | */
|
---|
| 1204 | LogRel(("Recording: Audio driver is using %RU32Hz, %RU16bit, %RU8 channel%s\n",
|
---|
| 1205 | PDMAudioPropsHz(&pThis->CodecParms.PCMProps), PDMAudioPropsSampleBits(&pThis->CodecParms.PCMProps),
|
---|
| 1206 | PDMAudioPropsChannels(&pThis->CodecParms.PCMProps), PDMAudioPropsChannels(&pThis->CodecParms.PCMProps) == 1 ? "" : "s"));
|
---|
| 1207 |
|
---|
| 1208 | rc = avRecSinkInit(pThis, &pThis->Sink, &pThis->ContainerParms, &pThis->CodecParms);
|
---|
| 1209 | if (RT_SUCCESS(rc))
|
---|
| 1210 | LogRel2(("Recording: Audio recording driver initialized\n"));
|
---|
| 1211 | else
|
---|
| 1212 | LogRel(("Recording: Audio recording driver initialization failed: %Rrc\n", rc));
|
---|
| 1213 |
|
---|
| 1214 | return rc;
|
---|
[53442] | 1215 | }
|
---|
| 1216 |
|
---|
[65162] | 1217 |
|
---|
| 1218 | /**
|
---|
| 1219 | * Video recording audio driver registration record.
|
---|
[53442] | 1220 | */
|
---|
| 1221 | const PDMDRVREG AudioVideoRec::DrvReg =
|
---|
| 1222 | {
|
---|
| 1223 | PDM_DRVREG_VERSION,
|
---|
| 1224 | /* szName */
|
---|
| 1225 | "AudioVideoRec",
|
---|
| 1226 | /* szRCMod */
|
---|
| 1227 | "",
|
---|
| 1228 | /* szR0Mod */
|
---|
| 1229 | "",
|
---|
| 1230 | /* pszDescription */
|
---|
| 1231 | "Audio driver for video recording",
|
---|
| 1232 | /* fFlags */
|
---|
| 1233 | PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
|
---|
| 1234 | /* fClass. */
|
---|
| 1235 | PDM_DRVREG_CLASS_AUDIO,
|
---|
| 1236 | /* cMaxInstances */
|
---|
| 1237 | ~0U,
|
---|
| 1238 | /* cbInstance */
|
---|
[75354] | 1239 | sizeof(DRVAUDIORECORDING),
|
---|
[53442] | 1240 | /* pfnConstruct */
|
---|
| 1241 | AudioVideoRec::drvConstruct,
|
---|
| 1242 | /* pfnDestruct */
|
---|
| 1243 | AudioVideoRec::drvDestruct,
|
---|
| 1244 | /* pfnRelocate */
|
---|
| 1245 | NULL,
|
---|
| 1246 | /* pfnIOCtl */
|
---|
| 1247 | NULL,
|
---|
| 1248 | /* pfnPowerOn */
|
---|
| 1249 | NULL,
|
---|
| 1250 | /* pfnReset */
|
---|
| 1251 | NULL,
|
---|
| 1252 | /* pfnSuspend */
|
---|
| 1253 | NULL,
|
---|
| 1254 | /* pfnResume */
|
---|
| 1255 | NULL,
|
---|
| 1256 | /* pfnAttach */
|
---|
[88382] | 1257 | NULL,
|
---|
[53442] | 1258 | /* pfnDetach */
|
---|
[88382] | 1259 | NULL,
|
---|
[53442] | 1260 | /* pfnPowerOff */
|
---|
[88382] | 1261 | AudioVideoRec::drvPowerOff,
|
---|
[53442] | 1262 | /* pfnSoftReset */
|
---|
| 1263 | NULL,
|
---|
| 1264 | /* u32EndVersion */
|
---|
| 1265 | PDM_DRVREG_VERSION
|
---|
| 1266 | };
|
---|
| 1267 |
|
---|