VirtualBox

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

Last change on this file since 69194 was 69194, 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: 36.8 KB
Line 
1/* $Id: DrvAudioVideoRec.cpp 69194 2017-10-24 09:23:34Z 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.215389.xyz. 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 /** Timestamp (in ms) of when the sink was created. */
235 uint64_t tsStartMs;
236} AVRECSINK, *PAVRECSINK;
237
238/**
239 * Audio video recording (output) stream.
240 */
241typedef struct AVRECSTREAM
242{
243 /** The stream's acquired configuration. */
244 PPDMAUDIOSTREAMCFG pCfg;
245 /** (Audio) frame buffer. */
246 PRTCIRCBUF pCircBuf;
247 /** Pointer to sink to use for writing. */
248 PAVRECSINK pSink;
249 /** Last encoded PTS (in ms). */
250 uint64_t uLastPTSMs;
251} AVRECSTREAM, *PAVRECSTREAM;
252
253/**
254 * Video recording audio driver instance data.
255 */
256typedef struct DRVAUDIOVIDEOREC
257{
258 /** Pointer to audio video recording object. */
259 AudioVideoRec *pAudioVideoRec;
260 /** Pointer to the driver instance structure. */
261 PPDMDRVINS pDrvIns;
262 /** Pointer to host audio interface. */
263 PDMIHOSTAUDIO IHostAudio;
264 /** Pointer to the console object. */
265 ComObjPtr<Console> pConsole;
266 /** Pointer to the DrvAudio port interface that is above us. */
267 PPDMIAUDIOCONNECTOR pDrvAudio;
268 /** The driver's sink for writing output to. */
269 AVRECSINK Sink;
270} DRVAUDIOVIDEOREC, *PDRVAUDIOVIDEOREC;
271
272/** Makes DRVAUDIOVIDEOREC out of PDMIHOSTAUDIO. */
273#define PDMIHOSTAUDIO_2_DRVAUDIOVIDEOREC(pInterface) \
274 ( (PDRVAUDIOVIDEOREC)((uintptr_t)pInterface - RT_OFFSETOF(DRVAUDIOVIDEOREC, IHostAudio)) )
275
276/**
277 * Initializes a recording sink.
278 *
279 * @returns IPRT status code.
280 * @param pThis Driver instance.
281 * @param pSink Sink to initialize.
282 * @param pConParms Container parameters to set.
283 * @param pCodecParms Codec parameters to set.
284 */
285static int avRecSinkInit(PDRVAUDIOVIDEOREC pThis, PAVRECSINK pSink, PAVRECCONTAINERPARMS pConParms, PAVRECCODECPARMS pCodecParms)
286{
287 uint32_t uHz = pCodecParms->uHz;
288 uint8_t cBits = pCodecParms->cBits;
289 uint8_t cChannels = pCodecParms->cChannels;
290 uint32_t uBitrate = pCodecParms->uBitrate;
291
292 /* Opus only supports certain input sample rates in an efficient manner.
293 * So make sure that we use those by resampling the data to the requested rate. */
294 if (uHz > 24000) uHz = AVREC_OPUS_HZ_MAX;
295 else if (uHz > 16000) uHz = 24000;
296 else if (uHz > 12000) uHz = 16000;
297 else if (uHz > 8000 ) uHz = 12000;
298 else uHz = 8000;
299
300 if (cChannels > 2)
301 {
302 LogRel(("VideoRec: More than 2 (stereo) channels are not supported at the moment\n"));
303 cChannels = 2;
304 }
305
306 LogRel2(("VideoRec: Recording audio in %RU16Hz, %RU8 channels, %RU32 bitrate\n", uHz, cChannels, uBitrate));
307
308 int orc;
309 OpusEncoder *pEnc = opus_encoder_create(uHz, cChannels, OPUS_APPLICATION_AUDIO, &orc);
310 if (orc != OPUS_OK)
311 {
312 LogRel(("VideoRec: Audio codec failed to initialize: %s\n", opus_strerror(orc)));
313 return VERR_AUDIO_BACKEND_INIT_FAILED;
314 }
315
316 AssertPtr(pEnc);
317
318 opus_encoder_ctl(pEnc, OPUS_SET_BITRATE(uBitrate));
319 if (orc != OPUS_OK)
320 {
321 opus_encoder_destroy(pEnc);
322 pEnc = NULL;
323
324 LogRel(("VideoRec: Audio codec failed to set bitrate (%RU32): %s\n", uBitrate, opus_strerror(orc)));
325 return VERR_AUDIO_BACKEND_INIT_FAILED;
326 }
327
328 int rc = VINF_SUCCESS;
329
330 try
331 {
332 switch (pConParms->enmType)
333 {
334 case AVRECCONTAINERTYPE_MAIN_CONSOLE:
335 {
336 if (pThis->pConsole)
337 {
338 pSink->Con.Main.pConsole = pThis->pConsole;
339 }
340 else
341 rc = VERR_NOT_SUPPORTED;
342 break;
343 }
344
345 case AVRECCONTAINERTYPE_WEBM:
346 {
347#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
348 /* If we only record audio, create our own WebM writer instance here. */
349 if (!pSink->Con.WebM.pWebM) /* Do we already have our WebM writer instance? */
350 {
351 char szFile[RTPATH_MAX];
352 if (RTStrPrintf(szFile, sizeof(szFile), "%s%s",
353 VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH, "DrvAudioVideoRec.webm"))
354 {
355 /** @todo Add sink name / number to file name. */
356
357 pSink->Con.WebM.pWebM = new WebMWriter();
358 rc = pSink->Con.WebM.pWebM->Create(szFile,
359 /** @todo Add option to add some suffix if file exists instead of overwriting? */
360 RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE,
361 WebMWriter::AudioCodec_Opus, WebMWriter::VideoCodec_None);
362 if (RT_SUCCESS(rc))
363 {
364 rc = pSink->Con.WebM.pWebM->AddAudioTrack(uHz, cChannels, cBits,
365 &pSink->Con.WebM.uTrack);
366 if (RT_SUCCESS(rc))
367 {
368 LogRel(("VideoRec: Recording audio to file '%s'\n", szFile));
369 }
370 else
371 LogRel(("VideoRec: Error creating audio track for file '%s' (%Rrc)\n", szFile, rc));
372 }
373 else
374 LogRel(("VideoRec: Error creating audio file '%s' (%Rrc)\n", szFile, rc));
375 }
376 else
377 {
378 AssertFailed(); /* Should never happen. */
379 LogRel(("VideoRec: Error creating audio file path\n"));
380 }
381 }
382#else
383 rc = VERR_NOT_SUPPORTED;
384#endif /* VBOX_AUDIO_DEBUG_DUMP_PCM_DATA */
385 break;
386 }
387
388 default:
389 rc = VERR_NOT_SUPPORTED;
390 break;
391 }
392 }
393 catch (std::bad_alloc)
394 {
395#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
396 rc = VERR_NO_MEMORY;
397#endif
398 }
399
400 if (RT_SUCCESS(rc))
401 {
402 pSink->Con.Parms.enmType = pConParms->enmType;
403
404 pSink->Codec.Parms.uHz = uHz;
405 pSink->Codec.Parms.cChannels = cChannels;
406 pSink->Codec.Parms.cBits = cBits;
407 pSink->Codec.Parms.uBitrate = uBitrate;
408
409 pSink->Codec.Opus.pEnc = pEnc;
410 pSink->Codec.Opus.msFrame = 20; /** @todo 20 ms of audio data. Make this configurable? */
411
412#ifdef VBOX_WITH_STATISTICS
413 pSink->Codec.STAM.cEncFrames = 0;
414 pSink->Codec.STAM.msEncTotal = 0;
415#endif
416
417 pSink->tsStartMs = RTTimeMilliTS();
418 }
419 else
420 {
421 if (pEnc)
422 {
423 opus_encoder_destroy(pEnc);
424 pEnc = NULL;
425 }
426
427 LogRel(("VideoRec: Error creating sink (%Rrc)\n", rc));
428 }
429
430 return rc;
431}
432
433
434/**
435 * Shuts down (closes) a recording sink,
436 *
437 * @returns IPRT status code.
438 * @param pSink Recording sink to shut down.
439 */
440static void avRecSinkShutdown(PAVRECSINK pSink)
441{
442 AssertPtrReturnVoid(pSink);
443
444#ifdef VBOX_WITH_LIBOPUS
445 if (pSink->Codec.Opus.pEnc)
446 {
447 opus_encoder_destroy(pSink->Codec.Opus.pEnc);
448 pSink->Codec.Opus.pEnc = NULL;
449 }
450#endif
451 switch (pSink->Con.Parms.enmType)
452 {
453 case AVRECCONTAINERTYPE_WEBM:
454 {
455 if (pSink->Con.WebM.pWebM)
456 {
457 LogRel2(("VideoRec: Finished recording audio to file '%s' (%zu bytes)\n",
458 pSink->Con.WebM.pWebM->GetFileName().c_str(), pSink->Con.WebM.pWebM->GetFileSize()));
459
460 int rc2 = pSink->Con.WebM.pWebM->Close();
461 AssertRC(rc2);
462
463 delete pSink->Con.WebM.pWebM;
464 pSink->Con.WebM.pWebM = NULL;
465 }
466 break;
467 }
468
469 case AVRECCONTAINERTYPE_MAIN_CONSOLE:
470 default:
471 break;
472 }
473}
474
475
476/**
477 * Creates an audio output stream and associates it with the specified recording sink.
478 *
479 * @returns IPRT status code.
480 * @param pThis Driver instance.
481 * @param pStreamAV Audio output stream to create.
482 * @param pSink Recording sink to associate audio output stream to.
483 * @param pCfgReq Requested configuration by the audio backend.
484 * @param pCfgAcq Acquired configuration by the audio output stream.
485 */
486static int avRecCreateStreamOut(PDRVAUDIOVIDEOREC pThis, PAVRECSTREAM pStreamAV,
487 PAVRECSINK pSink, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
488{
489 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
490 AssertPtrReturn(pStreamAV, VERR_INVALID_POINTER);
491 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
492 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
493 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
494
495 if (pCfgReq->DestSource.Dest != PDMAUDIOPLAYBACKDEST_FRONT)
496 {
497 AssertFailed();
498
499 if (pCfgAcq)
500 pCfgAcq->cFrameBufferHint = 0;
501
502 LogRel2(("VideoRec: Support for surround audio not implemented yet\n"));
503 return VERR_NOT_SUPPORTED;
504 }
505
506 int rc = VINF_SUCCESS;
507
508#ifdef VBOX_WITH_LIBOPUS
509 const unsigned cFrames = 2; /** @todo Use the PreRoll param for that? */
510
511 const uint32_t csFrame = pSink->Codec.Parms.uHz / (1000 /* s in ms */ / pSink->Codec.Opus.msFrame);
512 const uint32_t cbFrame = csFrame * pSink->Codec.Parms.cChannels * (pSink->Codec.Parms.cBits / 8 /* Bytes */);
513
514 rc = RTCircBufCreate(&pStreamAV->pCircBuf, cbFrame * cFrames);
515 if (RT_SUCCESS(rc))
516 {
517 pStreamAV->pSink = pSink; /* Assign sink to stream. */
518 pStreamAV->uLastPTSMs = 0;
519
520 if (pCfgAcq)
521 {
522 /* Make sure to let the driver backend know that we need the audio data in
523 * a specific sampling rate Opus is optimized for. */
524 pCfgAcq->Props.uHz = pSink->Codec.Parms.uHz;
525 pCfgAcq->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfgAcq->Props.cBits, pCfgAcq->Props.cChannels);
526 pCfgAcq->cFrameBufferHint = _4K; /** @todo Make this configurable. */
527 }
528 }
529#else
530 RT_NOREF(pThis, pSink, pStreamAV, pCfgReq, pCfgAcq);
531 rc = VERR_NOT_SUPPORTED;
532#endif /* VBOX_WITH_LIBOPUS */
533
534 LogFlowFuncLeaveRC(rc);
535 return rc;
536}
537
538
539/**
540 * Destroys (closes) an audio output stream.
541 *
542 * @returns IPRT status code.
543 * @param pThis Driver instance.
544 * @param pStreamAV Audio output stream to destroy.
545 */
546static int avRecDestroyStreamOut(PDRVAUDIOVIDEOREC pThis, PAVRECSTREAM pStreamAV)
547{
548 RT_NOREF(pThis);
549
550 if (pStreamAV->pCircBuf)
551 {
552 RTCircBufDestroy(pStreamAV->pCircBuf);
553 pStreamAV->pCircBuf = NULL;
554 }
555
556 return VINF_SUCCESS;
557}
558
559
560/**
561 * Controls an audio output stream
562 *
563 * @returns IPRT status code.
564 * @param pThis Driver instance.
565 * @param pStreamAV Audio output stream to control.
566 * @param enmStreamCmd Stream command to issue.
567 */
568static int avRecControlStreamOut(PDRVAUDIOVIDEOREC pThis,
569 PAVRECSTREAM pStreamAV, PDMAUDIOSTREAMCMD enmStreamCmd)
570{
571 RT_NOREF(pThis, pStreamAV);
572
573 switch (enmStreamCmd)
574 {
575 case PDMAUDIOSTREAMCMD_ENABLE:
576 case PDMAUDIOSTREAMCMD_DISABLE:
577 case PDMAUDIOSTREAMCMD_RESUME:
578 case PDMAUDIOSTREAMCMD_PAUSE:
579 break;
580
581 default:
582 AssertMsgFailed(("Invalid command %ld\n", enmStreamCmd));
583 break;
584 }
585
586 return VINF_SUCCESS;
587}
588
589
590/**
591 * @interface_method_impl{PDMIHOSTAUDIO,pfnInit}
592 */
593static DECLCALLBACK(int) drvAudioVideoRecInit(PPDMIHOSTAUDIO pInterface)
594{
595 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
596
597 LogFlowFuncEnter();
598
599 PDRVAUDIOVIDEOREC pThis = PDMIHOSTAUDIO_2_DRVAUDIOVIDEOREC(pInterface);
600
601 AVRECCONTAINERPARMS ContainerParms;
602 ContainerParms.enmType = AVRECCONTAINERTYPE_MAIN_CONSOLE; /** @todo Make this configurable. */
603
604 AVRECCODECPARMS CodecParms;
605 CodecParms.uHz = AVREC_OPUS_HZ_MAX; /** @todo Make this configurable. */
606 CodecParms.cChannels = 2; /** @todo Make this configurable. */
607 CodecParms.cBits = 16; /** @todo Make this configurable. */
608 CodecParms.uBitrate = 196000; /** @todo Make this configurable. */
609
610 int rc = avRecSinkInit(pThis, &pThis->Sink, &ContainerParms, &CodecParms);
611 if (RT_FAILURE(rc))
612 {
613 LogRel(("VideoRec: Audio recording driver failed to initialize, rc=%Rrc\n", rc));
614 }
615 else
616 LogRel2(("VideoRec: Audio recording driver initialized\n"));
617
618 return rc;
619}
620
621
622/**
623 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
624 */
625static DECLCALLBACK(int) drvAudioVideoRecStreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
626 void *pvBuf, uint32_t cxBuf, uint32_t *pcxRead)
627{
628 RT_NOREF(pInterface, pStream, pvBuf, cxBuf);
629
630 if (pcxRead)
631 *pcxRead = 0;
632
633 return VINF_SUCCESS;
634}
635
636
637/**
638 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
639 */
640static DECLCALLBACK(int) drvAudioVideoRecStreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
641 const void *pvBuf, uint32_t cxBuf, uint32_t *pcxWritten)
642{
643 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
644 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
645 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
646 AssertReturn(cxBuf, VERR_INVALID_PARAMETER);
647 /* pcxWritten is optional. */
648
649 PDRVAUDIOVIDEOREC pThis = PDMIHOSTAUDIO_2_DRVAUDIOVIDEOREC(pInterface);
650 RT_NOREF(pThis);
651 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
652
653 int rc = VINF_SUCCESS;
654
655 uint32_t cbWrittenTotal = 0;
656
657 /*
658 * Call the encoder with the data.
659 */
660#ifdef VBOX_WITH_LIBOPUS
661 PAVRECSINK pSink = pStreamAV->pSink;
662 AssertPtr(pSink);
663 PRTCIRCBUF pCircBuf = pStreamAV->pCircBuf;
664 AssertPtr(pCircBuf);
665
666 void *pvCircBuf;
667 size_t cbCircBuf;
668
669 uint32_t cbToWrite = cxBuf;
670
671 /*
672 * Fetch as much as we can into our internal ring buffer.
673 */
674 while ( cbToWrite
675 && RTCircBufFree(pCircBuf))
676 {
677 RTCircBufAcquireWriteBlock(pCircBuf, cbToWrite, &pvCircBuf, &cbCircBuf);
678
679 if (cbCircBuf)
680 {
681 memcpy(pvCircBuf, (uint8_t *)pvBuf + cbWrittenTotal, cbCircBuf),
682 cbWrittenTotal += (uint32_t)cbCircBuf;
683 Assert(cbToWrite >= cbCircBuf);
684 cbToWrite -= (uint32_t)cbCircBuf;
685 }
686
687 RTCircBufReleaseWriteBlock(pCircBuf, cbCircBuf);
688
689 if ( RT_FAILURE(rc)
690 || !cbCircBuf)
691 {
692 break;
693 }
694 }
695
696 /*
697 * Process our internal ring buffer and encode the data.
698 */
699
700 uint8_t abSrc[_64K]; /** @todo Fix! */
701 size_t cbSrc;
702
703 const uint32_t csFrame = pSink->Codec.Parms.uHz / (1000 /* s in ms */ / pSink->Codec.Opus.msFrame);
704 const uint32_t cbFrame = csFrame * pSink->Codec.Parms.cChannels * (pSink->Codec.Parms.cBits / 8 /* Bytes */);
705
706 /* Only encode data if we have data for the given time period (or more). */
707 while (RTCircBufUsed(pCircBuf) >= cbFrame)
708 {
709 cbSrc = 0;
710
711 while (cbSrc < cbFrame)
712 {
713 RTCircBufAcquireReadBlock(pCircBuf, cbFrame - cbSrc, &pvCircBuf, &cbCircBuf);
714
715 if (cbCircBuf)
716 {
717 memcpy(&abSrc[cbSrc], pvCircBuf, cbCircBuf);
718
719 cbSrc += cbCircBuf;
720 Assert(cbSrc <= sizeof(abSrc));
721 }
722
723 RTCircBufReleaseReadBlock(pCircBuf, cbCircBuf);
724
725 if (!cbCircBuf)
726 break;
727 }
728
729# ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
730 RTFILE fh;
731 RTFileOpen(&fh, VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "DrvAudioVideoRec.pcm",
732 RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
733 RTFileWrite(fh, abSrc, cbSrc, NULL);
734 RTFileClose(fh);
735# endif
736
737 Assert(cbSrc == cbFrame);
738
739 /*
740 * Opus always encodes PER FRAME, that is, exactly 2.5, 5, 10, 20, 40 or 60 ms of audio data.
741 *
742 * A packet can have up to 120ms worth of audio data.
743 * Anything > 120ms of data will result in a "corrupted package" error message by
744 * by decoding application.
745 */
746 uint8_t abDst[_64K]; /** @todo Fix! */
747 size_t cbDst = sizeof(abDst);
748
749 /* Call the encoder to encode one frame per iteration. */
750 opus_int32 cbWritten = opus_encode(pSink->Codec.Opus.pEnc,
751 (opus_int16 *)abSrc, csFrame, abDst, (opus_int32)cbDst);
752 if (cbWritten > 0)
753 {
754# ifdef VBOX_WITH_STATISTICS
755 /* Get overall frames encoded. */
756 const uint32_t cEncFrames = opus_packet_get_nb_frames(abDst, cbWritten);
757
758 pSink->Codec.STAM.cEncFrames += cEncFrames;
759 pSink->Codec.STAM.msEncTotal += pSink->Codec.Opus.msFrame * cEncFrames;
760
761 LogFunc(("%RU64ms [%RU64 frames]: cbSrc=%zu, cbDst=%zu, cEncFrames=%RU32\n",
762 pSink->Codec.STAM.msEncTotal, pSink->Codec.STAM.cEncFrames, cbSrc, cbDst, cEncFrames));
763# endif
764 Assert((uint32_t)cbWritten <= cbDst);
765 cbDst = RT_MIN((uint32_t)cbWritten, cbDst); /* Update cbDst to actual bytes encoded (written). */
766
767 Assert(cEncFrames == 1); /* At the moment we encode exactly *one* frame per frame. */
768
769 if (pStreamAV->uLastPTSMs == 0)
770 pStreamAV->uLastPTSMs = RTTimeMilliTS() - pSink->tsStartMs;
771
772 const uint64_t uDurationMs = pSink->Codec.Opus.msFrame * cEncFrames;
773 const uint64_t uPTSMs = pStreamAV->uLastPTSMs + uDurationMs;
774
775 pStreamAV->uLastPTSMs += uDurationMs;
776
777 switch (pSink->Con.Parms.enmType)
778 {
779 case AVRECCONTAINERTYPE_MAIN_CONSOLE:
780 {
781 HRESULT hr = pSink->Con.Main.pConsole->i_audioVideoRecSendAudio(abDst, cbDst, uPTSMs);
782 Assert(hr == S_OK);
783 RT_NOREF(hr);
784
785 break;
786 }
787
788 case AVRECCONTAINERTYPE_WEBM:
789 {
790 WebMWriter::BlockData_Opus blockData = { abDst, cbDst, uPTSMs };
791 rc = pSink->Con.WebM.pWebM->WriteBlock(pSink->Con.WebM.uTrack, &blockData, sizeof(blockData));
792 AssertRC(rc);
793
794 break;
795 }
796
797 default:
798 AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
799 break;
800 }
801 }
802 else if (cbWritten < 0)
803 {
804 AssertMsgFailed(("Encoding failed: %s\n", opus_strerror(cbWritten)));
805 rc = VERR_INVALID_PARAMETER;
806 }
807
808 if (RT_FAILURE(rc))
809 break;
810 }
811
812 if (pcxWritten)
813 *pcxWritten = cbWrittenTotal;
814#else
815 /* Report back all data as being processed. */
816 if (pcxWritten)
817 *pcxWritten = cxBuf;
818
819 rc = VERR_NOT_SUPPORTED;
820#endif /* VBOX_WITH_LIBOPUS */
821
822 LogFlowFunc(("csReadTotal=%RU32, rc=%Rrc\n", cbWrittenTotal, rc));
823 return rc;
824}
825
826
827/**
828 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
829 */
830static DECLCALLBACK(int) drvAudioVideoRecGetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
831{
832 RT_NOREF(pInterface);
833 AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
834
835 pBackendCfg->cbStreamOut = sizeof(AVRECSTREAM);
836 pBackendCfg->cbStreamIn = 0;
837 pBackendCfg->cMaxStreamsIn = 0;
838 pBackendCfg->cMaxStreamsOut = UINT32_MAX;
839
840 return VINF_SUCCESS;
841}
842
843
844/**
845 * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown}
846 */
847static DECLCALLBACK(void) drvAudioVideoRecShutdown(PPDMIHOSTAUDIO pInterface)
848{
849 LogFlowFuncEnter();
850
851 PDRVAUDIOVIDEOREC pThis = PDMIHOSTAUDIO_2_DRVAUDIOVIDEOREC(pInterface);
852
853 avRecSinkShutdown(&pThis->Sink);
854}
855
856
857/**
858 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
859 */
860static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvAudioVideoRecGetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
861{
862 RT_NOREF(enmDir);
863 AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN);
864
865 return PDMAUDIOBACKENDSTS_RUNNING;
866}
867
868
869/**
870 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
871 */
872static DECLCALLBACK(int) drvAudioVideoRecStreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
873 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
874{
875 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
876 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
877 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
878
879 if (pCfgReq->enmDir == PDMAUDIODIR_IN)
880 return VERR_NOT_SUPPORTED;
881
882 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
883
884 PDRVAUDIOVIDEOREC pThis = PDMIHOSTAUDIO_2_DRVAUDIOVIDEOREC(pInterface);
885 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
886
887 /* For now we only have one sink, namely the driver's one.
888 * Later each stream could have its own one, to e.g. router different stream to different sinks .*/
889 PAVRECSINK pSink = &pThis->Sink;
890
891 int rc = avRecCreateStreamOut(pThis, pStreamAV, pSink, pCfgReq, pCfgAcq);
892 if (RT_SUCCESS(rc))
893 {
894 pStreamAV->pCfg = DrvAudioHlpStreamCfgDup(pCfgAcq);
895 if (!pStreamAV->pCfg)
896 rc = VERR_NO_MEMORY;
897 }
898
899 return rc;
900}
901
902
903/**
904 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
905 */
906static DECLCALLBACK(int) drvAudioVideoRecStreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
907{
908 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
909 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
910
911 PDRVAUDIOVIDEOREC pThis = PDMIHOSTAUDIO_2_DRVAUDIOVIDEOREC(pInterface);
912 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
913
914 if (!pStreamAV->pCfg) /* Not (yet) configured? Skip. */
915 return VINF_SUCCESS;
916
917 int rc = VINF_SUCCESS;
918
919 if (pStreamAV->pCfg->enmDir == PDMAUDIODIR_OUT)
920 rc = avRecDestroyStreamOut(pThis, pStreamAV);
921
922 if (RT_SUCCESS(rc))
923 {
924 DrvAudioHlpStreamCfgFree(pStreamAV->pCfg);
925 pStreamAV->pCfg = NULL;
926 }
927
928 return rc;
929}
930
931
932/**
933 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}
934 */
935static DECLCALLBACK(int) drvAudioVideoRecStreamControl(PPDMIHOSTAUDIO pInterface,
936 PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
937{
938 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
939 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
940
941 PDRVAUDIOVIDEOREC pThis = PDMIHOSTAUDIO_2_DRVAUDIOVIDEOREC(pInterface);
942 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
943
944 if (!pStreamAV->pCfg) /* Not (yet) configured? Skip. */
945 return VINF_SUCCESS;
946
947 if (pStreamAV->pCfg->enmDir == PDMAUDIODIR_OUT)
948 return avRecControlStreamOut(pThis, pStreamAV, enmStreamCmd);
949
950 return VINF_SUCCESS;
951}
952
953
954/**
955 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
956 */
957static DECLCALLBACK(uint32_t) drvAudioVideoRecStreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
958{
959 RT_NOREF(pInterface, pStream);
960
961 return 0; /* Video capturing does not provide any input. */
962}
963
964
965/**
966 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
967 */
968static DECLCALLBACK(uint32_t) drvAudioVideoRecStreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
969{
970 RT_NOREF(pInterface, pStream);
971
972 return UINT32_MAX;
973}
974
975
976/**
977 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus}
978 */
979static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvAudioVideoRecStreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
980{
981 RT_NOREF(pInterface, pStream);
982
983 return (PDMAUDIOSTREAMSTS_FLAG_INITIALIZED | PDMAUDIOSTREAMSTS_FLAG_ENABLED);
984}
985
986
987/**
988 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamIterate}
989 */
990static DECLCALLBACK(int) drvAudioVideoRecStreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
991{
992 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
993 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
994
995 LogFlowFuncEnter();
996
997 /* Nothing to do here for video recording. */
998 return VINF_SUCCESS;
999}
1000
1001
1002/**
1003 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
1004 */
1005static DECLCALLBACK(void *) drvAudioVideoRecQueryInterface(PPDMIBASE pInterface, const char *pszIID)
1006{
1007 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
1008 PDRVAUDIOVIDEOREC pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVIDEOREC);
1009
1010 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
1011 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
1012 return NULL;
1013}
1014
1015
1016AudioVideoRec::AudioVideoRec(Console *pConsole)
1017 : mpDrv(NULL)
1018 , mpConsole(pConsole)
1019{
1020}
1021
1022
1023AudioVideoRec::~AudioVideoRec(void)
1024{
1025 if (mpDrv)
1026 {
1027 mpDrv->pAudioVideoRec = NULL;
1028 mpDrv = NULL;
1029 }
1030}
1031
1032
1033/**
1034 * Construct a audio video recording driver instance.
1035 *
1036 * @copydoc FNPDMDRVCONSTRUCT
1037 */
1038/* static */
1039DECLCALLBACK(int) AudioVideoRec::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
1040{
1041 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
1042 PDRVAUDIOVIDEOREC pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVIDEOREC);
1043 RT_NOREF(fFlags);
1044
1045 LogRel(("Audio: Initializing video recording audio driver\n"));
1046 LogFlowFunc(("fFlags=0x%x\n", fFlags));
1047
1048 AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
1049 ("Configuration error: Not possible to attach anything to this driver!\n"),
1050 VERR_PDM_DRVINS_NO_ATTACH);
1051
1052 /*
1053 * Init the static parts.
1054 */
1055 pThis->pDrvIns = pDrvIns;
1056 /* IBase */
1057 pDrvIns->IBase.pfnQueryInterface = drvAudioVideoRecQueryInterface;
1058 /* IHostAudio */
1059 PDMAUDIO_IHOSTAUDIO_CALLBACKS(drvAudioVideoRec);
1060
1061 /*
1062 * Get the Console object pointer.
1063 */
1064 void *pvUser;
1065 int rc = CFGMR3QueryPtr(pCfg, "ObjectConsole", &pvUser); /** @todo r=andy Get rid of this hack and use IHostAudio::SetCallback. */
1066 AssertRCReturn(rc, rc);
1067
1068 /* CFGM tree saves the pointer to Console in the Object node of AudioVideoRec. */
1069 pThis->pConsole = (Console *)pvUser;
1070 AssertReturn(!pThis->pConsole.isNull(), VERR_INVALID_POINTER);
1071
1072 /*
1073 * Get the pointer to the audio driver instance.
1074 */
1075 rc = CFGMR3QueryPtr(pCfg, "Object", &pvUser); /** @todo r=andy Get rid of this hack and use IHostAudio::SetCallback. */
1076 AssertRCReturn(rc, rc);
1077
1078 pThis->pAudioVideoRec = (AudioVideoRec *)pvUser;
1079 AssertPtrReturn(pThis->pAudioVideoRec, VERR_INVALID_POINTER);
1080
1081 pThis->pAudioVideoRec->mpDrv = pThis;
1082
1083 /*
1084 * Get the interface for the above driver (DrvAudio) to make mixer/conversion calls.
1085 * Described in CFGM tree.
1086 */
1087 pThis->pDrvAudio = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIAUDIOCONNECTOR);
1088 AssertMsgReturn(pThis->pDrvAudio, ("Configuration error: No upper interface specified!\n"), VERR_PDM_MISSING_INTERFACE_ABOVE);
1089
1090#ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA
1091 RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "DrvAudioVideoRec.webm");
1092 RTFileDelete(VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "DrvAudioVideoRec.pcm");
1093#endif
1094
1095 return VINF_SUCCESS;
1096}
1097
1098
1099/**
1100 * @interface_method_impl{PDMDRVREG,pfnDestruct}
1101 */
1102/* static */
1103DECLCALLBACK(void) AudioVideoRec::drvDestruct(PPDMDRVINS pDrvIns)
1104{
1105 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
1106 PDRVAUDIOVIDEOREC pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVIDEOREC);
1107 LogFlowFuncEnter();
1108
1109 /*
1110 * If the AudioVideoRec object is still alive, we must clear it's reference to
1111 * us since we'll be invalid when we return from this method.
1112 */
1113 if (pThis->pAudioVideoRec)
1114 {
1115 pThis->pAudioVideoRec->mpDrv = NULL;
1116 pThis->pAudioVideoRec = NULL;
1117 }
1118}
1119
1120
1121/**
1122 * @interface_method_impl{PDMDRVREG,pfnAttach}
1123 */
1124/* static */
1125DECLCALLBACK(int) AudioVideoRec::drvAttach(PPDMDRVINS pDrvIns, uint32_t fFlags)
1126{
1127 RT_NOREF(pDrvIns, fFlags);
1128
1129 LogFlowFuncEnter();
1130
1131 return VINF_SUCCESS;
1132}
1133
1134/**
1135 * @interface_method_impl{PDMDRVREG,pfnDetach}
1136 */
1137/* static */
1138DECLCALLBACK(void) AudioVideoRec::drvDetach(PPDMDRVINS pDrvIns, uint32_t fFlags)
1139{
1140 RT_NOREF(pDrvIns, fFlags);
1141
1142 LogFlowFuncEnter();
1143}
1144
1145/**
1146 * Video recording audio driver registration record.
1147 */
1148const PDMDRVREG AudioVideoRec::DrvReg =
1149{
1150 PDM_DRVREG_VERSION,
1151 /* szName */
1152 "AudioVideoRec",
1153 /* szRCMod */
1154 "",
1155 /* szR0Mod */
1156 "",
1157 /* pszDescription */
1158 "Audio driver for video recording",
1159 /* fFlags */
1160 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
1161 /* fClass. */
1162 PDM_DRVREG_CLASS_AUDIO,
1163 /* cMaxInstances */
1164 ~0U,
1165 /* cbInstance */
1166 sizeof(DRVAUDIOVIDEOREC),
1167 /* pfnConstruct */
1168 AudioVideoRec::drvConstruct,
1169 /* pfnDestruct */
1170 AudioVideoRec::drvDestruct,
1171 /* pfnRelocate */
1172 NULL,
1173 /* pfnIOCtl */
1174 NULL,
1175 /* pfnPowerOn */
1176 NULL,
1177 /* pfnReset */
1178 NULL,
1179 /* pfnSuspend */
1180 NULL,
1181 /* pfnResume */
1182 NULL,
1183 /* pfnAttach */
1184 AudioVideoRec::drvAttach,
1185 /* pfnDetach */
1186 AudioVideoRec::drvDetach,
1187 /* pfnPowerOff */
1188 NULL,
1189 /* pfnSoftReset */
1190 NULL,
1191 /* u32EndVersion */
1192 PDM_DRVREG_VERSION
1193};
1194
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