VirtualBox

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

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

Recording: Implemented support for Vorbis codec (provided by libvorbis, not enabled by default yet). This also makes all the codec handling more abstract by using a simple codec wrapper, to keep other places free from codec-specific as much as possible. Initial implementation works and output files are being recognized by media players, but there still are some timing bugs to resolve, as well as optimizing the performance. bugref:10275

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