VirtualBox

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

Last change on this file since 105006 was 105006, checked in by vboxsync, 5 months ago

Video Recording: Big revamp to improve overall performance. We now don't rely on the periodic display refresh callback anymore to render the entire framebuffer but now rely on delta updates ("dirty rectangles"). Also, we now only encode new frames when an area has changed. This also needed cursor position + change change notifications, as we render the cursor on the host side if mouse integration is enabled (requires 7.1 Guest Additions as of now). Optimized the BGRA32->YUV IV420 color space conversion as well as the overall amount of pixel data shuffled forth and back. Added a new testcase for the cropping/centering code. bugref:10650

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 31.5 KB
Line 
1/* $Id: DrvAudioRec.cpp 105006 2024-06-24 17:43:00Z 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-2023 Oracle and/or its affiliates.
20 *
21 * This file is part of VirtualBox base platform packages, as
22 * available from https://www.virtualbox.org.
23 *
24 * This program is free software; you can redistribute it and/or
25 * modify it under the terms of the GNU General Public License
26 * as published by the Free Software Foundation, in version 3 of the
27 * License.
28 *
29 * This program is distributed in the hope that it will be useful, but
30 * WITHOUT ANY WARRANTY; without even the implied warranty of
31 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
32 * General Public License for more details.
33 *
34 * You should have received a copy of the GNU General Public License
35 * along with this program; if not, see <https://www.gnu.org/licenses>.
36 *
37 * SPDX-License-Identifier: GPL-3.0-only
38 */
39
40
41/*********************************************************************************************************************************
42* Header Files *
43*********************************************************************************************************************************/
44#define LOG_GROUP LOG_GROUP_RECORDING
45#include "LoggingNew.h"
46
47#include "DrvAudioRec.h"
48#include "ConsoleImpl.h"
49
50#include "WebMWriter.h"
51
52#include <iprt/mem.h>
53#include <iprt/cdefs.h>
54
55#include "VBox/com/VirtualBox.h"
56#include <VBox/vmm/cfgm.h>
57#include <VBox/vmm/pdmdrv.h>
58#include <VBox/vmm/pdmaudioifs.h>
59#include <VBox/vmm/pdmaudioinline.h>
60#include <VBox/vmm/vmmr3vtable.h>
61#include <VBox/err.h>
62#include "VBox/settings.h"
63
64
65/*********************************************************************************************************************************
66* Structures and Typedefs *
67*********************************************************************************************************************************/
68/**
69 * Enumeration for specifying the recording container type.
70 */
71typedef enum AVRECCONTAINERTYPE
72{
73 /** Unknown / invalid container type. */
74 AVRECCONTAINERTYPE_UNKNOWN = 0,
75 /** Recorded data goes to Main / Console. */
76 AVRECCONTAINERTYPE_MAIN_CONSOLE = 1,
77 /** Recorded data will be written to a .webm file. */
78 AVRECCONTAINERTYPE_WEBM = 2
79} AVRECCONTAINERTYPE;
80
81/**
82 * Structure for keeping generic container parameters.
83 */
84typedef struct AVRECCONTAINERPARMS
85{
86 /** Stream index (hint). */
87 uint32_t idxStream;
88 /** The container's type. */
89 AVRECCONTAINERTYPE enmType;
90 union
91 {
92 /** WebM file specifics. */
93 struct
94 {
95 /** Allocated file name to write .webm file to. Must be free'd. */
96 char *pszFile;
97 } WebM;
98 };
99
100} AVRECCONTAINERPARMS, *PAVRECCONTAINERPARMS;
101
102/**
103 * Structure for keeping container-specific data.
104 */
105typedef struct AVRECCONTAINER
106{
107 /** Generic container parameters. */
108 AVRECCONTAINERPARMS Parms;
109
110 union
111 {
112 struct
113 {
114 /** Pointer to Console. */
115 Console *pConsole;
116 } Main;
117
118 struct
119 {
120 /** Pointer to WebM container to write recorded audio data to.
121 * See the AVRECMODE enumeration for more information. */
122 WebMWriter *pWebM;
123 /** Assigned track number from WebM container. */
124 uint8_t uTrack;
125 } WebM;
126 };
127} AVRECCONTAINER, *PAVRECCONTAINER;
128
129/**
130 * Audio video recording sink.
131 */
132typedef struct AVRECSINK
133{
134 /** Pointer (weak) to recording stream to bind to. */
135 RecordingStream *pRecStream;
136 /** Container data to use for data processing. */
137 AVRECCONTAINER Con;
138 /** Timestamp (in ms) of when the sink was created. */
139 uint64_t tsStartMs;
140} AVRECSINK, *PAVRECSINK;
141
142/**
143 * Audio video recording (output) stream.
144 */
145typedef struct AVRECSTREAM
146{
147 /** Common part. */
148 PDMAUDIOBACKENDSTREAM Core;
149 /** The stream's acquired configuration. */
150 PDMAUDIOSTREAMCFG Cfg;
151 /** (Audio) frame buffer. */
152 PRTCIRCBUF pCircBuf;
153 /** Pointer to sink to use for writing. */
154 PAVRECSINK pSink;
155 /** Last encoded PTS (in ms). */
156 uint64_t uLastPTSMs;
157 /** Temporary buffer for the input (source) data to encode. */
158 void *pvSrcBuf;
159 /** Size (in bytes) of the temporary buffer holding the input (source) data to encode. */
160 size_t cbSrcBuf;
161} AVRECSTREAM, *PAVRECSTREAM;
162
163/**
164 * Video recording audio driver instance data.
165 */
166typedef struct DRVAUDIORECORDING
167{
168 /** Pointer to audio video recording object. */
169 AudioVideoRec *pAudioVideoRec;
170 /** Pointer to the driver instance structure. */
171 PPDMDRVINS pDrvIns;
172 /** Pointer to host audio interface. */
173 PDMIHOSTAUDIO IHostAudio;
174 /** Pointer to the console object. */
175 ComPtr<Console> pConsole;
176 /** Pointer to the DrvAudio port interface that is above us. */
177 AVRECCONTAINERPARMS ContainerParms;
178 /** Weak pointer to recording context to use. */
179 RecordingContext *pRecCtx;
180 /** The driver's sink for writing output to. */
181 AVRECSINK Sink;
182} DRVAUDIORECORDING, *PDRVAUDIORECORDING;
183
184
185AudioVideoRec::AudioVideoRec(Console *pConsole)
186 : AudioDriver(pConsole)
187 , mpDrv(NULL)
188{
189}
190
191
192AudioVideoRec::~AudioVideoRec(void)
193{
194 if (mpDrv)
195 {
196 mpDrv->pAudioVideoRec = NULL;
197 mpDrv = NULL;
198 }
199}
200
201
202/**
203 * Applies recording settings to this driver instance.
204 *
205 * @returns VBox status code.
206 * @param Settings Recording settings to apply.
207 */
208int AudioVideoRec::applyConfiguration(const settings::RecordingSettings &Settings)
209{
210 /** @todo Do some validation here. */
211 mSettings = Settings; /* Note: Does have an own copy operator. */
212 return VINF_SUCCESS;
213}
214
215
216int AudioVideoRec::configureDriver(PCFGMNODE pLunCfg, PCVMMR3VTABLE pVMM)
217{
218 /** @todo For now we're using the configuration of the first screen (screen 0) here audio-wise. */
219 unsigned const idxScreen = 0;
220
221 AssertReturn(mSettings.mapScreens.size() >= 1, VERR_INVALID_PARAMETER);
222 const settings::RecordingScreenSettings &screenSettings = mSettings.mapScreens[idxScreen];
223
224 int vrc = pVMM->pfnCFGMR3InsertInteger(pLunCfg, "ContainerType", (uint64_t)screenSettings.enmDest);
225 AssertRCReturn(vrc, vrc);
226 if (screenSettings.enmDest == RecordingDestination_File)
227 {
228 vrc = pVMM->pfnCFGMR3InsertString(pLunCfg, "ContainerFileName", Utf8Str(screenSettings.File.strName).c_str());
229 AssertRCReturn(vrc, vrc);
230 }
231
232 vrc = pVMM->pfnCFGMR3InsertInteger(pLunCfg, "StreamIndex", (uint32_t)idxScreen);
233 AssertRCReturn(vrc, vrc);
234
235 return AudioDriver::configureDriver(pLunCfg, pVMM);
236}
237
238
239/*********************************************************************************************************************************
240* PDMIHOSTAUDIO *
241*********************************************************************************************************************************/
242
243/**
244 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
245 */
246static DECLCALLBACK(int) drvAudioVideoRecHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
247{
248 RT_NOREF(pInterface);
249 AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
250
251 /*
252 * Fill in the config structure.
253 */
254 RTStrCopy(pBackendCfg->szName, sizeof(pBackendCfg->szName), "VideoRec");
255 pBackendCfg->cbStream = sizeof(AVRECSTREAM);
256 pBackendCfg->fFlags = 0;
257 pBackendCfg->cMaxStreamsIn = 0;
258 pBackendCfg->cMaxStreamsOut = UINT32_MAX;
259
260 return VINF_SUCCESS;
261}
262
263
264/**
265 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
266 */
267static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvAudioVideoRecHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
268{
269 RT_NOREF(pInterface, enmDir);
270 return PDMAUDIOBACKENDSTS_RUNNING;
271}
272
273
274/**
275 * Creates an audio output stream and associates it with the specified recording sink.
276 *
277 * @returns VBox status code.
278 * @param pThis Driver instance.
279 * @param pStreamAV Audio output stream to create.
280 * @param pSink Recording sink to associate audio output stream to.
281 * @param pCfgReq Requested configuration by the audio backend.
282 * @param pCfgAcq Acquired configuration by the audio output stream.
283 */
284static int avRecCreateStreamOut(PDRVAUDIORECORDING pThis, PAVRECSTREAM pStreamAV,
285 PAVRECSINK pSink, PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
286{
287 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
288 AssertPtrReturn(pStreamAV, VERR_INVALID_POINTER);
289 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
290 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
291 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
292
293 if (pCfgReq->enmPath != PDMAUDIOPATH_OUT_FRONT)
294 {
295 LogRel(("Recording: Support for surround audio not implemented yet\n"));
296 AssertFailed();
297 return VERR_NOT_SUPPORTED;
298 }
299
300 PRECORDINGCODEC pCodec = pSink->pRecStream->GetAudioCodec();
301
302 /* Stuff which has to be set by now. */
303 Assert(pCodec->Parms.cbFrame);
304 Assert(pCodec->Parms.msFrame);
305
306 int vrc = RTCircBufCreate(&pStreamAV->pCircBuf, pCodec->Parms.cbFrame * 2 /* Use "double buffering" */);
307 if (RT_SUCCESS(vrc))
308 {
309 size_t cbScratchBuf = pCodec->Parms.cbFrame;
310 pStreamAV->pvSrcBuf = RTMemAlloc(cbScratchBuf);
311 if (pStreamAV->pvSrcBuf)
312 {
313 pStreamAV->cbSrcBuf = cbScratchBuf;
314
315 pStreamAV->pSink = pSink; /* Assign sink to stream. */
316 pStreamAV->uLastPTSMs = 0;
317
318 /* Make sure to let the driver backend know that we need the audio data in
319 * a specific sampling rate the codec is optimized for. */
320 pCfgAcq->Props = pCodec->Parms.u.Audio.PCMProps;
321
322 /* Every codec frame marks a period for now. Optimize this later. */
323 pCfgAcq->Backend.cFramesPeriod = PDMAudioPropsMilliToFrames(&pCfgAcq->Props, pCodec->Parms.msFrame);
324 pCfgAcq->Backend.cFramesBufferSize = pCfgAcq->Backend.cFramesPeriod * 2;
325 pCfgAcq->Backend.cFramesPreBuffering = pCfgAcq->Backend.cFramesPeriod;
326 }
327 else
328 vrc = VERR_NO_MEMORY;
329 }
330
331 LogFlowFuncLeaveRC(vrc);
332 return vrc;
333}
334
335
336/**
337 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
338 */
339static DECLCALLBACK(int) drvAudioVideoRecHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
340 PCPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
341{
342 PDRVAUDIORECORDING pThis = RT_FROM_CPP_MEMBER(pInterface, DRVAUDIORECORDING, IHostAudio);
343 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
344 AssertPtrReturn(pStreamAV, VERR_INVALID_POINTER);
345 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
346 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
347
348 if (pCfgReq->enmDir == PDMAUDIODIR_IN)
349 return VERR_NOT_SUPPORTED;
350
351 /* For now we only have one sink, namely the driver's one.
352 * Later each stream could have its own one, to e.g. router different stream to different sinks .*/
353 PAVRECSINK pSink = &pThis->Sink;
354
355 int vrc = avRecCreateStreamOut(pThis, pStreamAV, pSink, pCfgReq, pCfgAcq);
356 PDMAudioStrmCfgCopy(&pStreamAV->Cfg, pCfgAcq);
357
358 return vrc;
359}
360
361
362/**
363 * Destroys (closes) an audio output stream.
364 *
365 * @returns VBox status code.
366 * @param pThis Driver instance.
367 * @param pStreamAV Audio output stream to destroy.
368 */
369static int avRecDestroyStreamOut(PDRVAUDIORECORDING pThis, PAVRECSTREAM pStreamAV)
370{
371 RT_NOREF(pThis);
372
373 if (pStreamAV->pCircBuf)
374 {
375 RTCircBufDestroy(pStreamAV->pCircBuf);
376 pStreamAV->pCircBuf = NULL;
377 }
378
379 if (pStreamAV->pvSrcBuf)
380 {
381 Assert(pStreamAV->cbSrcBuf);
382 RTMemFree(pStreamAV->pvSrcBuf);
383 pStreamAV->pvSrcBuf = NULL;
384 pStreamAV->cbSrcBuf = 0;
385 }
386
387 return VINF_SUCCESS;
388}
389
390
391/**
392 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
393 */
394static DECLCALLBACK(int) drvAudioVideoRecHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
395 bool fImmediate)
396{
397 PDRVAUDIORECORDING pThis = RT_FROM_CPP_MEMBER(pInterface, DRVAUDIORECORDING, IHostAudio);
398 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
399 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
400 RT_NOREF(fImmediate);
401
402 int vrc = VINF_SUCCESS;
403 if (pStreamAV->Cfg.enmDir == PDMAUDIODIR_OUT)
404 vrc = avRecDestroyStreamOut(pThis, pStreamAV);
405
406 return vrc;
407}
408
409
410/**
411 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamEnable}
412 */
413static DECLCALLBACK(int) drvAudioVideoRecHA_StreamEnable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
414{
415 RT_NOREF(pInterface, pStream);
416 return VINF_SUCCESS;
417}
418
419
420/**
421 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDisable}
422 */
423static DECLCALLBACK(int) drvAudioVideoRecHA_StreamDisable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
424{
425 RT_NOREF(pInterface, pStream);
426 return VINF_SUCCESS;
427}
428
429
430/**
431 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPause}
432 */
433static DECLCALLBACK(int) drvAudioVideoRecHA_StreamPause(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
434{
435 RT_NOREF(pInterface, pStream);
436 return VINF_SUCCESS;
437}
438
439
440/**
441 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamResume}
442 */
443static DECLCALLBACK(int) drvAudioVideoRecHA_StreamResume(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
444{
445 RT_NOREF(pInterface, pStream);
446 return VINF_SUCCESS;
447}
448
449
450/**
451 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain}
452 */
453static DECLCALLBACK(int) drvAudioVideoRecHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
454{
455 RT_NOREF(pInterface, pStream);
456 return VINF_SUCCESS;
457}
458
459
460/**
461 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetState}
462 */
463static DECLCALLBACK(PDMHOSTAUDIOSTREAMSTATE) drvAudioVideoRecHA_StreamGetState(PPDMIHOSTAUDIO pInterface,
464 PPDMAUDIOBACKENDSTREAM pStream)
465{
466 RT_NOREF(pInterface);
467 AssertPtrReturn(pStream, PDMHOSTAUDIOSTREAMSTATE_INVALID);
468 return PDMHOSTAUDIOSTREAMSTATE_OKAY;
469}
470
471
472/**
473 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
474 */
475static DECLCALLBACK(uint32_t) drvAudioVideoRecHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
476{
477 RT_NOREF(pInterface);
478 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
479
480 RecordingStream *pRecStream = pStreamAV->pSink->pRecStream;
481 PRECORDINGCODEC pCodec = pRecStream->GetAudioCodec();
482
483 return pCodec->Parms.cbFrame;
484}
485
486
487/**
488 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
489 */
490static DECLCALLBACK(int) drvAudioVideoRecHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
491 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)
492{
493 PDRVAUDIORECORDING pThis = RT_FROM_CPP_MEMBER(pInterface, DRVAUDIORECORDING, IHostAudio);
494 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
495 AssertPtrReturn(pStreamAV, VERR_INVALID_POINTER);
496 if (cbBuf)
497 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
498 AssertReturn(pcbWritten, VERR_INVALID_PARAMETER);
499
500 int vrc = VINF_SUCCESS;
501
502 uint32_t cbWrittenTotal = 0;
503
504 PRTCIRCBUF pCircBuf = pStreamAV->pCircBuf;
505 AssertPtr(pCircBuf);
506 uint32_t const cbFree = (uint32_t)RTCircBufFree(pCircBuf);
507 uint32_t cbToWrite = RT_MIN(cbBuf, cbFree);
508 AssertReturn(cbToWrite, VERR_BUFFER_OVERFLOW);
509
510 /*
511 * Write as much as we can into our internal ring buffer.
512 */
513 while (cbToWrite)
514 {
515 void *pvCircBuf = NULL;
516 size_t cbCircBuf = 0;
517 RTCircBufAcquireWriteBlock(pCircBuf, cbToWrite, &pvCircBuf, &cbCircBuf);
518
519 Log3Func(("cbToWrite=%RU32, cbCircBuf=%zu\n", cbToWrite, cbCircBuf));
520
521 memcpy(pvCircBuf, (uint8_t *)pvBuf + cbWrittenTotal, cbCircBuf),
522 cbWrittenTotal += (uint32_t)cbCircBuf;
523 Assert(cbWrittenTotal <= cbBuf);
524 Assert(cbToWrite >= cbCircBuf);
525 cbToWrite -= (uint32_t)cbCircBuf;
526
527 RTCircBufReleaseWriteBlock(pCircBuf, cbCircBuf);
528 }
529
530 RecordingStream *pRecStream = pStreamAV->pSink->pRecStream;
531 PRECORDINGCODEC pCodec = pRecStream->GetAudioCodec();
532
533 /*
534 * Process our internal ring buffer and send the obtained audio data to our encoding thread.
535 */
536 cbToWrite = (uint32_t)RTCircBufUsed(pCircBuf);
537
538 /** @todo Can we encode more than a frame at a time? Optimize this! */
539 uint32_t const cbFrame = pCodec->Parms.cbFrame;
540
541 /* Only encode data if we have data for at least one full codec frame. */
542 while (cbToWrite >= cbFrame)
543 {
544 uint32_t cbSrc = 0;
545 do
546 {
547 void *pvCircBuf = NULL;
548 size_t cbCircBuf = 0;
549 RTCircBufAcquireReadBlock(pCircBuf, cbFrame - cbSrc, &pvCircBuf, &cbCircBuf);
550
551 Log3Func(("cbSrc=%RU32, cbCircBuf=%zu\n", cbSrc, cbCircBuf));
552
553 memcpy((uint8_t *)pStreamAV->pvSrcBuf + cbSrc, pvCircBuf, cbCircBuf);
554
555 cbSrc += (uint32_t)cbCircBuf;
556 Assert(cbSrc <= pStreamAV->cbSrcBuf);
557 Assert(cbSrc <= cbFrame);
558
559 RTCircBufReleaseReadBlock(pCircBuf, cbCircBuf);
560
561 if (cbSrc == cbFrame) /* Only send full codec frames. */
562 {
563 AssertPtr(pThis->pRecCtx);
564 vrc = pRecStream->SendAudioFrame(pStreamAV->pvSrcBuf, cbSrc, pThis->pRecCtx->GetCurrentPTS());
565 if (RT_FAILURE(vrc))
566 break;
567 }
568
569 } while (cbSrc < cbFrame);
570
571 Assert(cbToWrite >= cbFrame);
572 cbToWrite -= cbFrame;
573
574 if (RT_FAILURE(vrc))
575 break;
576
577 } /* while */
578
579 *pcbWritten = cbWrittenTotal;
580
581 LogFlowFunc(("cbBuf=%RU32, cbWrittenTotal=%RU32, vrc=%Rrc\n", cbBuf, cbWrittenTotal, vrc));
582 return VINF_SUCCESS; /* Don't propagate encoding errors to the caller. */
583}
584
585
586/**
587 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
588 */
589static DECLCALLBACK(uint32_t) drvAudioVideoRecHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
590{
591 RT_NOREF(pInterface, pStream);
592 return 0; /* Video capturing does not provide any input. */
593}
594
595
596/**
597 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
598 */
599static DECLCALLBACK(int) drvAudioVideoRecHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
600 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)
601{
602 RT_NOREF(pInterface, pStream, pvBuf, cbBuf);
603 *pcbRead = 0;
604 return VINF_SUCCESS;
605}
606
607
608/*********************************************************************************************************************************
609* PDMIBASE *
610*********************************************************************************************************************************/
611
612/**
613 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
614 */
615static DECLCALLBACK(void *) drvAudioVideoRecQueryInterface(PPDMIBASE pInterface, const char *pszIID)
616{
617 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
618 PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
619
620 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
621 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
622 return NULL;
623}
624
625
626/*********************************************************************************************************************************
627* PDMDRVREG *
628*********************************************************************************************************************************/
629
630/**
631 * Shuts down (closes) a recording sink,
632 *
633 * @param pSink Recording sink to shut down.
634 */
635static void avRecSinkShutdown(PAVRECSINK pSink)
636{
637 AssertPtrReturnVoid(pSink);
638
639 pSink->pRecStream = NULL;
640
641 switch (pSink->Con.Parms.enmType)
642 {
643 case AVRECCONTAINERTYPE_WEBM:
644 {
645 if (pSink->Con.WebM.pWebM)
646 {
647 LogRel2(("Recording: Finished recording audio to file '%s' (%zu bytes)\n",
648 pSink->Con.WebM.pWebM->GetFileName().c_str(), pSink->Con.WebM.pWebM->GetFileSize()));
649
650 int vrc2 = pSink->Con.WebM.pWebM->Close();
651 AssertRC(vrc2);
652
653 delete pSink->Con.WebM.pWebM;
654 pSink->Con.WebM.pWebM = NULL;
655 }
656 break;
657 }
658
659 case AVRECCONTAINERTYPE_MAIN_CONSOLE:
660 RT_FALL_THROUGH();
661 default:
662 break;
663 }
664}
665
666
667/**
668 * @interface_method_impl{PDMDRVREG,pfnPowerOff}
669 */
670/*static*/ DECLCALLBACK(void) AudioVideoRec::drvPowerOff(PPDMDRVINS pDrvIns)
671{
672 PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
673 LogFlowFuncEnter();
674 avRecSinkShutdown(&pThis->Sink);
675}
676
677
678/**
679 * @interface_method_impl{PDMDRVREG,pfnDestruct}
680 */
681/*static*/ DECLCALLBACK(void) AudioVideoRec::drvDestruct(PPDMDRVINS pDrvIns)
682{
683 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
684 PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
685
686 LogFlowFuncEnter();
687
688 switch (pThis->ContainerParms.enmType)
689 {
690 case AVRECCONTAINERTYPE_WEBM:
691 {
692 avRecSinkShutdown(&pThis->Sink);
693 RTStrFree(pThis->ContainerParms.WebM.pszFile);
694 break;
695 }
696
697 default:
698 break;
699 }
700
701 /*
702 * If the AudioVideoRec object is still alive, we must clear it's reference to
703 * us since we'll be invalid when we return from this method.
704 */
705 if (pThis->pAudioVideoRec)
706 {
707 pThis->pAudioVideoRec->mpDrv = NULL;
708 pThis->pAudioVideoRec = NULL;
709 }
710
711 LogFlowFuncLeave();
712}
713
714
715/**
716 * Initializes a recording sink.
717 *
718 * @returns VBox status code.
719 * @param pThis Driver instance.
720 * @param pSink Sink to initialize.
721 * @param pConParms Container parameters to set.
722 * @param pStream Recording stream to asssign sink to.
723 */
724static int avRecSinkInit(PDRVAUDIORECORDING pThis, PAVRECSINK pSink, PAVRECCONTAINERPARMS pConParms, RecordingStream *pStream)
725{
726 pSink->pRecStream = pStream;
727
728 int vrc = VINF_SUCCESS;
729
730 /*
731 * Container setup.
732 */
733 try
734 {
735 switch (pConParms->enmType)
736 {
737 case AVRECCONTAINERTYPE_MAIN_CONSOLE:
738 {
739 if (pThis->pConsole)
740 {
741 pSink->Con.Main.pConsole = pThis->pConsole;
742 }
743 else
744 vrc = VERR_NOT_SUPPORTED;
745 break;
746 }
747
748 case AVRECCONTAINERTYPE_WEBM:
749 {
750 #if 0
751 /* If we only record audio, create our own WebM writer instance here. */
752 if (!pSink->Con.WebM.pWebM) /* Do we already have our WebM writer instance? */
753 {
754 /** @todo Add sink name / number to file name. */
755 const char *pszFile = pSink->Con.Parms.WebM.pszFile;
756 AssertPtr(pszFile);
757
758 pSink->Con.WebM.pWebM = new WebMWriter();
759 vrc = pSink->Con.WebM.pWebM->Open(pszFile,
760 /** @todo Add option to add some suffix if file exists instead of overwriting? */
761 RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE,
762 pSink->pCodec->Parms.enmAudioCodec, RecordingVideoCodec_None);
763 if (RT_SUCCESS(vrc))
764 {
765 const PPDMAUDIOPCMPROPS pPCMProps = &pCodec->Parms.Audio.PCMProps;
766
767 vrc = pSink->Con.WebM.pWebM->AddAudioTrack(pSink->pCodec,
768 PDMAudioPropsHz(pPCMProps), PDMAudioPropsChannels(pPCMProps),
769 PDMAudioPropsSampleBits(pPCMProps), &pSink->Con.WebM.uTrack);
770 if (RT_SUCCESS(vrc))
771 {
772 LogRel(("Recording: Recording audio to audio file '%s'\n", pszFile));
773 }
774 else
775 LogRel(("Recording: Error creating audio track for audio file '%s' (%Rrc)\n", pszFile, vrc));
776 }
777 else
778 LogRel(("Recording: Error creating audio file '%s' (%Rrc)\n", pszFile, vrc));
779 }
780 break;
781 #else
782 vrc = VERR_NOT_SUPPORTED;
783 break;
784 #endif
785 }
786
787 default:
788 vrc = VERR_NOT_SUPPORTED;
789 break;
790 }
791 }
792 catch (std::bad_alloc &)
793 {
794 vrc = VERR_NO_MEMORY;
795 }
796
797 if (RT_SUCCESS(vrc))
798 {
799 pSink->Con.Parms.enmType = pConParms->enmType;
800 pSink->tsStartMs = RTTimeMilliTS();
801
802 return VINF_SUCCESS;
803 }
804
805 LogRel(("Recording: Error creating sink (%Rrc)\n", vrc));
806 return vrc;
807}
808
809
810/**
811 * Construct a audio video recording driver instance.
812 *
813 * @copydoc FNPDMDRVCONSTRUCT
814 */
815/*static*/ DECLCALLBACK(int) AudioVideoRec::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
816{
817 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
818 PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);
819 RT_NOREF(fFlags);
820
821 LogRel(("Audio: Initializing video recording audio driver\n"));
822 LogFlowFunc(("fFlags=0x%x\n", fFlags));
823
824 AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
825 ("Configuration error: Not possible to attach anything to this driver!\n"),
826 VERR_PDM_DRVINS_NO_ATTACH);
827
828 /*
829 * Init the static parts.
830 */
831 pThis->pDrvIns = pDrvIns;
832 /* IBase */
833 pDrvIns->IBase.pfnQueryInterface = drvAudioVideoRecQueryInterface;
834 /* IHostAudio */
835 pThis->IHostAudio.pfnGetConfig = drvAudioVideoRecHA_GetConfig;
836 pThis->IHostAudio.pfnGetDevices = NULL;
837 pThis->IHostAudio.pfnSetDevice = NULL;
838 pThis->IHostAudio.pfnGetStatus = drvAudioVideoRecHA_GetStatus;
839 pThis->IHostAudio.pfnDoOnWorkerThread = NULL;
840 pThis->IHostAudio.pfnStreamConfigHint = NULL;
841 pThis->IHostAudio.pfnStreamCreate = drvAudioVideoRecHA_StreamCreate;
842 pThis->IHostAudio.pfnStreamInitAsync = NULL;
843 pThis->IHostAudio.pfnStreamDestroy = drvAudioVideoRecHA_StreamDestroy;
844 pThis->IHostAudio.pfnStreamNotifyDeviceChanged = NULL;
845 pThis->IHostAudio.pfnStreamEnable = drvAudioVideoRecHA_StreamEnable;
846 pThis->IHostAudio.pfnStreamDisable = drvAudioVideoRecHA_StreamDisable;
847 pThis->IHostAudio.pfnStreamPause = drvAudioVideoRecHA_StreamPause;
848 pThis->IHostAudio.pfnStreamResume = drvAudioVideoRecHA_StreamResume;
849 pThis->IHostAudio.pfnStreamDrain = drvAudioVideoRecHA_StreamDrain;
850 pThis->IHostAudio.pfnStreamGetState = drvAudioVideoRecHA_StreamGetState;
851 pThis->IHostAudio.pfnStreamGetPending = NULL;
852 pThis->IHostAudio.pfnStreamGetWritable = drvAudioVideoRecHA_StreamGetWritable;
853 pThis->IHostAudio.pfnStreamPlay = drvAudioVideoRecHA_StreamPlay;
854 pThis->IHostAudio.pfnStreamGetReadable = drvAudioVideoRecHA_StreamGetReadable;
855 pThis->IHostAudio.pfnStreamCapture = drvAudioVideoRecHA_StreamCapture;
856
857 /*
858 * Read configuration.
859 */
860 PCPDMDRVHLPR3 const pHlp = pDrvIns->pHlpR3;
861 /** @todo validate it. */
862
863 /*
864 * Get the Console object pointer.
865 */
866 com::Guid ConsoleUuid(COM_IIDOF(IConsole));
867 IConsole *pIConsole = (IConsole *)PDMDrvHlpQueryGenericUserObject(pDrvIns, ConsoleUuid.raw());
868 AssertLogRelReturn(pIConsole, VERR_INTERNAL_ERROR_3);
869 Console *pConsole = static_cast<Console *>(pIConsole);
870 AssertLogRelReturn(pConsole, VERR_INTERNAL_ERROR_3);
871
872 pThis->pConsole = pConsole;
873 AssertReturn(!pThis->pConsole.isNull(), VERR_INVALID_POINTER);
874 pThis->pAudioVideoRec = pConsole->i_recordingGetAudioDrv();
875 AssertPtrReturn(pThis->pAudioVideoRec, VERR_INVALID_POINTER);
876
877 pThis->pAudioVideoRec->mpDrv = pThis;
878
879 /*
880 * Get the recording container parameters from the audio driver instance.
881 */
882 RT_ZERO(pThis->ContainerParms);
883 PAVRECCONTAINERPARMS pConParams = &pThis->ContainerParms;
884
885 int vrc = pHlp->pfnCFGMQueryU32(pCfg, "StreamIndex", (uint32_t *)&pConParams->idxStream);
886 AssertRCReturn(vrc, vrc);
887
888 vrc = pHlp->pfnCFGMQueryU32(pCfg, "ContainerType", (uint32_t *)&pConParams->enmType);
889 AssertRCReturn(vrc, vrc);
890
891 switch (pConParams->enmType)
892 {
893 case AVRECCONTAINERTYPE_WEBM:
894 vrc = pHlp->pfnCFGMQueryStringAlloc(pCfg, "ContainerFileName", &pConParams->WebM.pszFile);
895 AssertRCReturn(vrc, vrc);
896 break;
897
898 default:
899 break;
900 }
901
902 /*
903 * Obtain the recording context.
904 */
905 pThis->pRecCtx = pConsole->i_recordingGetContext();
906 AssertPtrReturn(pThis->pRecCtx, VERR_INVALID_POINTER);
907
908 /*
909 * Get the codec configuration.
910 */
911 RecordingStream *pStream = pThis->pRecCtx->GetStream(pConParams->idxStream);
912 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
913
914 /*
915 * Init the recording sink.
916 */
917 vrc = avRecSinkInit(pThis, &pThis->Sink, &pThis->ContainerParms, pStream);
918 if (RT_SUCCESS(vrc))
919 LogRel2(("Recording: Audio recording driver initialized\n"));
920 else
921 LogRel(("Recording: Audio recording driver initialization failed: %Rrc\n", vrc));
922
923 return vrc;
924}
925
926
927/**
928 * Video recording audio driver registration record.
929 */
930const PDMDRVREG AudioVideoRec::DrvReg =
931{
932 PDM_DRVREG_VERSION,
933 /* szName */
934 "AudioVideoRec",
935 /* szRCMod */
936 "",
937 /* szR0Mod */
938 "",
939 /* pszDescription */
940 "Audio driver for video recording",
941 /* fFlags */
942 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
943 /* fClass. */
944 PDM_DRVREG_CLASS_AUDIO,
945 /* cMaxInstances */
946 ~0U,
947 /* cbInstance */
948 sizeof(DRVAUDIORECORDING),
949 /* pfnConstruct */
950 AudioVideoRec::drvConstruct,
951 /* pfnDestruct */
952 AudioVideoRec::drvDestruct,
953 /* pfnRelocate */
954 NULL,
955 /* pfnIOCtl */
956 NULL,
957 /* pfnPowerOn */
958 NULL,
959 /* pfnReset */
960 NULL,
961 /* pfnSuspend */
962 NULL,
963 /* pfnResume */
964 NULL,
965 /* pfnAttach */
966 NULL,
967 /* pfnDetach */
968 NULL,
969 /* pfnPowerOff */
970 AudioVideoRec::drvPowerOff,
971 /* pfnSoftReset */
972 NULL,
973 /* u32EndVersion */
974 PDM_DRVREG_VERSION
975};
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