VirtualBox

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

Last change on this file since 96187 was 96187, checked in by vboxsync, 3 years ago

Main/Recording: doxgyen fix. bugref:10275

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 36.0 KB
Line 
1/* $Id: DrvAudioRec.cpp 96187 2022-08-13 01:12:42Z 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-2022 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 Vorbis (libvorbis) and 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_RECORDING
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/com/VirtualBox.h"
95#include <VBox/vmm/cfgm.h>
96#include <VBox/vmm/pdmdrv.h>
97#include <VBox/vmm/pdmaudioifs.h>
98#include <VBox/vmm/pdmaudioinline.h>
99#include <VBox/vmm/vmmr3vtable.h>
100#include <VBox/err.h>
101#include "VBox/settings.h"
102
103
104/*********************************************************************************************************************************
105* Structures and Typedefs *
106*********************************************************************************************************************************/
107/**
108 * Enumeration for specifying the recording container type.
109 */
110typedef enum AVRECCONTAINERTYPE
111{
112 /** Unknown / invalid container type. */
113 AVRECCONTAINERTYPE_UNKNOWN = 0,
114 /** Recorded data goes to Main / Console. */
115 AVRECCONTAINERTYPE_MAIN_CONSOLE = 1,
116 /** Recorded data will be written to a .webm file. */
117 AVRECCONTAINERTYPE_WEBM = 2
118} AVRECCONTAINERTYPE;
119
120/**
121 * Structure for keeping generic container parameters.
122 */
123typedef struct AVRECCONTAINERPARMS
124{
125 /** Stream index (hint). */
126 uint32_t idxStream;
127 /** The container's type. */
128 AVRECCONTAINERTYPE enmType;
129 union
130 {
131 /** WebM file specifics. */
132 struct
133 {
134 /** Allocated file name to write .webm file to. Must be free'd. */
135 char *pszFile;
136 } WebM;
137 };
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 * Audio video recording sink.
170 */
171typedef struct AVRECSINK
172{
173 /** Pointer to audio codec to use. */
174 PRECORDINGCODEC pCodec;
175 /** Container data to use for data processing. */
176 AVRECCONTAINER Con;
177 /** Timestamp (in ms) of when the sink was created. */
178 uint64_t tsStartMs;
179} AVRECSINK, *PAVRECSINK;
180
181/**
182 * Audio video recording (output) stream.
183 */
184typedef struct AVRECSTREAM
185{
186 /** Common part. */
187 PDMAUDIOBACKENDSTREAM Core;
188 /** The stream's acquired configuration. */
189 PDMAUDIOSTREAMCFG Cfg;
190 /** (Audio) frame buffer. */
191 PRTCIRCBUF pCircBuf;
192 /** Pointer to sink to use for writing. */
193 PAVRECSINK pSink;
194 /** Last encoded PTS (in ms). */
195 uint64_t uLastPTSMs;
196 /** Temporary buffer for the input (source) data to encode. */
197 void *pvSrcBuf;
198 /** Size (in bytes) of the temporary buffer holding the input (source) data to encode. */
199 size_t cbSrcBuf;
200 /** Temporary buffer for the encoded output (destination) data. */
201 void *pvDstBuf;
202 /** Size (in bytes) of the temporary buffer holding the encoded output (destination) data. */
203 size_t cbDstBuf;
204} AVRECSTREAM, *PAVRECSTREAM;
205
206/**
207 * Video recording audio driver instance data.
208 */
209typedef struct DRVAUDIORECORDING
210{
211 /** Pointer to audio video recording object. */
212 AudioVideoRec *pAudioVideoRec;
213 /** Pointer to the driver instance structure. */
214 PPDMDRVINS pDrvIns;
215 /** Pointer to host audio interface. */
216 PDMIHOSTAUDIO IHostAudio;
217 /** Pointer to the console object. */
218 ComPtr<Console> pConsole;
219 /** Pointer to the DrvAudio port interface that is above us. */
220 AVRECCONTAINERPARMS ContainerParms;
221 /** Weak pointer to recording context to use. */
222 RecordingContext *pRecCtx;
223 /** The driver's sink for writing output to. */
224 AVRECSINK Sink;
225} DRVAUDIORECORDING, *PDRVAUDIORECORDING;
226
227
228AudioVideoRec::AudioVideoRec(Console *pConsole)
229 : AudioDriver(pConsole)
230 , mpDrv(NULL)
231{
232}
233
234
235AudioVideoRec::~AudioVideoRec(void)
236{
237 if (mpDrv)
238 {
239 mpDrv->pAudioVideoRec = NULL;
240 mpDrv = NULL;
241 }
242}
243
244
245/**
246 * Applies recording settings to this driver instance.
247 *
248 * @returns VBox status code.
249 * @param Settings Recording settings to apply.
250 */
251int AudioVideoRec::applyConfiguration(const settings::RecordingSettings &Settings)
252{
253 /** @todo Do some validation here. */
254 mSettings = Settings; /* Note: Does have an own copy operator. */
255 return VINF_SUCCESS;
256}
257
258
259int AudioVideoRec::configureDriver(PCFGMNODE pLunCfg, PCVMMR3VTABLE pVMM)
260{
261 /** @todo For now we're using the configuration of the first screen (screen 0) here audio-wise. */
262 unsigned const idxScreen = 0;
263
264 AssertReturn(mSettings.mapScreens.size() >= 1, VERR_INVALID_PARAMETER);
265 const settings::RecordingScreenSettings &screenSettings = mSettings.mapScreens[idxScreen];
266
267 int vrc = pVMM->pfnCFGMR3InsertInteger(pLunCfg, "ContainerType", (uint64_t)screenSettings.enmDest);
268 AssertRCReturn(vrc, vrc);
269 if (screenSettings.enmDest == RecordingDestination_File)
270 {
271 vrc = pVMM->pfnCFGMR3InsertString(pLunCfg, "ContainerFileName", Utf8Str(screenSettings.File.strName).c_str());
272 AssertRCReturn(vrc, vrc);
273 }
274
275 vrc = pVMM->pfnCFGMR3InsertInteger(pLunCfg, "StreamIndex", (uint32_t)idxScreen);
276 AssertRCReturn(vrc, vrc);
277
278 return AudioDriver::configureDriver(pLunCfg, pVMM);
279}
280
281
282/*********************************************************************************************************************************
283* PDMIHOSTAUDIO *
284*********************************************************************************************************************************/
285
286/**
287 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
288 */
289static DECLCALLBACK(int) drvAudioVideoRecHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
290{
291 RT_NOREF(pInterface);
292 AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
293
294 /*
295 * Fill in the config structure.
296 */
297 RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "VideoRec");
298 pBackendCfg->cbStream = sizeof(AVRECSTREAM);
299 pBackendCfg->fFlags = 0;
300 pBackendCfg->cMaxStreamsIn = 0;
301 pBackendCfg->cMaxStreamsOut = UINT32_MAX;
302
303 return VINF_SUCCESS;
304}
305
306
307/**
308 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
309 */
310static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvAudioVideoRecHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
311{
312 RT_NOREF(pInterface, enmDir);
313 return PDMAUDIOBACKENDSTS_RUNNING;
314}
315
316
317/**
318 * Creates an audio output stream and associates it with the specified recording sink.
319 *
320 * @returns VBox status code.
321 * @param pThis Driver instance.
322 * @param pStreamAV Audio output stream to create.
323 * @param pSink Recording sink to associate audio output stream to.
324 * @param pCodec The audio codec, for stream parameters.
325 * @param pCfgReq Requested configuration by the audio backend.
326 * @param pCfgAcq Acquired configuration by the audio output stream.
327 */
328static int avRecCreateStreamOut(PDRVAUDIORECORDING pThis, PAVRECSTREAM pStreamAV,
329 PAVRECSINK pSink, PRECORDINGCODEC pCodec, PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
330{
331 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
332 AssertPtrReturn(pStreamAV, VERR_INVALID_POINTER);
333 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
334 AssertPtrReturn(pCodec, VERR_INVALID_POINTER);
335 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
336 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
337
338 if (pCfgReq->enmPath != PDMAUDIOPATH_OUT_FRONT)
339 {
340 LogRel(("Recording: Support for surround audio not implemented yet\n"));
341 AssertFailed();
342 return VERR_NOT_SUPPORTED;
343 }
344
345 /* Stuff which has to be set by now. */
346 Assert(pCodec->Parms.cbFrame);
347 Assert(pCodec->Parms.msFrame);
348
349 int vrc = RTCircBufCreate(&pStreamAV->pCircBuf, pCodec->Parms.cbFrame * 2 /* Use "double buffering" */);
350 if (RT_SUCCESS(vrc))
351 {
352 size_t cbScratchBuf = pCodec->Parms.cbFrame;
353 pStreamAV->pvSrcBuf = RTMemAlloc(cbScratchBuf);
354 if (pStreamAV->pvSrcBuf)
355 {
356 pStreamAV->cbSrcBuf = cbScratchBuf;
357 pStreamAV->pvDstBuf = RTMemAlloc(cbScratchBuf);
358 if (pStreamAV->pvDstBuf)
359 {
360 pStreamAV->cbDstBuf = cbScratchBuf;
361
362 pStreamAV->pSink = pSink; /* Assign sink to stream. */
363 pStreamAV->uLastPTSMs = 0;
364
365 /* Make sure to let the driver backend know that we need the audio data in
366 * a specific sampling rate the codec is optimized for. */
367/** @todo r=bird: pCfgAcq->Props isn't initialized at all, except for uHz... */
368 pCfgAcq->Props.uHz = pCodec->Parms.Audio.PCMProps.uHz;
369// pCfgAcq->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfgAcq->Props.cbSample, pCfgAcq->Props.cChannels);
370
371 /* Every Opus frame marks a period for now. Optimize this later. */
372 pCfgAcq->Backend.cFramesPeriod = PDMAudioPropsMilliToFrames(&pCfgAcq->Props, pCodec->Parms.msFrame);
373 pCfgAcq->Backend.cFramesBufferSize = PDMAudioPropsMilliToFrames(&pCfgAcq->Props, 100 /*ms*/); /** @todo Make this configurable. */
374 pCfgAcq->Backend.cFramesPreBuffering = pCfgAcq->Backend.cFramesPeriod * 2;
375 }
376 else
377 vrc = VERR_NO_MEMORY;
378 }
379 else
380 vrc = VERR_NO_MEMORY;
381 }
382
383 LogFlowFuncLeaveRC(vrc);
384 return vrc;
385}
386
387
388/**
389 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
390 */
391static DECLCALLBACK(int) drvAudioVideoRecHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
392 PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
393{
394 PDRVAUDIORECORDING pThis = RT_FROM_CPP_MEMBER(pInterface, DRVAUDIORECORDING, IHostAudio);
395 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
396 AssertPtrReturn(pStreamAV, VERR_INVALID_POINTER);
397 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
398 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
399
400 if (pCfgReq->enmDir == PDMAUDIODIR_IN)
401 return VERR_NOT_SUPPORTED;
402
403 /* For now we only have one sink, namely the driver's one.
404 * Later each stream could have its own one, to e.g. router different stream to different sinks .*/
405 PAVRECSINK pSink = &pThis->Sink;
406
407 int vrc = avRecCreateStreamOut(pThis, pStreamAV, pSink, pSink->pCodec, pCfgReq, pCfgAcq);
408 PDMAudioStrmCfgCopy(&pStreamAV->Cfg, pCfgAcq);
409
410 return vrc;
411}
412
413
414/**
415 * Destroys (closes) an audio output stream.
416 *
417 * @returns VBox status code.
418 * @param pThis Driver instance.
419 * @param pStreamAV Audio output stream to destroy.
420 */
421static int avRecDestroyStreamOut(PDRVAUDIORECORDING pThis, PAVRECSTREAM pStreamAV)
422{
423 RT_NOREF(pThis);
424
425 if (pStreamAV->pCircBuf)
426 {
427 RTCircBufDestroy(pStreamAV->pCircBuf);
428 pStreamAV->pCircBuf = NULL;
429 }
430
431 if (pStreamAV->pvSrcBuf)
432 {
433 Assert(pStreamAV->cbSrcBuf);
434 RTMemFree(pStreamAV->pvSrcBuf);
435 pStreamAV->pvSrcBuf = NULL;
436 pStreamAV->cbSrcBuf = 0;
437 }
438
439 if (pStreamAV->pvDstBuf)
440 {
441 Assert(pStreamAV->cbDstBuf);
442 RTMemFree(pStreamAV->pvDstBuf);
443 pStreamAV->pvDstBuf = NULL;
444 pStreamAV->cbDstBuf = 0;
445 }
446
447 return VINF_SUCCESS;
448}
449
450
451/**
452 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
453 */
454static DECLCALLBACK(int) drvAudioVideoRecHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
455 bool fImmediate)
456{
457 PDRVAUDIORECORDING pThis = RT_FROM_CPP_MEMBER(pInterface, DRVAUDIORECORDING, IHostAudio);
458 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
459 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
460 RT_NOREF(fImmediate);
461
462 int vrc = VINF_SUCCESS;
463 if (pStreamAV->Cfg.enmDir == PDMAUDIODIR_OUT)
464 vrc = avRecDestroyStreamOut(pThis, pStreamAV);
465
466 return vrc;
467}
468
469
470/**
471 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable}
472 */
473static DECLCALLBACK(int) drvAudioVideoRecHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
474{
475 RT_NOREF(pInterface, pStream);
476 return VINF_SUCCESS;
477}
478
479
480/**
481 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable}
482 */
483static DECLCALLBACK(int) drvAudioVideoRecHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
484{
485 RT_NOREF(pInterface, pStream);
486 return VINF_SUCCESS;
487}
488
489
490/**
491 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause}
492 */
493static DECLCALLBACK(int) drvAudioVideoRecHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
494{
495 RT_NOREF(pInterface, pStream);
496 return VINF_SUCCESS;
497}
498
499
500/**
501 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume}
502 */
503static DECLCALLBACK(int) drvAudioVideoRecHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
504{
505 RT_NOREF(pInterface, pStream);
506 return VINF_SUCCESS;
507}
508
509
510/**
511 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain}
512 */
513static DECLCALLBACK(int) drvAudioVideoRecHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
514{
515 RT_NOREF(pInterface, pStream);
516 return VINF_SUCCESS;
517}
518
519
520/**
521 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState}
522 */
523static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvAudioVideoRecHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
524 PPDMAUDIOBACKENDSTREAM pStream)
525{
526 RT_NOREF(pInterface);
527 AssertPtrReturn(pStream, PDMHOSTAUDIOSTREAMSTATE_INVALID);
528 return PDMHOSTAUDIOSTREAMSTATE_OKAY;
529}
530
531
532/**
533 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
534 */
535static DECLCALLBACK(uint32_t) drvAudioVideoRecHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
536{
537 RT_NOREF(pInterface, pStream);
538 return UINT32_MAX;
539}
540
541
542/**
543 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
544 */
545static DECLCALLBACK(int) drvAudioVideoRecHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
546 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
547{
548 RT_NOREF(pInterface);
549 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
550 AssertPtrReturn(pStreamAV, VERR_INVALID_POINTER);
551 if (cbBuf)
552 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
553 AssertReturn(pcbWritten, VERR_INVALID_PARAMETER);
554
555 int vrc = VINF_SUCCESS;
556
557 uint32_t cbWrittenTotal = 0;
558
559 PAVRECSINK pSink = pStreamAV->pSink;
560 AssertPtr(pSink);
561 PRECORDINGCODEC pCodec = pSink->pCodec;
562 AssertPtr(pCodec);
563 PRTCIRCBUF pCircBuf = pStreamAV->pCircBuf;
564 AssertPtr(pCircBuf);
565
566 uint32_t cbToWrite = cbBuf;
567
568 /*
569 * Write as much as we can into our internal ring buffer.
570 */
571 while ( cbToWrite > 0
572 && RTCircBufFree(pCircBuf))
573 {
574 void *pvCircBuf = NULL;
575 size_t cbCircBuf = 0;
576 RTCircBufAcquireWriteBlock(pCircBuf, cbToWrite, &pvCircBuf, &cbCircBuf);
577
578 if (cbCircBuf)
579 {
580 memcpy(pvCircBuf, (uint8_t *)pvBuf + cbWrittenTotal, cbCircBuf),
581 cbWrittenTotal += (uint32_t)cbCircBuf;
582 Assert(cbToWrite >= cbCircBuf);
583 cbToWrite -= (uint32_t)cbCircBuf;
584 }
585
586 RTCircBufReleaseWriteBlock(pCircBuf, cbCircBuf);
587 AssertBreak(cbCircBuf);
588 }
589
590 /*
591 * Process our internal ring buffer and encode the data.
592 */
593
594 /* Only encode data if we have data for at least one full frame. */
595 while (RTCircBufUsed(pCircBuf) >= pCodec->Parms.cbFrame)
596 {
597 LogFunc(("cbAvail=%zu, csFrame=%RU32, cbFrame=%RU32\n",
598 RTCircBufUsed(pCircBuf), pCodec->Parms.csFrame, pCodec->Parms.cbFrame));
599
600 /** @todo Can we encode more than a frame at a time? Optimize this! */
601 uint32_t const cbFramesToEncode = pCodec->Parms.cbFrame; /* 1 frame. */
602
603 uint32_t cbSrc = 0;
604 while (cbSrc < cbFramesToEncode)
605 {
606 void *pvCircBuf = NULL;
607 size_t cbCircBuf = 0;
608 RTCircBufAcquireReadBlock(pCircBuf, cbFramesToEncode - cbSrc, &pvCircBuf, &cbCircBuf);
609
610 if (cbCircBuf)
611 {
612 memcpy((uint8_t *)pStreamAV->pvSrcBuf + cbSrc, pvCircBuf, cbCircBuf);
613
614 cbSrc += (uint32_t)cbCircBuf;
615 Assert(cbSrc <= pStreamAV->cbSrcBuf);
616 }
617
618 RTCircBufReleaseReadBlock(pCircBuf, cbCircBuf);
619 AssertBreak(cbCircBuf);
620 }
621
622 Assert(cbSrc == cbFramesToEncode);
623
624 RECORDINGFRAME Frame;
625 Frame.Audio.cbBuf = cbFramesToEncode;
626 Frame.Audio.pvBuf = (uint8_t *)pStreamAV->pvSrcBuf;
627
628 size_t cEncoded /* Blocks encoded */, cbEncoded /* Bytes encoded */;
629 vrc = recordingCodecEncode(pSink->pCodec,
630 /* Source */
631 &Frame,
632 /* Dest */
633 pStreamAV->pvDstBuf, pStreamAV->cbDstBuf, &cEncoded, &cbEncoded);
634 if ( RT_SUCCESS(vrc)
635 && cEncoded)
636 {
637 Assert(cbEncoded);
638
639 if (pStreamAV->uLastPTSMs == 0)
640 pStreamAV->uLastPTSMs = RTTimeProgramMilliTS(); /* We want the absolute time (in ms) since program start. */
641
642 const uint64_t uDurationMs = pSink->pCodec->Parms.msFrame * cEncoded;
643 const uint64_t uPTSMs = pStreamAV->uLastPTSMs;
644
645 pStreamAV->uLastPTSMs += uDurationMs;
646
647 switch (pSink->Con.Parms.enmType)
648 {
649 case AVRECCONTAINERTYPE_MAIN_CONSOLE:
650 {
651 HRESULT hrc = pSink->Con.Main.pConsole->i_recordingSendAudio(pStreamAV->pvDstBuf, cbEncoded, uPTSMs);
652 Assert(hrc == S_OK);
653 RT_NOREF(hrc);
654 break;
655 }
656
657 case AVRECCONTAINERTYPE_WEBM:
658 {
659 WebMWriter::BlockData_Audio blockData = { pStreamAV->pvDstBuf, cbEncoded, uPTSMs };
660 vrc = pSink->Con.WebM.pWebM->WriteBlock(pSink->Con.WebM.uTrack, &blockData, sizeof(blockData));
661 AssertRC(vrc);
662 break;
663 }
664
665 default:
666 AssertFailedStmt(vrc = VERR_NOT_IMPLEMENTED);
667 break;
668 }
669 }
670 else if (RT_FAILURE(vrc)) /* Something went wrong -- report all bytes as being processed, to not hold up others. */
671 cbWrittenTotal = cbBuf;
672
673 if (RT_FAILURE(vrc))
674 break;
675
676 } /* while */
677
678 *pcbWritten = cbWrittenTotal;
679
680 LogFlowFunc(("csReadTotal=%RU32, vrc=%Rrc\n", cbWrittenTotal, vrc));
681 return VINF_SUCCESS; /* Don't propagate encoding errors to the caller. */
682}
683
684
685/**
686 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
687 */
688static DECLCALLBACK(uint32_t) drvAudioVideoRecHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
689{
690 RT_NOREF(pInterface, pStream);
691 return 0; /* Video capturing does not provide any input. */
692}
693
694
695/**
696 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
697 */
698static DECLCALLBACK(int) drvAudioVideoRecHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
699 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
700{
701 RT_NOREF(pInterface, pStream, pvBuf, cbBuf);
702 *pcbRead = 0;
703 return VINF_SUCCESS;
704}
705
706
707/*********************************************************************************************************************************
708* PDMIBASE *
709*********************************************************************************************************************************/
710
711/**
712 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
713 */
714static DECLCALLBACK(void *) drvAudioVideoRecQueryInterface(PPDMIBASE pInterface, const char *pszIID)
715{
716 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
717 PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
718
719 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
720 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
721 return NULL;
722}
723
724
725/*********************************************************************************************************************************
726* PDMDRVREG *
727*********************************************************************************************************************************/
728
729/**
730 * Shuts down (closes) a recording sink,
731 *
732 * @returns VBox status code.
733 * @param pSink Recording sink to shut down.
734 */
735static void avRecSinkShutdown(PAVRECSINK pSink)
736{
737 AssertPtrReturnVoid(pSink);
738
739 recordingCodecFinalize(pSink->pCodec);
740 recordingCodecDestroy(pSink->pCodec);
741 pSink->pCodec = NULL;
742
743 switch (pSink->Con.Parms.enmType)
744 {
745 case AVRECCONTAINERTYPE_WEBM:
746 {
747 if (pSink->Con.WebM.pWebM)
748 {
749 LogRel2(("Recording: Finished recording audio to file '%s' (%zu bytes)\n",
750 pSink->Con.WebM.pWebM->GetFileName().c_str(), pSink->Con.WebM.pWebM->GetFileSize()));
751
752 int vrc2 = pSink->Con.WebM.pWebM->Close();
753 AssertRC(vrc2);
754
755 delete pSink->Con.WebM.pWebM;
756 pSink->Con.WebM.pWebM = NULL;
757 }
758 break;
759 }
760
761 case AVRECCONTAINERTYPE_MAIN_CONSOLE:
762 RT_FALL_THROUGH();
763 default:
764 break;
765 }
766}
767
768
769/**
770 * @interface_method_impl{PDMDRVREG,pfnPowerOff}
771 */
772/*static*/ DECLCALLBACK(void) AudioVideoRec::drvPowerOff(PPDMDRVINS pDrvIns)
773{
774 PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
775 LogFlowFuncEnter();
776 avRecSinkShutdown(&pThis->Sink);
777}
778
779
780/**
781 * @interface_method_impl{PDMDRVREG,pfnDestruct}
782 */
783/*static*/ DECLCALLBACK(void) AudioVideoRec::drvDestruct(PPDMDRVINS pDrvIns)
784{
785 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
786 PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
787
788 LogFlowFuncEnter();
789
790 switch (pThis->ContainerParms.enmType)
791 {
792 case AVRECCONTAINERTYPE_WEBM:
793 {
794 avRecSinkShutdown(&pThis->Sink);
795 RTStrFree(pThis->ContainerParms.WebM.pszFile);
796 break;
797 }
798
799 default:
800 break;
801 }
802
803 /*
804 * If the AudioVideoRec object is still alive, we must clear it's reference to
805 * us since we'll be invalid when we return from this method.
806 */
807 if (pThis->pAudioVideoRec)
808 {
809 pThis->pAudioVideoRec->mpDrv = NULL;
810 pThis->pAudioVideoRec = NULL;
811 }
812
813 LogFlowFuncLeave();
814}
815
816
817/**
818 * Initializes a recording sink.
819 *
820 * @returns VBox status code.
821 * @param pThis Driver instance.
822 * @param pSink Sink to initialize.
823 * @param pConParms Container parameters to set.
824 * @param pCodec Codec to use.
825 */
826static int avRecSinkInit(PDRVAUDIORECORDING pThis, PAVRECSINK pSink, PAVRECCONTAINERPARMS pConParms, PRECORDINGCODEC pCodec)
827{
828 AssertReturn(pCodec->Parms.enmType == RECORDINGCODECTYPE_AUDIO, VERR_INVALID_PARAMETER);
829
830 int vrc;
831
832 pSink->pCodec = pCodec;
833
834 /*
835 * Container setup.
836 */
837 try
838 {
839 switch (pConParms->enmType)
840 {
841 case AVRECCONTAINERTYPE_MAIN_CONSOLE:
842 {
843 if (pThis->pConsole)
844 {
845 pSink->Con.Main.pConsole = pThis->pConsole;
846 }
847 else
848 vrc = VERR_NOT_SUPPORTED;
849 break;
850 }
851
852 case AVRECCONTAINERTYPE_WEBM:
853 {
854 /* If we only record audio, create our own WebM writer instance here. */
855 if (!pSink->Con.WebM.pWebM) /* Do we already have our WebM writer instance? */
856 {
857 /** @todo Add sink name / number to file name. */
858 const char *pszFile = pSink->Con.Parms.WebM.pszFile;
859 AssertPtr(pszFile);
860
861 pSink->Con.WebM.pWebM = new WebMWriter();
862 vrc = pSink->Con.WebM.pWebM->Open(pszFile,
863 /** @todo Add option to add some suffix if file exists instead of overwriting? */
864 RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE,
865 pSink->pCodec->Parms.enmAudioCodec, RecordingVideoCodec_None);
866 if (RT_SUCCESS(vrc))
867 {
868 const PPDMAUDIOPCMPROPS pPCMProps = &pCodec->Parms.Audio.PCMProps;
869
870 vrc = pSink->Con.WebM.pWebM->AddAudioTrack(pSink->pCodec,
871 PDMAudioPropsHz(pPCMProps), PDMAudioPropsChannels(pPCMProps),
872 PDMAudioPropsSampleBits(pPCMProps), &pSink->Con.WebM.uTrack);
873 if (RT_SUCCESS(vrc))
874 {
875 LogRel(("Recording: Recording audio to audio file '%s'\n", pszFile));
876 }
877 else
878 LogRel(("Recording: Error creating audio track for audio file '%s' (%Rrc)\n", pszFile, vrc));
879 }
880 else
881 LogRel(("Recording: Error creating audio file '%s' (%Rrc)\n", pszFile, vrc));
882 }
883 break;
884 }
885
886 default:
887 vrc = VERR_NOT_SUPPORTED;
888 break;
889 }
890 }
891 catch (std::bad_alloc &)
892 {
893 vrc = VERR_NO_MEMORY;
894 }
895
896 if (RT_SUCCESS(vrc))
897 {
898 pSink->Con.Parms.enmType = pConParms->enmType;
899 pSink->tsStartMs = RTTimeMilliTS();
900
901 return VINF_SUCCESS;
902 }
903
904 recordingCodecDestroy(pSink->pCodec);
905 pSink->pCodec = NULL;
906
907 LogRel(("Recording: Error creating sink (%Rrc)\n", vrc));
908 return vrc;
909}
910
911
912/**
913 * Construct a audio video recording driver instance.
914 *
915 * @copydoc FNPDMDRVCONSTRUCT
916 */
917/*static*/ DECLCALLBACK(int) AudioVideoRec::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
918{
919 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
920 PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
921 RT_NOREF(fFlags);
922
923 LogRel(("Audio: Initializing video recording audio driver\n"));
924 LogFlowFunc(("fFlags=0x%x\n", fFlags));
925
926 AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
927 ("Configuration error: Not possible to attach anything to this driver!\n"),
928 VERR_PDM_DRVINS_NO_ATTACH);
929
930 /*
931 * Init the static parts.
932 */
933 pThis->pDrvIns = pDrvIns;
934 /* IBase */
935 pDrvIns->IBase.pfnQueryInterface = drvAudioVideoRecQueryInterface;
936 /* IHostAudio */
937 pThis->IHostAudio.pfnGetConfig = drvAudioVideoRecHA_GetConfig;
938 pThis->IHostAudio.pfnGetDevices = NULL;
939 pThis->IHostAudio.pfnSetDevice = NULL;
940 pThis->IHostAudio.pfnGetStatus = drvAudioVideoRecHA_GetStatus;
941 pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
942 pThis->IHostAudio.pfnStreamConfigHint = NULL;
943 pThis->IHostAudio.pfnStreamCreate = drvAudioVideoRecHA_StreamCreate;
944 pThis->IHostAudio.pfnStreamInitAsync = NULL;
945 pThis->IHostAudio.pfnStreamDestroy = drvAudioVideoRecHA_StreamDestroy;
946 pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
947 pThis->IHostAudio.pfnStreamEnable = drvAudioVideoRecHA_StreamEnable;
948 pThis->IHostAudio.pfnStreamDisable = drvAudioVideoRecHA_StreamDisable;
949 pThis->IHostAudio.pfnStreamPause = drvAudioVideoRecHA_StreamPause;
950 pThis->IHostAudio.pfnStreamResume = drvAudioVideoRecHA_StreamResume;
951 pThis->IHostAudio.pfnStreamDrain = drvAudioVideoRecHA_StreamDrain;
952 pThis->IHostAudio.pfnStreamGetState = drvAudioVideoRecHA_StreamGetState;
953 pThis->IHostAudio.pfnStreamGetPending = NULL;
954 pThis->IHostAudio.pfnStreamGetWritable = drvAudioVideoRecHA_StreamGetWritable;
955 pThis->IHostAudio.pfnStreamPlay = drvAudioVideoRecHA_StreamPlay;
956 pThis->IHostAudio.pfnStreamGetReadable = drvAudioVideoRecHA_StreamGetReadable;
957 pThis->IHostAudio.pfnStreamCapture = drvAudioVideoRecHA_StreamCapture;
958
959 /*
960 * Read configuration.
961 */
962 PCPDMDRVHLPR3 const pHlp = pDrvIns->pHlpR3;
963 /** @todo validate it. */
964
965 /*
966 * Get the Console object pointer.
967 */
968 com::Guid ConsoleUuid(COM_IIDOF(IConsole));
969 IConsole *pIConsole = (IConsole *)PDMDrvHlpQueryGenericUserObject(pDrvIns, ConsoleUuid.raw());
970 AssertLogRelReturn(pIConsole, VERR_INTERNAL_ERROR_3);
971 Console *pConsole = static_cast<Console *>(pIConsole);
972 AssertLogRelReturn(pConsole, VERR_INTERNAL_ERROR_3);
973
974 pThis->pConsole = pConsole;
975 AssertReturn(!pThis->pConsole.isNull(), VERR_INVALID_POINTER);
976 pThis->pAudioVideoRec = pConsole->i_recordingGetAudioDrv();
977 AssertPtrReturn(pThis->pAudioVideoRec, VERR_INVALID_POINTER);
978
979 pThis->pAudioVideoRec->mpDrv = pThis;
980
981 /*
982 * Get the recording container parameters from the audio driver instance.
983 */
984 RT_ZERO(pThis->ContainerParms);
985 PAVRECCONTAINERPARMS pConParams = &pThis->ContainerParms;
986
987 int vrc = pHlp->pfnCFGMQueryU32(pCfg, "StreamIndex", (uint32_t *)&pConParams->idxStream);
988 AssertRCReturn(vrc, vrc);
989
990 vrc = pHlp->pfnCFGMQueryU32(pCfg, "ContainerType", (uint32_t *)&pConParams->enmType);
991 AssertRCReturn(vrc, vrc);
992
993 switch (pConParams->enmType)
994 {
995 case AVRECCONTAINERTYPE_WEBM:
996 vrc = pHlp->pfnCFGMQueryStringAlloc(pCfg, "ContainerFileName", &pConParams->WebM.pszFile);
997 AssertRCReturn(vrc, vrc);
998 break;
999
1000 default:
1001 break;
1002 }
1003
1004 /*
1005 * Obtain the recording context.
1006 */
1007 pThis->pRecCtx = pConsole->i_recordingGetContext();
1008 AssertPtrReturn(pThis->pRecCtx, VERR_INVALID_POINTER);
1009
1010 /*
1011 * Get the codec configuration.
1012 */
1013 RecordingStream *pStream = pThis->pRecCtx->GetStream(pConParams->idxStream);
1014 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
1015
1016 const PRECORDINGCODEC pCodec = pStream->GetAudioCodec();
1017 AssertPtrReturn(pCodec, VERR_INVALID_POINTER);
1018
1019 /*
1020 * Init the recording sink.
1021 */
1022 vrc = avRecSinkInit(pThis, &pThis->Sink, &pThis->ContainerParms, pCodec);
1023 if (RT_SUCCESS(vrc))
1024 LogRel2(("Recording: Audio recording driver initialized\n"));
1025 else
1026 LogRel(("Recording: Audio recording driver initialization failed: %Rrc\n", vrc));
1027
1028 return vrc;
1029}
1030
1031
1032/**
1033 * Video recording audio driver registration record.
1034 */
1035const PDMDRVREG AudioVideoRec::DrvReg =
1036{
1037 PDM_DRVREG_VERSION,
1038 /* szName */
1039 "AudioVideoRec",
1040 /* szRCMod */
1041 "",
1042 /* szR0Mod */
1043 "",
1044 /* pszDescription */
1045 "Audio driver for video recording",
1046 /* fFlags */
1047 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
1048 /* fClass. */
1049 PDM_DRVREG_CLASS_AUDIO,
1050 /* cMaxInstances */
1051 ~0U,
1052 /* cbInstance */
1053 sizeof(DRVAUDIORECORDING),
1054 /* pfnConstruct */
1055 AudioVideoRec::drvConstruct,
1056 /* pfnDestruct */
1057 AudioVideoRec::drvDestruct,
1058 /* pfnRelocate */
1059 NULL,
1060 /* pfnIOCtl */
1061 NULL,
1062 /* pfnPowerOn */
1063 NULL,
1064 /* pfnReset */
1065 NULL,
1066 /* pfnSuspend */
1067 NULL,
1068 /* pfnResume */
1069 NULL,
1070 /* pfnAttach */
1071 NULL,
1072 /* pfnDetach */
1073 NULL,
1074 /* pfnPowerOff */
1075 AudioVideoRec::drvPowerOff,
1076 /* pfnSoftReset */
1077 NULL,
1078 /* u32EndVersion */
1079 PDM_DRVREG_VERSION
1080};
1081
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