VirtualBox

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

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

Recording/Main: Decoupled the WebM writer class from codec dependencies. Various bugfixes. bugref:10275

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