VirtualBox

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

Last change on this file since 98273 was 98103, checked in by vboxsync, 23 months ago

Copyright year updates by scm.

  • 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 98103 2023-01-17 14:15:46Z 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
507 uint32_t cbToWrite = RT_MIN(cbBuf, (uint32_t)RTCircBufFree(pCircBuf));
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 * @returns VBox status code.
633 * @param pSink Recording sink to shut down.
634 */
635static void avRecSinkShutdown(PAVRECSINK pSink)
636{
637 AssertPtrReturnVoid(pSink);
638
639 pSink->pRecStream = NULL;
640
641 switch (pSink->Con.Parms.enmType)
642 {
643 case AVRECCONTAINERTYPE_WEBM:
644 {
645 if (pSink->Con.WebM.pWebM)
646 {
647 LogRel2(("Recording: Finished recording audio to file '%s' (%zu bytes)\n",
648 pSink->Con.WebM.pWebM->GetFileName().c_str(), pSink->Con.WebM.pWebM->GetFileSize()));
649
650 int vrc2 = pSink->Con.WebM.pWebM->Close();
651 AssertRC(vrc2);
652
653 delete pSink->Con.WebM.pWebM;
654 pSink->Con.WebM.pWebM = NULL;
655 }
656 break;
657 }
658
659 case AVRECCONTAINERTYPE_MAIN_CONSOLE:
660 RT_FALL_THROUGH();
661 default:
662 break;
663 }
664}
665
666
667/**
668 * @interface_method_impl{PDMDRVREG,pfnPowerOff}
669 */
670/*static*/ DECLCALLBACK(void) AudioVideoRec::drvPowerOff(PPDMDRVINS pDrvIns)
671{
672 PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
673 LogFlowFuncEnter();
674 avRecSinkShutdown(&pThis->Sink);
675}
676
677
678/**
679 * @interface_method_impl{PDMDRVREG,pfnDestruct}
680 */
681/*static*/ DECLCALLBACK(void) AudioVideoRec::drvDestruct(PPDMDRVINS pDrvIns)
682{
683 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
684 PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
685
686 LogFlowFuncEnter();
687
688 switch (pThis->ContainerParms.enmType)
689 {
690 case AVRECCONTAINERTYPE_WEBM:
691 {
692 avRecSinkShutdown(&pThis->Sink);
693 RTStrFree(pThis->ContainerParms.WebM.pszFile);
694 break;
695 }
696
697 default:
698 break;
699 }
700
701 /*
702 * If the AudioVideoRec object is still alive, we must clear it's reference to
703 * us since we'll be invalid when we return from this method.
704 */
705 if (pThis->pAudioVideoRec)
706 {
707 pThis->pAudioVideoRec->mpDrv = NULL;
708 pThis->pAudioVideoRec = NULL;
709 }
710
711 LogFlowFuncLeave();
712}
713
714
715/**
716 * Initializes a recording sink.
717 *
718 * @returns VBox status code.
719 * @param pThis Driver instance.
720 * @param pSink Sink to initialize.
721 * @param pConParms Container parameters to set.
722 * @param pStream Recording stream to asssign sink to.
723 */
724static int avRecSinkInit(PDRVAUDIORECORDING pThis, PAVRECSINK pSink, PAVRECCONTAINERPARMS pConParms, RecordingStream *pStream)
725{
726 pSink->pRecStream = pStream;
727
728 int vrc = VINF_SUCCESS;
729
730 /*
731 * Container setup.
732 */
733 try
734 {
735 switch (pConParms->enmType)
736 {
737 case AVRECCONTAINERTYPE_MAIN_CONSOLE:
738 {
739 if (pThis->pConsole)
740 {
741 pSink->Con.Main.pConsole = pThis->pConsole;
742 }
743 else
744 vrc = VERR_NOT_SUPPORTED;
745 break;
746 }
747
748 case AVRECCONTAINERTYPE_WEBM:
749 {
750 #if 0
751 /* If we only record audio, create our own WebM writer instance here. */
752 if (!pSink->Con.WebM.pWebM) /* Do we already have our WebM writer instance? */
753 {
754 /** @todo Add sink name / number to file name. */
755 const char *pszFile = pSink->Con.Parms.WebM.pszFile;
756 AssertPtr(pszFile);
757
758 pSink->Con.WebM.pWebM = new WebMWriter();
759 vrc = pSink->Con.WebM.pWebM->Open(pszFile,
760 /** @todo Add option to add some suffix if file exists instead of overwriting? */
761 RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE,
762 pSink->pCodec->Parms.enmAudioCodec, RecordingVideoCodec_None);
763 if (RT_SUCCESS(vrc))
764 {
765 const PPDMAUDIOPCMPROPS pPCMProps = &pCodec->Parms.Audio.PCMProps;
766
767 vrc = pSink->Con.WebM.pWebM->AddAudioTrack(pSink->pCodec,
768 PDMAudioPropsHz(pPCMProps), PDMAudioPropsChannels(pPCMProps),
769 PDMAudioPropsSampleBits(pPCMProps), &pSink->Con.WebM.uTrack);
770 if (RT_SUCCESS(vrc))
771 {
772 LogRel(("Recording: Recording audio to audio file '%s'\n", pszFile));
773 }
774 else
775 LogRel(("Recording: Error creating audio track for audio file '%s' (%Rrc)\n", pszFile, vrc));
776 }
777 else
778 LogRel(("Recording: Error creating audio file '%s' (%Rrc)\n", pszFile, vrc));
779 }
780 break;
781 #endif
782 }
783
784 default:
785 vrc = VERR_NOT_SUPPORTED;
786 break;
787 }
788 }
789 catch (std::bad_alloc &)
790 {
791 vrc = VERR_NO_MEMORY;
792 }
793
794 if (RT_SUCCESS(vrc))
795 {
796 pSink->Con.Parms.enmType = pConParms->enmType;
797 pSink->tsStartMs = RTTimeMilliTS();
798
799 return VINF_SUCCESS;
800 }
801
802 LogRel(("Recording: Error creating sink (%Rrc)\n", vrc));
803 return vrc;
804}
805
806
807/**
808 * Construct a audio video recording driver instance.
809 *
810 * @copydoc FNPDMDRVCONSTRUCT
811 */
812/*static*/ DECLCALLBACK(int) AudioVideoRec::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
813{
814 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
815 PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
816 RT_NOREF(fFlags);
817
818 LogRel(("Audio: Initializing video recording audio driver\n"));
819 LogFlowFunc(("fFlags=0x%x\n", fFlags));
820
821 AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
822 ("Configuration error: Not possible to attach anything to this driver!\n"),
823 VERR_PDM_DRVINS_NO_ATTACH);
824
825 /*
826 * Init the static parts.
827 */
828 pThis->pDrvIns = pDrvIns;
829 /* IBase */
830 pDrvIns->IBase.pfnQueryInterface = drvAudioVideoRecQueryInterface;
831 /* IHostAudio */
832 pThis->IHostAudio.pfnGetConfig = drvAudioVideoRecHA_GetConfig;
833 pThis->IHostAudio.pfnGetDevices = NULL;
834 pThis->IHostAudio.pfnSetDevice = NULL;
835 pThis->IHostAudio.pfnGetStatus = drvAudioVideoRecHA_GetStatus;
836 pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
837 pThis->IHostAudio.pfnStreamConfigHint = NULL;
838 pThis->IHostAudio.pfnStreamCreate = drvAudioVideoRecHA_StreamCreate;
839 pThis->IHostAudio.pfnStreamInitAsync = NULL;
840 pThis->IHostAudio.pfnStreamDestroy = drvAudioVideoRecHA_StreamDestroy;
841 pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
842 pThis->IHostAudio.pfnStreamEnable = drvAudioVideoRecHA_StreamEnable;
843 pThis->IHostAudio.pfnStreamDisable = drvAudioVideoRecHA_StreamDisable;
844 pThis->IHostAudio.pfnStreamPause = drvAudioVideoRecHA_StreamPause;
845 pThis->IHostAudio.pfnStreamResume = drvAudioVideoRecHA_StreamResume;
846 pThis->IHostAudio.pfnStreamDrain = drvAudioVideoRecHA_StreamDrain;
847 pThis->IHostAudio.pfnStreamGetState = drvAudioVideoRecHA_StreamGetState;
848 pThis->IHostAudio.pfnStreamGetPending = NULL;
849 pThis->IHostAudio.pfnStreamGetWritable = drvAudioVideoRecHA_StreamGetWritable;
850 pThis->IHostAudio.pfnStreamPlay = drvAudioVideoRecHA_StreamPlay;
851 pThis->IHostAudio.pfnStreamGetReadable = drvAudioVideoRecHA_StreamGetReadable;
852 pThis->IHostAudio.pfnStreamCapture = drvAudioVideoRecHA_StreamCapture;
853
854 /*
855 * Read configuration.
856 */
857 PCPDMDRVHLPR3 const pHlp = pDrvIns->pHlpR3;
858 /** @todo validate it. */
859
860 /*
861 * Get the Console object pointer.
862 */
863 com::Guid ConsoleUuid(COM_IIDOF(IConsole));
864 IConsole *pIConsole = (IConsole *)PDMDrvHlpQueryGenericUserObject(pDrvIns, ConsoleUuid.raw());
865 AssertLogRelReturn(pIConsole, VERR_INTERNAL_ERROR_3);
866 Console *pConsole = static_cast<Console *>(pIConsole);
867 AssertLogRelReturn(pConsole, VERR_INTERNAL_ERROR_3);
868
869 pThis->pConsole = pConsole;
870 AssertReturn(!pThis->pConsole.isNull(), VERR_INVALID_POINTER);
871 pThis->pAudioVideoRec = pConsole->i_recordingGetAudioDrv();
872 AssertPtrReturn(pThis->pAudioVideoRec, VERR_INVALID_POINTER);
873
874 pThis->pAudioVideoRec->mpDrv = pThis;
875
876 /*
877 * Get the recording container parameters from the audio driver instance.
878 */
879 RT_ZERO(pThis->ContainerParms);
880 PAVRECCONTAINERPARMS pConParams = &pThis->ContainerParms;
881
882 int vrc = pHlp->pfnCFGMQueryU32(pCfg, "StreamIndex", (uint32_t *)&pConParams->idxStream);
883 AssertRCReturn(vrc, vrc);
884
885 vrc = pHlp->pfnCFGMQueryU32(pCfg, "ContainerType", (uint32_t *)&pConParams->enmType);
886 AssertRCReturn(vrc, vrc);
887
888 switch (pConParams->enmType)
889 {
890 case AVRECCONTAINERTYPE_WEBM:
891 vrc = pHlp->pfnCFGMQueryStringAlloc(pCfg, "ContainerFileName", &pConParams->WebM.pszFile);
892 AssertRCReturn(vrc, vrc);
893 break;
894
895 default:
896 break;
897 }
898
899 /*
900 * Obtain the recording context.
901 */
902 pThis->pRecCtx = pConsole->i_recordingGetContext();
903 AssertPtrReturn(pThis->pRecCtx, VERR_INVALID_POINTER);
904
905 /*
906 * Get the codec configuration.
907 */
908 RecordingStream *pStream = pThis->pRecCtx->GetStream(pConParams->idxStream);
909 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
910
911 /*
912 * Init the recording sink.
913 */
914 vrc = avRecSinkInit(pThis, &pThis->Sink, &pThis->ContainerParms, pStream);
915 if (RT_SUCCESS(vrc))
916 LogRel2(("Recording: Audio recording driver initialized\n"));
917 else
918 LogRel(("Recording: Audio recording driver initialization failed: %Rrc\n", vrc));
919
920 return vrc;
921}
922
923
924/**
925 * Video recording audio driver registration record.
926 */
927const PDMDRVREG AudioVideoRec::DrvReg =
928{
929 PDM_DRVREG_VERSION,
930 /* szName */
931 "AudioVideoRec",
932 /* szRCMod */
933 "",
934 /* szR0Mod */
935 "",
936 /* pszDescription */
937 "Audio driver for video recording",
938 /* fFlags */
939 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
940 /* fClass. */
941 PDM_DRVREG_CLASS_AUDIO,
942 /* cMaxInstances */
943 ~0U,
944 /* cbInstance */
945 sizeof(DRVAUDIORECORDING),
946 /* pfnConstruct */
947 AudioVideoRec::drvConstruct,
948 /* pfnDestruct */
949 AudioVideoRec::drvDestruct,
950 /* pfnRelocate */
951 NULL,
952 /* pfnIOCtl */
953 NULL,
954 /* pfnPowerOn */
955 NULL,
956 /* pfnReset */
957 NULL,
958 /* pfnSuspend */
959 NULL,
960 /* pfnResume */
961 NULL,
962 /* pfnAttach */
963 NULL,
964 /* pfnDetach */
965 NULL,
966 /* pfnPowerOff */
967 AudioVideoRec::drvPowerOff,
968 /* pfnSoftReset */
969 NULL,
970 /* u32EndVersion */
971 PDM_DRVREG_VERSION
972};
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette