VirtualBox

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

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

Audio: More abstraction for the backends: Now the backend stream's data is completely separate from the audio connector interface. That way the backends cannot mess with the audio connector's data (e.g. mixing buffers and friends) anymore, and those are forced to use the audio connector API as meant now. Needs more testing, partly work in progress.

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