VirtualBox

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

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

Audio/VideoRec: Only open one (output) sink per VM and encode into that, be more specific about frame time calculation.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 33.3 KB
Line 
1/* $Id: DrvAudioVideoRec.cpp 68338 2017-08-08 10:34:58Z 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 size 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 = 16; /** @ŧodo Make this configurable? */
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 LogRel2(("VideoRec: Recording audio to file '%s'\n", szFile));
301
302 pSink->Con.WebM.pWebM = new WebMWriter();
303 rc = pSink->Con.WebM.pWebM->Create(szFile,
304 /** @ŧodo Add option to add some suffix if file exists instead of overwriting? */
305 RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE,
306 WebMWriter::AudioCodec_Opus, WebMWriter::VideoCodec_None);
307 if (RT_SUCCESS(rc))
308 {
309 rc = pSink->Con.WebM.pWebM->AddAudioTrack(uHz, cChannels, cBits,
310 &pSink->Con.WebM.uTrack);
311 if (RT_FAILURE(rc))
312 LogRel(("VideoRec: Error creating audio track for file '%s' (%Rrc)\n", szFile, rc));
313 }
314 else
315 LogRel(("VideoRec: Error creating audio file '%s' (%Rrc)\n", szFile, rc));
316 }
317 else
318 {
319 AssertFailed(); /* Should never happen. */
320 LogRel(("VideoRec: Error creating audio file path\n"));
321 }
322 }
323
324 break;
325 }
326
327 default:
328 rc = VERR_NOT_SUPPORTED;
329 break;
330 }
331 }
332 catch (std::bad_alloc)
333 {
334 rc = VERR_NO_MEMORY;
335 }
336
337 if (RT_SUCCESS(rc))
338 {
339 pSink->Con.Parms.enmType = pConParms->enmType;
340
341 pSink->Codec.Parms.uHz = uHz;
342 pSink->Codec.Parms.cChannels = cChannels;
343 pSink->Codec.Parms.cBits = cBits;
344 pSink->Codec.Parms.uBitrate = uBitrate;
345
346 pSink->Codec.Opus.pEnc = pEnc;
347 pSink->Codec.Opus.msFrame = 20; /** @todo 20 ms of audio data. Make this configurable? */
348
349#ifdef VBOX_WITH_STATISTICS
350 pSink->Codec.STAM.cEncFrames = 0;
351 pSink->Codec.STAM.msEncTotal = 0;
352#endif
353 }
354 else
355 {
356 if (pEnc)
357 {
358 opus_encoder_destroy(pEnc);
359 pEnc = NULL;
360 }
361
362 LogRel(("VideoRec: Error creating sink (%Rrc)\n", rc));
363 }
364
365 return rc;
366}
367
368
369/**
370 * Shuts down (closes) a recording sink,
371 *
372 * @returns IPRT status code.
373 * @param pSink Recording sink to shut down.
374 */
375static void avRecSinkShutdown(PAVRECSINK pSink)
376{
377 AssertPtrReturnVoid(pSink);
378
379#ifdef VBOX_WITH_LIBOPUS
380 if (pSink->Codec.Opus.pEnc)
381 {
382 opus_encoder_destroy(pSink->Codec.Opus.pEnc);
383 pSink->Codec.Opus.pEnc = NULL;
384 }
385#endif
386 switch (pSink->Con.Parms.enmType)
387 {
388 case AVRECCONTAINERTYPE_WEBM:
389 {
390 if (pSink->Con.WebM.pWebM)
391 {
392 LogRel2(("VideoRec: Finished recording audio to file '%s' (%zu bytes)\n",
393 pSink->Con.WebM.pWebM->GetFileName().c_str(), pSink->Con.WebM.pWebM->GetFileSize()));
394
395 int rc2 = pSink->Con.WebM.pWebM->Close();
396 AssertRC(rc2);
397
398 delete pSink->Con.WebM.pWebM;
399 pSink->Con.WebM.pWebM = NULL;
400 }
401 break;
402 }
403
404 case AVRECCONTAINERTYPE_MAIN_CONSOLE:
405 default:
406 break;
407 }
408}
409
410
411/**
412 * Creates an audio output stream and associates it with the specified recording sink.
413 *
414 * @returns IPRT status code.
415 * @param pThis Driver instance.
416 * @param pStreamAV Audio output stream to create.
417 * @param pSink Recording sink to associate audio output stream to.
418 * @param pCfgReq Requested configuration by the audio backend.
419 * @param pCfgAcq Acquired configuration by the audio output stream.
420 */
421static int avRecCreateStreamOut(PDRVAUDIOVIDEOREC pThis, PAVRECSTREAM pStreamAV,
422 PAVRECSINK pSink, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
423{
424 AssertPtrReturn(pThis, VERR_INVALID_POINTER);
425 AssertPtrReturn(pStreamAV, VERR_INVALID_POINTER);
426 AssertPtrReturn(pSink, VERR_INVALID_POINTER);
427 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
428 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
429
430 if (pCfgReq->DestSource.Dest != PDMAUDIOPLAYBACKDEST_FRONT)
431 {
432 AssertFailed();
433
434 if (pCfgAcq)
435 pCfgAcq->cFrameBufferHint = 0;
436
437 LogRel2(("VideoRec: Support for surround audio not implemented yet\n"));
438 return VERR_NOT_SUPPORTED;
439 }
440
441 int rc = VINF_SUCCESS;
442
443#ifdef VBOX_WITH_LIBOPUS
444 const unsigned cFrames = 2; /** @todo Use the PreRoll param for that? */
445
446 const uint32_t csFrame = pSink->Codec.Parms.uHz / (1000 /* s in ms */ / pSink->Codec.Opus.msFrame);
447 const uint32_t cbFrame = csFrame * pSink->Codec.Parms.cChannels * (pSink->Codec.Parms.cBits / 8 /* Bytes */);
448
449 rc = RTCircBufCreate(&pStreamAV->pCircBuf, cbFrame * cFrames);
450 if (RT_SUCCESS(rc))
451 {
452 pStreamAV->pSink = pSink; /* Assign sink to stream. */
453
454 if (pCfgAcq)
455 {
456 /* Make sure to let the driver backend know that we need the audio data in
457 * a specific sampling rate Opus is optimized for. */
458 pCfgAcq->Props.uHz = pSink->Codec.Parms.uHz;
459 pCfgAcq->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfgAcq->Props.cBits, pCfgAcq->Props.cChannels);
460 pCfgAcq->cFrameBufferHint = _4K; /** @todo Make this configurable. */
461 }
462 }
463#else
464 RT_NOREF(pThis, pSink, pStreamAV, pCfgReq, pCfgAcq);
465 rc = VERR_NOT_SUPPORTED;
466#endif /* VBOX_WITH_LIBOPUS */
467
468 LogFlowFuncLeaveRC(rc);
469 return rc;
470}
471
472
473/**
474 * Destroys (closes) an audio output stream.
475 *
476 * @returns IPRT status code.
477 * @param pThis Driver instance.
478 * @param pStreamAV Audio output stream to destroy.
479 */
480static int avRecDestroyStreamOut(PDRVAUDIOVIDEOREC pThis, PAVRECSTREAM pStreamAV)
481{
482 RT_NOREF(pThis);
483
484 if (pStreamAV->pCircBuf)
485 {
486 RTCircBufDestroy(pStreamAV->pCircBuf);
487 pStreamAV->pCircBuf = NULL;
488 }
489
490 return VINF_SUCCESS;
491}
492
493
494/**
495 * Controls an audio output stream
496 *
497 * @returns IPRT status code.
498 * @param pThis Driver instance.
499 * @param pStreamAV Audio output stream to control.
500 * @param enmStreamCmd Stream command to issue.
501 */
502static int avRecControlStreamOut(PDRVAUDIOVIDEOREC pThis,
503 PAVRECSTREAM pStreamAV, PDMAUDIOSTREAMCMD enmStreamCmd)
504{
505 RT_NOREF(pThis, pStreamAV);
506
507 switch (enmStreamCmd)
508 {
509 case PDMAUDIOSTREAMCMD_ENABLE:
510 case PDMAUDIOSTREAMCMD_DISABLE:
511 case PDMAUDIOSTREAMCMD_RESUME:
512 case PDMAUDIOSTREAMCMD_PAUSE:
513 break;
514
515 default:
516 AssertMsgFailed(("Invalid command %ld\n", enmStreamCmd));
517 break;
518 }
519
520 return VINF_SUCCESS;
521}
522
523
524/**
525 * @interface_method_impl{PDMIHOSTAUDIO,pfnInit}
526 */
527static DECLCALLBACK(int) drvAudioVideoRecInit(PPDMIHOSTAUDIO pInterface)
528{
529 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
530
531 LogFlowFuncEnter();
532
533 PDRVAUDIOVIDEOREC pThis = PDMIHOSTAUDIO_2_DRVAUDIOVIDEOREC(pInterface);
534
535 AVRECCONTAINERPARMS ContainerParms;
536 ContainerParms.enmType = AVRECCONTAINERTYPE_WEBM; /** @todo Make this configurable. */
537
538 AVRECCODECPARMS CodecParms;
539 CodecParms.uHz = AVREC_OPUS_HZ_MAX; /** @todo Make this configurable. */
540 CodecParms.cChannels = 2; /** @todo Make this configurable. */
541 CodecParms.uBitrate = 196000; /** @todo Make this configurable. */
542
543 int rc = avRecSinkInit(pThis, &pThis->Sink, &ContainerParms, &CodecParms);
544 if (RT_FAILURE(rc))
545 {
546 LogRel(("VideoRec: Audio recording driver failed to initialize, rc=%Rrc\n", rc));
547 }
548 else
549 LogRel2(("VideoRec: Audio recording driver initialized\n"));
550
551 return rc;
552}
553
554
555/**
556 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}
557 */
558static DECLCALLBACK(int) drvAudioVideoRecStreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
559 void *pvBuf, uint32_t cxBuf, uint32_t *pcxRead)
560{
561 RT_NOREF(pInterface, pStream, pvBuf, cxBuf);
562
563 if (pcxRead)
564 *pcxRead = 0;
565
566 return VINF_SUCCESS;
567}
568
569
570/**
571 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}
572 */
573static DECLCALLBACK(int) drvAudioVideoRecStreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
574 const void *pvBuf, uint32_t cxBuf, uint32_t *pcxWritten)
575{
576 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
577 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
578 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
579 AssertReturn(cxBuf, VERR_INVALID_PARAMETER);
580 /* pcxWritten is optional. */
581
582 PDRVAUDIOVIDEOREC pThis = PDMIHOSTAUDIO_2_DRVAUDIOVIDEOREC(pInterface);
583 RT_NOREF(pThis);
584 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
585
586 int rc = VINF_SUCCESS;
587
588 uint32_t cbWrittenTotal = 0;
589
590 /*
591 * Call the encoder with the data.
592 */
593#ifdef VBOX_WITH_LIBOPUS
594 PAVRECSINK pSink = pStreamAV->pSink;
595 AssertPtr(pSink);
596 PRTCIRCBUF pCircBuf = pStreamAV->pCircBuf;
597 AssertPtr(pCircBuf);
598
599 void *pvCircBuf;
600 size_t cbCircBuf;
601
602 uint32_t cbToWrite = cxBuf;
603
604 /*
605 * Fetch as much as we can into our internal ring buffer.
606 */
607 while ( cbToWrite
608 && RTCircBufFree(pCircBuf))
609 {
610 RTCircBufAcquireWriteBlock(pCircBuf, cbToWrite, &pvCircBuf, &cbCircBuf);
611
612 if (cbCircBuf)
613 {
614 memcpy(pvCircBuf, (uint8_t *)pvBuf + cbWrittenTotal, cbCircBuf),
615 cbWrittenTotal += cbCircBuf;
616 Assert(cbToWrite >= cbCircBuf);
617 cbToWrite -= cbCircBuf;
618 }
619
620 RTCircBufReleaseWriteBlock(pCircBuf, cbCircBuf);
621
622 if ( RT_FAILURE(rc)
623 || !cbCircBuf)
624 {
625 break;
626 }
627 }
628
629 /*
630 * Process our internal ring buffer and encode the data.
631 */
632
633 uint8_t abSrc[_64K]; /** @todo Fix! */
634 size_t cbSrc;
635
636 const uint32_t csFrame = pSink->Codec.Parms.uHz / (1000 /* s in ms */ / pSink->Codec.Opus.msFrame);
637 const uint32_t cbFrame = csFrame * pSink->Codec.Parms.cChannels * (pSink->Codec.Parms.cBits / 8 /* Bytes */);
638
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 uint32_t cEncFrames = opus_packet_get_nb_frames(abDst, cbDst);
689 uint32_t cEncSamplesPerFrame = opus_packet_get_samples_per_frame(abDst, pSink->Codec.Parms.uHz);
690 uint32_t csEnc = cEncFrames * cEncSamplesPerFrame;
691
692 pSink->Codec.STAM.cEncFrames += cEncFrames;
693 pSink->Codec.STAM.msEncTotal += 20 /* Default 20 ms */ * cEncFrames;
694
695 LogFunc(("%RU64ms [%RU64 frames]: cbSrc=%zu, cbDst=%zu, cEncFrames=%RU32, cEncSamplesPerFrame=%RU32, csEnc=%RU32\n",
696 pSink->Codec.STAM.msEncTotal, pSink->Codec.STAM.cEncFrames,
697 cbSrc, cbDst, cEncFrames, cEncSamplesPerFrame, csEnc));
698# endif
699 Assert((uint32_t)cbWritten <= cbDst);
700 cbDst = RT_MIN((uint32_t)cbWritten, cbDst); /* Update cbDst to actual bytes encoded (written). */
701
702 switch (pSink->Con.Parms.enmType)
703 {
704 case AVRECCONTAINERTYPE_MAIN_CONSOLE:
705 {
706 HRESULT hr = pSink->Con.Main.pConsole->i_audioVideoRecSendAudio(abDst, cbDst, RTTimeMilliTS() /* Now */);
707 Assert(hr == S_OK);
708 RT_NOREF(hr);
709
710 break;
711 }
712
713 case AVRECCONTAINERTYPE_WEBM:
714 {
715 WebMWriter::BlockData_Opus blockData = { abDst, cbDst, RTTimeMilliTS() };
716 rc = pSink->Con.WebM.pWebM->WriteBlock(pSink->Con.WebM.uTrack, &blockData, sizeof(blockData));
717 AssertRC(rc);
718
719 break;
720 }
721
722 default:
723 AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
724 break;
725 }
726 }
727 else if (cbWritten < 0)
728 {
729 AssertMsgFailed(("Encoding failed: %s\n", opus_strerror(cbWritten)));
730 rc = VERR_INVALID_PARAMETER;
731 }
732
733 if (RT_FAILURE(rc))
734 break;
735 }
736#else
737 rc = VERR_NOT_SUPPORTED;
738#endif /* VBOX_WITH_LIBOPUS */
739
740 /*
741 * Always report back all samples acquired, regardless of whether the
742 * encoder actually did process those.
743 */
744 if (pcxWritten)
745 *pcxWritten = cbWrittenTotal;
746
747 LogFlowFunc(("csReadTotal=%RU32, rc=%Rrc\n", cbWrittenTotal, rc));
748 return rc;
749}
750
751
752/**
753 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
754 */
755static DECLCALLBACK(int) drvAudioVideoRecGetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
756{
757 RT_NOREF(pInterface);
758 AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
759
760 pBackendCfg->cbStreamOut = sizeof(AVRECSTREAM);
761 pBackendCfg->cbStreamIn = 0;
762 pBackendCfg->cMaxStreamsIn = 0;
763 pBackendCfg->cMaxStreamsOut = UINT32_MAX;
764
765 return VINF_SUCCESS;
766}
767
768
769/**
770 * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown}
771 */
772static DECLCALLBACK(void) drvAudioVideoRecShutdown(PPDMIHOSTAUDIO pInterface)
773{
774 LogFlowFuncEnter();
775
776 PDRVAUDIOVIDEOREC pThis = PDMIHOSTAUDIO_2_DRVAUDIOVIDEOREC(pInterface);
777
778 avRecSinkShutdown(&pThis->Sink);
779}
780
781
782/**
783 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
784 */
785static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvAudioVideoRecGetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
786{
787 RT_NOREF(enmDir);
788 AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN);
789
790 return PDMAUDIOBACKENDSTS_RUNNING;
791}
792
793
794/**
795 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
796 */
797static DECLCALLBACK(int) drvAudioVideoRecStreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
798 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
799{
800 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
801 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
802 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
803
804 if (pCfgReq->enmDir == PDMAUDIODIR_IN)
805 return VERR_NOT_SUPPORTED;
806
807 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
808
809 PDRVAUDIOVIDEOREC pThis = PDMIHOSTAUDIO_2_DRVAUDIOVIDEOREC(pInterface);
810 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
811
812 /* For now we only have one sink, namely the driver's one.
813 * Later each stream could have its own one, to e.g. router different stream to different sinks .*/
814 PAVRECSINK pSink = &pThis->Sink;
815
816 int rc = avRecCreateStreamOut(pThis, pStreamAV, pSink, pCfgReq, pCfgAcq);
817 if (RT_SUCCESS(rc))
818 {
819 pStreamAV->pCfg = DrvAudioHlpStreamCfgDup(pCfgAcq);
820 if (!pStreamAV->pCfg)
821 rc = VERR_NO_MEMORY;
822 }
823
824 return rc;
825}
826
827
828/**
829 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
830 */
831static DECLCALLBACK(int) drvAudioVideoRecStreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
832{
833 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
834 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
835
836 PDRVAUDIOVIDEOREC pThis = PDMIHOSTAUDIO_2_DRVAUDIOVIDEOREC(pInterface);
837 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
838
839 if (!pStreamAV->pCfg) /* Not (yet) configured? Skip. */
840 return VINF_SUCCESS;
841
842 int rc = VINF_SUCCESS;
843
844 if (pStreamAV->pCfg->enmDir == PDMAUDIODIR_OUT)
845 rc = avRecDestroyStreamOut(pThis, pStreamAV);
846
847 if (RT_SUCCESS(rc))
848 {
849 DrvAudioHlpStreamCfgFree(pStreamAV->pCfg);
850 pStreamAV->pCfg = NULL;
851 }
852
853 return rc;
854}
855
856
857/**
858 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}
859 */
860static DECLCALLBACK(int) drvAudioVideoRecStreamControl(PPDMIHOSTAUDIO pInterface,
861 PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
862{
863 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
864 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
865
866 PDRVAUDIOVIDEOREC pThis = PDMIHOSTAUDIO_2_DRVAUDIOVIDEOREC(pInterface);
867 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
868
869 if (!pStreamAV->pCfg) /* Not (yet) configured? Skip. */
870 return VINF_SUCCESS;
871
872 if (pStreamAV->pCfg->enmDir == PDMAUDIODIR_OUT)
873 return avRecControlStreamOut(pThis, pStreamAV, enmStreamCmd);
874
875 return VINF_SUCCESS;
876}
877
878
879/**
880 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
881 */
882static DECLCALLBACK(uint32_t) drvAudioVideoRecStreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
883{
884 RT_NOREF(pInterface, pStream);
885
886 return 0; /* Video capturing does not provide any input. */
887}
888
889
890/**
891 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
892 */
893static DECLCALLBACK(uint32_t) drvAudioVideoRecStreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
894{
895 RT_NOREF(pInterface, pStream);
896
897 return UINT32_MAX;
898}
899
900
901/**
902 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus}
903 */
904static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvAudioVideoRecStreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
905{
906 RT_NOREF(pInterface, pStream);
907
908 return (PDMAUDIOSTREAMSTS_FLAG_INITIALIZED | PDMAUDIOSTREAMSTS_FLAG_ENABLED);
909}
910
911
912/**
913 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamIterate}
914 */
915static DECLCALLBACK(int) drvAudioVideoRecStreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
916{
917 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
918 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
919
920 LogFlowFuncEnter();
921
922 /* Nothing to do here for video recording. */
923 return VINF_SUCCESS;
924}
925
926
927/**
928 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
929 */
930static DECLCALLBACK(void *) drvAudioVideoRecQueryInterface(PPDMIBASE pInterface, const char *pszIID)
931{
932 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
933 PDRVAUDIOVIDEOREC pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVIDEOREC);
934
935 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
936 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
937 return NULL;
938}
939
940
941AudioVideoRec::AudioVideoRec(Console *pConsole)
942 : mpDrv(NULL)
943 , mpConsole(pConsole)
944{
945}
946
947
948AudioVideoRec::~AudioVideoRec(void)
949{
950 if (mpDrv)
951 {
952 mpDrv->pAudioVideoRec = NULL;
953 mpDrv = NULL;
954 }
955}
956
957
958/**
959 * Construct a audio video recording driver instance.
960 *
961 * @copydoc FNPDMDRVCONSTRUCT
962 */
963/* static */
964DECLCALLBACK(int) AudioVideoRec::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
965{
966 RT_NOREF(fFlags);
967
968 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
969 PDRVAUDIOVIDEOREC pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVIDEOREC);
970
971 AssertPtrReturn(pDrvIns, VERR_INVALID_POINTER);
972 AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
973
974 LogRel(("Audio: Initializing video recording audio driver\n"));
975 LogFlowFunc(("fFlags=0x%x\n", fFlags));
976
977 AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
978 ("Configuration error: Not possible to attach anything to this driver!\n"),
979 VERR_PDM_DRVINS_NO_ATTACH);
980
981 /*
982 * Init the static parts.
983 */
984 pThis->pDrvIns = pDrvIns;
985 /* IBase */
986 pDrvIns->IBase.pfnQueryInterface = drvAudioVideoRecQueryInterface;
987 /* IHostAudio */
988 PDMAUDIO_IHOSTAUDIO_CALLBACKS(drvAudioVideoRec);
989
990 /*
991 * Get the Console object pointer.
992 */
993 void *pvUser;
994 int rc = CFGMR3QueryPtr(pCfg, "ObjectConsole", &pvUser); /** @todo r=andy Get rid of this hack and use IHostAudio::SetCallback. */
995 AssertMsgRCReturn(rc, ("Confguration error: No/bad \"ObjectConsole\" value, rc=%Rrc\n", rc), rc);
996
997 /* CFGM tree saves the pointer to Console in the Object node of AudioVideoRec. */
998 pThis->pConsole = (Console *)pvUser;
999
1000 /*
1001 * Get the pointer to the audio driver instance.
1002 */
1003 rc = CFGMR3QueryPtr(pCfg, "Object", &pvUser); /** @todo r=andy Get rid of this hack and use IHostAudio::SetCallback. */
1004 AssertMsgRCReturn(rc, ("Confguration error: No/bad \"Object\" value, rc=%Rrc\n", rc), rc);
1005
1006 pThis->pAudioVideoRec = (AudioVideoRec *)pvUser;
1007 pThis->pAudioVideoRec->mpDrv = pThis;
1008
1009 /*
1010 * Get the interface for the above driver (DrvAudio) to make mixer/conversion calls.
1011 * Described in CFGM tree.
1012 */
1013 pThis->pDrvAudio = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIAUDIOCONNECTOR);
1014 AssertMsgReturn(pThis->pDrvAudio, ("Configuration error: No upper interface specified!\n"), VERR_PDM_MISSING_INTERFACE_ABOVE);
1015
1016 return VINF_SUCCESS;
1017}
1018
1019
1020/**
1021 * @interface_method_impl{PDMDRVREG,pfnDestruct}
1022 */
1023/* static */
1024DECLCALLBACK(void) AudioVideoRec::drvDestruct(PPDMDRVINS pDrvIns)
1025{
1026 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
1027 PDRVAUDIOVIDEOREC pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVIDEOREC);
1028 LogFlowFuncEnter();
1029
1030 /*
1031 * If the AudioVideoRec object is still alive, we must clear it's reference to
1032 * us since we'll be invalid when we return from this method.
1033 */
1034 if (pThis->pAudioVideoRec)
1035 {
1036 pThis->pAudioVideoRec->mpDrv = NULL;
1037 pThis->pAudioVideoRec = NULL;
1038 }
1039}
1040
1041
1042/**
1043 * Video recording audio driver registration record.
1044 */
1045const PDMDRVREG AudioVideoRec::DrvReg =
1046{
1047 PDM_DRVREG_VERSION,
1048 /* szName */
1049 "AudioVideoRec",
1050 /* szRCMod */
1051 "",
1052 /* szR0Mod */
1053 "",
1054 /* pszDescription */
1055 "Audio driver for video recording",
1056 /* fFlags */
1057 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
1058 /* fClass. */
1059 PDM_DRVREG_CLASS_AUDIO,
1060 /* cMaxInstances */
1061 ~0U,
1062 /* cbInstance */
1063 sizeof(DRVAUDIOVIDEOREC),
1064 /* pfnConstruct */
1065 AudioVideoRec::drvConstruct,
1066 /* pfnDestruct */
1067 AudioVideoRec::drvDestruct,
1068 /* pfnRelocate */
1069 NULL,
1070 /* pfnIOCtl */
1071 NULL,
1072 /* pfnPowerOn */
1073 NULL,
1074 /* pfnReset */
1075 NULL,
1076 /* pfnSuspend */
1077 NULL,
1078 /* pfnResume */
1079 NULL,
1080 /* pfnAttach */
1081 NULL,
1082 /* pfnDetach */
1083 NULL,
1084 /* pfnPowerOff */
1085 NULL,
1086 /* pfnSoftReset */
1087 NULL,
1088 /* u32EndVersion */
1089 PDM_DRVREG_VERSION
1090};
1091
Note: See TracBrowser for help on using the repository browser.

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