VirtualBox

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

Last change on this file since 89083 was 88991, checked in by vboxsync, 4 years ago

Audio: Worked over draining, starting with the internal DMA buffer (instead of just the pre-buffer and backend buffer) and using the async I/O thread to keep calling PDMIAUDIOCONNECTOR::pfnStreamIterate and PDMIHOSTAUDIO::pfnStreamPlay (NULL buffer) every so often till the draining is done. Also put a rough deadline on the draining. The PDMAUDIOSTREAMCMD_DISABLE is now defined to stop playback/capturing immediately, even when already draining (if possible). This gets rid of the timers in DrvAudio and windows backends. DrvAudio no longer issue an DISABLE command at the end of the drain, it assumes the backend does that internally itself. After issuing PDMAUDIOSTREAMCMD_DRAIN the client (be it mixer or drvaudio) will not provide any more data for the buffers via pfnStreamPlay. Only tested windows, needs to re-test all platforms. bugref:9890

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 43.0 KB
Line 
1/* $Id: DrvAudioRec.cpp 88991 2021-05-12 00:46:35Z 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 /** Common part. */
249 PDMAUDIOBACKENDSTREAM Core;
250 /** The stream's acquired configuration. */
251 PDMAUDIOSTREAMCFG Cfg;
252 /** (Audio) frame buffer. */
253 PRTCIRCBUF pCircBuf;
254 /** Pointer to sink to use for writing. */
255 PAVRECSINK pSink;
256 /** Last encoded PTS (in ms). */
257 uint64_t uLastPTSMs;
258 /** Temporary buffer for the input (source) data to encode. */
259 void *pvSrcBuf;
260 /** Size (in bytes) of the temporary buffer holding the input (source) data to encode. */
261 size_t cbSrcBuf;
262 /** Temporary buffer for the encoded output (destination) data. */
263 void *pvDstBuf;
264 /** Size (in bytes) of the temporary buffer holding the encoded output (destination) data. */
265 size_t cbDstBuf;
266} AVRECSTREAM, *PAVRECSTREAM;
267
268/**
269 * Video recording audio driver instance data.
270 */
271typedef struct DRVAUDIORECORDING
272{
273 /** Pointer to audio video recording object. */
274 AudioVideoRec *pAudioVideoRec;
275 /** Pointer to the driver instance structure. */
276 PPDMDRVINS pDrvIns;
277 /** Pointer to host audio interface. */
278 PDMIHOSTAUDIO IHostAudio;
279 /** Pointer to the console object. */
280 ComPtr<Console> pConsole;
281 /** Pointer to the DrvAudio port interface that is above us. */
282 PPDMIAUDIOCONNECTOR pDrvAudio;
283 /** The driver's configured container parameters. */
284 AVRECCONTAINERPARMS ContainerParms;
285 /** The driver's configured codec parameters. */
286 AVRECCODECPARMS CodecParms;
287 /** The driver's sink for writing output to. */
288 AVRECSINK Sink;
289} DRVAUDIORECORDING, *PDRVAUDIORECORDING;
290
291
292AudioVideoRec::AudioVideoRec(Console *pConsole)
293 : AudioDriver(pConsole)
294 , mpDrv(NULL)
295{
296}
297
298
299AudioVideoRec::~AudioVideoRec(void)
300{
301 if (mpDrv)
302 {
303 mpDrv->pAudioVideoRec = NULL;
304 mpDrv = NULL;
305 }
306}
307
308
309/**
310 * Applies a video recording configuration to this driver instance.
311 *
312 * @returns VBox status code.
313 * @param Settings Capturing configuration to apply.
314 */
315int AudioVideoRec::applyConfiguration(const settings::RecordingSettings &Settings)
316{
317 /** @todo Do some validation here. */
318 mVideoRecCfg = Settings; /* Note: Does have an own copy operator. */
319 return VINF_SUCCESS;
320}
321
322
323/**
324 * @copydoc AudioDriver::configureDriver
325 */
326int AudioVideoRec::configureDriver(PCFGMNODE pLunCfg)
327{
328 int rc = CFGMR3InsertInteger(pLunCfg, "Object", (uintptr_t)mpConsole->i_recordingGetAudioDrv());
329 AssertRCReturn(rc, rc);
330 rc = CFGMR3InsertInteger(pLunCfg, "ObjectConsole", (uintptr_t)mpConsole);
331 AssertRCReturn(rc, rc);
332
333 /** @todo For now we're using the configuration of the first screen here audio-wise. */
334 Assert(mVideoRecCfg.mapScreens.size() >= 1);
335 const settings::RecordingScreenSettings &Screen0Settings = mVideoRecCfg.mapScreens[0];
336
337 rc = CFGMR3InsertInteger(pLunCfg, "ContainerType", (uint64_t)Screen0Settings.enmDest);
338 AssertRCReturn(rc, rc);
339 if (Screen0Settings.enmDest == RecordingDestination_File)
340 {
341 rc = CFGMR3InsertString(pLunCfg, "ContainerFileName", Utf8Str(Screen0Settings.File.strName).c_str());
342 AssertRCReturn(rc, rc);
343 }
344 rc = CFGMR3InsertInteger(pLunCfg, "CodecHz", Screen0Settings.Audio.uHz);
345 AssertRCReturn(rc, rc);
346 rc = CFGMR3InsertInteger(pLunCfg, "CodecBits", Screen0Settings.Audio.cBits);
347 AssertRCReturn(rc, rc);
348 rc = CFGMR3InsertInteger(pLunCfg, "CodecChannels", Screen0Settings.Audio.cChannels);
349 AssertRCReturn(rc, rc);
350 rc = CFGMR3InsertInteger(pLunCfg, "CodecBitrate", 0); /* Let Opus decide for now. */
351 AssertRCReturn(rc, rc);
352
353 return AudioDriver::configureDriver(pLunCfg);
354}
355
356
357/*********************************************************************************************************************************
358* PDMIHOSTAUDIO *
359*********************************************************************************************************************************/
360
361/**
362 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
363 */
364static DECLCALLBACK(int) drvAudioVideoRecHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
365{
366 RT_NOREF(pInterface);
367 AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
368
369 /*
370 * Fill in the config structure.
371 */
372 RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "VideoRec");
373 pBackendCfg->cbStream = sizeof(AVRECSTREAM);
374 pBackendCfg->fFlags = 0;
375 pBackendCfg->cMaxStreamsIn = 0;
376 pBackendCfg->cMaxStreamsOut = UINT32_MAX;
377
378 return VINF_SUCCESS;
379}
380
381
382/**
383 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
384 */
385static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvAudioVideoRecHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
386{
387 RT_NOREF(pInterface, enmDir);
388 return PDMAUDIOBACKENDSTS_RUNNING;
389}
390
391
392/**
393 * Creates an audio output stream and associates it with the specified recording sink.
394 *
395 * @returns VBox status code.
396 * @param pThis Driver instance.
397 * @param pStreamAV Audio output stream to create.
398 * @param pSink Recording sink to associate audio output stream to.
399 * @param pCfgReq Requested configuration by the audio backend.
400 * @param pCfgAcq Acquired configuration by the audio output stream.
401 */
402static int avRecCreateStreamOut(PDRVAUDIORECORDING pThis, PAVRECSTREAM pStreamAV,
403 PAVRECSINK pSink, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
404{
405 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
406 AssertPtrReturn(pStreamAV, VERR_INVALID_POINTER);
407 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
408 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
409 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
410
411 if (pCfgReq->u.enmDst != PDMAUDIOPLAYBACKDST_FRONT)
412 {
413 LogRel2(("Recording: Support for surround audio not implemented yet\n"));
414 AssertFailed();
415 return VERR_NOT_SUPPORTED;
416 }
417
418#ifdef VBOX_WITH_LIBOPUS
419 int rc = RTCircBufCreate(&pStreamAV->pCircBuf, pSink->Codec.Opus.cbFrame * 2 /* Use "double buffering" */);
420 if (RT_SUCCESS(rc))
421 {
422 size_t cbScratchBuf = pSink->Codec.Opus.cbFrame;
423 pStreamAV->pvSrcBuf = RTMemAlloc(cbScratchBuf);
424 if (pStreamAV->pvSrcBuf)
425 {
426 pStreamAV->cbSrcBuf = cbScratchBuf;
427 pStreamAV->pvDstBuf = RTMemAlloc(cbScratchBuf);
428 if (pStreamAV->pvDstBuf)
429 {
430 pStreamAV->cbDstBuf = cbScratchBuf;
431
432 pStreamAV->pSink = pSink; /* Assign sink to stream. */
433 pStreamAV->uLastPTSMs = 0;
434
435 /* Make sure to let the driver backend know that we need the audio data in
436 * a specific sampling rate Opus is optimized for. */
437/** @todo r=bird: pCfgAcq->Props isn't initialized at all, except for uHz... */
438 pCfgAcq->Props.uHz = pSink->Codec.Parms.PCMProps.uHz;
439// pCfgAcq->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfgAcq->Props.cbSample, pCfgAcq->Props.cChannels);
440
441 /* Every Opus frame marks a period for now. Optimize this later. */
442 pCfgAcq->Backend.cFramesPeriod = PDMAudioPropsMilliToFrames(&pCfgAcq->Props, pSink->Codec.Opus.msFrame);
443 pCfgAcq->Backend.cFramesBufferSize = PDMAudioPropsMilliToFrames(&pCfgAcq->Props, 100 /*ms*/); /** @todo Make this configurable. */
444 pCfgAcq->Backend.cFramesPreBuffering = pCfgAcq->Backend.cFramesPeriod * 2;
445 }
446 else
447 rc = VERR_NO_MEMORY;
448 }
449 else
450 rc = VERR_NO_MEMORY;
451 }
452#else
453 RT_NOREF(pThis, pSink, pStreamAV, pCfgReq, pCfgAcq);
454 int rc = VERR_NOT_SUPPORTED;
455#endif /* VBOX_WITH_LIBOPUS */
456
457 LogFlowFuncLeaveRC(rc);
458 return rc;
459}
460
461
462/**
463 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
464 */
465static DECLCALLBACK(int) drvAudioVideoRecHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
466 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
467{
468 PDRVAUDIORECORDING pThis = RT_FROM_CPP_MEMBER(pInterface, DRVAUDIORECORDING, IHostAudio);
469 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
470 AssertPtrReturn(pStreamAV, VERR_INVALID_POINTER);
471 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
472 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
473
474 if (pCfgReq->enmDir == PDMAUDIODIR_IN)
475 return VERR_NOT_SUPPORTED;
476
477 /* For now we only have one sink, namely the driver's one.
478 * Later each stream could have its own one, to e.g. router different stream to different sinks .*/
479 PAVRECSINK pSink = &pThis->Sink;
480
481 int rc = avRecCreateStreamOut(pThis, pStreamAV, pSink, pCfgReq, pCfgAcq);
482 PDMAudioStrmCfgCopy(&pStreamAV->Cfg, pCfgAcq);
483
484 return rc;
485}
486
487
488/**
489 * Destroys (closes) an audio output stream.
490 *
491 * @returns VBox status code.
492 * @param pThis Driver instance.
493 * @param pStreamAV Audio output stream to destroy.
494 */
495static int avRecDestroyStreamOut(PDRVAUDIORECORDING pThis, PAVRECSTREAM pStreamAV)
496{
497 RT_NOREF(pThis);
498
499 if (pStreamAV->pCircBuf)
500 {
501 RTCircBufDestroy(pStreamAV->pCircBuf);
502 pStreamAV->pCircBuf = NULL;
503 }
504
505 if (pStreamAV->pvSrcBuf)
506 {
507 Assert(pStreamAV->cbSrcBuf);
508 RTMemFree(pStreamAV->pvSrcBuf);
509 pStreamAV->pvSrcBuf = NULL;
510 pStreamAV->cbSrcBuf = 0;
511 }
512
513 if (pStreamAV->pvDstBuf)
514 {
515 Assert(pStreamAV->cbDstBuf);
516 RTMemFree(pStreamAV->pvDstBuf);
517 pStreamAV->pvDstBuf = NULL;
518 pStreamAV->cbDstBuf = 0;
519 }
520
521 return VINF_SUCCESS;
522}
523
524
525/**
526 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
527 */
528static DECLCALLBACK(int) drvAudioVideoRecHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
529{
530 PDRVAUDIORECORDING pThis = RT_FROM_CPP_MEMBER(pInterface, DRVAUDIORECORDING, IHostAudio);
531 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
532 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
533
534 int rc = VINF_SUCCESS;
535 if (pStreamAV->Cfg.enmDir == PDMAUDIODIR_OUT)
536 rc = avRecDestroyStreamOut(pThis, pStreamAV);
537
538 return rc;
539}
540
541
542/**
543 * Controls an audio output stream
544 *
545 * @returns VBox status code.
546 * @param pThis Driver instance.
547 * @param pStreamAV Audio output stream to control.
548 * @param enmStreamCmd Stream command to issue.
549 */
550static int avRecControlStreamOut(PDRVAUDIORECORDING pThis, PAVRECSTREAM pStreamAV, PDMAUDIOSTREAMCMD enmStreamCmd)
551{
552 RT_NOREF(pThis, pStreamAV);
553
554 int rc;
555 switch (enmStreamCmd)
556 {
557 case PDMAUDIOSTREAMCMD_ENABLE:
558 case PDMAUDIOSTREAMCMD_DISABLE:
559 case PDMAUDIOSTREAMCMD_RESUME:
560 case PDMAUDIOSTREAMCMD_PAUSE:
561 case PDMAUDIOSTREAMCMD_DRAIN:
562 rc = VINF_SUCCESS;
563 break;
564
565 default:
566 rc = VERR_NOT_SUPPORTED;
567 break;
568 }
569
570 return rc;
571}
572
573
574/**
575 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}
576 */
577static DECLCALLBACK(int) drvAudioVideoRecHA_StreamControl(PPDMIHOSTAUDIO pInterface,
578 PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
579{
580 PDRVAUDIORECORDING pThis = RT_FROM_CPP_MEMBER(pInterface, DRVAUDIORECORDING, IHostAudio);
581 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
582 AssertPtrReturn(pStreamAV, VERR_INVALID_POINTER);
583
584 if (pStreamAV->Cfg.enmDir == PDMAUDIODIR_OUT)
585 return avRecControlStreamOut(pThis, pStreamAV, enmStreamCmd);
586
587 return VINF_SUCCESS;
588}
589
590
591/**
592 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
593 */
594static DECLCALLBACK(uint32_t) drvAudioVideoRecHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
595{
596 RT_NOREF(pInterface, pStream);
597 return 0; /* Video capturing does not provide any input. */
598}
599
600
601/**
602 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
603 */
604static DECLCALLBACK(uint32_t) drvAudioVideoRecHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
605{
606 RT_NOREF(pInterface, pStream);
607 return UINT32_MAX;
608}
609
610
611/**
612 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState}
613 */
614static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvAudioVideoRecHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
615 PPDMAUDIOBACKENDSTREAM pStream)
616{
617 RT_NOREF(pInterface);
618 AssertPtrReturn(pStream, PDMHOSTAUDIOSTREAMSTATE_INVALID);
619 return PDMHOSTAUDIOSTREAMSTATE_OKAY;
620}
621
622
623/**
624 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
625 */
626static DECLCALLBACK(int) drvAudioVideoRecHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
627 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
628{
629 RT_NOREF(pInterface);
630 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
631 AssertPtrReturn(pStreamAV, VERR_INVALID_POINTER);
632 if (cbBuf)
633 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
634 AssertReturn(pcbWritten, VERR_INVALID_PARAMETER);
635
636 int rc = VINF_SUCCESS;
637
638 uint32_t cbWrittenTotal = 0;
639
640 /*
641 * Call the encoder with the data.
642 */
643#ifdef VBOX_WITH_LIBOPUS
644 PAVRECSINK pSink = pStreamAV->pSink;
645 AssertPtr(pSink);
646 PAVRECCODEC pCodec = &pSink->Codec;
647 AssertPtr(pCodec);
648 PRTCIRCBUF pCircBuf = pStreamAV->pCircBuf;
649 AssertPtr(pCircBuf);
650
651 uint32_t cbToWrite = cbBuf;
652
653 /*
654 * Write as much as we can into our internal ring buffer.
655 */
656 while ( cbToWrite > 0
657 && RTCircBufFree(pCircBuf))
658 {
659 void *pvCircBuf = NULL;
660 size_t cbCircBuf = 0;
661 RTCircBufAcquireWriteBlock(pCircBuf, cbToWrite, &pvCircBuf, &cbCircBuf);
662
663 if (cbCircBuf)
664 {
665 memcpy(pvCircBuf, (uint8_t *)pvBuf + cbWrittenTotal, cbCircBuf),
666 cbWrittenTotal += (uint32_t)cbCircBuf;
667 Assert(cbToWrite >= cbCircBuf);
668 cbToWrite -= (uint32_t)cbCircBuf;
669 }
670
671 RTCircBufReleaseWriteBlock(pCircBuf, cbCircBuf);
672 AssertBreak(cbCircBuf);
673 }
674
675 /*
676 * Process our internal ring buffer and encode the data.
677 */
678
679 /* Only encode data if we have data for the given time period (or more). */
680 while (RTCircBufUsed(pCircBuf) >= pCodec->Opus.cbFrame)
681 {
682 LogFunc(("cbAvail=%zu, csFrame=%RU32, cbFrame=%RU32\n",
683 RTCircBufUsed(pCircBuf), pCodec->Opus.csFrame, pCodec->Opus.cbFrame));
684
685 uint32_t cbSrc = 0;
686 while (cbSrc < pCodec->Opus.cbFrame)
687 {
688 void *pvCircBuf = NULL;
689 size_t cbCircBuf = 0;
690 RTCircBufAcquireReadBlock(pCircBuf, pCodec->Opus.cbFrame - cbSrc, &pvCircBuf, &cbCircBuf);
691
692 if (cbCircBuf)
693 {
694 memcpy((uint8_t *)pStreamAV->pvSrcBuf + cbSrc, pvCircBuf, cbCircBuf);
695
696 cbSrc += (uint32_t)cbCircBuf;
697 Assert(cbSrc <= pStreamAV->cbSrcBuf);
698 }
699
700 RTCircBufReleaseReadBlock(pCircBuf, cbCircBuf);
701 AssertBreak(cbCircBuf);
702 }
703
704 Assert(cbSrc == pCodec->Opus.cbFrame);
705
706# ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
707 RTFILE fh;
708 RTFileOpen(&fh, VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "DrvAudioVideoRec.pcm",
709 RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
710 RTFileWrite(fh, pStreamAV->pvSrcBuf, cbSrc, NULL);
711 RTFileClose(fh);
712# endif
713
714 /*
715 * Opus always encodes PER "OPUS FRAME", that is, exactly 2.5, 5, 10, 20, 40 or 60 ms of audio data.
716 *
717 * A packet can have up to 120ms worth of audio data.
718 * Anything > 120ms of data will result in a "corrupted package" error message by
719 * by decoding application.
720 */
721
722 /* Call the encoder to encode one "Opus frame" per iteration. */
723 opus_int32 cbWritten = opus_encode(pSink->Codec.Opus.pEnc,
724 (opus_int16 *)pStreamAV->pvSrcBuf, pCodec->Opus.csFrame,
725 (uint8_t *)pStreamAV->pvDstBuf, (opus_int32)pStreamAV->cbDstBuf);
726 if (cbWritten > 0)
727 {
728 /* Get overall frames encoded. */
729 const uint32_t cEncFrames = opus_packet_get_nb_frames((uint8_t *)pStreamAV->pvDstBuf, cbWritten);
730
731# ifdef VBOX_WITH_STATISTICS
732 pSink->Codec.Stats.cEncFrames += cEncFrames;
733 pSink->Codec.Stats.msEncTotal += pSink->Codec.Opus.msFrame * cEncFrames;
734# endif
735 Assert((uint32_t)cbWritten <= (uint32_t)pStreamAV->cbDstBuf);
736 const uint32_t cbDst = RT_MIN((uint32_t)cbWritten, (uint32_t)pStreamAV->cbDstBuf);
737
738 Assert(cEncFrames == 1);
739
740 if (pStreamAV->uLastPTSMs == 0)
741 pStreamAV->uLastPTSMs = RTTimeProgramMilliTS(); /* We want the absolute time (in ms) since program start. */
742
743 const uint64_t uDurationMs = pSink->Codec.Opus.msFrame * cEncFrames;
744 const uint64_t uPTSMs = pStreamAV->uLastPTSMs;
745
746 pStreamAV->uLastPTSMs += uDurationMs;
747
748 switch (pSink->Con.Parms.enmType)
749 {
750 case AVRECCONTAINERTYPE_MAIN_CONSOLE:
751 {
752 HRESULT hr = pSink->Con.Main.pConsole->i_recordingSendAudio(pStreamAV->pvDstBuf, cbDst, uPTSMs);
753 Assert(hr == S_OK);
754 RT_NOREF(hr);
755 break;
756 }
757
758 case AVRECCONTAINERTYPE_WEBM:
759 {
760 WebMWriter::BlockData_Opus blockData = { pStreamAV->pvDstBuf, cbDst, uPTSMs };
761 rc = pSink->Con.WebM.pWebM->WriteBlock(pSink->Con.WebM.uTrack, &blockData, sizeof(blockData));
762 AssertRC(rc);
763 break;
764 }
765
766 default:
767 AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
768 break;
769 }
770 }
771 else if (cbWritten < 0)
772 {
773 AssertMsgFailed(("Encoding failed: %s\n", opus_strerror(cbWritten)));
774 rc = VERR_INVALID_PARAMETER;
775 }
776
777 if (RT_FAILURE(rc))
778 break;
779 }
780
781 *pcbWritten = cbWrittenTotal;
782#else
783 /* Report back all data as being processed. */
784 *pcbWritten = cbBuf;
785
786 rc = VERR_NOT_SUPPORTED;
787#endif /* VBOX_WITH_LIBOPUS */
788
789 LogFlowFunc(("csReadTotal=%RU32, rc=%Rrc\n", cbWrittenTotal, rc));
790 return rc;
791}
792
793
794/**
795 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
796 */
797static DECLCALLBACK(int) drvAudioVideoRecHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
798 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
799{
800 RT_NOREF(pInterface, pStream, pvBuf, cbBuf);
801 *pcbRead = 0;
802 return VINF_SUCCESS;
803}
804
805
806/*********************************************************************************************************************************
807* PDMIBASE *
808*********************************************************************************************************************************/
809
810/**
811 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
812 */
813static DECLCALLBACK(void *) drvAudioVideoRecQueryInterface(PPDMIBASE pInterface, const char *pszIID)
814{
815 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
816 PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
817
818 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
819 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
820 return NULL;
821}
822
823
824/*********************************************************************************************************************************
825* PDMDRVREG *
826*********************************************************************************************************************************/
827
828/**
829 * Shuts down (closes) a recording sink,
830 *
831 * @returns VBox status code.
832 * @param pSink Recording sink to shut down.
833 */
834static void avRecSinkShutdown(PAVRECSINK pSink)
835{
836 AssertPtrReturnVoid(pSink);
837
838#ifdef VBOX_WITH_LIBOPUS
839 if (pSink->Codec.Opus.pEnc)
840 {
841 opus_encoder_destroy(pSink->Codec.Opus.pEnc);
842 pSink->Codec.Opus.pEnc = NULL;
843 }
844#endif
845 switch (pSink->Con.Parms.enmType)
846 {
847 case AVRECCONTAINERTYPE_WEBM:
848 {
849 if (pSink->Con.WebM.pWebM)
850 {
851 LogRel2(("Recording: Finished recording audio to file '%s' (%zu bytes)\n",
852 pSink->Con.WebM.pWebM->GetFileName().c_str(), pSink->Con.WebM.pWebM->GetFileSize()));
853
854 int rc2 = pSink->Con.WebM.pWebM->Close();
855 AssertRC(rc2);
856
857 delete pSink->Con.WebM.pWebM;
858 pSink->Con.WebM.pWebM = NULL;
859 }
860 break;
861 }
862
863 case AVRECCONTAINERTYPE_MAIN_CONSOLE:
864 default:
865 break;
866 }
867}
868
869
870/**
871 * @interface_method_impl{PDMDRVREG,pfnPowerOff}
872 */
873/*static*/ DECLCALLBACK(void) AudioVideoRec::drvPowerOff(PPDMDRVINS pDrvIns)
874{
875 PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
876 LogFlowFuncEnter();
877 avRecSinkShutdown(&pThis->Sink);
878}
879
880
881/**
882 * @interface_method_impl{PDMDRVREG,pfnDestruct}
883 */
884/*static*/ DECLCALLBACK(void) AudioVideoRec::drvDestruct(PPDMDRVINS pDrvIns)
885{
886 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
887 PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
888
889 LogFlowFuncEnter();
890
891 switch (pThis->ContainerParms.enmType)
892 {
893 case AVRECCONTAINERTYPE_WEBM:
894 {
895 avRecSinkShutdown(&pThis->Sink);
896 RTStrFree(pThis->ContainerParms.WebM.pszFile);
897 break;
898 }
899
900 default:
901 break;
902 }
903
904 /*
905 * If the AudioVideoRec object is still alive, we must clear it's reference to
906 * us since we'll be invalid when we return from this method.
907 */
908 if (pThis->pAudioVideoRec)
909 {
910 pThis->pAudioVideoRec->mpDrv = NULL;
911 pThis->pAudioVideoRec = NULL;
912 }
913
914 LogFlowFuncLeave();
915}
916
917
918/**
919 * Initializes a recording sink.
920 *
921 * @returns VBox status code.
922 * @param pThis Driver instance.
923 * @param pSink Sink to initialize.
924 * @param pConParms Container parameters to set.
925 * @param pCodecParms Codec parameters to set.
926 */
927static int avRecSinkInit(PDRVAUDIORECORDING pThis, PAVRECSINK pSink, PAVRECCONTAINERPARMS pConParms, PAVRECCODECPARMS pCodecParms)
928{
929 uint32_t uHz = PDMAudioPropsHz(&pCodecParms->PCMProps);
930 uint8_t const cbSample = PDMAudioPropsSampleSize(&pCodecParms->PCMProps);
931 uint8_t cChannels = PDMAudioPropsChannels(&pCodecParms->PCMProps);
932 uint32_t uBitrate = pCodecParms->uBitrate;
933
934 /* Opus only supports certain input sample rates in an efficient manner.
935 * So make sure that we use those by resampling the data to the requested rate. */
936 if (uHz > 24000) uHz = AVREC_OPUS_HZ_MAX;
937 else if (uHz > 16000) uHz = 24000;
938 else if (uHz > 12000) uHz = 16000;
939 else if (uHz > 8000 ) uHz = 12000;
940 else uHz = 8000;
941
942 if (cChannels > 2)
943 {
944 LogRel(("Recording: Warning: More than 2 (stereo) channels are not supported at the moment\n"));
945 cChannels = 2;
946 }
947
948 int orc;
949 OpusEncoder *pEnc = opus_encoder_create(uHz, cChannels, OPUS_APPLICATION_AUDIO, &orc);
950 if (orc != OPUS_OK)
951 {
952 LogRel(("Recording: Audio codec failed to initialize: %s\n", opus_strerror(orc)));
953 return VERR_AUDIO_BACKEND_INIT_FAILED;
954 }
955
956 AssertPtr(pEnc);
957
958 if (uBitrate) /* Only explicitly set the bitrate if we specified one. Otherwise let Opus decide. */
959 {
960 opus_encoder_ctl(pEnc, OPUS_SET_BITRATE(uBitrate));
961 if (orc != OPUS_OK)
962 {
963 opus_encoder_destroy(pEnc);
964 pEnc = NULL;
965
966 LogRel(("Recording: Audio codec failed to set bitrate (%RU32): %s\n", uBitrate, opus_strerror(orc)));
967 return VERR_AUDIO_BACKEND_INIT_FAILED;
968 }
969 }
970
971 const bool fUseVBR = true; /** Use Variable Bit Rate (VBR) by default. @todo Make this configurable? */
972
973 orc = opus_encoder_ctl(pEnc, OPUS_SET_VBR(fUseVBR ? 1 : 0));
974 if (orc != OPUS_OK)
975 {
976 opus_encoder_destroy(pEnc);
977 pEnc = NULL;
978
979 LogRel(("Recording: Audio codec failed to %s VBR mode: %s\n", fUseVBR ? "enable" : "disable", opus_strerror(orc)));
980 return VERR_AUDIO_BACKEND_INIT_FAILED;
981 }
982
983 int rc = VINF_SUCCESS;
984
985 try
986 {
987 switch (pConParms->enmType)
988 {
989 case AVRECCONTAINERTYPE_MAIN_CONSOLE:
990 {
991 if (pThis->pConsole)
992 {
993 pSink->Con.Main.pConsole = pThis->pConsole;
994 }
995 else
996 rc = VERR_NOT_SUPPORTED;
997 break;
998 }
999
1000 case AVRECCONTAINERTYPE_WEBM:
1001 {
1002 /* If we only record audio, create our own WebM writer instance here. */
1003 if (!pSink->Con.WebM.pWebM) /* Do we already have our WebM writer instance? */
1004 {
1005 /** @todo Add sink name / number to file name. */
1006 const char *pszFile = pSink->Con.Parms.WebM.pszFile;
1007 AssertPtr(pszFile);
1008
1009 pSink->Con.WebM.pWebM = new WebMWriter();
1010 rc = pSink->Con.WebM.pWebM->Open(pszFile,
1011 /** @todo Add option to add some suffix if file exists instead of overwriting? */
1012 RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE,
1013 WebMWriter::AudioCodec_Opus, WebMWriter::VideoCodec_None);
1014 if (RT_SUCCESS(rc))
1015 {
1016 rc = pSink->Con.WebM.pWebM->AddAudioTrack(uHz, cChannels, cbSample * 8 /* Bits */,
1017 &pSink->Con.WebM.uTrack);
1018 if (RT_SUCCESS(rc))
1019 {
1020 LogRel(("Recording: Recording audio to audio file '%s'\n", pszFile));
1021 }
1022 else
1023 LogRel(("Recording: Error creating audio track for audio file '%s' (%Rrc)\n", pszFile, rc));
1024 }
1025 else
1026 LogRel(("Recording: Error creating audio file '%s' (%Rrc)\n", pszFile, rc));
1027 }
1028 break;
1029 }
1030
1031 default:
1032 rc = VERR_NOT_SUPPORTED;
1033 break;
1034 }
1035 }
1036 catch (std::bad_alloc &)
1037 {
1038 rc = VERR_NO_MEMORY;
1039 }
1040
1041 if (RT_SUCCESS(rc))
1042 {
1043 pSink->Con.Parms.enmType = pConParms->enmType;
1044
1045 PAVRECCODEC pCodec = &pSink->Codec;
1046
1047 PDMAudioPropsInit(&pCodec->Parms.PCMProps, cbSample, pCodecParms->PCMProps.fSigned, cChannels, uHz);
1048 pCodec->Parms.uBitrate = uBitrate;
1049
1050 pCodec->Opus.pEnc = pEnc;
1051 pCodec->Opus.msFrame = AVREC_OPUS_FRAME_MS_DEFAULT;
1052
1053 if (!pCodec->Opus.msFrame)
1054 pCodec->Opus.msFrame = AVREC_OPUS_FRAME_MS_DEFAULT; /* 20ms by default; to prevent division by zero. */
1055 pCodec->Opus.csFrame = pSink->Codec.Parms.PCMProps.uHz / (1000 /* s in ms */ / pSink->Codec.Opus.msFrame);
1056 pCodec->Opus.cbFrame = PDMAudioPropsFramesToBytes(&pSink->Codec.Parms.PCMProps, pCodec->Opus.csFrame);
1057
1058#ifdef VBOX_WITH_STATISTICS
1059 pSink->Codec.Stats.cEncFrames = 0;
1060 pSink->Codec.Stats.msEncTotal = 0;
1061#endif
1062 pSink->tsStartMs = RTTimeMilliTS();
1063 }
1064 else
1065 {
1066 if (pEnc)
1067 {
1068 opus_encoder_destroy(pEnc);
1069 pEnc = NULL;
1070 }
1071
1072 LogRel(("Recording: Error creating sink (%Rrc)\n", rc));
1073 }
1074
1075 return rc;
1076}
1077
1078
1079/**
1080 * Construct a audio video recording driver instance.
1081 *
1082 * @copydoc FNPDMDRVCONSTRUCT
1083 */
1084/*static*/ DECLCALLBACK(int) AudioVideoRec::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
1085{
1086 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
1087 PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
1088 RT_NOREF(fFlags);
1089
1090 LogRel(("Audio: Initializing video recording audio driver\n"));
1091 LogFlowFunc(("fFlags=0x%x\n", fFlags));
1092
1093 AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
1094 ("Configuration error: Not possible to attach anything to this driver!\n"),
1095 VERR_PDM_DRVINS_NO_ATTACH);
1096
1097 /*
1098 * Init the static parts.
1099 */
1100 pThis->pDrvIns = pDrvIns;
1101 /* IBase */
1102 pDrvIns->IBase.pfnQueryInterface = drvAudioVideoRecQueryInterface;
1103 /* IHostAudio */
1104 pThis->IHostAudio.pfnGetConfig = drvAudioVideoRecHA_GetConfig;
1105 pThis->IHostAudio.pfnGetDevices = NULL;
1106 pThis->IHostAudio.pfnGetStatus = drvAudioVideoRecHA_GetStatus;
1107 pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
1108 pThis->IHostAudio.pfnStreamConfigHint = NULL;
1109 pThis->IHostAudio.pfnStreamCreate = drvAudioVideoRecHA_StreamCreate;
1110 pThis->IHostAudio.pfnStreamInitAsync = NULL;
1111 pThis->IHostAudio.pfnStreamDestroy = drvAudioVideoRecHA_StreamDestroy;
1112 pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
1113 pThis->IHostAudio.pfnStreamControl = drvAudioVideoRecHA_StreamControl;
1114 pThis->IHostAudio.pfnStreamGetReadable = drvAudioVideoRecHA_StreamGetReadable;
1115 pThis->IHostAudio.pfnStreamGetWritable = drvAudioVideoRecHA_StreamGetWritable;
1116 pThis->IHostAudio.pfnStreamGetPending = NULL;
1117 pThis->IHostAudio.pfnStreamGetState = drvAudioVideoRecHA_StreamGetState;
1118 pThis->IHostAudio.pfnStreamPlay = drvAudioVideoRecHA_StreamPlay;
1119 pThis->IHostAudio.pfnStreamCapture = drvAudioVideoRecHA_StreamCapture;
1120
1121 /*
1122 * Get the Console object pointer.
1123 */
1124 void *pvUser;
1125 int rc = CFGMR3QueryPtr(pCfg, "ObjectConsole", &pvUser); /** @todo r=andy Get rid of this hack and use IHostAudio::SetCallback. */
1126 AssertRCReturn(rc, rc);
1127
1128 /* CFGM tree saves the pointer to Console in the Object node of AudioVideoRec. */
1129 pThis->pConsole = (Console *)pvUser;
1130 AssertReturn(!pThis->pConsole.isNull(), VERR_INVALID_POINTER);
1131
1132 /*
1133 * Get the pointer to the audio driver instance.
1134 */
1135 rc = CFGMR3QueryPtr(pCfg, "Object", &pvUser); /** @todo r=andy Get rid of this hack and use IHostAudio::SetCallback. */
1136 AssertRCReturn(rc, rc);
1137
1138 pThis->pAudioVideoRec = (AudioVideoRec *)pvUser;
1139 AssertPtrReturn(pThis->pAudioVideoRec, VERR_INVALID_POINTER);
1140
1141 /*
1142 * Get the recording container and codec parameters from the audio driver instance.
1143 */
1144 PAVRECCONTAINERPARMS pConParams = &pThis->ContainerParms;
1145 PAVRECCODECPARMS pCodecParms = &pThis->CodecParms;
1146
1147 RT_ZERO(pThis->ContainerParms);
1148 RT_ZERO(pThis->CodecParms);
1149
1150 rc = CFGMR3QueryU32(pCfg, "ContainerType", (uint32_t *)&pConParams->enmType);
1151 AssertRCReturn(rc, rc);
1152
1153 switch (pConParams->enmType)
1154 {
1155 case AVRECCONTAINERTYPE_WEBM:
1156 rc = CFGMR3QueryStringAlloc(pCfg, "ContainerFileName", &pConParams->WebM.pszFile);
1157 AssertRCReturn(rc, rc);
1158 break;
1159
1160 default:
1161 break;
1162 }
1163
1164 uint32_t uHz = 0;
1165 rc = CFGMR3QueryU32(pCfg, "CodecHz", &uHz);
1166 AssertRCReturn(rc, rc);
1167
1168 uint8_t cSampleBits = 0;
1169 rc = CFGMR3QueryU8(pCfg, "CodecBits", &cSampleBits); /** @todo CodecBits != CodecBytes */
1170 AssertRCReturn(rc, rc);
1171
1172 uint8_t cChannels = 0;
1173 rc = CFGMR3QueryU8(pCfg, "CodecChannels", &cChannels);
1174 AssertRCReturn(rc, rc);
1175
1176 PDMAudioPropsInit(&pCodecParms->PCMProps, cSampleBits / 8, true /*fSigned*/, cChannels, uHz);
1177 AssertMsgReturn(PDMAudioPropsAreValid(&pCodecParms->PCMProps),
1178 ("Configuration error: Audio configuration is invalid!\n"), VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES); /** @todo wrong status code. */
1179
1180 rc = CFGMR3QueryU32(pCfg, "CodecBitrate", &pCodecParms->uBitrate);
1181 AssertRCReturn(rc, rc);
1182
1183 pThis->pAudioVideoRec = (AudioVideoRec *)pvUser;
1184 AssertPtrReturn(pThis->pAudioVideoRec, VERR_INVALID_POINTER);
1185
1186 pThis->pAudioVideoRec->mpDrv = pThis;
1187
1188 /*
1189 * Get the interface for the above driver (DrvAudio) to make mixer/conversion calls.
1190 * Described in CFGM tree.
1191 */
1192/** @todo r=bird: What on earth do you think you need this for?!? It's not an
1193 * interface lower drivers are supposed to be messing with! */
1194 pThis->pDrvAudio = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIAUDIOCONNECTOR);
1195 AssertMsgReturn(pThis->pDrvAudio, ("Configuration error: No upper interface specified!\n"), VERR_PDM_MISSING_INTERFACE_ABOVE);
1196
1197#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
1198 RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "DrvAudioVideoRec.webm");
1199 RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "DrvAudioVideoRec.pcm");
1200#endif
1201
1202 /*
1203 * Init the recording sink.
1204 */
1205 LogRel(("Recording: Audio driver is using %RU32Hz, %RU16bit, %RU8 channel%s\n",
1206 PDMAudioPropsHz(&pThis->CodecParms.PCMProps), PDMAudioPropsSampleBits(&pThis->CodecParms.PCMProps),
1207 PDMAudioPropsChannels(&pThis->CodecParms.PCMProps), PDMAudioPropsChannels(&pThis->CodecParms.PCMProps) == 1 ? "" : "s"));
1208
1209 rc = avRecSinkInit(pThis, &pThis->Sink, &pThis->ContainerParms, &pThis->CodecParms);
1210 if (RT_SUCCESS(rc))
1211 LogRel2(("Recording: Audio recording driver initialized\n"));
1212 else
1213 LogRel(("Recording: Audio recording driver initialization failed: %Rrc\n", rc));
1214
1215 return rc;
1216}
1217
1218
1219/**
1220 * Video recording audio driver registration record.
1221 */
1222const PDMDRVREG AudioVideoRec::DrvReg =
1223{
1224 PDM_DRVREG_VERSION,
1225 /* szName */
1226 "AudioVideoRec",
1227 /* szRCMod */
1228 "",
1229 /* szR0Mod */
1230 "",
1231 /* pszDescription */
1232 "Audio driver for video recording",
1233 /* fFlags */
1234 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
1235 /* fClass. */
1236 PDM_DRVREG_CLASS_AUDIO,
1237 /* cMaxInstances */
1238 ~0U,
1239 /* cbInstance */
1240 sizeof(DRVAUDIORECORDING),
1241 /* pfnConstruct */
1242 AudioVideoRec::drvConstruct,
1243 /* pfnDestruct */
1244 AudioVideoRec::drvDestruct,
1245 /* pfnRelocate */
1246 NULL,
1247 /* pfnIOCtl */
1248 NULL,
1249 /* pfnPowerOn */
1250 NULL,
1251 /* pfnReset */
1252 NULL,
1253 /* pfnSuspend */
1254 NULL,
1255 /* pfnResume */
1256 NULL,
1257 /* pfnAttach */
1258 NULL,
1259 /* pfnDetach */
1260 NULL,
1261 /* pfnPowerOff */
1262 AudioVideoRec::drvPowerOff,
1263 /* pfnSoftReset */
1264 NULL,
1265 /* u32EndVersion */
1266 PDM_DRVREG_VERSION
1267};
1268
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