VirtualBox

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

Last change on this file since 67914 was 67914, checked in by vboxsync, 7 years ago

src-client: Define LOG_GROUP according to interface or similar.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 30.7 KB
Line 
1/* $Id: DrvAudioVideoRec.cpp 67914 2017-07-11 20:46:37Z 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 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->cSampleBufferHint = 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->cSampleBufferHint = _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 cbBuf, uint32_t *pcbRead)
511{
512 RT_NOREF(pInterface, pStream, pvBuf, cbBuf);
513
514 if (pcbRead)
515 *pcbRead = 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 cbBuf, uint32_t *pcbWritten)
526{
527 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
528 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
529 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
530 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
531 /* pcbWritten 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 = cbBuf;
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_S2B(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 DEBUG_andy
613 RTFILE fh;
614 RTFileOpen(&fh, "/tmp/acap.pcm", RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);
615 RTFileWrite(fh, abSrc, cbSrc, NULL);
616 RTFileClose(fh);
617# endif
618
619 Assert(cbSrc == cbFrame);
620
621 /*
622 * Opus always encodes PER FRAME, that is, exactly 2.5, 5, 10, 20, 40 or 60 ms of audio data.
623 *
624 * A packet can have up to 120ms worth of audio data.
625 * Anything > 120ms of data will result in a "corrupted package" error message by
626 * by decoding application.
627 */
628 uint8_t abDst[_64K]; /** @todo Fix! */
629 size_t cbDst = sizeof(abDst);
630
631 /* Call the encoder to encode one frame per iteration. */
632 opus_int32 cbWritten = opus_encode(pSink->Codec.Opus.pEnc,
633 (opus_int16 *)abSrc, csFrame, abDst, cbDst);
634 if (cbWritten > 0)
635 {
636# ifdef VBOX_WITH_STATISTICS
637 /* Get overall frames encoded. */
638 uint32_t cEncFrames = opus_packet_get_nb_frames(abDst, cbDst);
639 uint32_t cEncSamplesPerFrame = opus_packet_get_samples_per_frame(abDst, pSink->Codec.Parms.uHz);
640 uint32_t csEnc = cEncFrames * cEncSamplesPerFrame;
641
642 pSink->Codec.STAM.cEncFrames += cEncFrames;
643 pSink->Codec.STAM.msEncTotal += 20 /* Default 20 ms */ * cEncFrames;
644
645 LogFunc(("%RU64ms [%RU64 frames]: cbSrc=%zu, cbDst=%zu, cEncFrames=%RU32, cEncSamplesPerFrame=%RU32, csEnc=%RU32\n",
646 pSink->Codec.STAM.msEncTotal, pSink->Codec.STAM.cEncFrames,
647 cbSrc, cbDst, cEncFrames, cEncSamplesPerFrame, csEnc));
648# endif
649 Assert((uint32_t)cbWritten <= cbDst);
650 cbDst = RT_MIN((uint32_t)cbWritten, cbDst); /* Update cbDst to actual bytes encoded (written). */
651
652 switch (pSink->Con.Parms.enmType)
653 {
654 case AVRECCONTAINERTYPE_MAIN_CONSOLE:
655 {
656 HRESULT hr = pSink->Con.Main.pConsole->i_audioVideoRecSendAudio(abDst, cbDst, RTTimeMilliTS() /* Now */);
657 Assert(hr == S_OK);
658 RT_NOREF(hr);
659
660 break;
661 }
662
663 case AVRECCONTAINERTYPE_WEBM:
664 {
665 WebMWriter::BlockData_Opus blockData = { abDst, cbDst, RTTimeMilliTS() };
666 rc = pSink->Con.WebM.pWebM->WriteBlock(pSink->Con.WebM.uTrack, &blockData, sizeof(blockData));
667 AssertRC(rc);
668
669 break;
670 }
671
672 default:
673 AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);
674 break;
675 }
676 }
677 else if (cbWritten < 0)
678 {
679 AssertMsgFailed(("Encoding failed: %s\n", opus_strerror(cbWritten)));
680 rc = VERR_INVALID_PARAMETER;
681 }
682
683 if (RT_FAILURE(rc))
684 break;
685 }
686#else
687 rc = VERR_NOT_SUPPORTED;
688#endif /* VBOX_WITH_LIBOPUS */
689
690 /*
691 * Always report back all samples acquired, regardless of whether the
692 * encoder actually did process those.
693 */
694 if (pcbWritten)
695 *pcbWritten = cbWrittenTotal;
696
697 LogFlowFunc(("csReadTotal=%RU32, rc=%Rrc\n", cbWrittenTotal, rc));
698 return rc;
699}
700
701
702/**
703 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}
704 */
705static DECLCALLBACK(int) drvAudioVideoRecGetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)
706{
707 RT_NOREF(pInterface);
708 AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);
709
710 pBackendCfg->cbStreamOut = sizeof(AVRECSTREAM);
711 pBackendCfg->cbStreamIn = 0;
712 pBackendCfg->cMaxStreamsIn = 0;
713 pBackendCfg->cMaxStreamsOut = UINT32_MAX;
714
715 return VINF_SUCCESS;
716}
717
718
719/**
720 * @interface_method_impl{PDMIHOSTAUDIO,pfnShutdown}
721 */
722static DECLCALLBACK(void) drvAudioVideoRecShutdown(PPDMIHOSTAUDIO pInterface)
723{
724 LogFlowFuncEnter();
725
726 PDRVAUDIOVIDEOREC pThis = PDMIHOSTAUDIO_2_DRVAUDIOVIDEOREC(pInterface);
727
728 avRecSinkShutdown(&pThis->Sink);
729}
730
731
732/**
733 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}
734 */
735static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvAudioVideoRecGetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)
736{
737 RT_NOREF(enmDir);
738 AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN);
739
740 return PDMAUDIOBACKENDSTS_RUNNING;
741}
742
743
744/**
745 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}
746 */
747static DECLCALLBACK(int) drvAudioVideoRecStreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,
748 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)
749{
750 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
751 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);
752 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);
753
754 if (pCfgReq->enmDir == PDMAUDIODIR_IN)
755 return VERR_NOT_SUPPORTED;
756
757 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
758
759 PDRVAUDIOVIDEOREC pThis = PDMIHOSTAUDIO_2_DRVAUDIOVIDEOREC(pInterface);
760 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
761
762 /* For now we only have one sink, namely the driver's one.
763 * Later each stream could have its own one, to e.g. router different stream to different sinks .*/
764 PAVRECSINK pSink = &pThis->Sink;
765
766 int rc = avRecCreateStreamOut(pThis, pStreamAV, pSink, pCfgReq, pCfgAcq);
767 if (RT_SUCCESS(rc))
768 {
769 pStreamAV->pCfg = DrvAudioHlpStreamCfgDup(pCfgAcq);
770 if (!pStreamAV->pCfg)
771 rc = VERR_NO_MEMORY;
772 }
773
774 return rc;
775}
776
777
778/**
779 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}
780 */
781static DECLCALLBACK(int) drvAudioVideoRecStreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
782{
783 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
784 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
785
786 PDRVAUDIOVIDEOREC pThis = PDMIHOSTAUDIO_2_DRVAUDIOVIDEOREC(pInterface);
787 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
788
789 if (!pStreamAV->pCfg) /* Not (yet) configured? Skip. */
790 return VINF_SUCCESS;
791
792 int rc = VINF_SUCCESS;
793
794 if (pStreamAV->pCfg->enmDir == PDMAUDIODIR_OUT)
795 rc = avRecDestroyStreamOut(pThis, pStreamAV);
796
797 if (RT_SUCCESS(rc))
798 {
799 DrvAudioHlpStreamCfgFree(pStreamAV->pCfg);
800 pStreamAV->pCfg = NULL;
801 }
802
803 return rc;
804}
805
806
807/**
808 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}
809 */
810static DECLCALLBACK(int) drvAudioVideoRecStreamControl(PPDMIHOSTAUDIO pInterface,
811 PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)
812{
813 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
814 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
815
816 PDRVAUDIOVIDEOREC pThis = PDMIHOSTAUDIO_2_DRVAUDIOVIDEOREC(pInterface);
817 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;
818
819 if (!pStreamAV->pCfg) /* Not (yet) configured? Skip. */
820 return VINF_SUCCESS;
821
822 if (pStreamAV->pCfg->enmDir == PDMAUDIODIR_OUT)
823 return avRecControlStreamOut(pThis, pStreamAV, enmStreamCmd);
824
825 return VINF_SUCCESS;
826}
827
828
829/**
830 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}
831 */
832static DECLCALLBACK(uint32_t) drvAudioVideoRecStreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
833{
834 RT_NOREF(pInterface, pStream);
835
836 return 0; /* Video capturing does not provide any input. */
837}
838
839
840/**
841 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}
842 */
843static DECLCALLBACK(uint32_t) drvAudioVideoRecStreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
844{
845 RT_NOREF(pInterface, pStream);
846
847 return UINT32_MAX;
848}
849
850
851/**
852 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus}
853 */
854static DECLCALLBACK(PDMAUDIOSTRMSTS) drvAudioVideoRecStreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
855{
856 RT_NOREF(pInterface, pStream);
857
858 return (PDMAUDIOSTRMSTS_FLAG_INITIALIZED | PDMAUDIOSTRMSTS_FLAG_ENABLED);
859}
860
861
862/**
863 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamIterate}
864 */
865static DECLCALLBACK(int) drvAudioVideoRecStreamIterate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
866{
867 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
868 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
869
870 LogFlowFuncEnter();
871
872 /* Nothing to do here for video recording. */
873 return VINF_SUCCESS;
874}
875
876
877/**
878 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
879 */
880static DECLCALLBACK(void *) drvAudioVideoRecQueryInterface(PPDMIBASE pInterface, const char *pszIID)
881{
882 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
883 PDRVAUDIOVIDEOREC pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVIDEOREC);
884
885 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
886 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);
887 return NULL;
888}
889
890
891AudioVideoRec::AudioVideoRec(Console *pConsole)
892 : mpDrv(NULL)
893 , mpConsole(pConsole)
894{
895}
896
897
898AudioVideoRec::~AudioVideoRec(void)
899{
900 if (mpDrv)
901 {
902 mpDrv->pAudioVideoRec = NULL;
903 mpDrv = NULL;
904 }
905}
906
907
908/**
909 * Construct a audio video recording driver instance.
910 *
911 * @copydoc FNPDMDRVCONSTRUCT
912 */
913/* static */
914DECLCALLBACK(int) AudioVideoRec::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
915{
916 RT_NOREF(fFlags);
917
918 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
919 PDRVAUDIOVIDEOREC pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVIDEOREC);
920
921 AssertPtrReturn(pDrvIns, VERR_INVALID_POINTER);
922 AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
923
924 LogRel(("Audio: Initializing video recording audio driver\n"));
925 LogFlowFunc(("fFlags=0x%x\n", fFlags));
926
927 AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
928 ("Configuration error: Not possible to attach anything to this driver!\n"),
929 VERR_PDM_DRVINS_NO_ATTACH);
930
931 /*
932 * Init the static parts.
933 */
934 pThis->pDrvIns = pDrvIns;
935 /* IBase */
936 pDrvIns->IBase.pfnQueryInterface = drvAudioVideoRecQueryInterface;
937 /* IHostAudio */
938 PDMAUDIO_IHOSTAUDIO_CALLBACKS(drvAudioVideoRec);
939
940 /*
941 * Get the Console object pointer.
942 */
943 void *pvUser;
944 int rc = CFGMR3QueryPtr(pCfg, "ObjectConsole", &pvUser); /** @todo r=andy Get rid of this hack and use IHostAudio::SetCallback. */
945 AssertMsgRCReturn(rc, ("Confguration error: No/bad \"ObjectConsole\" value, rc=%Rrc\n", rc), rc);
946
947 /* CFGM tree saves the pointer to Console in the Object node of AudioVideoRec. */
948 pThis->pConsole = (Console *)pvUser;
949
950 /*
951 * Get the pointer to the audio driver instance.
952 */
953 rc = CFGMR3QueryPtr(pCfg, "Object", &pvUser); /** @todo r=andy Get rid of this hack and use IHostAudio::SetCallback. */
954 AssertMsgRCReturn(rc, ("Confguration error: No/bad \"Object\" value, rc=%Rrc\n", rc), rc);
955
956 pThis->pAudioVideoRec = (AudioVideoRec *)pvUser;
957 pThis->pAudioVideoRec->mpDrv = pThis;
958
959 /*
960 * Get the interface for the above driver (DrvAudio) to make mixer/conversion calls.
961 * Described in CFGM tree.
962 */
963 pThis->pDrvAudio = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIAUDIOCONNECTOR);
964 AssertMsgReturn(pThis->pDrvAudio, ("Configuration error: No upper interface specified!\n"), VERR_PDM_MISSING_INTERFACE_ABOVE);
965
966 return VINF_SUCCESS;
967}
968
969
970/**
971 * @interface_method_impl{PDMDRVREG,pfnDestruct}
972 */
973/* static */
974DECLCALLBACK(void) AudioVideoRec::drvDestruct(PPDMDRVINS pDrvIns)
975{
976 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
977 PDRVAUDIOVIDEOREC pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIOVIDEOREC);
978 LogFlowFuncEnter();
979
980 /*
981 * If the AudioVideoRec object is still alive, we must clear it's reference to
982 * us since we'll be invalid when we return from this method.
983 */
984 if (pThis->pAudioVideoRec)
985 {
986 pThis->pAudioVideoRec->mpDrv = NULL;
987 pThis->pAudioVideoRec = NULL;
988 }
989}
990
991
992/**
993 * Video recording audio driver registration record.
994 */
995const PDMDRVREG AudioVideoRec::DrvReg =
996{
997 PDM_DRVREG_VERSION,
998 /* szName */
999 "AudioVideoRec",
1000 /* szRCMod */
1001 "",
1002 /* szR0Mod */
1003 "",
1004 /* pszDescription */
1005 "Audio driver for video recording",
1006 /* fFlags */
1007 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
1008 /* fClass. */
1009 PDM_DRVREG_CLASS_AUDIO,
1010 /* cMaxInstances */
1011 ~0U,
1012 /* cbInstance */
1013 sizeof(DRVAUDIOVIDEOREC),
1014 /* pfnConstruct */
1015 AudioVideoRec::drvConstruct,
1016 /* pfnDestruct */
1017 AudioVideoRec::drvDestruct,
1018 /* pfnRelocate */
1019 NULL,
1020 /* pfnIOCtl */
1021 NULL,
1022 /* pfnPowerOn */
1023 NULL,
1024 /* pfnReset */
1025 NULL,
1026 /* pfnSuspend */
1027 NULL,
1028 /* pfnResume */
1029 NULL,
1030 /* pfnAttach */
1031 NULL,
1032 /* pfnDetach */
1033 NULL,
1034 /* pfnPowerOff */
1035 NULL,
1036 /* pfnSoftReset */
1037 NULL,
1038 /* u32EndVersion */
1039 PDM_DRVREG_VERSION
1040};
1041
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