VirtualBox

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

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

Build fix.

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