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
Line 
1/* $Id: DrvAudioRec.cpp 103420 2024-02-19 09:08:34Z vboxsync $ */
2/** @file
3 * Video recording audio backend for Main.
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.
16 */
17
18/*
19 * Copyright (C) 2016-2023 Oracle and/or its affiliates.
20 *
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
38 */
39
40
41/*********************************************************************************************************************************
42* Header Files *
43*********************************************************************************************************************************/
44#define LOG_GROUP LOG_GROUP_RECORDING
45#include "LoggingNew.h"
46
47#include "DrvAudioRec.h"
48#include "ConsoleImpl.h"
49
50#include "WebMWriter.h"
51
52#include <iprt/mem.h>
53#include <iprt/cdefs.h>
54
55#include "VBox/com/VirtualBox.h"
56#include <VBox/vmm/cfgm.h>
57#include <VBox/vmm/pdmdrv.h>
58#include <VBox/vmm/pdmaudioifs.h>
59#include <VBox/vmm/pdmaudioinline.h>
60#include <VBox/vmm/vmmr3vtable.h>
61#include <VBox/err.h>
62#include "VBox/settings.h"
63
64
65/*********************************************************************************************************************************
66* Structures and Typedefs *
67*********************************************************************************************************************************/
68/**
69 * Enumeration for specifying the recording container type.
70 */
71typedef enum AVRECCONTAINERTYPE
72{
73 /** Unknown / invalid container type. */
74 AVRECCONTAINERTYPE_UNKNOWN = 0,
75 /** Recorded data goes to Main / Console. */
76 AVRECCONTAINERTYPE_MAIN_CONSOLE = 1,
77 /** Recorded data will be written to a .webm file. */
78 AVRECCONTAINERTYPE_WEBM = 2
79} AVRECCONTAINERTYPE;
80
81/**
82 * Structure for keeping generic container parameters.
83 */
84typedef struct AVRECCONTAINERPARMS
85{
86 /** Stream index (hint). */
87 uint32_t idxStream;
88 /** The container's type. */
89 AVRECCONTAINERTYPE enmType;
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 };
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/**
130 * Audio video recording sink.
131 */
132typedef struct AVRECSINK
133{
134 /** Pointer (weak) to recording stream to bind to. */
135 RecordingStream *pRecStream;
136 /** Container data to use for data processing. */
137 AVRECCONTAINER Con;
138 /** Timestamp (in ms) of when the sink was created. */
139 uint64_t tsStartMs;
140} AVRECSINK, *PAVRECSINK;
141
142/**
143 * Audio video recording (output) stream.
144 */
145typedef struct AVRECSTREAM
146{
147 /** Common part. */
148 PDMAUDIOBACKENDSTREAM Core;
149 /** The stream's acquired configuration. */
150 PDMAUDIOSTREAMCFG Cfg;
151 /** (Audio) frame buffer. */
152 PRTCIRCBUF pCircBuf;
153 /** Pointer to sink to use for writing. */
154 PAVRECSINK pSink;
155 /** Last encoded PTS (in ms). */
156 uint64_t uLastPTSMs;
157 /** Temporary buffer for the input (source) data to encode. */
158 void *pvSrcBuf;
159 /** Size (in bytes) of the temporary buffer holding the input (source) data to encode. */
160 size_t cbSrcBuf;
161} AVRECSTREAM, *PAVRECSTREAM;
162
163/**
164 * Video recording audio driver instance data.
165 */
166typedef struct DRVAUDIORECORDING
167{
168 /** Pointer to audio video recording object. */
169 AudioVideoRec *pAudioVideoRec;
170 /** Pointer to the driver instance structure. */
171 PPDMDRVINS pDrvIns;
172 /** Pointer to host audio interface. */
173 PDMIHOSTAUDIO IHostAudio;
174 /** Pointer to the console object. */
175 ComPtr<Console> pConsole;
176 /** Pointer to the DrvAudio port interface that is above us. */
177 AVRECCONTAINERPARMS ContainerParms;
178 /** Weak pointer to recording context to use. */
179 RecordingContext *pRecCtx;
180 /** The driver's sink for writing output to. */
181 AVRECSINK Sink;
182} DRVAUDIORECORDING, *PDRVAUDIORECORDING;
183
184
185AudioVideoRec::AudioVideoRec(Console *pConsole)
186 : AudioDriver(pConsole)
187 , mpDrv(NULL)
188{
189}
190
191
192AudioVideoRec::~AudioVideoRec(void)
193{
194 if (mpDrv)
195 {
196 mpDrv->pAudioVideoRec = NULL;
197 mpDrv = NULL;
198 }
199}
200
201
202/**
203 * Applies recording settings to this driver instance.
204 *
205 * @returns VBox status code.
206 * @param Settings Recording settings to apply.
207 */
208int AudioVideoRec::applyConfiguration(const settings::RecordingSettings &Settings)
209{
210 /** @todo Do some validation here. */
211 mSettings = Settings; /* Note: Does have an own copy operator. */
212 return VINF_SUCCESS;
213}
214
215
216int AudioVideoRec::configureDriver(PCFGMNODE pLunCfg, PCVMMR3VTABLE pVMM)
217{
218 /** @todo For now we're using the configuration of the first screen (screen 0) here audio-wise. */
219 unsigned const idxScreen = 0;
220
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);
225 AssertRCReturn(vrc, vrc);
226 if (screenSettings.enmDest == RecordingDestination_File)
227 {
228 vrc = pVMM->pfnCFGMR3InsertString(pLunCfg, "ContainerFileName", Utf8Str(screenSettings.File.strName).c_str());
229 AssertRCReturn(vrc, vrc);
230 }
231
232 vrc = pVMM->pfnCFGMR3InsertInteger(pLunCfg, "StreamIndex", (uint32_t)idxScreen);
233 AssertRCReturn(vrc, vrc);
234
235 return AudioDriver::configureDriver(pLunCfg, pVMM);
236}
237
238
239/*********************************************************************************************************************************
240* PDMIHOSTAUDIO *
241*********************************************************************************************************************************/
242
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);
250
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;
257 pBackendCfg->cMaxStreamsIn = 0;
258 pBackendCfg->cMaxStreamsOut = UINT32_MAX;
259
260 return VINF_SUCCESS;
261}
262
263
264/**
265 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
266 */
267static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvAudioVideoRecHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
268{
269 RT_NOREF(pInterface, enmDir);
270 return PDMAUDIOBACKENDSTS_RUNNING;
271}
272
273
274/**
275 * Creates an audio output stream and associates it with the specified recording sink.
276 *
277 * @returns VBox status code.
278 * @param pThis Driver instance.
279 * @param pStreamAV Audio output stream to create.
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 */
284static int avRecCreateStreamOut(PDRVAUDIORECORDING pThis, PAVRECSTREAM pStreamAV,
285 PAVRECSINK pSink, PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
286{
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);
292
293 if (pCfgReq->enmPath != PDMAUDIOPATH_OUT_FRONT)
294 {
295 LogRel(("Recording: Support for surround audio not implemented yet\n"));
296 AssertFailed();
297 return VERR_NOT_SUPPORTED;
298 }
299
300 PRECORDINGCODEC pCodec = pSink->pRecStream->GetAudioCodec();
301
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" */);
307 if (RT_SUCCESS(vrc))
308 {
309 size_t cbScratchBuf = pCodec->Parms.cbFrame;
310 pStreamAV->pvSrcBuf = RTMemAlloc(cbScratchBuf);
311 if (pStreamAV->pvSrcBuf)
312 {
313 pStreamAV->cbSrcBuf = cbScratchBuf;
314
315 pStreamAV->pSink = pSink; /* Assign sink to stream. */
316 pStreamAV->uLastPTSMs = 0;
317
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. */
320 pCfgAcq->Props = pCodec->Parms.Audio.PCMProps;
321
322 /* Every codec frame marks a period for now. Optimize this later. */
323 pCfgAcq->Backend.cFramesPeriod = PDMAudioPropsMilliToFrames(&pCfgAcq->Props, pCodec->Parms.msFrame);
324 pCfgAcq->Backend.cFramesBufferSize = pCfgAcq->Backend.cFramesPeriod * 2;
325 pCfgAcq->Backend.cFramesPreBuffering = pCfgAcq->Backend.cFramesPeriod;
326 }
327 else
328 vrc = VERR_NO_MEMORY;
329 }
330
331 LogFlowFuncLeaveRC(vrc);
332 return vrc;
333}
334
335
336/**
337 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
338 */
339static DECLCALLBACK(int) drvAudioVideoRecHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
340 PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
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
355 int vrc = avRecCreateStreamOut(pThis, pStreamAV, pSink, pCfgReq, pCfgAcq);
356 PDMAudioStrmCfgCopy(&pStreamAV->Cfg, pCfgAcq);
357
358 return vrc;
359}
360
361
362/**
363 * Destroys (closes) an audio output stream.
364 *
365 * @returns VBox status code.
366 * @param pThis Driver instance.
367 * @param pStreamAV Audio output stream to destroy.
368 */
369static int avRecDestroyStreamOut(PDRVAUDIORECORDING pThis, PAVRECSTREAM pStreamAV)
370{
371 RT_NOREF(pThis);
372
373 if (pStreamAV->pCircBuf)
374 {
375 RTCircBufDestroy(pStreamAV->pCircBuf);
376 pStreamAV->pCircBuf = NULL;
377 }
378
379 if (pStreamAV->pvSrcBuf)
380 {
381 Assert(pStreamAV->cbSrcBuf);
382 RTMemFree(pStreamAV->pvSrcBuf);
383 pStreamAV->pvSrcBuf = NULL;
384 pStreamAV->cbSrcBuf = 0;
385 }
386
387 return VINF_SUCCESS;
388}
389
390
391/**
392 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
393 */
394static DECLCALLBACK(int) drvAudioVideoRecHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
395 bool fImmediate)
396{
397 PDRVAUDIORECORDING pThis = RT_FROM_CPP_MEMBER(pInterface, DRVAUDIORECORDING, IHostAudio);
398 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
399 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
400 RT_NOREF(fImmediate);
401
402 int vrc = VINF_SUCCESS;
403 if (pStreamAV->Cfg.enmDir == PDMAUDIODIR_OUT)
404 vrc = avRecDestroyStreamOut(pThis, pStreamAV);
405
406 return vrc;
407}
408
409
410/**
411 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable}
412 */
413static DECLCALLBACK(int) drvAudioVideoRecHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
414{
415 RT_NOREF(pInterface, pStream);
416 return VINF_SUCCESS;
417}
418
419
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}
428
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;
437}
438
439
440/**
441 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume}
442 */
443static DECLCALLBACK(int) drvAudioVideoRecHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
444{
445 RT_NOREF(pInterface, pStream);
446 return VINF_SUCCESS;
447}
448
449
450/**
451 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain}
452 */
453static DECLCALLBACK(int) drvAudioVideoRecHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
454{
455 RT_NOREF(pInterface, pStream);
456 return VINF_SUCCESS;
457}
458
459
460/**
461 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState}
462 */
463static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvAudioVideoRecHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
464 PPDMAUDIOBACKENDSTREAM pStream)
465{
466 RT_NOREF(pInterface);
467 AssertPtrReturn(pStream, PDMHOSTAUDIOSTREAMSTATE_INVALID);
468 return PDMHOSTAUDIOSTREAMSTATE_OKAY;
469}
470
471
472/**
473 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
474 */
475static DECLCALLBACK(uint32_t) drvAudioVideoRecHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
476{
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;
484}
485
486
487/**
488 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
489 */
490static DECLCALLBACK(int) drvAudioVideoRecHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
491 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
492{
493 RT_NOREF(pInterface);
494 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
495 AssertPtrReturn(pStreamAV, VERR_INVALID_POINTER);
496 if (cbBuf)
497 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
498 AssertReturn(pcbWritten, VERR_INVALID_PARAMETER);
499
500 int vrc = VINF_SUCCESS;
501
502 uint32_t cbWrittenTotal = 0;
503
504 PRTCIRCBUF pCircBuf = pStreamAV->pCircBuf;
505 AssertPtr(pCircBuf);
506 uint32_t const cbFree = (uint32_t)RTCircBufFree(pCircBuf);
507 uint32_t cbToWrite = RT_MIN(cbBuf, cbFree);
508 AssertReturn(cbToWrite, VERR_BUFFER_OVERFLOW);
509
510 /*
511 * Write as much as we can into our internal ring buffer.
512 */
513 while (cbToWrite)
514 {
515 void *pvCircBuf = NULL;
516 size_t cbCircBuf = 0;
517 RTCircBufAcquireWriteBlock(pCircBuf, cbToWrite, &pvCircBuf, &cbCircBuf);
518
519 Log3Func(("cbToWrite=%RU32, cbCircBuf=%zu\n", cbToWrite, cbCircBuf));
520
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
527 RTCircBufReleaseWriteBlock(pCircBuf, cbCircBuf);
528 }
529
530 RecordingStream *pRecStream = pStreamAV->pSink->pRecStream;
531 PRECORDINGCODEC pCodec = pRecStream->GetAudioCodec();
532
533 /*
534 * Process our internal ring buffer and send the obtained audio data to our encoding thread.
535 */
536 cbToWrite = (uint32_t)RTCircBufUsed(pCircBuf);
537
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)
543 {
544 uint32_t cbSrc = 0;
545 do
546 {
547 void *pvCircBuf = NULL;
548 size_t cbCircBuf = 0;
549 RTCircBufAcquireReadBlock(pCircBuf, cbFrame - cbSrc, &pvCircBuf, &cbCircBuf);
550
551 Log3Func(("cbSrc=%RU32, cbCircBuf=%zu\n", cbSrc, cbCircBuf));
552
553 memcpy((uint8_t *)pStreamAV->pvSrcBuf + cbSrc, pvCircBuf, cbCircBuf);
554
555 cbSrc += (uint32_t)cbCircBuf;
556 Assert(cbSrc <= pStreamAV->cbSrcBuf);
557 Assert(cbSrc <= cbFrame);
558
559 RTCircBufReleaseReadBlock(pCircBuf, cbCircBuf);
560
561 if (cbSrc == cbFrame) /* Only send full codec frames. */
562 {
563 vrc = pRecStream->SendAudioFrame(pStreamAV->pvSrcBuf, cbSrc, RTTimeProgramMilliTS());
564 if (RT_FAILURE(vrc))
565 break;
566 }
567
568 } while (cbSrc < cbFrame);
569
570 Assert(cbToWrite >= cbFrame);
571 cbToWrite -= cbFrame;
572
573 if (RT_FAILURE(vrc))
574 break;
575
576 } /* while */
577
578 *pcbWritten = cbWrittenTotal;
579
580 LogFlowFunc(("cbBuf=%RU32, cbWrittenTotal=%RU32, vrc=%Rrc\n", cbBuf, cbWrittenTotal, vrc));
581 return VINF_SUCCESS; /* Don't propagate encoding errors to the caller. */
582}
583
584
585/**
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/**
596 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
597 */
598static DECLCALLBACK(int) drvAudioVideoRecHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
599 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
600{
601 RT_NOREF(pInterface, pStream, pvBuf, cbBuf);
602 *pcbRead = 0;
603 return VINF_SUCCESS;
604}
605
606
607/*********************************************************************************************************************************
608* PDMIBASE *
609*********************************************************************************************************************************/
610
611/**
612 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
613 */
614static DECLCALLBACK(void *) drvAudioVideoRecQueryInterface(PPDMIBASE pInterface, const char *pszIID)
615{
616 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
617 PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
618
619 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
620 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
621 return NULL;
622}
623
624
625/*********************************************************************************************************************************
626* PDMDRVREG *
627*********************************************************************************************************************************/
628
629/**
630 * Shuts down (closes) a recording sink,
631 *
632 * @param pSink Recording sink to shut down.
633 */
634static void avRecSinkShutdown(PAVRECSINK pSink)
635{
636 AssertPtrReturnVoid(pSink);
637
638 pSink->pRecStream = NULL;
639
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()));
648
649 int vrc2 = pSink->Con.WebM.pWebM->Close();
650 AssertRC(vrc2);
651
652 delete pSink->Con.WebM.pWebM;
653 pSink->Con.WebM.pWebM = NULL;
654 }
655 break;
656 }
657
658 case AVRECCONTAINERTYPE_MAIN_CONSOLE:
659 RT_FALL_THROUGH();
660 default:
661 break;
662 }
663}
664
665
666/**
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/**
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.
721 * @param pStream Recording stream to asssign sink to.
722 */
723static int avRecSinkInit(PDRVAUDIORECORDING pThis, PAVRECSINK pSink, PAVRECCONTAINERPARMS pConParms, RecordingStream *pStream)
724{
725 pSink->pRecStream = pStream;
726
727 int vrc = VINF_SUCCESS;
728
729 /*
730 * Container setup.
731 */
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
743 vrc = VERR_NOT_SUPPORTED;
744 break;
745 }
746
747 case AVRECCONTAINERTYPE_WEBM:
748 {
749 #if 0
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();
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,
761 pSink->pCodec->Parms.enmAudioCodec, RecordingVideoCodec_None);
762 if (RT_SUCCESS(vrc))
763 {
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);
769 if (RT_SUCCESS(vrc))
770 {
771 LogRel(("Recording: Recording audio to audio file '%s'\n", pszFile));
772 }
773 else
774 LogRel(("Recording: Error creating audio track for audio file '%s' (%Rrc)\n", pszFile, vrc));
775 }
776 else
777 LogRel(("Recording: Error creating audio file '%s' (%Rrc)\n", pszFile, vrc));
778 }
779 break;
780 #endif
781 }
782
783 default:
784 vrc = VERR_NOT_SUPPORTED;
785 break;
786 }
787 }
788 catch (std::bad_alloc &)
789 {
790 vrc = VERR_NO_MEMORY;
791 }
792
793 if (RT_SUCCESS(vrc))
794 {
795 pSink->Con.Parms.enmType = pConParms->enmType;
796 pSink->tsStartMs = RTTimeMilliTS();
797
798 return VINF_SUCCESS;
799 }
800
801 LogRel(("Recording: Error creating sink (%Rrc)\n", vrc));
802 return vrc;
803}
804
805
806/**
807 * Construct a audio video recording driver instance.
808 *
809 * @copydoc FNPDMDRVCONSTRUCT
810 */
811/*static*/ DECLCALLBACK(int) AudioVideoRec::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
812{
813 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
814 PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
815 RT_NOREF(fFlags);
816
817 LogRel(("Audio: Initializing video recording audio driver\n"));
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 */
827 pThis->pDrvIns = pDrvIns;
828 /* IBase */
829 pDrvIns->IBase.pfnQueryInterface = drvAudioVideoRecQueryInterface;
830 /* IHostAudio */
831 pThis->IHostAudio.pfnGetConfig = drvAudioVideoRecHA_GetConfig;
832 pThis->IHostAudio.pfnGetDevices = NULL;
833 pThis->IHostAudio.pfnSetDevice = NULL;
834 pThis->IHostAudio.pfnGetStatus = drvAudioVideoRecHA_GetStatus;
835 pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
836 pThis->IHostAudio.pfnStreamConfigHint = NULL;
837 pThis->IHostAudio.pfnStreamCreate = drvAudioVideoRecHA_StreamCreate;
838 pThis->IHostAudio.pfnStreamInitAsync = NULL;
839 pThis->IHostAudio.pfnStreamDestroy = drvAudioVideoRecHA_StreamDestroy;
840 pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
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;
846 pThis->IHostAudio.pfnStreamGetState = drvAudioVideoRecHA_StreamGetState;
847 pThis->IHostAudio.pfnStreamGetPending = NULL;
848 pThis->IHostAudio.pfnStreamGetWritable = drvAudioVideoRecHA_StreamGetWritable;
849 pThis->IHostAudio.pfnStreamPlay = drvAudioVideoRecHA_StreamPlay;
850 pThis->IHostAudio.pfnStreamGetReadable = drvAudioVideoRecHA_StreamGetReadable;
851 pThis->IHostAudio.pfnStreamCapture = drvAudioVideoRecHA_StreamCapture;
852
853 /*
854 * Read configuration.
855 */
856 PCPDMDRVHLPR3 const pHlp = pDrvIns->pHlpR3;
857 /** @todo validate it. */
858
859 /*
860 * Get the Console object pointer.
861 */
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);
867
868 pThis->pConsole = pConsole;
869 AssertReturn(!pThis->pConsole.isNull(), VERR_INVALID_POINTER);
870 pThis->pAudioVideoRec = pConsole->i_recordingGetAudioDrv();
871 AssertPtrReturn(pThis->pAudioVideoRec, VERR_INVALID_POINTER);
872
873 pThis->pAudioVideoRec->mpDrv = pThis;
874
875 /*
876 * Get the recording container parameters from the audio driver instance.
877 */
878 RT_ZERO(pThis->ContainerParms);
879 PAVRECCONTAINERPARMS pConParams = &pThis->ContainerParms;
880
881 int vrc = pHlp->pfnCFGMQueryU32(pCfg, "StreamIndex", (uint32_t *)&pConParams->idxStream);
882 AssertRCReturn(vrc, vrc);
883
884 vrc = pHlp->pfnCFGMQueryU32(pCfg, "ContainerType", (uint32_t *)&pConParams->enmType);
885 AssertRCReturn(vrc, vrc);
886
887 switch (pConParams->enmType)
888 {
889 case AVRECCONTAINERTYPE_WEBM:
890 vrc = pHlp->pfnCFGMQueryStringAlloc(pCfg, "ContainerFileName", &pConParams->WebM.pszFile);
891 AssertRCReturn(vrc, vrc);
892 break;
893
894 default:
895 break;
896 }
897
898 /*
899 * Obtain the recording context.
900 */
901 pThis->pRecCtx = pConsole->i_recordingGetContext();
902 AssertPtrReturn(pThis->pRecCtx, VERR_INVALID_POINTER);
903
904 /*
905 * Get the codec configuration.
906 */
907 RecordingStream *pStream = pThis->pRecCtx->GetStream(pConParams->idxStream);
908 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
909
910 /*
911 * Init the recording sink.
912 */
913 vrc = avRecSinkInit(pThis, &pThis->Sink, &pThis->ContainerParms, pStream);
914 if (RT_SUCCESS(vrc))
915 LogRel2(("Recording: Audio recording driver initialized\n"));
916 else
917 LogRel(("Recording: Audio recording driver initialization failed: %Rrc\n", vrc));
918
919 return vrc;
920}
921
922
923/**
924 * Video recording audio driver registration record.
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 */
944 sizeof(DRVAUDIORECORDING),
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 */
962 NULL,
963 /* pfnDetach */
964 NULL,
965 /* pfnPowerOff */
966 AudioVideoRec::drvPowerOff,
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