VirtualBox

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

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

Warning.

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