VirtualBox

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

Last change on this file since 88484 was 88459, checked in by vboxsync, 4 years ago

DrvAudioRec: Cleanups. bugref:9890

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

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