Changeset 88459 in vbox for trunk/src/VBox
- Timestamp:
- Apr 12, 2021 10:07:51 AM (4 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/VBox/Main/src-client/DrvAudioRec.cpp
r88395 r88459 113 113 * Structures and Typedefs * 114 114 *********************************************************************************************************************************/ 115 116 115 /** 117 116 * Enumeration for specifying the recording container type. … … 219 218 }; 220 219 221 #ifdef VBOX_WITH_STATISTICS /** @todo Make these real STAMvalues. */220 #ifdef VBOX_WITH_STATISTICS /** @todo Register these values. */ 222 221 struct 223 222 { … … 226 225 /** Total time (in ms) of already encoded audio data. */ 227 226 uint64_t msEncTotal; 228 } STAM; 229 #endif /* VBOX_WITH_STATISTICS */ 230 227 } Stats; 228 #endif 231 229 } AVRECCODEC, *PAVRECCODEC; 232 230 … … 249 247 { 250 248 /** The stream's acquired configuration. */ 251 P PDMAUDIOSTREAMCFG pCfg;249 PDMAUDIOSTREAMCFG Cfg; 252 250 /** (Audio) frame buffer. */ 253 251 PRTCIRCBUF pCircBuf; … … 289 287 } DRVAUDIORECORDING, *PDRVAUDIORECORDING; 290 288 291 /** Makes DRVAUDIORECORDING out of PDMIHOSTAUDIO. */ 292 #define PDMIHOSTAUDIO_2_DRVAUDIORECORDING(pInterface) /* (clang doesn't think it is a POD, thus _DYN.) */ \ 293 ( (PDRVAUDIORECORDING)((uintptr_t)pInterface - RT_UOFFSETOF_DYN(DRVAUDIORECORDING, IHostAudio)) ) 289 290 AudioVideoRec::AudioVideoRec(Console *pConsole) 291 : AudioDriver(pConsole) 292 , mpDrv(NULL) 293 { 294 } 295 296 297 AudioVideoRec::~AudioVideoRec(void) 298 { 299 if (mpDrv) 300 { 301 mpDrv->pAudioVideoRec = NULL; 302 mpDrv = NULL; 303 } 304 } 305 306 307 /** 308 * Applies a video recording configuration to this driver instance. 309 * 310 * @returns VBox status code. 311 * @param Settings Capturing configuration to apply. 312 */ 313 int AudioVideoRec::applyConfiguration(const settings::RecordingSettings &Settings) 314 { 315 /** @todo Do some validation here. */ 316 mVideoRecCfg = Settings; /* Note: Does have an own copy operator. */ 317 return VINF_SUCCESS; 318 } 319 320 321 /** 322 * @copydoc AudioDriver::configureDriver 323 */ 324 int AudioVideoRec::configureDriver(PCFGMNODE pLunCfg) 325 { 326 int rc = CFGMR3InsertInteger(pLunCfg, "Object", (uintptr_t)mpConsole->i_recordingGetAudioDrv()); 327 AssertRCReturn(rc, rc); 328 rc = CFGMR3InsertInteger(pLunCfg, "ObjectConsole", (uintptr_t)mpConsole); 329 AssertRCReturn(rc, rc); 330 331 /** @todo For now we're using the configuration of the first screen here audio-wise. */ 332 Assert(mVideoRecCfg.mapScreens.size() >= 1); 333 const settings::RecordingScreenSettings &Screen0Settings = mVideoRecCfg.mapScreens[0]; 334 335 rc = CFGMR3InsertInteger(pLunCfg, "ContainerType", (uint64_t)Screen0Settings.enmDest); 336 AssertRCReturn(rc, rc); 337 if (Screen0Settings.enmDest == RecordingDestination_File) 338 { 339 rc = CFGMR3InsertString(pLunCfg, "ContainerFileName", Utf8Str(Screen0Settings.File.strName).c_str()); 340 AssertRCReturn(rc, rc); 341 } 342 rc = CFGMR3InsertInteger(pLunCfg, "CodecHz", Screen0Settings.Audio.uHz); 343 AssertRCReturn(rc, rc); 344 rc = CFGMR3InsertInteger(pLunCfg, "CodecBits", Screen0Settings.Audio.cBits); 345 AssertRCReturn(rc, rc); 346 rc = CFGMR3InsertInteger(pLunCfg, "CodecChannels", Screen0Settings.Audio.cChannels); 347 AssertRCReturn(rc, rc); 348 rc = CFGMR3InsertInteger(pLunCfg, "CodecBitrate", 0); /* Let Opus decide for now. */ 349 AssertRCReturn(rc, rc); 350 351 return AudioDriver::configureDriver(pLunCfg); 352 } 353 354 355 /********************************************************************************************************************************* 356 * PDMIHOSTAUDIO * 357 *********************************************************************************************************************************/ 358 359 /** 360 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig} 361 */ 362 static DECLCALLBACK(int) drvAudioVideoRecHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg) 363 { 364 RT_NOREF(pInterface); 365 AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER); 366 367 RTStrPrintf2(pBackendCfg->szName, sizeof(pBackendCfg->szName), "VideoRec"); 368 369 pBackendCfg->cbStreamOut = sizeof(AVRECSTREAM); 370 pBackendCfg->cbStreamIn = 0; 371 pBackendCfg->cMaxStreamsIn = 0; 372 pBackendCfg->cMaxStreamsOut = UINT32_MAX; 373 374 return VINF_SUCCESS; 375 } 376 377 378 /** 379 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus} 380 */ 381 static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvAudioVideoRecHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir) 382 { 383 RT_NOREF(pInterface, enmDir); 384 return PDMAUDIOBACKENDSTS_RUNNING; 385 } 386 387 388 /** 389 * Creates an audio output stream and associates it with the specified recording sink. 390 * 391 * @returns VBox status code. 392 * @param pThis Driver instance. 393 * @param pStreamAV Audio output stream to create. 394 * @param pSink Recording sink to associate audio output stream to. 395 * @param pCfgReq Requested configuration by the audio backend. 396 * @param pCfgAcq Acquired configuration by the audio output stream. 397 */ 398 static int avRecCreateStreamOut(PDRVAUDIORECORDING pThis, PAVRECSTREAM pStreamAV, 399 PAVRECSINK pSink, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) 400 { 401 AssertPtrReturn(pThis, VERR_INVALID_POINTER); 402 AssertPtrReturn(pStreamAV, VERR_INVALID_POINTER); 403 AssertPtrReturn(pSink, VERR_INVALID_POINTER); 404 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); 405 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); 406 407 if (pCfgReq->u.enmDst != PDMAUDIOPLAYBACKDST_FRONT) 408 { 409 LogRel2(("Recording: Support for surround audio not implemented yet\n")); 410 AssertFailed(); 411 return VERR_NOT_SUPPORTED; 412 } 413 414 #ifdef VBOX_WITH_LIBOPUS 415 int rc = RTCircBufCreate(&pStreamAV->pCircBuf, pSink->Codec.Opus.cbFrame * 2 /* Use "double buffering" */); 416 if (RT_SUCCESS(rc)) 417 { 418 size_t cbScratchBuf = pSink->Codec.Opus.cbFrame; 419 pStreamAV->pvSrcBuf = RTMemAlloc(cbScratchBuf); 420 if (pStreamAV->pvSrcBuf) 421 { 422 pStreamAV->cbSrcBuf = cbScratchBuf; 423 pStreamAV->pvDstBuf = RTMemAlloc(cbScratchBuf); 424 if (pStreamAV->pvDstBuf) 425 { 426 pStreamAV->cbDstBuf = cbScratchBuf; 427 428 pStreamAV->pSink = pSink; /* Assign sink to stream. */ 429 pStreamAV->uLastPTSMs = 0; 430 431 /* Make sure to let the driver backend know that we need the audio data in 432 * a specific sampling rate Opus is optimized for. */ 433 /** @todo r=bird: pCfgAcq->Props isn't initialized at all, except for uHz... */ 434 pCfgAcq->Props.uHz = pSink->Codec.Parms.PCMProps.uHz; 435 // pCfgAcq->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfgAcq->Props.cbSample, pCfgAcq->Props.cChannels); 436 437 /* Every Opus frame marks a period for now. Optimize this later. */ 438 pCfgAcq->Backend.cFramesPeriod = PDMAudioPropsMilliToFrames(&pCfgAcq->Props, pSink->Codec.Opus.msFrame); 439 pCfgAcq->Backend.cFramesBufferSize = PDMAudioPropsMilliToFrames(&pCfgAcq->Props, 100 /*ms*/); /** @todo Make this configurable. */ 440 pCfgAcq->Backend.cFramesPreBuffering = pCfgAcq->Backend.cFramesPeriod * 2; 441 } 442 else 443 rc = VERR_NO_MEMORY; 444 } 445 else 446 rc = VERR_NO_MEMORY; 447 } 448 #else 449 RT_NOREF(pThis, pSink, pStreamAV, pCfgReq, pCfgAcq); 450 int rc = VERR_NOT_SUPPORTED; 451 #endif /* VBOX_WITH_LIBOPUS */ 452 453 LogFlowFuncLeaveRC(rc); 454 return rc; 455 } 456 457 458 /** 459 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate} 460 */ 461 static DECLCALLBACK(int) drvAudioVideoRecHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, 462 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) 463 { 464 PDRVAUDIORECORDING pThis = RT_FROM_CPP_MEMBER(pInterface, DRVAUDIORECORDING, IHostAudio); 465 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream; 466 AssertPtrReturn(pStreamAV, VERR_INVALID_POINTER); 467 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER); 468 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER); 469 470 if (pCfgReq->enmDir == PDMAUDIODIR_IN) 471 return VERR_NOT_SUPPORTED; 472 473 /* For now we only have one sink, namely the driver's one. 474 * Later each stream could have its own one, to e.g. router different stream to different sinks .*/ 475 PAVRECSINK pSink = &pThis->Sink; 476 477 int rc = avRecCreateStreamOut(pThis, pStreamAV, pSink, pCfgReq, pCfgAcq); 478 PDMAudioStrmCfgCopy(&pStreamAV->Cfg, pCfgAcq); 479 480 return rc; 481 } 482 483 484 /** 485 * Destroys (closes) an audio output stream. 486 * 487 * @returns VBox status code. 488 * @param pThis Driver instance. 489 * @param pStreamAV Audio output stream to destroy. 490 */ 491 static int avRecDestroyStreamOut(PDRVAUDIORECORDING pThis, PAVRECSTREAM pStreamAV) 492 { 493 RT_NOREF(pThis); 494 495 if (pStreamAV->pCircBuf) 496 { 497 RTCircBufDestroy(pStreamAV->pCircBuf); 498 pStreamAV->pCircBuf = NULL; 499 } 500 501 if (pStreamAV->pvSrcBuf) 502 { 503 Assert(pStreamAV->cbSrcBuf); 504 RTMemFree(pStreamAV->pvSrcBuf); 505 pStreamAV->pvSrcBuf = NULL; 506 pStreamAV->cbSrcBuf = 0; 507 } 508 509 if (pStreamAV->pvDstBuf) 510 { 511 Assert(pStreamAV->cbDstBuf); 512 RTMemFree(pStreamAV->pvDstBuf); 513 pStreamAV->pvDstBuf = NULL; 514 pStreamAV->cbDstBuf = 0; 515 } 516 517 return VINF_SUCCESS; 518 } 519 520 521 /** 522 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy} 523 */ 524 static DECLCALLBACK(int) drvAudioVideoRecHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) 525 { 526 PDRVAUDIORECORDING pThis = RT_FROM_CPP_MEMBER(pInterface, DRVAUDIORECORDING, IHostAudio); 527 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream; 528 AssertPtrReturn(pStream, VERR_INVALID_POINTER); 529 530 int rc = VINF_SUCCESS; 531 if (pStreamAV->Cfg.enmDir == PDMAUDIODIR_OUT) 532 rc = avRecDestroyStreamOut(pThis, pStreamAV); 533 534 return rc; 535 } 536 537 538 /** 539 * Controls an audio output stream 540 * 541 * @returns VBox status code. 542 * @param pThis Driver instance. 543 * @param pStreamAV Audio output stream to control. 544 * @param enmStreamCmd Stream command to issue. 545 */ 546 static int avRecControlStreamOut(PDRVAUDIORECORDING pThis, PAVRECSTREAM pStreamAV, PDMAUDIOSTREAMCMD enmStreamCmd) 547 { 548 RT_NOREF(pThis, pStreamAV); 549 550 int rc; 551 switch (enmStreamCmd) 552 { 553 case PDMAUDIOSTREAMCMD_ENABLE: 554 case PDMAUDIOSTREAMCMD_DISABLE: 555 case PDMAUDIOSTREAMCMD_RESUME: 556 case PDMAUDIOSTREAMCMD_PAUSE: 557 rc = VINF_SUCCESS; 558 break; 559 560 default: 561 rc = VERR_NOT_SUPPORTED; 562 break; 563 } 564 565 return rc; 566 } 567 568 569 /** 570 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl} 571 */ 572 static DECLCALLBACK(int) drvAudioVideoRecHA_StreamControl(PPDMIHOSTAUDIO pInterface, 573 PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd) 574 { 575 PDRVAUDIORECORDING pThis = RT_FROM_CPP_MEMBER(pInterface, DRVAUDIORECORDING, IHostAudio); 576 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream; 577 AssertPtrReturn(pStreamAV, VERR_INVALID_POINTER); 578 579 if (pStreamAV->Cfg.enmDir == PDMAUDIODIR_OUT) 580 return avRecControlStreamOut(pThis, pStreamAV, enmStreamCmd); 581 582 return VINF_SUCCESS; 583 } 584 585 586 /** 587 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable} 588 */ 589 static DECLCALLBACK(uint32_t) drvAudioVideoRecHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) 590 { 591 RT_NOREF(pInterface, pStream); 592 return 0; /* Video capturing does not provide any input. */ 593 } 594 595 596 /** 597 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable} 598 */ 599 static DECLCALLBACK(uint32_t) drvAudioVideoRecHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream) 600 { 601 RT_NOREF(pInterface, pStream); 602 return UINT32_MAX; 603 } 604 605 606 /** 607 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus} 608 */ 609 static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvAudioVideoRecHA_StreamGetStatus(PPDMIHOSTAUDIO pInterface, 610 PPDMAUDIOBACKENDSTREAM pStream) 611 { 612 RT_NOREF(pInterface, pStream); 613 return PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED | PDMAUDIOSTREAMSTS_FLAGS_ENABLED; 614 } 615 616 617 /** 618 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay} 619 */ 620 static DECLCALLBACK(int) drvAudioVideoRecHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, 621 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten) 622 { 623 RT_NOREF(pInterface); 624 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream; 625 AssertPtrReturn(pStreamAV, VERR_INVALID_POINTER); 626 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); 627 AssertReturn(cbBuf, VERR_INVALID_PARAMETER); 628 AssertReturn(pcbWritten, VERR_INVALID_PARAMETER); 629 630 int rc = VINF_SUCCESS; 631 632 uint32_t cbWrittenTotal = 0; 633 634 /* 635 * Call the encoder with the data. 636 */ 637 #ifdef VBOX_WITH_LIBOPUS 638 PAVRECSINK pSink = pStreamAV->pSink; 639 AssertPtr(pSink); 640 PAVRECCODEC pCodec = &pSink->Codec; 641 AssertPtr(pCodec); 642 PRTCIRCBUF pCircBuf = pStreamAV->pCircBuf; 643 AssertPtr(pCircBuf); 644 645 uint32_t cbToWrite = cbBuf; 646 647 /* 648 * Write as much as we can into our internal ring buffer. 649 */ 650 while ( cbToWrite 651 && RTCircBufFree(pCircBuf)) 652 { 653 void *pvCircBuf = NULL; 654 size_t cbCircBuf = 0; 655 RTCircBufAcquireWriteBlock(pCircBuf, cbToWrite, &pvCircBuf, &cbCircBuf); 656 657 if (cbCircBuf) 658 { 659 memcpy(pvCircBuf, (uint8_t *)pvBuf + cbWrittenTotal, cbCircBuf), 660 cbWrittenTotal += (uint32_t)cbCircBuf; 661 Assert(cbToWrite >= cbCircBuf); 662 cbToWrite -= (uint32_t)cbCircBuf; 663 } 664 665 RTCircBufReleaseWriteBlock(pCircBuf, cbCircBuf); 666 AssertBreak(cbCircBuf); 667 } 668 669 /* 670 * Process our internal ring buffer and encode the data. 671 */ 672 673 /* Only encode data if we have data for the given time period (or more). */ 674 while (RTCircBufUsed(pCircBuf) >= pCodec->Opus.cbFrame) 675 { 676 LogFunc(("cbAvail=%zu, csFrame=%RU32, cbFrame=%RU32\n", 677 RTCircBufUsed(pCircBuf), pCodec->Opus.csFrame, pCodec->Opus.cbFrame)); 678 679 uint32_t cbSrc = 0; 680 while (cbSrc < pCodec->Opus.cbFrame) 681 { 682 void *pvCircBuf = NULL; 683 size_t cbCircBuf = 0; 684 RTCircBufAcquireReadBlock(pCircBuf, pCodec->Opus.cbFrame - cbSrc, &pvCircBuf, &cbCircBuf); 685 686 if (cbCircBuf) 687 { 688 memcpy((uint8_t *)pStreamAV->pvSrcBuf + cbSrc, pvCircBuf, cbCircBuf); 689 690 cbSrc += (uint32_t)cbCircBuf; 691 Assert(cbSrc <= pStreamAV->cbSrcBuf); 692 } 693 694 RTCircBufReleaseReadBlock(pCircBuf, cbCircBuf); 695 AssertBreak(cbCircBuf); 696 } 697 698 Assert(cbSrc == pCodec->Opus.cbFrame); 699 700 # ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA 701 RTFILE fh; 702 RTFileOpen(&fh, VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "DrvAudioVideoRec.pcm", 703 RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); 704 RTFileWrite(fh, pStreamAV->pvSrcBuf, cbSrc, NULL); 705 RTFileClose(fh); 706 # endif 707 708 /* 709 * Opus always encodes PER "OPUS FRAME", that is, exactly 2.5, 5, 10, 20, 40 or 60 ms of audio data. 710 * 711 * A packet can have up to 120ms worth of audio data. 712 * Anything > 120ms of data will result in a "corrupted package" error message by 713 * by decoding application. 714 */ 715 716 /* Call the encoder to encode one "Opus frame" per iteration. */ 717 opus_int32 cbWritten = opus_encode(pSink->Codec.Opus.pEnc, 718 (opus_int16 *)pStreamAV->pvSrcBuf, pCodec->Opus.csFrame, 719 (uint8_t *)pStreamAV->pvDstBuf, (opus_int32)pStreamAV->cbDstBuf); 720 if (cbWritten > 0) 721 { 722 /* Get overall frames encoded. */ 723 const uint32_t cEncFrames = opus_packet_get_nb_frames((uint8_t *)pStreamAV->pvDstBuf, cbWritten); 724 725 # ifdef VBOX_WITH_STATISTICS 726 pSink->Codec.Stats.cEncFrames += cEncFrames; 727 pSink->Codec.Stats.msEncTotal += pSink->Codec.Opus.msFrame * cEncFrames; 728 # endif 729 Assert((uint32_t)cbWritten <= (uint32_t)pStreamAV->cbDstBuf); 730 const uint32_t cbDst = RT_MIN((uint32_t)cbWritten, (uint32_t)pStreamAV->cbDstBuf); 731 732 Assert(cEncFrames == 1); 733 734 if (pStreamAV->uLastPTSMs == 0) 735 pStreamAV->uLastPTSMs = RTTimeProgramMilliTS(); /* We want the absolute time (in ms) since program start. */ 736 737 const uint64_t uDurationMs = pSink->Codec.Opus.msFrame * cEncFrames; 738 const uint64_t uPTSMs = pStreamAV->uLastPTSMs; 739 740 pStreamAV->uLastPTSMs += uDurationMs; 741 742 switch (pSink->Con.Parms.enmType) 743 { 744 case AVRECCONTAINERTYPE_MAIN_CONSOLE: 745 { 746 HRESULT hr = pSink->Con.Main.pConsole->i_recordingSendAudio(pStreamAV->pvDstBuf, cbDst, uPTSMs); 747 Assert(hr == S_OK); 748 RT_NOREF(hr); 749 break; 750 } 751 752 case AVRECCONTAINERTYPE_WEBM: 753 { 754 WebMWriter::BlockData_Opus blockData = { pStreamAV->pvDstBuf, cbDst, uPTSMs }; 755 rc = pSink->Con.WebM.pWebM->WriteBlock(pSink->Con.WebM.uTrack, &blockData, sizeof(blockData)); 756 AssertRC(rc); 757 break; 758 } 759 760 default: 761 AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED); 762 break; 763 } 764 } 765 else if (cbWritten < 0) 766 { 767 AssertMsgFailed(("Encoding failed: %s\n", opus_strerror(cbWritten))); 768 rc = VERR_INVALID_PARAMETER; 769 } 770 771 if (RT_FAILURE(rc)) 772 break; 773 } 774 775 *pcbWritten = cbWrittenTotal; 776 #else 777 /* Report back all data as being processed. */ 778 *pcbWritten = cbBuf; 779 780 rc = VERR_NOT_SUPPORTED; 781 #endif /* VBOX_WITH_LIBOPUS */ 782 783 LogFlowFunc(("csReadTotal=%RU32, rc=%Rrc\n", cbWrittenTotal, rc)); 784 return rc; 785 } 786 787 788 /** 789 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture} 790 */ 791 static DECLCALLBACK(int) drvAudioVideoRecHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream, 792 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead) 793 { 794 RT_NOREF(pInterface, pStream, pvBuf, cbBuf); 795 *pcbRead = 0; 796 return VINF_SUCCESS; 797 } 798 799 800 /********************************************************************************************************************************* 801 * PDMIBASE * 802 *********************************************************************************************************************************/ 803 804 /** 805 * @interface_method_impl{PDMIBASE,pfnQueryInterface} 806 */ 807 static DECLCALLBACK(void *) drvAudioVideoRecQueryInterface(PPDMIBASE pInterface, const char *pszIID) 808 { 809 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); 810 PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING); 811 812 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); 813 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio); 814 return NULL; 815 } 816 817 818 /********************************************************************************************************************************* 819 * PDMDRVREG * 820 *********************************************************************************************************************************/ 821 822 /** 823 * Shuts down (closes) a recording sink, 824 * 825 * @returns VBox status code. 826 * @param pSink Recording sink to shut down. 827 */ 828 static void avRecSinkShutdown(PAVRECSINK pSink) 829 { 830 AssertPtrReturnVoid(pSink); 831 832 #ifdef VBOX_WITH_LIBOPUS 833 if (pSink->Codec.Opus.pEnc) 834 { 835 opus_encoder_destroy(pSink->Codec.Opus.pEnc); 836 pSink->Codec.Opus.pEnc = NULL; 837 } 838 #endif 839 switch (pSink->Con.Parms.enmType) 840 { 841 case AVRECCONTAINERTYPE_WEBM: 842 { 843 if (pSink->Con.WebM.pWebM) 844 { 845 LogRel2(("Recording: Finished recording audio to file '%s' (%zu bytes)\n", 846 pSink->Con.WebM.pWebM->GetFileName().c_str(), pSink->Con.WebM.pWebM->GetFileSize())); 847 848 int rc2 = pSink->Con.WebM.pWebM->Close(); 849 AssertRC(rc2); 850 851 delete pSink->Con.WebM.pWebM; 852 pSink->Con.WebM.pWebM = NULL; 853 } 854 break; 855 } 856 857 case AVRECCONTAINERTYPE_MAIN_CONSOLE: 858 default: 859 break; 860 } 861 } 862 863 864 /** 865 * @interface_method_impl{PDMDRVREG,pfnPowerOff} 866 */ 867 /*static*/ DECLCALLBACK(void) AudioVideoRec::drvPowerOff(PPDMDRVINS pDrvIns) 868 { 869 PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING); 870 LogFlowFuncEnter(); 871 avRecSinkShutdown(&pThis->Sink); 872 } 873 874 875 /** 876 * @interface_method_impl{PDMDRVREG,pfnDestruct} 877 */ 878 /*static*/ DECLCALLBACK(void) AudioVideoRec::drvDestruct(PPDMDRVINS pDrvIns) 879 { 880 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); 881 PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING); 882 883 LogFlowFuncEnter(); 884 885 switch (pThis->ContainerParms.enmType) 886 { 887 case AVRECCONTAINERTYPE_WEBM: 888 { 889 avRecSinkShutdown(&pThis->Sink); 890 RTStrFree(pThis->ContainerParms.WebM.pszFile); 891 break; 892 } 893 894 default: 895 break; 896 } 897 898 /* 899 * If the AudioVideoRec object is still alive, we must clear it's reference to 900 * us since we'll be invalid when we return from this method. 901 */ 902 if (pThis->pAudioVideoRec) 903 { 904 pThis->pAudioVideoRec->mpDrv = NULL; 905 pThis->pAudioVideoRec = NULL; 906 } 907 908 LogFlowFuncLeave(); 909 } 910 294 911 295 912 /** … … 434 1051 435 1052 #ifdef VBOX_WITH_STATISTICS 436 pSink->Codec.S TAM.cEncFrames = 0;437 pSink->Codec.S TAM.msEncTotal = 0;1053 pSink->Codec.Stats.cEncFrames = 0; 1054 pSink->Codec.Stats.msEncTotal = 0; 438 1055 #endif 439 pSink->tsStartMs = RTTimeMilliTS();1056 pSink->tsStartMs = RTTimeMilliTS(); 440 1057 } 441 1058 else … … 451 1068 452 1069 return rc; 453 }454 455 456 /**457 * Shuts down (closes) a recording sink,458 *459 * @returns VBox status code.460 * @param pSink Recording sink to shut down.461 */462 static void avRecSinkShutdown(PAVRECSINK pSink)463 {464 AssertPtrReturnVoid(pSink);465 466 #ifdef VBOX_WITH_LIBOPUS467 if (pSink->Codec.Opus.pEnc)468 {469 opus_encoder_destroy(pSink->Codec.Opus.pEnc);470 pSink->Codec.Opus.pEnc = NULL;471 }472 #endif473 switch (pSink->Con.Parms.enmType)474 {475 case AVRECCONTAINERTYPE_WEBM:476 {477 if (pSink->Con.WebM.pWebM)478 {479 LogRel2(("Recording: Finished recording audio to file '%s' (%zu bytes)\n",480 pSink->Con.WebM.pWebM->GetFileName().c_str(), pSink->Con.WebM.pWebM->GetFileSize()));481 482 int rc2 = pSink->Con.WebM.pWebM->Close();483 AssertRC(rc2);484 485 delete pSink->Con.WebM.pWebM;486 pSink->Con.WebM.pWebM = NULL;487 }488 break;489 }490 491 case AVRECCONTAINERTYPE_MAIN_CONSOLE:492 default:493 break;494 }495 }496 497 498 /**499 * Creates an audio output stream and associates it with the specified recording sink.500 *501 * @returns VBox status code.502 * @param pThis Driver instance.503 * @param pStreamAV Audio output stream to create.504 * @param pSink Recording sink to associate audio output stream to.505 * @param pCfgReq Requested configuration by the audio backend.506 * @param pCfgAcq Acquired configuration by the audio output stream.507 */508 static int avRecCreateStreamOut(PDRVAUDIORECORDING pThis, PAVRECSTREAM pStreamAV,509 PAVRECSINK pSink, PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)510 {511 AssertPtrReturn(pThis, VERR_INVALID_POINTER);512 AssertPtrReturn(pStreamAV, VERR_INVALID_POINTER);513 AssertPtrReturn(pSink, VERR_INVALID_POINTER);514 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);515 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);516 517 if (pCfgReq->u.enmDst != PDMAUDIOPLAYBACKDST_FRONT)518 {519 LogRel2(("Recording: Support for surround audio not implemented yet\n"));520 AssertFailed();521 return VERR_NOT_SUPPORTED;522 }523 524 #ifdef VBOX_WITH_LIBOPUS525 int rc = RTCircBufCreate(&pStreamAV->pCircBuf, pSink->Codec.Opus.cbFrame * 2 /* Use "double buffering" */);526 if (RT_SUCCESS(rc))527 {528 size_t cbScratchBuf = pSink->Codec.Opus.cbFrame;529 pStreamAV->pvSrcBuf = RTMemAlloc(cbScratchBuf);530 if (pStreamAV->pvSrcBuf)531 {532 pStreamAV->cbSrcBuf = cbScratchBuf;533 pStreamAV->pvDstBuf = RTMemAlloc(cbScratchBuf);534 if (pStreamAV->pvDstBuf)535 {536 pStreamAV->cbDstBuf = cbScratchBuf;537 538 pStreamAV->pSink = pSink; /* Assign sink to stream. */539 pStreamAV->uLastPTSMs = 0;540 541 /* Make sure to let the driver backend know that we need the audio data in542 * a specific sampling rate Opus is optimized for. */543 /** @todo r=bird: pCfgAcq->Props isn't initialized at all, except for uHz... */544 pCfgAcq->Props.uHz = pSink->Codec.Parms.PCMProps.uHz;545 // pCfgAcq->Props.cShift = PDMAUDIOPCMPROPS_MAKE_SHIFT_PARMS(pCfgAcq->Props.cbSample, pCfgAcq->Props.cChannels);546 547 /* Every Opus frame marks a period for now. Optimize this later. */548 pCfgAcq->Backend.cFramesPeriod = PDMAudioPropsMilliToFrames(&pCfgAcq->Props, pSink->Codec.Opus.msFrame);549 pCfgAcq->Backend.cFramesBufferSize = PDMAudioPropsMilliToFrames(&pCfgAcq->Props, 100 /*ms*/); /** @todo Make this configurable. */550 pCfgAcq->Backend.cFramesPreBuffering = pCfgAcq->Backend.cFramesPeriod * 2;551 }552 else553 rc = VERR_NO_MEMORY;554 }555 else556 rc = VERR_NO_MEMORY;557 }558 #else559 RT_NOREF(pThis, pSink, pStreamAV, pCfgReq, pCfgAcq);560 int rc = VERR_NOT_SUPPORTED;561 #endif /* VBOX_WITH_LIBOPUS */562 563 LogFlowFuncLeaveRC(rc);564 return rc;565 }566 567 568 /**569 * Destroys (closes) an audio output stream.570 *571 * @returns VBox status code.572 * @param pThis Driver instance.573 * @param pStreamAV Audio output stream to destroy.574 */575 static int avRecDestroyStreamOut(PDRVAUDIORECORDING pThis, PAVRECSTREAM pStreamAV)576 {577 RT_NOREF(pThis);578 579 if (pStreamAV->pCircBuf)580 {581 RTCircBufDestroy(pStreamAV->pCircBuf);582 pStreamAV->pCircBuf = NULL;583 }584 585 if (pStreamAV->pvSrcBuf)586 {587 Assert(pStreamAV->cbSrcBuf);588 RTMemFree(pStreamAV->pvSrcBuf);589 pStreamAV->pvSrcBuf = NULL;590 pStreamAV->cbSrcBuf = 0;591 }592 593 if (pStreamAV->pvDstBuf)594 {595 Assert(pStreamAV->cbDstBuf);596 RTMemFree(pStreamAV->pvDstBuf);597 pStreamAV->pvDstBuf = NULL;598 pStreamAV->cbDstBuf = 0;599 }600 601 return VINF_SUCCESS;602 }603 604 605 /**606 * Controls an audio output stream607 *608 * @returns VBox status code.609 * @param pThis Driver instance.610 * @param pStreamAV Audio output stream to control.611 * @param enmStreamCmd Stream command to issue.612 */613 static int avRecControlStreamOut(PDRVAUDIORECORDING pThis, PAVRECSTREAM pStreamAV, PDMAUDIOSTREAMCMD enmStreamCmd)614 {615 RT_NOREF(pThis, pStreamAV);616 617 int rc = VINF_SUCCESS;618 619 switch (enmStreamCmd)620 {621 case PDMAUDIOSTREAMCMD_ENABLE:622 case PDMAUDIOSTREAMCMD_DISABLE:623 case PDMAUDIOSTREAMCMD_RESUME:624 case PDMAUDIOSTREAMCMD_PAUSE:625 break;626 627 default:628 rc = VERR_NOT_SUPPORTED;629 break;630 }631 632 return rc;633 }634 635 636 /**637 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCapture}638 */639 static DECLCALLBACK(int) drvAudioVideoRecHA_StreamCapture(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,640 void *pvBuf, uint32_t uBufSize, uint32_t *puRead)641 {642 RT_NOREF(pInterface, pStream, pvBuf, uBufSize);643 644 if (puRead)645 *puRead = 0;646 647 return VINF_SUCCESS;648 }649 650 651 /**652 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamPlay}653 */654 static DECLCALLBACK(int) drvAudioVideoRecHA_StreamPlay(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,655 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)656 {657 RT_NOREF(pInterface);658 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;659 AssertPtrReturn(pStreamAV, VERR_INVALID_POINTER);660 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);661 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);662 AssertReturn(pcbWritten, VERR_INVALID_PARAMETER);663 664 int rc = VINF_SUCCESS;665 666 uint32_t cbWrittenTotal = 0;667 668 /*669 * Call the encoder with the data.670 */671 #ifdef VBOX_WITH_LIBOPUS672 PAVRECSINK pSink = pStreamAV->pSink;673 AssertPtr(pSink);674 PAVRECCODEC pCodec = &pSink->Codec;675 AssertPtr(pCodec);676 PRTCIRCBUF pCircBuf = pStreamAV->pCircBuf;677 AssertPtr(pCircBuf);678 679 void *pvCircBuf;680 size_t cbCircBuf;681 682 uint32_t cbToWrite = cbBuf;683 684 /*685 * Fetch as much as we can into our internal ring buffer.686 */687 while ( cbToWrite688 && RTCircBufFree(pCircBuf))689 {690 RTCircBufAcquireWriteBlock(pCircBuf, cbToWrite, &pvCircBuf, &cbCircBuf);691 692 if (cbCircBuf)693 {694 memcpy(pvCircBuf, (uint8_t *)pvBuf + cbWrittenTotal, cbCircBuf),695 cbWrittenTotal += (uint32_t)cbCircBuf;696 Assert(cbToWrite >= cbCircBuf);697 cbToWrite -= (uint32_t)cbCircBuf;698 }699 700 RTCircBufReleaseWriteBlock(pCircBuf, cbCircBuf);701 702 if ( RT_FAILURE(rc)703 || !cbCircBuf)704 {705 break;706 }707 }708 709 /*710 * Process our internal ring buffer and encode the data.711 */712 713 uint32_t cbSrc;714 715 /* Only encode data if we have data for the given time period (or more). */716 while (RTCircBufUsed(pCircBuf) >= pCodec->Opus.cbFrame)717 {718 LogFunc(("cbAvail=%zu, csFrame=%RU32, cbFrame=%RU32\n",719 RTCircBufUsed(pCircBuf), pCodec->Opus.csFrame, pCodec->Opus.cbFrame));720 721 cbSrc = 0;722 723 while (cbSrc < pCodec->Opus.cbFrame)724 {725 RTCircBufAcquireReadBlock(pCircBuf, pCodec->Opus.cbFrame - cbSrc, &pvCircBuf, &cbCircBuf);726 727 if (cbCircBuf)728 {729 memcpy((uint8_t *)pStreamAV->pvSrcBuf + cbSrc, pvCircBuf, cbCircBuf);730 731 cbSrc += (uint32_t)cbCircBuf;732 Assert(cbSrc <= pStreamAV->cbSrcBuf);733 }734 735 RTCircBufReleaseReadBlock(pCircBuf, cbCircBuf);736 737 if (!cbCircBuf)738 break;739 }740 741 # ifdef VBOX_AUDIO_DEBUG_DUMP_PCM_DATA742 RTFILE fh;743 RTFileOpen(&fh, VBOX_AUDIO_DEBUG_DUMP_PCM_DATA_PATH "DrvAudioVideoRec.pcm",744 RTFILE_O_OPEN_CREATE | RTFILE_O_APPEND | RTFILE_O_WRITE | RTFILE_O_DENY_NONE);745 RTFileWrite(fh, pStreamAV->pvSrcBuf, cbSrc, NULL);746 RTFileClose(fh);747 # endif748 749 Assert(cbSrc == pCodec->Opus.cbFrame);750 751 /*752 * Opus always encodes PER "OPUS FRAME", that is, exactly 2.5, 5, 10, 20, 40 or 60 ms of audio data.753 *754 * A packet can have up to 120ms worth of audio data.755 * Anything > 120ms of data will result in a "corrupted package" error message by756 * by decoding application.757 */758 759 /* Call the encoder to encode one "Opus frame" per iteration. */760 opus_int32 cbWritten = opus_encode(pSink->Codec.Opus.pEnc,761 (opus_int16 *)pStreamAV->pvSrcBuf, pCodec->Opus.csFrame,762 (uint8_t *)pStreamAV->pvDstBuf, (opus_int32)pStreamAV->cbDstBuf);763 if (cbWritten > 0)764 {765 /* Get overall frames encoded. */766 const uint32_t cEncFrames = opus_packet_get_nb_frames((uint8_t *)pStreamAV->pvDstBuf, cbWritten);767 768 # ifdef VBOX_WITH_STATISTICS769 pSink->Codec.STAM.cEncFrames += cEncFrames;770 pSink->Codec.STAM.msEncTotal += pSink->Codec.Opus.msFrame * cEncFrames;771 # endif772 Assert((uint32_t)cbWritten <= (uint32_t)pStreamAV->cbDstBuf);773 const uint32_t cbDst = RT_MIN((uint32_t)cbWritten, (uint32_t)pStreamAV->cbDstBuf);774 775 Assert(cEncFrames == 1);776 777 if (pStreamAV->uLastPTSMs == 0)778 pStreamAV->uLastPTSMs = RTTimeProgramMilliTS(); /* We want the absolute time (in ms) since program start. */779 780 const uint64_t uDurationMs = pSink->Codec.Opus.msFrame * cEncFrames;781 const uint64_t uPTSMs = pStreamAV->uLastPTSMs;782 783 pStreamAV->uLastPTSMs += uDurationMs;784 785 switch (pSink->Con.Parms.enmType)786 {787 case AVRECCONTAINERTYPE_MAIN_CONSOLE:788 {789 HRESULT hr = pSink->Con.Main.pConsole->i_recordingSendAudio(pStreamAV->pvDstBuf, cbDst, uPTSMs);790 Assert(hr == S_OK);791 RT_NOREF(hr);792 793 break;794 }795 796 case AVRECCONTAINERTYPE_WEBM:797 {798 WebMWriter::BlockData_Opus blockData = { pStreamAV->pvDstBuf, cbDst, uPTSMs };799 rc = pSink->Con.WebM.pWebM->WriteBlock(pSink->Con.WebM.uTrack, &blockData, sizeof(blockData));800 AssertRC(rc);801 802 break;803 }804 805 default:806 AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED);807 break;808 }809 }810 else if (cbWritten < 0)811 {812 AssertMsgFailed(("Encoding failed: %s\n", opus_strerror(cbWritten)));813 rc = VERR_INVALID_PARAMETER;814 }815 816 if (RT_FAILURE(rc))817 break;818 }819 820 *pcbWritten = cbWrittenTotal;821 #else822 /* Report back all data as being processed. */823 *pcbWritten = cbBuf;824 825 rc = VERR_NOT_SUPPORTED;826 #endif /* VBOX_WITH_LIBOPUS */827 828 LogFlowFunc(("csReadTotal=%RU32, rc=%Rrc\n", cbWrittenTotal, rc));829 return rc;830 }831 832 833 /**834 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetConfig}835 */836 static DECLCALLBACK(int) drvAudioVideoRecHA_GetConfig(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDCFG pBackendCfg)837 {838 RT_NOREF(pInterface);839 AssertPtrReturn(pBackendCfg, VERR_INVALID_POINTER);840 841 RTStrPrintf2(pBackendCfg->szName, sizeof(pBackendCfg->szName), "VideoRec");842 843 pBackendCfg->cbStreamOut = sizeof(AVRECSTREAM);844 pBackendCfg->cbStreamIn = 0;845 pBackendCfg->cMaxStreamsIn = 0;846 pBackendCfg->cMaxStreamsOut = UINT32_MAX;847 848 return VINF_SUCCESS;849 }850 851 852 /**853 * @interface_method_impl{PDMIHOSTAUDIO,pfnGetStatus}854 */855 static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvAudioVideoRecHA_GetStatus(PPDMIHOSTAUDIO pInterface, PDMAUDIODIR enmDir)856 {857 RT_NOREF(enmDir);858 AssertPtrReturn(pInterface, PDMAUDIOBACKENDSTS_UNKNOWN);859 860 return PDMAUDIOBACKENDSTS_RUNNING;861 }862 863 864 /**865 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamCreate}866 */867 static DECLCALLBACK(int) drvAudioVideoRecHA_StreamCreate(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream,868 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)869 {870 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);871 AssertPtrReturn(pCfgReq, VERR_INVALID_POINTER);872 AssertPtrReturn(pCfgAcq, VERR_INVALID_POINTER);873 874 if (pCfgReq->enmDir == PDMAUDIODIR_IN)875 return VERR_NOT_SUPPORTED;876 877 AssertPtrReturn(pStream, VERR_INVALID_POINTER);878 879 PDRVAUDIORECORDING pThis = PDMIHOSTAUDIO_2_DRVAUDIORECORDING(pInterface);880 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;881 882 /* For now we only have one sink, namely the driver's one.883 * Later each stream could have its own one, to e.g. router different stream to different sinks .*/884 PAVRECSINK pSink = &pThis->Sink;885 886 int rc = avRecCreateStreamOut(pThis, pStreamAV, pSink, pCfgReq, pCfgAcq);887 if (RT_SUCCESS(rc))888 {889 pStreamAV->pCfg = PDMAudioStrmCfgDup(pCfgAcq);890 if (!pStreamAV->pCfg)891 rc = VERR_NO_MEMORY;892 }893 894 return rc;895 }896 897 898 /**899 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamDestroy}900 */901 static DECLCALLBACK(int) drvAudioVideoRecHA_StreamDestroy(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)902 {903 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);904 AssertPtrReturn(pStream, VERR_INVALID_POINTER);905 906 PDRVAUDIORECORDING pThis = PDMIHOSTAUDIO_2_DRVAUDIORECORDING(pInterface);907 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;908 909 if (!pStreamAV->pCfg) /* Not (yet) configured? Skip. */910 return VINF_SUCCESS;911 912 int rc = VINF_SUCCESS;913 914 if (pStreamAV->pCfg->enmDir == PDMAUDIODIR_OUT)915 rc = avRecDestroyStreamOut(pThis, pStreamAV);916 917 if (RT_SUCCESS(rc))918 {919 PDMAudioStrmCfgFree(pStreamAV->pCfg);920 pStreamAV->pCfg = NULL;921 }922 923 return rc;924 }925 926 927 /**928 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamControl}929 */930 static DECLCALLBACK(int) drvAudioVideoRecHA_StreamControl(PPDMIHOSTAUDIO pInterface,931 PPDMAUDIOBACKENDSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)932 {933 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);934 AssertPtrReturn(pStream, VERR_INVALID_POINTER);935 936 PDRVAUDIORECORDING pThis = PDMIHOSTAUDIO_2_DRVAUDIORECORDING(pInterface);937 PAVRECSTREAM pStreamAV = (PAVRECSTREAM)pStream;938 939 if (!pStreamAV->pCfg) /* Not (yet) configured? Skip. */940 return VINF_SUCCESS;941 942 if (pStreamAV->pCfg->enmDir == PDMAUDIODIR_OUT)943 return avRecControlStreamOut(pThis, pStreamAV, enmStreamCmd);944 945 return VINF_SUCCESS;946 }947 948 949 /**950 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetReadable}951 */952 static DECLCALLBACK(uint32_t) drvAudioVideoRecHA_StreamGetReadable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)953 {954 RT_NOREF(pInterface, pStream);955 956 return 0; /* Video capturing does not provide any input. */957 }958 959 960 /**961 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetWritable}962 */963 static DECLCALLBACK(uint32_t) drvAudioVideoRecHA_StreamGetWritable(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)964 {965 RT_NOREF(pInterface, pStream);966 967 return UINT32_MAX;968 }969 970 971 /**972 * @interface_method_impl{PDMIHOSTAUDIO,pfnStreamGetStatus}973 */974 static DECLCALLBACK(PDMAUDIOSTREAMSTS) drvAudioVideoRecHA_StreamGetStatus(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)975 {976 RT_NOREF(pInterface, pStream);977 978 return PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED | PDMAUDIOSTREAMSTS_FLAGS_ENABLED;979 }980 981 982 /**983 * @interface_method_impl{PDMIBASE,pfnQueryInterface}984 */985 static DECLCALLBACK(void *) drvAudioVideoRecQueryInterface(PPDMIBASE pInterface, const char *pszIID)986 {987 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);988 PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);989 990 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);991 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTAUDIO, &pThis->IHostAudio);992 return NULL;993 }994 995 996 AudioVideoRec::AudioVideoRec(Console *pConsole)997 : AudioDriver(pConsole)998 , mpDrv(NULL)999 {1000 }1001 1002 1003 AudioVideoRec::~AudioVideoRec(void)1004 {1005 if (mpDrv)1006 {1007 mpDrv->pAudioVideoRec = NULL;1008 mpDrv = NULL;1009 }1010 }1011 1012 1013 /**1014 * Applies a video recording configuration to this driver instance.1015 *1016 * @returns VBox status code.1017 * @param Settings Capturing configuration to apply.1018 */1019 int AudioVideoRec::applyConfiguration(const settings::RecordingSettings &Settings)1020 {1021 /** @todo Do some validation here. */1022 mVideoRecCfg = Settings; /* Note: Does have an own copy operator. */1023 return VINF_SUCCESS;1024 }1025 1026 1027 /**1028 * @copydoc AudioDriver::configureDriver1029 */1030 int AudioVideoRec::configureDriver(PCFGMNODE pLunCfg)1031 {1032 int rc = CFGMR3InsertInteger(pLunCfg, "Object", (uintptr_t)mpConsole->i_recordingGetAudioDrv());1033 AssertRCReturn(rc, rc);1034 rc = CFGMR3InsertInteger(pLunCfg, "ObjectConsole", (uintptr_t)mpConsole);1035 AssertRCReturn(rc, rc);1036 1037 /** @todo For now we're using the configuration of the first screen here audio-wise. */1038 Assert(mVideoRecCfg.mapScreens.size() >= 1);1039 const settings::RecordingScreenSettings &Screen0Settings = mVideoRecCfg.mapScreens[0];1040 1041 rc = CFGMR3InsertInteger(pLunCfg, "ContainerType", (uint64_t)Screen0Settings.enmDest);1042 AssertRCReturn(rc, rc);1043 if (Screen0Settings.enmDest == RecordingDestination_File)1044 {1045 rc = CFGMR3InsertString(pLunCfg, "ContainerFileName", Utf8Str(Screen0Settings.File.strName).c_str());1046 AssertRCReturn(rc, rc);1047 }1048 rc = CFGMR3InsertInteger(pLunCfg, "CodecHz", Screen0Settings.Audio.uHz);1049 AssertRCReturn(rc, rc);1050 rc = CFGMR3InsertInteger(pLunCfg, "CodecBits", Screen0Settings.Audio.cBits);1051 AssertRCReturn(rc, rc);1052 rc = CFGMR3InsertInteger(pLunCfg, "CodecChannels", Screen0Settings.Audio.cChannels);1053 AssertRCReturn(rc, rc);1054 rc = CFGMR3InsertInteger(pLunCfg, "CodecBitrate", 0); /* Let Opus decide for now. */1055 AssertRCReturn(rc, rc);1056 1057 return AudioDriver::configureDriver(pLunCfg);1058 }1059 1060 1061 /**1062 * @interface_method_impl{PDMDRVREG,pfnPowerOff}1063 */1064 /*static*/ DECLCALLBACK(void) AudioVideoRec::drvPowerOff(PPDMDRVINS pDrvIns)1065 {1066 PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);1067 LogFlowFuncEnter();1068 avRecSinkShutdown(&pThis->Sink);1069 }1070 1071 1072 /**1073 * @interface_method_impl{PDMDRVREG,pfnDestruct}1074 */1075 /*static*/ DECLCALLBACK(void) AudioVideoRec::drvDestruct(PPDMDRVINS pDrvIns)1076 {1077 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);1078 PDRVAUDIORECORDING pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIORECORDING);1079 1080 LogFlowFuncEnter();1081 1082 switch (pThis->ContainerParms.enmType)1083 {1084 case AVRECCONTAINERTYPE_WEBM:1085 {1086 avRecSinkShutdown(&pThis->Sink);1087 RTStrFree(pThis->ContainerParms.WebM.pszFile);1088 break;1089 }1090 1091 default:1092 break;1093 }1094 1095 /*1096 * If the AudioVideoRec object is still alive, we must clear it's reference to1097 * us since we'll be invalid when we return from this method.1098 */1099 if (pThis->pAudioVideoRec)1100 {1101 pThis->pAudioVideoRec->mpDrv = NULL;1102 pThis->pAudioVideoRec = NULL;1103 }1104 1105 LogFlowFuncLeave();1106 1070 } 1107 1071 … … 1133 1097 /* IHostAudio */ 1134 1098 pThis->IHostAudio.pfnGetConfig = drvAudioVideoRecHA_GetConfig; 1099 pThis->IHostAudio.pfnGetDevices = NULL; 1135 1100 pThis->IHostAudio.pfnGetStatus = drvAudioVideoRecHA_GetStatus; 1136 1101 pThis->IHostAudio.pfnStreamCreate = drvAudioVideoRecHA_StreamCreate; … … 1139 1104 pThis->IHostAudio.pfnStreamGetReadable = drvAudioVideoRecHA_StreamGetReadable; 1140 1105 pThis->IHostAudio.pfnStreamGetWritable = drvAudioVideoRecHA_StreamGetWritable; 1106 pThis->IHostAudio.pfnStreamGetPending = NULL; 1141 1107 pThis->IHostAudio.pfnStreamGetStatus = drvAudioVideoRecHA_StreamGetStatus; 1142 1108 pThis->IHostAudio.pfnStreamPlay = drvAudioVideoRecHA_StreamPlay; 1143 1109 pThis->IHostAudio.pfnStreamCapture = drvAudioVideoRecHA_StreamCapture; 1144 pThis->IHostAudio.pfnGetDevices = NULL;1145 pThis->IHostAudio.pfnStreamGetPending = NULL;1146 1110 1147 1111 /*
Note:
See TracChangeset
for help on using the changeset viewer.