VirtualBox

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

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

Comment typo.

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