VirtualBox

source: vbox/trunk/src/VBox/Main/src-client/DrvAudioRec.cpp

Last change on this file was 103420, checked in by vboxsync, 3 months ago

Main/DrvAudioRec: Don't evaluate RTCircBufFree() twice. bugref:3409

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 31.3 KB
RevLine 
[53442]1/* $Id: DrvAudioRec.cpp 103420 2024-02-19 09:08:34Z 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/*
[98103]19 * Copyright (C) 2016-2023 Oracle and/or its affiliates.
[53442]20 *
[96407]21 * This file is part of VirtualBox base platform packages, as
22 * available from https://www.virtualbox.org.
23 *
24 * This program is free software; you can redistribute it and/or
25 * modify it under the terms of the GNU General Public License
26 * as published by the Free Software Foundation, in version 3 of the
27 * License.
28 *
29 * This program is distributed in the hope that it will be useful, but
30 * WITHOUT ANY WARRANTY; without even the implied warranty of
31 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
32 * General Public License for more details.
33 *
34 * You should have received a copy of the GNU General Public License
35 * along with this program; if not, see <https://www.gnu.org/licenses>.
36 *
37 * SPDX-License-Identifier: GPL-3.0-only
[53442]38 */
[65162]39
[68715]40
[65162]41/*********************************************************************************************************************************
42* Header Files *
43*********************************************************************************************************************************/
[96140]44#define LOG_GROUP LOG_GROUP_RECORDING
[67914]45#include "LoggingNew.h"
46
[75344]47#include "DrvAudioRec.h"
[53442]48#include "ConsoleImpl.h"
49
[69683]50#include "WebMWriter.h"
[65162]51
[53442]52#include <iprt/mem.h>
53#include <iprt/cdefs.h>
54
[96175]55#include "VBox/com/VirtualBox.h"
[88028]56#include <VBox/vmm/cfgm.h>
57#include <VBox/vmm/pdmdrv.h>
[53442]58#include <VBox/vmm/pdmaudioifs.h>
[88028]59#include <VBox/vmm/pdmaudioinline.h>
[93444]60#include <VBox/vmm/vmmr3vtable.h>
[53442]61#include <VBox/err.h>
[96175]62#include "VBox/settings.h"
[53442]63
64
[65162]65/*********************************************************************************************************************************
66* Structures and Typedefs *
67*********************************************************************************************************************************/
[53442]68/**
[65410]69 * Enumeration for specifying the recording container type.
[65197]70 */
[65410]71typedef enum AVRECCONTAINERTYPE
[65197]72{
[65410]73 /** Unknown / invalid container type. */
74 AVRECCONTAINERTYPE_UNKNOWN = 0,
75 /** Recorded data goes to Main / Console. */
76 AVRECCONTAINERTYPE_MAIN_CONSOLE = 1,
[68321]77 /** Recorded data will be written to a .webm file. */
[65410]78 AVRECCONTAINERTYPE_WEBM = 2
79} AVRECCONTAINERTYPE;
[65197]80
81/**
[65410]82 * Structure for keeping generic container parameters.
[65197]83 */
[65410]84typedef struct AVRECCONTAINERPARMS
85{
[96175]86 /** Stream index (hint). */
87 uint32_t idxStream;
[65410]88 /** The container's type. */
89 AVRECCONTAINERTYPE enmType;
[74955]90 union
91 {
92 /** WebM file specifics. */
93 struct
94 {
95 /** Allocated file name to write .webm file to. Must be free'd. */
96 char *pszFile;
97 } WebM;
98 };
[65410]99
100} AVRECCONTAINERPARMS, *PAVRECCONTAINERPARMS;
101
102/**
103 * Structure for keeping container-specific data.
104 */
105typedef struct AVRECCONTAINER
106{
107 /** Generic container parameters. */
108 AVRECCONTAINERPARMS Parms;
109
110 union
111 {
112 struct
113 {
114 /** Pointer to Console. */
115 Console *pConsole;
116 } Main;
117
118 struct
119 {
120 /** Pointer to WebM container to write recorded audio data to.
121 * See the AVRECMODE enumeration for more information. */
122 WebMWriter *pWebM;
123 /** Assigned track number from WebM container. */
124 uint8_t uTrack;
125 } WebM;
126 };
127} AVRECCONTAINER, *PAVRECCONTAINER;
128
129/**
[96175]130 * Audio video recording sink.
[65410]131 */
132typedef struct AVRECSINK
133{
[96260]134 /** Pointer (weak) to recording stream to bind to. */
135 RecordingStream *pRecStream;
[65410]136 /** Container data to use for data processing. */
137 AVRECCONTAINER Con;
[69188]138 /** Timestamp (in ms) of when the sink was created. */
139 uint64_t tsStartMs;
[65410]140} AVRECSINK, *PAVRECSINK;
141
[65197]142/**
[65624]143 * Audio video recording (output) stream.
[65197]144 */
[65624]145typedef struct AVRECSTREAM
[65197]146{
[88718]147 /** Common part. */
148 PDMAUDIOBACKENDSTREAM Core;
[65624]149 /** The stream's acquired configuration. */
[88718]150 PDMAUDIOSTREAMCFG Cfg;
[65256]151 /** (Audio) frame buffer. */
[88718]152 PRTCIRCBUF pCircBuf;
[65410]153 /** Pointer to sink to use for writing. */
[88718]154 PAVRECSINK pSink;
[69188]155 /** Last encoded PTS (in ms). */
[88718]156 uint64_t uLastPTSMs;
[75082]157 /** Temporary buffer for the input (source) data to encode. */
[88718]158 void *pvSrcBuf;
[75082]159 /** Size (in bytes) of the temporary buffer holding the input (source) data to encode. */
[88718]160 size_t cbSrcBuf;
[65624]161} AVRECSTREAM, *PAVRECSTREAM;
[65197]162
163/**
[65162]164 * Video recording audio driver instance data.
[53442]165 */
[75354]166typedef struct DRVAUDIORECORDING
[53442]167{
168 /** Pointer to audio video recording object. */
[65162]169 AudioVideoRec *pAudioVideoRec;
[53442]170 /** Pointer to the driver instance structure. */
[65162]171 PPDMDRVINS pDrvIns;
172 /** Pointer to host audio interface. */
173 PDMIHOSTAUDIO IHostAudio;
[65410]174 /** Pointer to the console object. */
[73104]175 ComPtr<Console> pConsole;
[65162]176 /** Pointer to the DrvAudio port interface that is above us. */
[74955]177 AVRECCONTAINERPARMS ContainerParms;
[96175]178 /** Weak pointer to recording context to use. */
179 RecordingContext *pRecCtx;
[65410]180 /** The driver's sink for writing output to. */
181 AVRECSINK Sink;
[75354]182} DRVAUDIORECORDING, *PDRVAUDIORECORDING;
[53442]183
184
[88459]185AudioVideoRec::AudioVideoRec(Console *pConsole)
186 : AudioDriver(pConsole)
187 , mpDrv(NULL)
[53442]188{
[88459]189}
[53442]190
[65389]191
[88459]192AudioVideoRec::~AudioVideoRec(void)
193{
194 if (mpDrv)
[68338]195 {
[88459]196 mpDrv->pAudioVideoRec = NULL;
197 mpDrv = NULL;
[68338]198 }
[88459]199}
[65389]200
[65410]201
[88459]202/**
[96175]203 * Applies recording settings to this driver instance.
[88459]204 *
205 * @returns VBox status code.
[96175]206 * @param Settings Recording settings to apply.
[88459]207 */
208int AudioVideoRec::applyConfiguration(const settings::RecordingSettings &Settings)
209{
210 /** @todo Do some validation here. */
[96175]211 mSettings = Settings; /* Note: Does have an own copy operator. */
[88459]212 return VINF_SUCCESS;
213}
[65410]214
[68338]215
[93444]216int AudioVideoRec::configureDriver(PCFGMNODE pLunCfg, PCVMMR3VTABLE pVMM)
[88459]217{
[96175]218 /** @todo For now we're using the configuration of the first screen (screen 0) here audio-wise. */
219 unsigned const idxScreen = 0;
[74848]220
[96175]221 AssertReturn(mSettings.mapScreens.size() >= 1, VERR_INVALID_PARAMETER);
222 const settings::RecordingScreenSettings &screenSettings = mSettings.mapScreens[idxScreen];
223
224 int vrc = pVMM->pfnCFGMR3InsertInteger(pLunCfg, "ContainerType", (uint64_t)screenSettings.enmDest);
[94957]225 AssertRCReturn(vrc, vrc);
[96175]226 if (screenSettings.enmDest == RecordingDestination_File)
[74848]227 {
[96175]228 vrc = pVMM->pfnCFGMR3InsertString(pLunCfg, "ContainerFileName", Utf8Str(screenSettings.File.strName).c_str());
[94957]229 AssertRCReturn(vrc, vrc);
[74848]230 }
[96175]231
232 vrc = pVMM->pfnCFGMR3InsertInteger(pLunCfg, "StreamIndex", (uint32_t)idxScreen);
[94957]233 AssertRCReturn(vrc, vrc);
[74848]234
[93444]235 return AudioDriver::configureDriver(pLunCfg, pVMM);
[88459]236}
[65410]237
238
[88459]239/*********************************************************************************************************************************
240* PDMIHOSTAUDIO *
241*********************************************************************************************************************************/
[74955]242
[88459]243/**
244 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
245 */
246static DECLCALLBACK(int) drvAudioVideoRecHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
247{
248 RT_NOREF(pInterface);
249 AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
[65410]250
[88534]251 /*
252 * Fill in the config structure.
253 */
254 RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "VideoRec");
255 pBackendCfg->cbStream = sizeof(AVRECSTREAM);
256 pBackendCfg->fFlags = 0;
[88459]257 pBackendCfg->cMaxStreamsIn = 0;
258 pBackendCfg->cMaxStreamsOut = UINT32_MAX;
[65416]259
[88459]260 return VINF_SUCCESS;
[65410]261}
262
263
264/**
[88459]265 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
[65410]266 */
[88459]267static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvAudioVideoRecHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
[65410]268{
[88459]269 RT_NOREF(pInterface, enmDir);
270 return PDMAUDIOBACKENDSTS_RUNNING;
[65410]271}
272
273
274/**
275 * Creates an audio output stream and associates it with the specified recording sink.
276 *
[88300]277 * @returns VBox status code.
[65410]278 * @param pThis Driver instance.
[65624]279 * @param pStreamAV Audio output stream to create.
[65410]280 * @param pSink Recording sink to associate audio output stream to.
281 * @param pCfgReq Requested configuration by the audio backend.
282 * @param pCfgAcq Acquired configuration by the audio output stream.
283 */
[75354]284static int avRecCreateStreamOut(PDRVAUDIORECORDING pThis, PAVRECSTREAM pStreamAV,
[96260]285 PAVRECSINK pSink, PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
[65410]286{
[65624]287 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
288 AssertPtrReturn(pStreamAV, VERR_INVALID_POINTER);
289 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
290 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
291 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
[65410]292
[89218]293 if (pCfgReq->enmPath != PDMAUDIOPATH_OUT_FRONT)
[65410]294 {
[96175]295 LogRel(("Recording: Support for surround audio not implemented yet\n"));
[65410]296 AssertFailed();
297 return VERR_NOT_SUPPORTED;
298 }
299
[96260]300 PRECORDINGCODEC pCodec = pSink->pRecStream->GetAudioCodec();
301
[96175]302 /* Stuff which has to be set by now. */
303 Assert(pCodec->Parms.cbFrame);
304 Assert(pCodec->Parms.msFrame);
305
306 int vrc = RTCircBufCreate(&pStreamAV->pCircBuf, pCodec->Parms.cbFrame * 2 /* Use "double buffering" */);
[94957]307 if (RT_SUCCESS(vrc))
[53442]308 {
[96175]309 size_t cbScratchBuf = pCodec->Parms.cbFrame;
[75082]310 pStreamAV->pvSrcBuf = RTMemAlloc(cbScratchBuf);
311 if (pStreamAV->pvSrcBuf)
[65197]312 {
[75082]313 pStreamAV->cbSrcBuf = cbScratchBuf;
[73370]314
[96229]315 pStreamAV->pSink = pSink; /* Assign sink to stream. */
316 pStreamAV->uLastPTSMs = 0;
[75082]317
[96229]318 /* Make sure to let the driver backend know that we need the audio data in
319 * a specific sampling rate the codec is optimized for. */
[96260]320 pCfgAcq->Props = pCodec->Parms.Audio.PCMProps;
[75082]321
[96260]322 /* Every codec frame marks a period for now. Optimize this later. */
[96229]323 pCfgAcq->Backend.cFramesPeriod = PDMAudioPropsMilliToFrames(&pCfgAcq->Props, pCodec->Parms.msFrame);
[96260]324 pCfgAcq->Backend.cFramesBufferSize = pCfgAcq->Backend.cFramesPeriod * 2;
325 pCfgAcq->Backend.cFramesPreBuffering = pCfgAcq->Backend.cFramesPeriod;
[65353]326 }
[75082]327 else
[94957]328 vrc = VERR_NO_MEMORY;
[53442]329 }
330
[94957]331 LogFlowFuncLeaveRC(vrc);
332 return vrc;
[53442]333}
334
[65162]335
[65410]336/**
[88459]337 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
338 */
339static DECLCALLBACK(int) drvAudioVideoRecHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
[89487]340 PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
[88459]341{
342 PDRVAUDIORECORDING pThis = RT_FROM_CPP_MEMBER(pInterface, DRVAUDIORECORDING, IHostAudio);
343 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
344 AssertPtrReturn(pStreamAV, VERR_INVALID_POINTER);
345 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
346 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
347
348 if (pCfgReq->enmDir == PDMAUDIODIR_IN)
349 return VERR_NOT_SUPPORTED;
350
351 /* For now we only have one sink, namely the driver's one.
352 * Later each stream could have its own one, to e.g. router different stream to different sinks .*/
353 PAVRECSINK pSink = &pThis->Sink;
354
[96260]355 int vrc = avRecCreateStreamOut(pThis, pStreamAV, pSink, pCfgReq, pCfgAcq);
[88459]356 PDMAudioStrmCfgCopy(&pStreamAV->Cfg, pCfgAcq);
357
[94957]358 return vrc;
[88459]359}
360
361
362/**
[65410]363 * Destroys (closes) an audio output stream.
364 *
[88300]365 * @returns VBox status code.
[65410]366 * @param pThis Driver instance.
[65624]367 * @param pStreamAV Audio output stream to destroy.
[65410]368 */
[75354]369static int avRecDestroyStreamOut(PDRVAUDIORECORDING pThis, PAVRECSTREAM pStreamAV)
[65410]370{
371 RT_NOREF(pThis);
372
[65624]373 if (pStreamAV->pCircBuf)
[65410]374 {
[65624]375 RTCircBufDestroy(pStreamAV->pCircBuf);
376 pStreamAV->pCircBuf = NULL;
[65410]377 }
378
[75082]379 if (pStreamAV->pvSrcBuf)
380 {
381 Assert(pStreamAV->cbSrcBuf);
382 RTMemFree(pStreamAV->pvSrcBuf);
[82252]383 pStreamAV->pvSrcBuf = NULL;
[75082]384 pStreamAV->cbSrcBuf = 0;
385 }
386
[65410]387 return VINF_SUCCESS;
388}
389
390
391/**
[88459]392 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
393 */
[89213]394static DECLCALLBACK(int) drvAudioVideoRecHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
395 bool fImmediate)
[88459]396{
397 PDRVAUDIORECORDING pThis = RT_FROM_CPP_MEMBER(pInterface, DRVAUDIORECORDING, IHostAudio);
398 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
399 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
[89213]400 RT_NOREF(fImmediate);
[88459]401
[94957]402 int vrc = VINF_SUCCESS;
[88459]403 if (pStreamAV->Cfg.enmDir == PDMAUDIODIR_OUT)
[94957]404 vrc = avRecDestroyStreamOut(pThis, pStreamAV);
[88459]405
[94957]406 return vrc;
[88459]407}
408
409
410/**
[89510]411 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable}
[65410]412 */
[89510]413static DECLCALLBACK(int) drvAudioVideoRecHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
[53442]414{
[89510]415 RT_NOREF(pInterface, pStream);
416 return VINF_SUCCESS;
417}
[53442]418
[65353]419
[89510]420/**
421 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable}
422 */
423static DECLCALLBACK(int) drvAudioVideoRecHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
424{
425 RT_NOREF(pInterface, pStream);
426 return VINF_SUCCESS;
427}
[65353]428
[89510]429
430/**
431 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause}
432 */
433static DECLCALLBACK(int) drvAudioVideoRecHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
434{
435 RT_NOREF(pInterface, pStream);
436 return VINF_SUCCESS;
[53442]437}
438
439
[65162]440/**
[89510]441 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume}
[53442]442 */
[89510]443static DECLCALLBACK(int) drvAudioVideoRecHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
[53442]444{
[89510]445 RT_NOREF(pInterface, pStream);
446 return VINF_SUCCESS;
447}
[53442]448
449
[89510]450/**
451 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain}
452 */
453static DECLCALLBACK(int) drvAudioVideoRecHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
454{
455 RT_NOREF(pInterface, pStream);
[65162]456 return VINF_SUCCESS;
[53442]457}
458
459
[65162]460/**
[89504]461 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState}
[88459]462 */
[89504]463static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvAudioVideoRecHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
464 PPDMAUDIOBACKENDSTREAM pStream)
[88459]465{
[89504]466 RT_NOREF(pInterface);
467 AssertPtrReturn(pStream, PDMHOSTAUDIOSTREAMSTATE_INVALID);
468 return PDMHOSTAUDIOSTREAMSTATE_OKAY;
[88459]469}
470
471
472/**
473 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
474 */
475static DECLCALLBACK(uint32_t) drvAudioVideoRecHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
476{
[96260]477 RT_NOREF(pInterface);
478 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
479
480 RecordingStream *pRecStream = pStreamAV->pSink->pRecStream;
481 PRECORDINGCODEC pCodec = pRecStream->GetAudioCodec();
482
483 return pCodec->Parms.cbFrame;
[88459]484}
485
486
487/**
[65162]488 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
489 */
[82255]490static DECLCALLBACK(int) drvAudioVideoRecHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
[88394]491 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
[53442]492{
[88395]493 RT_NOREF(pInterface);
[88394]494 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
495 AssertPtrReturn(pStreamAV, VERR_INVALID_POINTER);
[88991]496 if (cbBuf)
497 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
[88394]498 AssertReturn(pcbWritten, VERR_INVALID_PARAMETER);
[53442]499
[94957]500 int vrc = VINF_SUCCESS;
[65429]501
[65565]502 uint32_t cbWrittenTotal = 0;
[53442]503
[96175]504 PRTCIRCBUF pCircBuf = pStreamAV->pCircBuf;
[65410]505 AssertPtr(pCircBuf);
[103420]506 uint32_t const cbFree = (uint32_t)RTCircBufFree(pCircBuf);
507 uint32_t cbToWrite = RT_MIN(cbBuf, cbFree);
[96260]508 AssertReturn(cbToWrite, VERR_BUFFER_OVERFLOW);
[65565]509
[65389]510 /*
[88459]511 * Write as much as we can into our internal ring buffer.
[65389]512 */
[96260]513 while (cbToWrite)
[53442]514 {
[88459]515 void *pvCircBuf = NULL;
516 size_t cbCircBuf = 0;
[65565]517 RTCircBufAcquireWriteBlock(pCircBuf, cbToWrite, &pvCircBuf, &cbCircBuf);
[53442]518
[96260]519 Log3Func(("cbToWrite=%RU32, cbCircBuf=%zu\n", cbToWrite, cbCircBuf));
[65330]520
[96260]521 memcpy(pvCircBuf, (uint8_t *)pvBuf + cbWrittenTotal, cbCircBuf),
522 cbWrittenTotal += (uint32_t)cbCircBuf;
523 Assert(cbWrittenTotal <= cbBuf);
524 Assert(cbToWrite >= cbCircBuf);
525 cbToWrite -= (uint32_t)cbCircBuf;
526
[65565]527 RTCircBufReleaseWriteBlock(pCircBuf, cbCircBuf);
[65389]528 }
[65330]529
[96260]530 RecordingStream *pRecStream = pStreamAV->pSink->pRecStream;
531 PRECORDINGCODEC pCodec = pRecStream->GetAudioCodec();
532
[65389]533 /*
[96260]534 * Process our internal ring buffer and send the obtained audio data to our encoding thread.
[65389]535 */
[96262]536 cbToWrite = (uint32_t)RTCircBufUsed(pCircBuf);
[65330]537
[96260]538 /** @todo Can we encode more than a frame at a time? Optimize this! */
539 uint32_t const cbFrame = pCodec->Parms.cbFrame;
540
541 /* Only encode data if we have data for at least one full codec frame. */
542 while (cbToWrite >= cbFrame)
[65389]543 {
[88459]544 uint32_t cbSrc = 0;
[96260]545 do
[65389]546 {
[88459]547 void *pvCircBuf = NULL;
548 size_t cbCircBuf = 0;
[96260]549 RTCircBufAcquireReadBlock(pCircBuf, cbFrame - cbSrc, &pvCircBuf, &cbCircBuf);
[65330]550
[96260]551 Log3Func(("cbSrc=%RU32, cbCircBuf=%zu\n", cbSrc, cbCircBuf));
[65353]552
[96260]553 memcpy((uint8_t *)pStreamAV->pvSrcBuf + cbSrc, pvCircBuf, cbCircBuf);
[65330]554
[96260]555 cbSrc += (uint32_t)cbCircBuf;
556 Assert(cbSrc <= pStreamAV->cbSrcBuf);
557 Assert(cbSrc <= cbFrame);
558
[65429]559 RTCircBufReleaseReadBlock(pCircBuf, cbCircBuf);
[65389]560
[96260]561 if (cbSrc == cbFrame) /* Only send full codec frames. */
562 {
[96284]563 vrc = pRecStream->SendAudioFrame(pStreamAV->pvSrcBuf, cbSrc, RTTimeProgramMilliTS());
[96260]564 if (RT_FAILURE(vrc))
565 break;
566 }
[88459]567
[96260]568 } while (cbSrc < cbFrame);
[65389]569
[96260]570 Assert(cbToWrite >= cbFrame);
571 cbToWrite -= cbFrame;
[65389]572
[94957]573 if (RT_FAILURE(vrc))
[65389]574 break;
[68772]575
[96175]576 } /* while */
577
[88394]578 *pcbWritten = cbWrittenTotal;
[68772]579
[96260]580 LogFlowFunc(("cbBuf=%RU32, cbWrittenTotal=%RU32, vrc=%Rrc\n", cbBuf, cbWrittenTotal, vrc));
[96175]581 return VINF_SUCCESS; /* Don't propagate encoding errors to the caller. */
[53442]582}
583
[65162]584
585/**
[89504]586 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
587 */
588static DECLCALLBACK(uint32_t) drvAudioVideoRecHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
589{
590 RT_NOREF(pInterface, pStream);
591 return 0; /* Video capturing does not provide any input. */
592}
593
594
595/**
[88459]596 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
[65162]597 */
[88459]598static DECLCALLBACK(int) drvAudioVideoRecHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
599 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
[53442]600{
[88459]601 RT_NOREF(pInterface, pStream, pvBuf, cbBuf);
602 *pcbRead = 0;
[65162]603 return VINF_SUCCESS;
604}
[53442]605
606
[88459]607/*********************************************************************************************************************************
608* PDMIBASE *
609*********************************************************************************************************************************/
[53442]610
[65162]611/**
[53442]612 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
613 */
614static DECLCALLBACK(void *) drvAudioVideoRecQueryInterface(PPDMIBASE pInterface, const char *pszIID)
615{
616 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
[75354]617 PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
[65162]618
[53442]619 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
[54368]620 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
[53442]621 return NULL;
622}
623
[65162]624
[88459]625/*********************************************************************************************************************************
626* PDMDRVREG *
627*********************************************************************************************************************************/
[53442]628
[70644]629/**
[88459]630 * Shuts down (closes) a recording sink,
[74955]631 *
[88459]632 * @param pSink Recording sink to shut down.
[74955]633 */
[88459]634static void avRecSinkShutdown(PAVRECSINK pSink)
[74955]635{
[88459]636 AssertPtrReturnVoid(pSink);
[74955]637
[96260]638 pSink->pRecStream = NULL;
[96175]639
[88459]640 switch (pSink->Con.Parms.enmType)
641 {
642 case AVRECCONTAINERTYPE_WEBM:
643 {
644 if (pSink->Con.WebM.pWebM)
645 {
646 LogRel2(("Recording: Finished recording audio to file '%s' (%zu bytes)\n",
647 pSink->Con.WebM.pWebM->GetFileName().c_str(), pSink->Con.WebM.pWebM->GetFileSize()));
[74955]648
[94957]649 int vrc2 = pSink->Con.WebM.pWebM->Close();
650 AssertRC(vrc2);
[70644]651
[88459]652 delete pSink->Con.WebM.pWebM;
653 pSink->Con.WebM.pWebM = NULL;
654 }
655 break;
656 }
[75251]657
[88459]658 case AVRECCONTAINERTYPE_MAIN_CONSOLE:
[96175]659 RT_FALL_THROUGH();
[88459]660 default:
661 break;
[74955]662 }
[70563]663}
664
665
[53442]666/**
[88382]667 * @interface_method_impl{PDMDRVREG,pfnPowerOff}
668 */
669/*static*/ DECLCALLBACK(void) AudioVideoRec::drvPowerOff(PPDMDRVINS pDrvIns)
670{
671 PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
672 LogFlowFuncEnter();
673 avRecSinkShutdown(&pThis->Sink);
674}
675
676
677/**
678 * @interface_method_impl{PDMDRVREG,pfnDestruct}
679 */
680/*static*/ DECLCALLBACK(void) AudioVideoRec::drvDestruct(PPDMDRVINS pDrvIns)
681{
682 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
683 PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
684
685 LogFlowFuncEnter();
686
687 switch (pThis->ContainerParms.enmType)
688 {
689 case AVRECCONTAINERTYPE_WEBM:
690 {
691 avRecSinkShutdown(&pThis->Sink);
692 RTStrFree(pThis->ContainerParms.WebM.pszFile);
693 break;
694 }
695
696 default:
697 break;
698 }
699
700 /*
701 * If the AudioVideoRec object is still alive, we must clear it's reference to
702 * us since we'll be invalid when we return from this method.
703 */
704 if (pThis->pAudioVideoRec)
705 {
706 pThis->pAudioVideoRec->mpDrv = NULL;
707 pThis->pAudioVideoRec = NULL;
708 }
709
710 LogFlowFuncLeave();
711}
712
713
714/**
[88459]715 * Initializes a recording sink.
716 *
717 * @returns VBox status code.
718 * @param pThis Driver instance.
719 * @param pSink Sink to initialize.
720 * @param pConParms Container parameters to set.
[96260]721 * @param pStream Recording stream to asssign sink to.
[88459]722 */
[96260]723static int avRecSinkInit(PDRVAUDIORECORDING pThis, PAVRECSINK pSink, PAVRECCONTAINERPARMS pConParms, RecordingStream *pStream)
[88459]724{
[96260]725 pSink->pRecStream = pStream;
[88459]726
[96207]727 int vrc = VINF_SUCCESS;
[88459]728
[96175]729 /*
730 * Container setup.
731 */
[88459]732 try
733 {
734 switch (pConParms->enmType)
735 {
736 case AVRECCONTAINERTYPE_MAIN_CONSOLE:
737 {
738 if (pThis->pConsole)
739 {
740 pSink->Con.Main.pConsole = pThis->pConsole;
741 }
742 else
[94957]743 vrc = VERR_NOT_SUPPORTED;
[88459]744 break;
745 }
746
747 case AVRECCONTAINERTYPE_WEBM:
748 {
[96260]749 #if 0
[88459]750 /* If we only record audio, create our own WebM writer instance here. */
751 if (!pSink->Con.WebM.pWebM) /* Do we already have our WebM writer instance? */
752 {
753 /** @todo Add sink name / number to file name. */
754 const char *pszFile = pSink->Con.Parms.WebM.pszFile;
755 AssertPtr(pszFile);
756
757 pSink->Con.WebM.pWebM = new WebMWriter();
[94957]758 vrc = pSink->Con.WebM.pWebM->Open(pszFile,
759 /** @todo Add option to add some suffix if file exists instead of overwriting? */
760 RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE,
[96175]761 pSink->pCodec->Parms.enmAudioCodec, RecordingVideoCodec_None);
[94957]762 if (RT_SUCCESS(vrc))
[88459]763 {
[96175]764 const PPDMAUDIOPCMPROPS pPCMProps = &pCodec->Parms.Audio.PCMProps;
765
766 vrc = pSink->Con.WebM.pWebM->AddAudioTrack(pSink->pCodec,
767 PDMAudioPropsHz(pPCMProps), PDMAudioPropsChannels(pPCMProps),
768 PDMAudioPropsSampleBits(pPCMProps), &pSink->Con.WebM.uTrack);
[94957]769 if (RT_SUCCESS(vrc))
[88459]770 {
771 LogRel(("Recording: Recording audio to audio file '%s'\n", pszFile));
772 }
773 else
[94957]774 LogRel(("Recording: Error creating audio track for audio file '%s' (%Rrc)\n", pszFile, vrc));
[88459]775 }
776 else
[94957]777 LogRel(("Recording: Error creating audio file '%s' (%Rrc)\n", pszFile, vrc));
[88459]778 }
779 break;
[96260]780 #endif
[88459]781 }
782
783 default:
[94957]784 vrc = VERR_NOT_SUPPORTED;
[88459]785 break;
786 }
787 }
788 catch (std::bad_alloc &)
789 {
[94957]790 vrc = VERR_NO_MEMORY;
[88459]791 }
792
[94957]793 if (RT_SUCCESS(vrc))
[88459]794 {
[96175]795 pSink->Con.Parms.enmType = pConParms->enmType;
796 pSink->tsStartMs = RTTimeMilliTS();
[88459]797
[96175]798 return VINF_SUCCESS;
[88459]799 }
800
[96175]801 LogRel(("Recording: Error creating sink (%Rrc)\n", vrc));
[94957]802 return vrc;
[88459]803}
804
805
806/**
[65162]807 * Construct a audio video recording driver instance.
[53442]808 *
809 * @copydoc FNPDMDRVCONSTRUCT
810 */
[88382]811/*static*/ DECLCALLBACK(int) AudioVideoRec::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
[53442]812{
[65162]813 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
[75354]814 PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
[68882]815 RT_NOREF(fFlags);
[65162]816
[65197]817 LogRel(("Audio: Initializing video recording audio driver\n"));
[53442]818 LogFlowFunc(("fFlags=0x%x\n", fFlags));
819
820 AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
821 ("Configuration error: Not possible to attach anything to this driver!\n"),
822 VERR_PDM_DRVINS_NO_ATTACH);
823
824 /*
825 * Init the static parts.
826 */
[54230]827 pThis->pDrvIns = pDrvIns;
[53442]828 /* IBase */
[54230]829 pDrvIns->IBase.pfnQueryInterface = drvAudioVideoRecQueryInterface;
[54368]830 /* IHostAudio */
[88761]831 pThis->IHostAudio.pfnGetConfig = drvAudioVideoRecHA_GetConfig;
832 pThis->IHostAudio.pfnGetDevices = NULL;
[89258]833 pThis->IHostAudio.pfnSetDevice = NULL;
[88761]834 pThis->IHostAudio.pfnGetStatus = drvAudioVideoRecHA_GetStatus;
[88819]835 pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
836 pThis->IHostAudio.pfnStreamConfigHint = NULL;
[88761]837 pThis->IHostAudio.pfnStreamCreate = drvAudioVideoRecHA_StreamCreate;
[88819]838 pThis->IHostAudio.pfnStreamInitAsync = NULL;
[88761]839 pThis->IHostAudio.pfnStreamDestroy = drvAudioVideoRecHA_StreamDestroy;
840 pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
[89510]841 pThis->IHostAudio.pfnStreamEnable = drvAudioVideoRecHA_StreamEnable;
842 pThis->IHostAudio.pfnStreamDisable = drvAudioVideoRecHA_StreamDisable;
843 pThis->IHostAudio.pfnStreamPause = drvAudioVideoRecHA_StreamPause;
844 pThis->IHostAudio.pfnStreamResume = drvAudioVideoRecHA_StreamResume;
845 pThis->IHostAudio.pfnStreamDrain = drvAudioVideoRecHA_StreamDrain;
[89504]846 pThis->IHostAudio.pfnStreamGetState = drvAudioVideoRecHA_StreamGetState;
847 pThis->IHostAudio.pfnStreamGetPending = NULL;
[88761]848 pThis->IHostAudio.pfnStreamGetWritable = drvAudioVideoRecHA_StreamGetWritable;
849 pThis->IHostAudio.pfnStreamPlay = drvAudioVideoRecHA_StreamPlay;
[89504]850 pThis->IHostAudio.pfnStreamGetReadable = drvAudioVideoRecHA_StreamGetReadable;
[88761]851 pThis->IHostAudio.pfnStreamCapture = drvAudioVideoRecHA_StreamCapture;
[53442]852
[65162]853 /*
[93444]854 * Read configuration.
855 */
856 PCPDMDRVHLPR3 const pHlp = pDrvIns->pHlpR3;
857 /** @todo validate it. */
858
859 /*
[65410]860 * Get the Console object pointer.
[65162]861 */
[94326]862 com::Guid ConsoleUuid(COM_IIDOF(IConsole));
863 IConsole *pIConsole = (IConsole *)PDMDrvHlpQueryGenericUserObject(pDrvIns, ConsoleUuid.raw());
864 AssertLogRelReturn(pIConsole, VERR_INTERNAL_ERROR_3);
865 Console *pConsole = static_cast<Console *>(pIConsole);
866 AssertLogRelReturn(pConsole, VERR_INTERNAL_ERROR_3);
[65410]867
[94326]868 pThis->pConsole = pConsole;
[68797]869 AssertReturn(!pThis->pConsole.isNull(), VERR_INVALID_POINTER);
[94326]870 pThis->pAudioVideoRec = pConsole->i_recordingGetAudioDrv();
871 AssertPtrReturn(pThis->pAudioVideoRec, VERR_INVALID_POINTER);
[65410]872
[94326]873 pThis->pAudioVideoRec->mpDrv = pThis;
[53442]874
[74955]875 /*
[96175]876 * Get the recording container parameters from the audio driver instance.
[74955]877 */
878 RT_ZERO(pThis->ContainerParms);
[96175]879 PAVRECCONTAINERPARMS pConParams = &pThis->ContainerParms;
[74955]880
[96175]881 int vrc = pHlp->pfnCFGMQueryU32(pCfg, "StreamIndex", (uint32_t *)&pConParams->idxStream);
[94957]882 AssertRCReturn(vrc, vrc);
[74955]883
[96175]884 vrc = pHlp->pfnCFGMQueryU32(pCfg, "ContainerType", (uint32_t *)&pConParams->enmType);
885 AssertRCReturn(vrc, vrc);
886
[74955]887 switch (pConParams->enmType)
888 {
889 case AVRECCONTAINERTYPE_WEBM:
[94957]890 vrc = pHlp->pfnCFGMQueryStringAlloc(pCfg, "ContainerFileName", &pConParams->WebM.pszFile);
891 AssertRCReturn(vrc, vrc);
[74955]892 break;
893
894 default:
895 break;
896 }
897
[96175]898 /*
899 * Obtain the recording context.
900 */
[96260]901 pThis->pRecCtx = pConsole->i_recordingGetContext();
[96175]902 AssertPtrReturn(pThis->pRecCtx, VERR_INVALID_POINTER);
[88269]903
[53442]904 /*
[96175]905 * Get the codec configuration.
[53442]906 */
[96175]907 RecordingStream *pStream = pThis->pRecCtx->GetStream(pConParams->idxStream);
908 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
[53442]909
[88381]910 /*
911 * Init the recording sink.
912 */
[96260]913 vrc = avRecSinkInit(pThis, &pThis->Sink, &pThis->ContainerParms, pStream);
[94957]914 if (RT_SUCCESS(vrc))
[88381]915 LogRel2(("Recording: Audio recording driver initialized\n"));
916 else
[94957]917 LogRel(("Recording: Audio recording driver initialization failed: %Rrc\n", vrc));
[88381]918
[94957]919 return vrc;
[53442]920}
921
[65162]922
923/**
924 * Video recording audio driver registration record.
[53442]925 */
926const PDMDRVREG AudioVideoRec::DrvReg =
927{
928 PDM_DRVREG_VERSION,
929 /* szName */
930 "AudioVideoRec",
931 /* szRCMod */
932 "",
933 /* szR0Mod */
934 "",
935 /* pszDescription */
936 "Audio driver for video recording",
937 /* fFlags */
938 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
939 /* fClass. */
940 PDM_DRVREG_CLASS_AUDIO,
941 /* cMaxInstances */
942 ~0U,
943 /* cbInstance */
[75354]944 sizeof(DRVAUDIORECORDING),
[53442]945 /* pfnConstruct */
946 AudioVideoRec::drvConstruct,
947 /* pfnDestruct */
948 AudioVideoRec::drvDestruct,
949 /* pfnRelocate */
950 NULL,
951 /* pfnIOCtl */
952 NULL,
953 /* pfnPowerOn */
954 NULL,
955 /* pfnReset */
956 NULL,
957 /* pfnSuspend */
958 NULL,
959 /* pfnResume */
960 NULL,
961 /* pfnAttach */
[88382]962 NULL,
[53442]963 /* pfnDetach */
[88382]964 NULL,
[53442]965 /* pfnPowerOff */
[88382]966 AudioVideoRec::drvPowerOff,
[53442]967 /* pfnSoftReset */
968 NULL,
969 /* u32EndVersion */
970 PDM_DRVREG_VERSION
971};
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use