VirtualBox

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

Last change on this file since 96265 was 96262, 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) [build fix]. 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 96262 2022-08-17 12:16:13Z vboxsync $ */
2/** @file
3 * Video recording audio backend for Main.
4 *
5 * This driver is part of Main and is responsible for providing audio
6 * data to Main's video capturing feature.
7 *
8 * The driver itself implements a PDM host audio backend, which in turn
9 * provides the driver with the required audio data and audio events.
10 *
11 * For now there is support for the following destinations (called "sinks"):
12 *
13 * - Direct writing of .webm files to the host.
14 * - Communicating with Main via the Console object to send the encoded audio data to.
15 * The Console object in turn then will route the data to the Display / video capturing interface then.
16 */
17
18/*
19 * Copyright (C) 2016-2022 Oracle Corporation
20 *
21 * This file is part of VirtualBox Open Source Edition (OSE), as
22 * available from http://www.virtualbox.org. This file is free software;
23 * you can redistribute it and/or modify it under the terms of the GNU
24 * General Public License (GPL) as published by the Free Software
25 * Foundation, in version 2 as it comes in the "COPYING" file of the
26 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
27 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
28 */
29
30/* This code makes use of the Vorbis (libvorbis) and Opus codec (libopus):
31 *
32 * Copyright 2001-2011 Xiph.Org, Skype Limited, Octasic,
33 * Jean-Marc Valin, Timothy B. Terriberry,
34 * CSIRO, Gregory Maxwell, Mark Borgerding,
35 * Erik de Castro Lopo
36 *
37 * Redistribution and use in source and binary forms, with or without
38 * modification, are permitted provided that the following conditions
39 * are met:
40 *
41 * - Redistributions of source code must retain the above copyright
42 * notice, this list of conditions and the following disclaimer.
43 *
44 * - Redistributions in binary form must reproduce the above copyright
45 * notice, this list of conditions and the following disclaimer in the
46 * documentation and/or other materials provided with the distribution.
47 *
48 * - Neither the name of Internet Society, IETF or IETF Trust, nor the
49 * names of specific contributors, may be used to endorse or promote
50 * products derived from this software without specific prior written
51 * permission.
52 *
53 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
54 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
55 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
56 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
57 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
58 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
59 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
60 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
61 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
62 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
63 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
64 *
65 * Opus is subject to the royalty-free patent licenses which are
66 * specified at:
67 *
68 * Xiph.Org Foundation:
69 * https://datatracker.ietf.org/ipr/1524/
70 *
71 * Microsoft Corporation:
72 * https://datatracker.ietf.org/ipr/1914/
73 *
74 * Broadcom Corporation:
75 * https://datatracker.ietf.org/ipr/1526/
76 *
77 */
78
79
80/*********************************************************************************************************************************
81* Header Files *
82*********************************************************************************************************************************/
83#define LOG_GROUP LOG_GROUP_RECORDING
84#include "LoggingNew.h"
85
86#include "DrvAudioRec.h"
87#include "ConsoleImpl.h"
88
89#include "WebMWriter.h"
90
91#include <iprt/mem.h>
92#include <iprt/cdefs.h>
93
94#include "VBox/com/VirtualBox.h"
95#include <VBox/vmm/cfgm.h>
96#include <VBox/vmm/pdmdrv.h>
97#include <VBox/vmm/pdmaudioifs.h>
98#include <VBox/vmm/pdmaudioinline.h>
99#include <VBox/vmm/vmmr3vtable.h>
100#include <VBox/err.h>
101#include "VBox/settings.h"
102
103
104/*********************************************************************************************************************************
105* Structures and Typedefs *
106*********************************************************************************************************************************/
107/**
108 * Enumeration for specifying the recording container type.
109 */
110typedef enum AVRECCONTAINERTYPE
111{
112 /** Unknown / invalid container type. */
113 AVRECCONTAINERTYPE_UNKNOWN = 0,
114 /** Recorded data goes to Main / Console. */
115 AVRECCONTAINERTYPE_MAIN_CONSOLE = 1,
116 /** Recorded data will be written to a .webm file. */
117 AVRECCONTAINERTYPE_WEBM = 2
118} AVRECCONTAINERTYPE;
119
120/**
121 * Structure for keeping generic container parameters.
122 */
123typedef struct AVRECCONTAINERPARMS
124{
125 /** Stream index (hint). */
126 uint32_t idxStream;
127 /** The container's type. */
128 AVRECCONTAINERTYPE enmType;
129 union
130 {
131 /** WebM file specifics. */
132 struct
133 {
134 /** Allocated file name to write .webm file to. Must be free'd. */
135 char *pszFile;
136 } WebM;
137 };
138
139} AVRECCONTAINERPARMS, *PAVRECCONTAINERPARMS;
140
141/**
142 * Structure for keeping container-specific data.
143 */
144typedef struct AVRECCONTAINER
145{
146 /** Generic container parameters. */
147 AVRECCONTAINERPARMS Parms;
148
149 union
150 {
151 struct
152 {
153 /** Pointer to Console. */
154 Console *pConsole;
155 } Main;
156
157 struct
158 {
159 /** Pointer to WebM container to write recorded audio data to.
160 * See the AVRECMODE enumeration for more information. */
161 WebMWriter *pWebM;
162 /** Assigned track number from WebM container. */
163 uint8_t uTrack;
164 } WebM;
165 };
166} AVRECCONTAINER, *PAVRECCONTAINER;
167
168/**
169 * Audio video recording sink.
170 */
171typedef struct AVRECSINK
172{
173 /** Pointer (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 PRTCIRCBUF pCircBuf = pStreamAV->pCircBuf;
545 AssertPtr(pCircBuf);
546
547 uint32_t cbToWrite = RT_MIN(cbBuf, (uint32_t)RTCircBufFree(pCircBuf));
548 AssertReturn(cbToWrite, VERR_BUFFER_OVERFLOW);
549
550 /*
551 * Write as much as we can into our internal ring buffer.
552 */
553 while (cbToWrite)
554 {
555 void *pvCircBuf = NULL;
556 size_t cbCircBuf = 0;
557 RTCircBufAcquireWriteBlock(pCircBuf, cbToWrite, &pvCircBuf, &cbCircBuf);
558
559 Log3Func(("cbToWrite=%RU32, cbCircBuf=%zu\n", cbToWrite, cbCircBuf));
560
561 memcpy(pvCircBuf, (uint8_t *)pvBuf + cbWrittenTotal, cbCircBuf),
562 cbWrittenTotal += (uint32_t)cbCircBuf;
563 Assert(cbWrittenTotal <= cbBuf);
564 Assert(cbToWrite >= cbCircBuf);
565 cbToWrite -= (uint32_t)cbCircBuf;
566
567 RTCircBufReleaseWriteBlock(pCircBuf, cbCircBuf);
568 }
569
570 RecordingStream *pRecStream = pStreamAV->pSink->pRecStream;
571 PRECORDINGCODEC pCodec = pRecStream->GetAudioCodec();
572
573 /*
574 * Process our internal ring buffer and send the obtained audio data to our encoding thread.
575 */
576 cbToWrite = (uint32_t)RTCircBufUsed(pCircBuf);
577
578 /** @todo Can we encode more than a frame at a time? Optimize this! */
579 uint32_t const cbFrame = pCodec->Parms.cbFrame;
580
581 /* Only encode data if we have data for at least one full codec frame. */
582 while (cbToWrite >= cbFrame)
583 {
584 uint32_t cbSrc = 0;
585 do
586 {
587 void *pvCircBuf = NULL;
588 size_t cbCircBuf = 0;
589 RTCircBufAcquireReadBlock(pCircBuf, cbFrame - cbSrc, &pvCircBuf, &cbCircBuf);
590
591 Log3Func(("cbSrc=%RU32, cbCircBuf=%zu\n", cbSrc, cbCircBuf));
592
593 memcpy((uint8_t *)pStreamAV->pvSrcBuf + cbSrc, pvCircBuf, cbCircBuf);
594
595 cbSrc += (uint32_t)cbCircBuf;
596 Assert(cbSrc <= pStreamAV->cbSrcBuf);
597 Assert(cbSrc <= cbFrame);
598
599 RTCircBufReleaseReadBlock(pCircBuf, cbCircBuf);
600
601 if (cbSrc == cbFrame) /* Only send full codec frames. */
602 {
603 vrc = pRecStream->SendAudioFrame(pStreamAV->pvSrcBuf, cbSrc, 0);
604 if (RT_FAILURE(vrc))
605 break;
606 }
607
608 } while (cbSrc < cbFrame);
609
610 Assert(cbToWrite >= cbFrame);
611 cbToWrite -= cbFrame;
612
613 if (RT_FAILURE(vrc))
614 break;
615
616 } /* while */
617
618 *pcbWritten = cbWrittenTotal;
619
620 LogFlowFunc(("cbBuf=%RU32, cbWrittenTotal=%RU32, vrc=%Rrc\n", cbBuf, cbWrittenTotal, vrc));
621 return VINF_SUCCESS; /* Don't propagate encoding errors to the caller. */
622}
623
624
625/**
626 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
627 */
628static DECLCALLBACK(uint32_t) drvAudioVideoRecHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
629{
630 RT_NOREF(pInterface, pStream);
631 return 0; /* Video capturing does not provide any input. */
632}
633
634
635/**
636 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
637 */
638static DECLCALLBACK(int) drvAudioVideoRecHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
639 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
640{
641 RT_NOREF(pInterface, pStream, pvBuf, cbBuf);
642 *pcbRead = 0;
643 return VINF_SUCCESS;
644}
645
646
647/*********************************************************************************************************************************
648* PDMIBASE *
649*********************************************************************************************************************************/
650
651/**
652 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
653 */
654static DECLCALLBACK(void *) drvAudioVideoRecQueryInterface(PPDMIBASE pInterface, const char *pszIID)
655{
656 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
657 PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
658
659 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
660 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
661 return NULL;
662}
663
664
665/*********************************************************************************************************************************
666* PDMDRVREG *
667*********************************************************************************************************************************/
668
669/**
670 * Shuts down (closes) a recording sink,
671 *
672 * @returns VBox status code.
673 * @param pSink Recording sink to shut down.
674 */
675static void avRecSinkShutdown(PAVRECSINK pSink)
676{
677 AssertPtrReturnVoid(pSink);
678
679 pSink->pRecStream = NULL;
680
681 switch (pSink->Con.Parms.enmType)
682 {
683 case AVRECCONTAINERTYPE_WEBM:
684 {
685 if (pSink->Con.WebM.pWebM)
686 {
687 LogRel2(("Recording: Finished recording audio to file '%s' (%zu bytes)\n",
688 pSink->Con.WebM.pWebM->GetFileName().c_str(), pSink->Con.WebM.pWebM->GetFileSize()));
689
690 int vrc2 = pSink->Con.WebM.pWebM->Close();
691 AssertRC(vrc2);
692
693 delete pSink->Con.WebM.pWebM;
694 pSink->Con.WebM.pWebM = NULL;
695 }
696 break;
697 }
698
699 case AVRECCONTAINERTYPE_MAIN_CONSOLE:
700 RT_FALL_THROUGH();
701 default:
702 break;
703 }
704}
705
706
707/**
708 * @interface_method_impl{PDMDRVREG,pfnPowerOff}
709 */
710/*static*/ DECLCALLBACK(void) AudioVideoRec::drvPowerOff(PPDMDRVINS pDrvIns)
711{
712 PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
713 LogFlowFuncEnter();
714 avRecSinkShutdown(&pThis->Sink);
715}
716
717
718/**
719 * @interface_method_impl{PDMDRVREG,pfnDestruct}
720 */
721/*static*/ DECLCALLBACK(void) AudioVideoRec::drvDestruct(PPDMDRVINS pDrvIns)
722{
723 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
724 PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
725
726 LogFlowFuncEnter();
727
728 switch (pThis->ContainerParms.enmType)
729 {
730 case AVRECCONTAINERTYPE_WEBM:
731 {
732 avRecSinkShutdown(&pThis->Sink);
733 RTStrFree(pThis->ContainerParms.WebM.pszFile);
734 break;
735 }
736
737 default:
738 break;
739 }
740
741 /*
742 * If the AudioVideoRec object is still alive, we must clear it's reference to
743 * us since we'll be invalid when we return from this method.
744 */
745 if (pThis->pAudioVideoRec)
746 {
747 pThis->pAudioVideoRec->mpDrv = NULL;
748 pThis->pAudioVideoRec = NULL;
749 }
750
751 LogFlowFuncLeave();
752}
753
754
755/**
756 * Initializes a recording sink.
757 *
758 * @returns VBox status code.
759 * @param pThis Driver instance.
760 * @param pSink Sink to initialize.
761 * @param pConParms Container parameters to set.
762 * @param pStream Recording stream to asssign sink to.
763 */
764static int avRecSinkInit(PDRVAUDIORECORDING pThis, PAVRECSINK pSink, PAVRECCONTAINERPARMS pConParms, RecordingStream *pStream)
765{
766 pSink->pRecStream = pStream;
767
768 int vrc = VINF_SUCCESS;
769
770 /*
771 * Container setup.
772 */
773 try
774 {
775 switch (pConParms->enmType)
776 {
777 case AVRECCONTAINERTYPE_MAIN_CONSOLE:
778 {
779 if (pThis->pConsole)
780 {
781 pSink->Con.Main.pConsole = pThis->pConsole;
782 }
783 else
784 vrc = VERR_NOT_SUPPORTED;
785 break;
786 }
787
788 case AVRECCONTAINERTYPE_WEBM:
789 {
790 #if 0
791 /* If we only record audio, create our own WebM writer instance here. */
792 if (!pSink->Con.WebM.pWebM) /* Do we already have our WebM writer instance? */
793 {
794 /** @todo Add sink name / number to file name. */
795 const char *pszFile = pSink->Con.Parms.WebM.pszFile;
796 AssertPtr(pszFile);
797
798 pSink->Con.WebM.pWebM = new WebMWriter();
799 vrc = pSink->Con.WebM.pWebM->Open(pszFile,
800 /** @todo Add option to add some suffix if file exists instead of overwriting? */
801 RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE,
802 pSink->pCodec->Parms.enmAudioCodec, RecordingVideoCodec_None);
803 if (RT_SUCCESS(vrc))
804 {
805 const PPDMAUDIOPCMPROPS pPCMProps = &pCodec->Parms.Audio.PCMProps;
806
807 vrc = pSink->Con.WebM.pWebM->AddAudioTrack(pSink->pCodec,
808 PDMAudioPropsHz(pPCMProps), PDMAudioPropsChannels(pPCMProps),
809 PDMAudioPropsSampleBits(pPCMProps), &pSink->Con.WebM.uTrack);
810 if (RT_SUCCESS(vrc))
811 {
812 LogRel(("Recording: Recording audio to audio file '%s'\n", pszFile));
813 }
814 else
815 LogRel(("Recording: Error creating audio track for audio file '%s' (%Rrc)\n", pszFile, vrc));
816 }
817 else
818 LogRel(("Recording: Error creating audio file '%s' (%Rrc)\n", pszFile, vrc));
819 }
820 break;
821 #endif
822 }
823
824 default:
825 vrc = VERR_NOT_SUPPORTED;
826 break;
827 }
828 }
829 catch (std::bad_alloc &)
830 {
831 vrc = VERR_NO_MEMORY;
832 }
833
834 if (RT_SUCCESS(vrc))
835 {
836 pSink->Con.Parms.enmType = pConParms->enmType;
837 pSink->tsStartMs = RTTimeMilliTS();
838
839 return VINF_SUCCESS;
840 }
841
842 LogRel(("Recording: Error creating sink (%Rrc)\n", vrc));
843 return vrc;
844}
845
846
847/**
848 * Construct a audio video recording driver instance.
849 *
850 * @copydoc FNPDMDRVCONSTRUCT
851 */
852/*static*/ DECLCALLBACK(int) AudioVideoRec::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
853{
854 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
855 PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
856 RT_NOREF(fFlags);
857
858 LogRel(("Audio: Initializing video recording audio driver\n"));
859 LogFlowFunc(("fFlags=0x%x\n", fFlags));
860
861 AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
862 ("Configuration error: Not possible to attach anything to this driver!\n"),
863 VERR_PDM_DRVINS_NO_ATTACH);
864
865 /*
866 * Init the static parts.
867 */
868 pThis->pDrvIns = pDrvIns;
869 /* IBase */
870 pDrvIns->IBase.pfnQueryInterface = drvAudioVideoRecQueryInterface;
871 /* IHostAudio */
872 pThis->IHostAudio.pfnGetConfig = drvAudioVideoRecHA_GetConfig;
873 pThis->IHostAudio.pfnGetDevices = NULL;
874 pThis->IHostAudio.pfnSetDevice = NULL;
875 pThis->IHostAudio.pfnGetStatus = drvAudioVideoRecHA_GetStatus;
876 pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
877 pThis->IHostAudio.pfnStreamConfigHint = NULL;
878 pThis->IHostAudio.pfnStreamCreate = drvAudioVideoRecHA_StreamCreate;
879 pThis->IHostAudio.pfnStreamInitAsync = NULL;
880 pThis->IHostAudio.pfnStreamDestroy = drvAudioVideoRecHA_StreamDestroy;
881 pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
882 pThis->IHostAudio.pfnStreamEnable = drvAudioVideoRecHA_StreamEnable;
883 pThis->IHostAudio.pfnStreamDisable = drvAudioVideoRecHA_StreamDisable;
884 pThis->IHostAudio.pfnStreamPause = drvAudioVideoRecHA_StreamPause;
885 pThis->IHostAudio.pfnStreamResume = drvAudioVideoRecHA_StreamResume;
886 pThis->IHostAudio.pfnStreamDrain = drvAudioVideoRecHA_StreamDrain;
887 pThis->IHostAudio.pfnStreamGetState = drvAudioVideoRecHA_StreamGetState;
888 pThis->IHostAudio.pfnStreamGetPending = NULL;
889 pThis->IHostAudio.pfnStreamGetWritable = drvAudioVideoRecHA_StreamGetWritable;
890 pThis->IHostAudio.pfnStreamPlay = drvAudioVideoRecHA_StreamPlay;
891 pThis->IHostAudio.pfnStreamGetReadable = drvAudioVideoRecHA_StreamGetReadable;
892 pThis->IHostAudio.pfnStreamCapture = drvAudioVideoRecHA_StreamCapture;
893
894 /*
895 * Read configuration.
896 */
897 PCPDMDRVHLPR3 const pHlp = pDrvIns->pHlpR3;
898 /** @todo validate it. */
899
900 /*
901 * Get the Console object pointer.
902 */
903 com::Guid ConsoleUuid(COM_IIDOF(IConsole));
904 IConsole *pIConsole = (IConsole *)PDMDrvHlpQueryGenericUserObject(pDrvIns, ConsoleUuid.raw());
905 AssertLogRelReturn(pIConsole, VERR_INTERNAL_ERROR_3);
906 Console *pConsole = static_cast<Console *>(pIConsole);
907 AssertLogRelReturn(pConsole, VERR_INTERNAL_ERROR_3);
908
909 pThis->pConsole = pConsole;
910 AssertReturn(!pThis->pConsole.isNull(), VERR_INVALID_POINTER);
911 pThis->pAudioVideoRec = pConsole->i_recordingGetAudioDrv();
912 AssertPtrReturn(pThis->pAudioVideoRec, VERR_INVALID_POINTER);
913
914 pThis->pAudioVideoRec->mpDrv = pThis;
915
916 /*
917 * Get the recording container parameters from the audio driver instance.
918 */
919 RT_ZERO(pThis->ContainerParms);
920 PAVRECCONTAINERPARMS pConParams = &pThis->ContainerParms;
921
922 int vrc = pHlp->pfnCFGMQueryU32(pCfg, "StreamIndex", (uint32_t *)&pConParams->idxStream);
923 AssertRCReturn(vrc, vrc);
924
925 vrc = pHlp->pfnCFGMQueryU32(pCfg, "ContainerType", (uint32_t *)&pConParams->enmType);
926 AssertRCReturn(vrc, vrc);
927
928 switch (pConParams->enmType)
929 {
930 case AVRECCONTAINERTYPE_WEBM:
931 vrc = pHlp->pfnCFGMQueryStringAlloc(pCfg, "ContainerFileName", &pConParams->WebM.pszFile);
932 AssertRCReturn(vrc, vrc);
933 break;
934
935 default:
936 break;
937 }
938
939 /*
940 * Obtain the recording context.
941 */
942 pThis->pRecCtx = pConsole->i_recordingGetContext();
943 AssertPtrReturn(pThis->pRecCtx, VERR_INVALID_POINTER);
944
945 /*
946 * Get the codec configuration.
947 */
948 RecordingStream *pStream = pThis->pRecCtx->GetStream(pConParams->idxStream);
949 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
950
951 /*
952 * Init the recording sink.
953 */
954 vrc = avRecSinkInit(pThis, &pThis->Sink, &pThis->ContainerParms, pStream);
955 if (RT_SUCCESS(vrc))
956 LogRel2(("Recording: Audio recording driver initialized\n"));
957 else
958 LogRel(("Recording: Audio recording driver initialization failed: %Rrc\n", vrc));
959
960 return vrc;
961}
962
963
964/**
965 * Video recording audio driver registration record.
966 */
967const PDMDRVREG AudioVideoRec::DrvReg =
968{
969 PDM_DRVREG_VERSION,
970 /* szName */
971 "AudioVideoRec",
972 /* szRCMod */
973 "",
974 /* szR0Mod */
975 "",
976 /* pszDescription */
977 "Audio driver for video recording",
978 /* fFlags */
979 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
980 /* fClass. */
981 PDM_DRVREG_CLASS_AUDIO,
982 /* cMaxInstances */
983 ~0U,
984 /* cbInstance */
985 sizeof(DRVAUDIORECORDING),
986 /* pfnConstruct */
987 AudioVideoRec::drvConstruct,
988 /* pfnDestruct */
989 AudioVideoRec::drvDestruct,
990 /* pfnRelocate */
991 NULL,
992 /* pfnIOCtl */
993 NULL,
994 /* pfnPowerOn */
995 NULL,
996 /* pfnReset */
997 NULL,
998 /* pfnSuspend */
999 NULL,
1000 /* pfnResume */
1001 NULL,
1002 /* pfnAttach */
1003 NULL,
1004 /* pfnDetach */
1005 NULL,
1006 /* pfnPowerOff */
1007 AudioVideoRec::drvPowerOff,
1008 /* pfnSoftReset */
1009 NULL,
1010 /* u32EndVersion */
1011 PDM_DRVREG_VERSION
1012};
1013
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette