VirtualBox

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

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

Recording/Main: Greatly reduced workload spent in the recording driver's async I/O thread by also encoding the audio data in the dedicated recording thread (using two different block maps, see comments for details). bugref:10275

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