VirtualBox

source: vbox/trunk/src/VBox/Main/src-client/DrvAudioVideoRec.cpp@ 74849

Last change on this file since 74849 was 74849, checked in by vboxsync, 7 years ago

VideoRec/Main: Let Opus decide which default bitrate to use.

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