VirtualBox

source: vbox/trunk/src/VBox/Main/src-client/DrvAudioVideoRec.cpp@ 68490

Last change on this file since 68490 was 68452, checked in by vboxsync, 8 years ago

Main/DrvAudioVideoRec: Also set the bits per sample, logging.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 33.1 KB
Line 
1/* $Id: DrvAudioVideoRec.cpp 68452 2017-08-17 19:57:56Z vboxsync $ */
2/** @file
3 * Video recording audio backend for Main.
4 */
5
6/*
7 * Copyright (C) 2016-2017 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18/**
19 * This driver is part of Main and is responsible for providing audio
20 * data to Main's video capturing feature.
21 *
22 * The driver itself implements a PDM host audio backend, which in turn
23 * provides the driver with the required audio data and audio events.
24 *
25 * For now there is support for the following destinations (called "sinks"):
26 *
27 * - Direct writing of .webm files to the host.
28 * - Communicating with Main via the Console object to send the encoded audio data to.
29 * The Console object in turn then will route the data to the Display / video capturing interface then.
30 */
31
32/*********************************************************************************************************************************
33* Header Files *
34*********************************************************************************************************************************/
35#define LOG_GROUP LOG_GROUP_DRV_HOST_AUDIO
36#include "LoggingNew.h"
37
38#include "DrvAudioVideoRec.h"
39#include "ConsoleImpl.h"
40
41#include "../../Devices/Audio/DrvAudio.h"
42#include "EbmlWriter.h"
43
44#include <iprt/mem.h>
45#include <iprt/cdefs.h>
46
47#include <VBox/vmm/pdmaudioifs.h>
48#include <VBox/vmm/pdmdrv.h>
49#include <VBox/vmm/cfgm.h>
50#include <VBox/err.h>
51
52#ifdef VBOX_WITH_LIBOPUS
53# include <opus.h>
54#endif
55
56
57/*********************************************************************************************************************************
58* Defines *
59*********************************************************************************************************************************/
60
61#define AVREC_OPUS_HZ_MAX 48000 /** Maximum sample rate (in Hz) Opus can handle. */
62
63
64/*********************************************************************************************************************************
65* Structures and Typedefs *
66*********************************************************************************************************************************/
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 /** The container's type. */
87 AVRECCONTAINERTYPE enmType;
88
89} AVRECCONTAINERPARMS, *PAVRECCONTAINERPARMS;
90
91/**
92 * Structure for keeping container-specific data.
93 */
94typedef struct AVRECCONTAINER
95{
96 /** Generic container parameters. */
97 AVRECCONTAINERPARMS Parms;
98
99 union
100 {
101 struct
102 {
103 /** Pointer to Console. */
104 Console *pConsole;
105 } Main;
106
107 struct
108 {
109 /** Pointer to WebM container to write recorded audio data to.
110 * See the AVRECMODE enumeration for more information. */
111 WebMWriter *pWebM;
112 /** Assigned track number from WebM container. */
113 uint8_t uTrack;
114 } WebM;
115 };
116} AVRECCONTAINER, *PAVRECCONTAINER;
117
118/**
119 * Structure for keeping generic codec parameters.
120 */
121typedef struct AVRECCODECPARMS
122{
123 /** The encoding rate to use. */
124 uint32_t uHz;
125 /** Number of audio channels to encode.
126 * Currently we only supported stereo (2) channels. */
127 uint8_t cChannels;
128 /** Bits per sample. */
129 uint8_t cBits;
130 /** The codec's bitrate. 0 if not used / cannot be specified. */
131 uint32_t uBitrate;
132
133} AVRECCODECPARMS, *PAVRECCODECPARMS;
134
135/**
136 * Structure for keeping codec-specific data.
137 */
138typedef struct AVRECCODEC
139{
140 /** Generic codec parameters. */
141 AVRECCODECPARMS Parms;
142 union
143 {
144#ifdef VBOX_WITH_LIBOPUS
145 struct
146 {
147 /** Encoder we're going to use. */
148 OpusEncoder *pEnc;
149 /** Time (in ms) an (encoded) frame takes.
150 *
151 * For Opus, valid frame sizes are:
152 * ms Frame size
153 * 2.5 120
154 * 5 240
155 * 10 480
156 * 20 (Default) 960
157 * 40 1920
158 * 60 2880
159 */
160 uint32_t msFrame;
161 } Opus;
162#endif /* VBOX_WITH_LIBOPUS */
163 };
164
165#ifdef VBOX_WITH_STATISTICS /** @todo Make these real STAM values. */
166 struct
167 {
168 /** Number of frames encoded. */
169 uint64_t cEncFrames;
170 /** Total time (in ms) of already encoded audio data. */
171 uint64_t msEncTotal;
172 } STAM;
173#endif /* VBOX_WITH_STATISTICS */
174
175} AVRECCODEC, *PAVRECCODEC;
176
177typedef struct AVRECSINK
178{
179 /** @todo Add types for container / codec as soon as we implement more stuff. */
180
181 /** Container data to use for data processing. */
182 AVRECCONTAINER Con;
183 /** Codec data this sink uses for encoding. */
184 AVRECCODEC Codec;
185} AVRECSINK, *PAVRECSINK;
186
187/**
188 * Audio video recording (output) stream.
189 */
190typedef struct AVRECSTREAM
191{
192 /** The stream's acquired configuration. */
193 PPDMAUDIOSTREAMCFG pCfg;
194 /** (Audio) frame buffer. */
195 PRTCIRCBUF pCircBuf;
196 /** Pointer to sink to use for writing. */
197 PAVRECSINK pSink;
198} AVRECSTREAM, *PAVRECSTREAM;
199
200/**
201 * Video recording audio driver instance data.
202 */
203typedef struct DRVAUDIOVIDEOREC
204{
205 /** Pointer to audio video recording object. */
206 AudioVideoRec *pAudioVideoRec;
207 /** Pointer to the driver instance structure. */
208 PPDMDRVINS pDrvIns;
209 /** Pointer to host audio interface. */
210 PDMIHOSTAUDIO IHostAudio;
211 /** Pointer to the console object. */
212 ComObjPtr<Console> pConsole;
213 /** Pointer to the DrvAudio port interface that is above us. */
214 PPDMIAUDIOCONNECTOR pDrvAudio;
215 /** The driver's sink for writing output to. */
216 AVRECSINK Sink;
217} DRVAUDIOVIDEOREC, *PDRVAUDIOVIDEOREC;
218
219/** Makes DRVAUDIOVIDEOREC out of PDMIHOSTAUDIO. */
220#define PDMIHOSTAUDIO_2_DRVAUDIOVIDEOREC(pInterface) \
221 ( (PDRVAUDIOVIDEOREC)((uintptr_t)pInterface - RT_OFFSETOF(DRVAUDIOVIDEOREC, IHostAudio)) )
222
223/**
224 * Initializes a recording sink.
225 *
226 * @returns IPRT status code.
227 * @param pThis Driver instance.
228 * @param pSink Sink to initialize.
229 * @param pConParms Container parameters to set.
230 * @param pCodecParms Codec parameters to set.
231 */
232static int avRecSinkInit(PDRVAUDIOVIDEOREC pThis, PAVRECSINK pSink, PAVRECCONTAINERPARMS pConParms, PAVRECCODECPARMS pCodecParms)
233{
234 uint32_t uHz = pCodecParms->uHz;
235 uint8_t cBits = pCodecParms->cBits;
236 uint8_t cChannels = pCodecParms->cChannels;
237 uint32_t uBitrate = pCodecParms->uBitrate;
238
239 /* Opus only supports certain input sample rates in an efficient manner.
240 * So make sure that we use those by resampling the data to the requested rate. */
241 if (uHz > 24000) uHz = AVREC_OPUS_HZ_MAX;
242 else if (uHz > 16000) uHz = 24000;
243 else if (uHz > 12000) uHz = 16000;
244 else if (uHz > 8000 ) uHz = 12000;
245 else uHz = 8000;
246
247 if (cChannels > 2)
248 {
249 LogRel(("VideoRec: More than 2 (stereo) channels are not supported at the moment\n"));
250 cChannels = 2;
251 }
252
253 LogRel2(("VideoRec: Recording audio in %RU16Hz, %RU8 channels, %RU32 bitrate\n", uHz, cChannels, uBitrate));
254
255 int orc;
256 OpusEncoder *pEnc = opus_encoder_create(uHz, cChannels, OPUS_APPLICATION_AUDIO, &orc);
257 if (orc != OPUS_OK)
258 {
259 LogRel(("VideoRec: Audio codec failed to initialize: %s\n", opus_strerror(orc)));
260 return VERR_AUDIO_BACKEND_INIT_FAILED;
261 }
262
263 AssertPtr(pEnc);
264
265 opus_encoder_ctl(pEnc, OPUS_SET_BITRATE(uBitrate));
266 if (orc != OPUS_OK)
267 {
268 opus_encoder_destroy(pEnc);
269 pEnc = NULL;
270
271 LogRel(("VideoRec: Audio codec failed to set bitrate (%RU32): %s\n", uBitrate, opus_strerror(orc)));
272 return VERR_AUDIO_BACKEND_INIT_FAILED;
273 }
274
275 int rc;
276
277 try
278 {
279 switch (pConParms->enmType)
280 {
281 case AVRECCONTAINERTYPE_MAIN_CONSOLE:
282 {
283 pSink->Con.Main.pConsole = pThis->pConsole;
284
285 rc = VINF_SUCCESS;
286 break;
287 }
288
289 case AVRECCONTAINERTYPE_WEBM:
290 {
291 /* If we only record audio, create our own WebM writer instance here. */
292 if (!pSink->Con.WebM.pWebM) /* Do we already have our WebM writer instance? */
293 {
294 char szFile[RTPATH_MAX];
295 if (RTStrPrintf(szFile, sizeof(szFile), "%s%s",
296 VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH, "DrvAudioVideoRec.webm" /** @todo Make this configurable. */))
297 {
298 /** @todo Add sink name / number to file name. */
299
300 pSink->Con.WebM.pWebM = new WebMWriter();
301 rc = pSink->Con.WebM.pWebM->Create(szFile,
302 /** @ŧodo Add option to add some suffix if file exists instead of overwriting? */
303 RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE,
304 WebMWriter::AudioCodec_Opus, WebMWriter::VideoCodec_None);
305 if (RT_SUCCESS(rc))
306 {
307 rc = pSink->Con.WebM.pWebM->AddAudioTrack(uHz, cChannels, cBits,
308 &pSink->Con.WebM.uTrack);
309 if (RT_FAILURE(rc))
310 LogRel(("VideoRec: Error creating audio track for file '%s' (%Rrc)\n", szFile, rc));
311 }
312 else
313 LogRel(("VideoRec: Error creating audio file '%s' (%Rrc)\n", szFile, rc));
314 }
315 else
316 {
317 AssertFailed(); /* Should never happen. */
318 LogRel(("VideoRec: Error creating audio file path\n"));
319 }
320 }
321
322 break;
323 }
324
325 default:
326 rc = VERR_NOT_SUPPORTED;
327 break;
328 }
329 }
330 catch (std::bad_alloc)
331 {
332 rc = VERR_NO_MEMORY;
333 }
334
335 if (RT_SUCCESS(rc))
336 {
337 pSink->Con.Parms.enmType = pConParms->enmType;
338
339 pSink->Codec.Parms.uHz = uHz;
340 pSink->Codec.Parms.cChannels = cChannels;
341 pSink->Codec.Parms.cBits = cBits;
342 pSink->Codec.Parms.uBitrate = uBitrate;
343
344 pSink->Codec.Opus.pEnc = pEnc;
345 pSink->Codec.Opus.msFrame = 20; /** @todo 20 ms of audio data. Make this configurable? */
346
347#ifdef VBOX_WITH_STATISTICS
348 pSink->Codec.STAM.cEncFrames = 0;
349 pSink->Codec.STAM.msEncTotal = 0;
350#endif
351 }
352 else
353 {
354 if (pEnc)
355 {
356 opus_encoder_destroy(pEnc);
357 pEnc = NULL;
358 }
359
360 LogRel(("VideoRec: Error creating sink (%Rrc)\n", rc));
361 }
362
363 return rc;
364}
365
366
367/**
368 * Shuts down (closes) a recording sink,
369 *
370 * @returns IPRT status code.
371 * @param pSink Recording sink to shut down.
372 */
373static void avRecSinkShutdown(PAVRECSINK pSink)
374{
375 AssertPtrReturnVoid(pSink);
376
377#ifdef VBOX_WITH_LIBOPUS
378 if (pSink->Codec.Opus.pEnc)
379 {
380 opus_encoder_destroy(pSink->Codec.Opus.pEnc);
381 pSink->Codec.Opus.pEnc = NULL;
382 }
383#endif
384 switch (pSink->Con.Parms.enmType)
385 {
386 case AVRECCONTAINERTYPE_WEBM:
387 {
388 if (pSink->Con.WebM.pWebM)
389 {
390 LogRel2(("VideoRec: Finished recording audio to file '%s' (%zu bytes)\n",
391 pSink->Con.WebM.pWebM->GetFileName().c_str(), pSink->Con.WebM.pWebM->GetFileSize()));
392
393 int rc2 = pSink->Con.WebM.pWebM->Close();
394 AssertRC(rc2);
395
396 delete pSink->Con.WebM.pWebM;
397 pSink->Con.WebM.pWebM = NULL;
398 }
399 break;
400 }
401
402 case AVRECCONTAINERTYPE_MAIN_CONSOLE:
403 default:
404 break;
405 }
406}
407
408
409/**
410 * Creates an audio output stream and associates it with the specified recording sink.
411 *
412 * @returns IPRT status code.
413 * @param pThis Driver instance.
414 * @param pStreamAV Audio output stream to create.
415 * @param pSink Recording sink to associate audio output stream to.
416 * @param pCfgReq Requested configuration by the audio backend.
417 * @param pCfgAcq Acquired configuration by the audio output stream.
418 */
419static int avRecCreateStreamOut(PDRVAUDIOVIDEOREC pThis, PAVRECSTREAM pStreamAV,
420 PAVRECSINK pSink, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
421{
422 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
423 AssertPtrReturn(pStreamAV, VERR_INVALID_POINTER);
424 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
425 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
426 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
427
428 if (pCfgReq->DestSource.Dest != PDMAUDIOPLAYBACKDEST_FRONT)
429 {
430 AssertFailed();
431
432 if (pCfgAcq)
433 pCfgAcq->cFrameBufferHint = 0;
434
435 LogRel2(("VideoRec: Support for surround audio not implemented yet\n"));
436 return VERR_NOT_SUPPORTED;
437 }
438
439 int rc = VINF_SUCCESS;
440
441#ifdef VBOX_WITH_LIBOPUS
442 const unsigned cFrames = 2; /** @todo Use the PreRoll param for that? */
443
444 const uint32_t csFrame = pSink->Codec.Parms.uHz / (1000 /* s in ms */ / pSink->Codec.Opus.msFrame);
445 const uint32_t cbFrame = csFrame * pSink->Codec.Parms.cChannels * (pSink->Codec.Parms.cBits / 8 /* Bytes */);
446
447 rc = RTCircBufCreate(&pStreamAV->pCircBuf, cbFrame * cFrames);
448 if (RT_SUCCESS(rc))
449 {
450 pStreamAV->pSink = pSink; /* Assign sink to stream. */
451
452 if (pCfgAcq)
453 {
454 /* Make sure to let the driver backend know that we need the audio data in
455 * a specific sampling rate Opus is optimized for. */
456 pCfgAcq->Props.uHz = pSink->Codec.Parms.uHz;
457 pCfgAcq->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfgAcq->Props.cBits, pCfgAcq->Props.cChannels);
458 pCfgAcq->cFrameBufferHint = _4K; /** @todo Make this configurable. */
459 }
460 }
461#else
462 RT_NOREF(pThis, pSink, pStreamAV, pCfgReq, pCfgAcq);
463 rc = VERR_NOT_SUPPORTED;
464#endif /* VBOX_WITH_LIBOPUS */
465
466 LogFlowFuncLeaveRC(rc);
467 return rc;
468}
469
470
471/**
472 * Destroys (closes) an audio output stream.
473 *
474 * @returns IPRT status code.
475 * @param pThis Driver instance.
476 * @param pStreamAV Audio output stream to destroy.
477 */
478static int avRecDestroyStreamOut(PDRVAUDIOVIDEOREC pThis, PAVRECSTREAM pStreamAV)
479{
480 RT_NOREF(pThis);
481
482 if (pStreamAV->pCircBuf)
483 {
484 RTCircBufDestroy(pStreamAV->pCircBuf);
485 pStreamAV->pCircBuf = NULL;
486 }
487
488 return VINF_SUCCESS;
489}
490
491
492/**
493 * Controls an audio output stream
494 *
495 * @returns IPRT status code.
496 * @param pThis Driver instance.
497 * @param pStreamAV Audio output stream to control.
498 * @param enmStreamCmd Stream command to issue.
499 */
500static int avRecControlStreamOut(PDRVAUDIOVIDEOREC pThis,
501 PAVRECSTREAM pStreamAV, PDMAUDIOSTREAMCMD enmStreamCmd)
502{
503 RT_NOREF(pThis, pStreamAV);
504
505 switch (enmStreamCmd)
506 {
507 case PDMAUDIOSTREAMCMD_ENABLE:
508 case PDMAUDIOSTREAMCMD_DISABLE:
509 case PDMAUDIOSTREAMCMD_RESUME:
510 case PDMAUDIOSTREAMCMD_PAUSE:
511 break;
512
513 default:
514 AssertMsgFailed(("Invalid command %ld\n", enmStreamCmd));
515 break;
516 }
517
518 return VINF_SUCCESS;
519}
520
521
522/**
523 * @interface_method_impl{PDMIHOSTAUDIO,pfnInit}
524 */
525static DECLCALLBACK(int) drvAudioVideoRecInit(PPDMIHOSTAUDIO pInterface)
526{
527 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
528
529 LogFlowFuncEnter();
530
531 PDRVAUDIOVIDEOREC pThis = PDMIHOSTAUDIO_2_DRVAUDIOVIDEOREC(pInterface);
532
533 AVRECCONTAINERPARMS ContainerParms;
534 ContainerParms.enmType = AVRECCONTAINERTYPE_MAIN_CONSOLE; /** @todo Make this configurable. */
535
536 AVRECCODECPARMS CodecParms;
537 CodecParms.uHz = AVREC_OPUS_HZ_MAX; /** @todo Make this configurable. */
538 CodecParms.cChannels = 2; /** @todo Make this configurable. */
539 CodecParms.cBits = 16; /** @todo Make this configurable. */
540 CodecParms.uBitrate = 196000; /** @todo Make this configurable. */
541
542 int rc = avRecSinkInit(pThis, &pThis->Sink, &ContainerParms, &CodecParms);
543 if (RT_FAILURE(rc))
544 {
545 LogRel(("VideoRec: Audio recording driver failed to initialize, rc=%Rrc\n", rc));
546 }
547 else
548 LogRel2(("VideoRec: Audio recording driver initialized\n"));
549
550 return rc;
551}
552
553
554/**
555 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
556 */
557static DECLCALLBACK(int) drvAudioVideoRecStreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
558 void *pvBuf, uint32_t cxBuf, uint32_t *pcxRead)
559{
560 RT_NOREF(pInterface, pStream, pvBuf, cxBuf);
561
562 if (pcxRead)
563 *pcxRead = 0;
564
565 return VINF_SUCCESS;
566}
567
568
569/**
570 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
571 */
572static DECLCALLBACK(int) drvAudioVideoRecStreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
573 const void *pvBuf, uint32_t cxBuf, uint32_t *pcxWritten)
574{
575 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
576 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
577 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
578 AssertReturn(cxBuf, VERR_INVALID_PARAMETER);
579 /* pcxWritten is optional. */
580
581 PDRVAUDIOVIDEOREC pThis = PDMIHOSTAUDIO_2_DRVAUDIOVIDEOREC(pInterface);
582 RT_NOREF(pThis);
583 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
584
585 int rc = VINF_SUCCESS;
586
587 uint32_t cbWrittenTotal = 0;
588
589 /*
590 * Call the encoder with the data.
591 */
592#ifdef VBOX_WITH_LIBOPUS
593 PAVRECSINK pSink = pStreamAV->pSink;
594 AssertPtr(pSink);
595 PRTCIRCBUF pCircBuf = pStreamAV->pCircBuf;
596 AssertPtr(pCircBuf);
597
598 void *pvCircBuf;
599 size_t cbCircBuf;
600
601 uint32_t cbToWrite = cxBuf;
602
603 /*
604 * Fetch as much as we can into our internal ring buffer.
605 */
606 while ( cbToWrite
607 && RTCircBufFree(pCircBuf))
608 {
609 RTCircBufAcquireWriteBlock(pCircBuf, cbToWrite, &pvCircBuf, &cbCircBuf);
610
611 if (cbCircBuf)
612 {
613 memcpy(pvCircBuf, (uint8_t *)pvBuf + cbWrittenTotal, cbCircBuf),
614 cbWrittenTotal += cbCircBuf;
615 Assert(cbToWrite >= cbCircBuf);
616 cbToWrite -= cbCircBuf;
617 }
618
619 RTCircBufReleaseWriteBlock(pCircBuf, cbCircBuf);
620
621 if ( RT_FAILURE(rc)
622 || !cbCircBuf)
623 {
624 break;
625 }
626 }
627
628 /*
629 * Process our internal ring buffer and encode the data.
630 */
631
632 uint8_t abSrc[_64K]; /** @todo Fix! */
633 size_t cbSrc;
634
635 const uint32_t csFrame = pSink->Codec.Parms.uHz / (1000 /* s in ms */ / pSink->Codec.Opus.msFrame);
636 const uint32_t cbFrame = csFrame * pSink->Codec.Parms.cChannels * (pSink->Codec.Parms.cBits / 8 /* Bytes */);
637
638 /* Only encode data if we have data for the given time period (or more). */
639 while (RTCircBufUsed(pCircBuf) >= cbFrame)
640 {
641 cbSrc = 0;
642
643 while (cbSrc < cbFrame)
644 {
645 RTCircBufAcquireReadBlock(pCircBuf, cbFrame - cbSrc, &pvCircBuf, &cbCircBuf);
646
647 if (cbCircBuf)
648 {
649 memcpy(&abSrc[cbSrc], pvCircBuf, cbCircBuf);
650
651 cbSrc += cbCircBuf;
652 Assert(cbSrc <= sizeof(abSrc));
653 }
654
655 RTCircBufReleaseReadBlock(pCircBuf, cbCircBuf);
656
657 if (!cbCircBuf)
658 break;
659 }
660
661# ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
662 RTFILE fh;
663 RTFileOpen(&fh, VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "DrvAudioVideoRec.pcm",
664 RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
665 RTFileWrite(fh, abSrc, cbSrc, NULL);
666 RTFileClose(fh);
667# endif
668
669 Assert(cbSrc == cbFrame);
670
671 /*
672 * Opus always encodes PER FRAME, that is, exactly 2.5, 5, 10, 20, 40 or 60 ms of audio data.
673 *
674 * A packet can have up to 120ms worth of audio data.
675 * Anything > 120ms of data will result in a "corrupted package" error message by
676 * by decoding application.
677 */
678 uint8_t abDst[_64K]; /** @todo Fix! */
679 size_t cbDst = sizeof(abDst);
680
681 /* Call the encoder to encode one frame per iteration. */
682 opus_int32 cbWritten = opus_encode(pSink->Codec.Opus.pEnc,
683 (opus_int16 *)abSrc, csFrame, abDst, cbDst);
684 if (cbWritten > 0)
685 {
686# ifdef VBOX_WITH_STATISTICS
687 /* Get overall frames encoded. */
688 const uint32_t cEncFrames = opus_packet_get_nb_frames(abDst, cbWritten);
689
690 pSink->Codec.STAM.cEncFrames += cEncFrames;
691 pSink->Codec.STAM.msEncTotal += pSink->Codec.Opus.msFrame * cEncFrames;
692
693 LogFunc(("%RU64ms [%RU64 frames]: cbSrc=%zu, cbDst=%zu, cEncFrames=%RU32\n",
694 pSink->Codec.STAM.msEncTotal, pSink->Codec.STAM.cEncFrames, cbSrc, cbDst, cEncFrames));
695# endif
696 Assert((uint32_t)cbWritten <= cbDst);
697 cbDst = RT_MIN((uint32_t)cbWritten, cbDst); /* Update cbDst to actual bytes encoded (written). */
698
699 switch (pSink->Con.Parms.enmType)
700 {
701 case AVRECCONTAINERTYPE_MAIN_CONSOLE:
702 {
703 HRESULT hr = pSink->Con.Main.pConsole->i_audioVideoRecSendAudio(abDst, cbDst, RTTimeMilliTS() /* Now */);
704 Assert(hr == S_OK);
705 RT_NOREF(hr);
706
707 break;
708 }
709
710 case AVRECCONTAINERTYPE_WEBM:
711 {
712 WebMWriter::BlockData_Opus blockData = { abDst, cbDst, RTTimeMilliTS() };
713 rc = pSink->Con.WebM.pWebM->WriteBlock(pSink->Con.WebM.uTrack, &blockData, sizeof(blockData));
714 AssertRC(rc);
715
716 break;
717 }
718
719 default:
720 AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
721 break;
722 }
723 }
724 else if (cbWritten < 0)
725 {
726 AssertMsgFailed(("Encoding failed: %s\n", opus_strerror(cbWritten)));
727 rc = VERR_INVALID_PARAMETER;
728 }
729
730 if (RT_FAILURE(rc))
731 break;
732 }
733#else
734 rc = VERR_NOT_SUPPORTED;
735#endif /* VBOX_WITH_LIBOPUS */
736
737 /*
738 * Always report back all samples acquired, regardless of whether the
739 * encoder actually did process those.
740 */
741 if (pcxWritten)
742 *pcxWritten = cbWrittenTotal;
743
744 LogFlowFunc(("csReadTotal=%RU32, rc=%Rrc\n", cbWrittenTotal, rc));
745 return rc;
746}
747
748
749/**
750 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
751 */
752static DECLCALLBACK(int) drvAudioVideoRecGetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
753{
754 RT_NOREF(pInterface);
755 AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
756
757 pBackendCfg->cbStreamOut = sizeof(AVRECSTREAM);
758 pBackendCfg->cbStreamIn = 0;
759 pBackendCfg->cMaxStreamsIn = 0;
760 pBackendCfg->cMaxStreamsOut = UINT32_MAX;
761
762 return VINF_SUCCESS;
763}
764
765
766/**
767 * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown}
768 */
769static DECLCALLBACK(void) drvAudioVideoRecShutdown(PPDMIHOSTAUDIO pInterface)
770{
771 LogFlowFuncEnter();
772
773 PDRVAUDIOVIDEOREC pThis = PDMIHOSTAUDIO_2_DRVAUDIOVIDEOREC(pInterface);
774
775 avRecSinkShutdown(&pThis->Sink);
776}
777
778
779/**
780 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
781 */
782static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvAudioVideoRecGetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
783{
784 RT_NOREF(enmDir);
785 AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN);
786
787 return PDMAUDIOBACKENDSTS_RUNNING;
788}
789
790
791/**
792 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
793 */
794static DECLCALLBACK(int) drvAudioVideoRecStreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
795 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
796{
797 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
798 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
799 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
800
801 if (pCfgReq->enmDir == PDMAUDIODIR_IN)
802 return VERR_NOT_SUPPORTED;
803
804 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
805
806 PDRVAUDIOVIDEOREC pThis = PDMIHOSTAUDIO_2_DRVAUDIOVIDEOREC(pInterface);
807 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
808
809 /* For now we only have one sink, namely the driver's one.
810 * Later each stream could have its own one, to e.g. router different stream to different sinks .*/
811 PAVRECSINK pSink = &pThis->Sink;
812
813 int rc = avRecCreateStreamOut(pThis, pStreamAV, pSink, pCfgReq, pCfgAcq);
814 if (RT_SUCCESS(rc))
815 {
816 pStreamAV->pCfg = DrvAudioHlpStreamCfgDup(pCfgAcq);
817 if (!pStreamAV->pCfg)
818 rc = VERR_NO_MEMORY;
819 }
820
821 return rc;
822}
823
824
825/**
826 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
827 */
828static DECLCALLBACK(int) drvAudioVideoRecStreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
829{
830 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
831 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
832
833 PDRVAUDIOVIDEOREC pThis = PDMIHOSTAUDIO_2_DRVAUDIOVIDEOREC(pInterface);
834 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
835
836 if (!pStreamAV->pCfg) /* Not (yet) configured? Skip. */
837 return VINF_SUCCESS;
838
839 int rc = VINF_SUCCESS;
840
841 if (pStreamAV->pCfg->enmDir == PDMAUDIODIR_OUT)
842 rc = avRecDestroyStreamOut(pThis, pStreamAV);
843
844 if (RT_SUCCESS(rc))
845 {
846 DrvAudioHlpStreamCfgFree(pStreamAV->pCfg);
847 pStreamAV->pCfg = NULL;
848 }
849
850 return rc;
851}
852
853
854/**
855 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}
856 */
857static DECLCALLBACK(int) drvAudioVideoRecStreamControl(PPDMIHOSTAUDIO pInterface,
858 PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
859{
860 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
861 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
862
863 PDRVAUDIOVIDEOREC pThis = PDMIHOSTAUDIO_2_DRVAUDIOVIDEOREC(pInterface);
864 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
865
866 if (!pStreamAV->pCfg) /* Not (yet) configured? Skip. */
867 return VINF_SUCCESS;
868
869 if (pStreamAV->pCfg->enmDir == PDMAUDIODIR_OUT)
870 return avRecControlStreamOut(pThis, pStreamAV, enmStreamCmd);
871
872 return VINF_SUCCESS;
873}
874
875
876/**
877 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
878 */
879static DECLCALLBACK(uint32_t) drvAudioVideoRecStreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
880{
881 RT_NOREF(pInterface, pStream);
882
883 return 0; /* Video capturing does not provide any input. */
884}
885
886
887/**
888 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
889 */
890static DECLCALLBACK(uint32_t) drvAudioVideoRecStreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
891{
892 RT_NOREF(pInterface, pStream);
893
894 return UINT32_MAX;
895}
896
897
898/**
899 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus}
900 */
901static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvAudioVideoRecStreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
902{
903 RT_NOREF(pInterface, pStream);
904
905 return (PDMAUDIOSTREAMSTS_FLAG_INITIALIZED | PDMAUDIOSTREAMSTS_FLAG_ENABLED);
906}
907
908
909/**
910 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamIterate}
911 */
912static DECLCALLBACK(int) drvAudioVideoRecStreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
913{
914 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
915 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
916
917 LogFlowFuncEnter();
918
919 /* Nothing to do here for video recording. */
920 return VINF_SUCCESS;
921}
922
923
924/**
925 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
926 */
927static DECLCALLBACK(void *) drvAudioVideoRecQueryInterface(PPDMIBASE pInterface, const char *pszIID)
928{
929 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
930 PDRVAUDIOVIDEOREC pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVIDEOREC);
931
932 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
933 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
934 return NULL;
935}
936
937
938AudioVideoRec::AudioVideoRec(Console *pConsole)
939 : mpDrv(NULL)
940 , mpConsole(pConsole)
941{
942}
943
944
945AudioVideoRec::~AudioVideoRec(void)
946{
947 if (mpDrv)
948 {
949 mpDrv->pAudioVideoRec = NULL;
950 mpDrv = NULL;
951 }
952}
953
954
955/**
956 * Construct a audio video recording driver instance.
957 *
958 * @copydoc FNPDMDRVCONSTRUCT
959 */
960/* static */
961DECLCALLBACK(int) AudioVideoRec::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
962{
963 RT_NOREF(fFlags);
964
965 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
966 PDRVAUDIOVIDEOREC pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVIDEOREC);
967
968 AssertPtrReturn(pDrvIns, VERR_INVALID_POINTER);
969 AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
970
971 LogRel(("Audio: Initializing video recording audio driver\n"));
972 LogFlowFunc(("fFlags=0x%x\n", fFlags));
973
974 AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
975 ("Configuration error: Not possible to attach anything to this driver!\n"),
976 VERR_PDM_DRVINS_NO_ATTACH);
977
978 /*
979 * Init the static parts.
980 */
981 pThis->pDrvIns = pDrvIns;
982 /* IBase */
983 pDrvIns->IBase.pfnQueryInterface = drvAudioVideoRecQueryInterface;
984 /* IHostAudio */
985 PDMAUDIO_IHOSTAUDIO_CALLBACKS(drvAudioVideoRec);
986
987 /*
988 * Get the Console object pointer.
989 */
990 void *pvUser;
991 int rc = CFGMR3QueryPtr(pCfg, "ObjectConsole", &pvUser); /** @todo r=andy Get rid of this hack and use IHostAudio::SetCallback. */
992 AssertMsgRCReturn(rc, ("Confguration error: No/bad \"ObjectConsole\" value, rc=%Rrc\n", rc), rc);
993
994 /* CFGM tree saves the pointer to Console in the Object node of AudioVideoRec. */
995 pThis->pConsole = (Console *)pvUser;
996
997 /*
998 * Get the pointer to the audio driver instance.
999 */
1000 rc = CFGMR3QueryPtr(pCfg, "Object", &pvUser); /** @todo r=andy Get rid of this hack and use IHostAudio::SetCallback. */
1001 AssertMsgRCReturn(rc, ("Confguration error: No/bad \"Object\" value, rc=%Rrc\n", rc), rc);
1002
1003 pThis->pAudioVideoRec = (AudioVideoRec *)pvUser;
1004 pThis->pAudioVideoRec->mpDrv = pThis;
1005
1006 /*
1007 * Get the interface for the above driver (DrvAudio) to make mixer/conversion calls.
1008 * Described in CFGM tree.
1009 */
1010 pThis->pDrvAudio = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIAUDIOCONNECTOR);
1011 AssertMsgReturn(pThis->pDrvAudio, ("Configuration error: No upper interface specified!\n"), VERR_PDM_MISSING_INTERFACE_ABOVE);
1012
1013 return VINF_SUCCESS;
1014}
1015
1016
1017/**
1018 * @interface_method_impl{PDMDRVREG,pfnDestruct}
1019 */
1020/* static */
1021DECLCALLBACK(void) AudioVideoRec::drvDestruct(PPDMDRVINS pDrvIns)
1022{
1023 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
1024 PDRVAUDIOVIDEOREC pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVIDEOREC);
1025 LogFlowFuncEnter();
1026
1027 /*
1028 * If the AudioVideoRec object is still alive, we must clear it's reference to
1029 * us since we'll be invalid when we return from this method.
1030 */
1031 if (pThis->pAudioVideoRec)
1032 {
1033 pThis->pAudioVideoRec->mpDrv = NULL;
1034 pThis->pAudioVideoRec = NULL;
1035 }
1036}
1037
1038
1039/**
1040 * Video recording audio driver registration record.
1041 */
1042const PDMDRVREG AudioVideoRec::DrvReg =
1043{
1044 PDM_DRVREG_VERSION,
1045 /* szName */
1046 "AudioVideoRec",
1047 /* szRCMod */
1048 "",
1049 /* szR0Mod */
1050 "",
1051 /* pszDescription */
1052 "Audio driver for video recording",
1053 /* fFlags */
1054 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
1055 /* fClass. */
1056 PDM_DRVREG_CLASS_AUDIO,
1057 /* cMaxInstances */
1058 ~0U,
1059 /* cbInstance */
1060 sizeof(DRVAUDIOVIDEOREC),
1061 /* pfnConstruct */
1062 AudioVideoRec::drvConstruct,
1063 /* pfnDestruct */
1064 AudioVideoRec::drvDestruct,
1065 /* pfnRelocate */
1066 NULL,
1067 /* pfnIOCtl */
1068 NULL,
1069 /* pfnPowerOn */
1070 NULL,
1071 /* pfnReset */
1072 NULL,
1073 /* pfnSuspend */
1074 NULL,
1075 /* pfnResume */
1076 NULL,
1077 /* pfnAttach */
1078 NULL,
1079 /* pfnDetach */
1080 NULL,
1081 /* pfnPowerOff */
1082 NULL,
1083 /* pfnSoftReset */
1084 NULL,
1085 /* u32EndVersion */
1086 PDM_DRVREG_VERSION
1087};
1088
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