Changeset 88715 in vbox for trunk/src/VBox/Devices
- Timestamp:
- Apr 26, 2021 8:13:59 PM (4 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/VBox/Devices/Audio/DrvAudio.cpp
r88714 r88715 398 398 #endif /* !VBOX_AUDIO_TESTCASE */ 399 399 400 /**401 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamControl}402 */403 static DECLCALLBACK(int) drvAudioStreamControl(PPDMIAUDIOCONNECTOR pInterface,404 PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd)405 {406 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);407 AssertPtr(pThis);408 409 /** @todo r=bird: why? It's not documented to ignore NULL streams. */410 if (!pStream)411 return VINF_SUCCESS;412 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;413 AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);414 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);415 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);416 417 int rc = RTCritSectEnter(&pThis->CritSect);418 AssertRCReturn(rc, rc);419 420 LogFlowFunc(("[%s] enmStreamCmd=%s\n", pStream->szName, PDMAudioStrmCmdGetName(enmStreamCmd)));421 422 rc = drvAudioStreamControlInternal(pThis, pStreamEx, enmStreamCmd);423 424 RTCritSectLeave(&pThis->CritSect);425 return rc;426 }427 428 /**429 * Controls an audio stream.430 *431 * @returns VBox status code.432 * @param pThis Pointer to driver instance.433 * @param pStreamEx Stream to control.434 * @param enmStreamCmd Control command.435 */436 static int drvAudioStreamControlInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, PDMAUDIOSTREAMCMD enmStreamCmd)437 {438 AssertPtr(pThis);439 AssertPtr(pStreamEx);440 441 #ifdef LOG_ENABLED442 char szStreamSts[DRVAUDIO_STATUS_STR_MAX];443 #endif444 LogFunc(("[%s] enmStreamCmd=%s fStatus=%s\n", pStreamEx->Core.szName, PDMAudioStrmCmdGetName(enmStreamCmd),445 dbgAudioStreamStatusToStr(szStreamSts, pStreamEx->Core.fStatus)));446 447 int rc = VINF_SUCCESS;448 449 switch (enmStreamCmd)450 {451 case PDMAUDIOSTREAMCMD_ENABLE:452 {453 if (!(pStreamEx->Core.fStatus & PDMAUDIOSTREAMSTS_FLAGS_ENABLED))454 {455 /* Is a pending disable outstanding? Then disable first. */456 if (pStreamEx->Core.fStatus & PDMAUDIOSTREAMSTS_FLAGS_PENDING_DISABLE)457 rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);458 459 if (RT_SUCCESS(rc))460 rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_ENABLE);461 462 if (RT_SUCCESS(rc))463 pStreamEx->Core.fStatus |= PDMAUDIOSTREAMSTS_FLAGS_ENABLED;464 }465 break;466 }467 468 case PDMAUDIOSTREAMCMD_DISABLE:469 {470 if (pStreamEx->Core.fStatus & PDMAUDIOSTREAMSTS_FLAGS_ENABLED)471 {472 /*473 * For playback (output) streams first mark the host stream as pending disable,474 * so that the rest of the remaining audio data will be played first before475 * closing the stream.476 */477 if (pStreamEx->Core.enmDir == PDMAUDIODIR_OUT)478 {479 LogFunc(("[%s] Pending disable/pause\n", pStreamEx->Core.szName));480 pStreamEx->Core.fStatus |= PDMAUDIOSTREAMSTS_FLAGS_PENDING_DISABLE;481 482 /* Schedule a follow up timer to the pending-disable state. We cannot rely483 on the device to provide further callouts to finish the state transition.484 10ms is taking out of thin air and may be too course grained, we should485 really consider the amount of unplayed buffer in the backend and what not... */486 if (!pThis->fTimerArmed)487 {488 LogFlowFunc(("Arming emergency pending-disable hack...\n"));489 int rc2 = PDMDrvHlpTimerSetMillies(pThis->pDrvIns, pThis->hTimer, 10 /*ms*/);490 AssertRC(rc2);491 pThis->fTimerArmed = true;492 }493 }494 495 /* Can we close the host stream as well (not in pending disable mode)? */496 if (!(pStreamEx->Core.fStatus & PDMAUDIOSTREAMSTS_FLAGS_PENDING_DISABLE))497 {498 rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);499 if (RT_SUCCESS(rc))500 drvAudioStreamResetInternal(pThis, pStreamEx);501 }502 }503 break;504 }505 506 case PDMAUDIOSTREAMCMD_PAUSE:507 {508 if (!(pStreamEx->Core.fStatus & PDMAUDIOSTREAMSTS_FLAGS_PAUSED))509 {510 rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_PAUSE);511 if (RT_SUCCESS(rc))512 pStreamEx->Core.fStatus |= PDMAUDIOSTREAMSTS_FLAGS_PAUSED;513 }514 break;515 }516 517 case PDMAUDIOSTREAMCMD_RESUME:518 {519 if (pStreamEx->Core.fStatus & PDMAUDIOSTREAMSTS_FLAGS_PAUSED)520 {521 rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_RESUME);522 if (RT_SUCCESS(rc))523 pStreamEx->Core.fStatus &= ~PDMAUDIOSTREAMSTS_FLAGS_PAUSED;524 }525 break;526 }527 528 default:529 rc = VERR_NOT_IMPLEMENTED;530 break;531 }532 533 if (RT_FAILURE(rc))534 LogFunc(("[%s] Failed with %Rrc\n", pStreamEx->Core.szName, rc));535 536 return rc;537 }538 539 /**540 * Controls a stream's backend.541 *542 * If the stream has no backend available, VERR_NOT_FOUND is returned543 * (bird: actually the code returns VINF_SUCCESS).544 *545 * @returns VBox status code.546 * @param pThis Pointer to driver instance.547 * @param pStreamEx Stream to control.548 * @param enmStreamCmd Control command.549 */550 static int drvAudioStreamControlInternalBackend(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, PDMAUDIOSTREAMCMD enmStreamCmd)551 {552 AssertPtr(pThis);553 AssertPtr(pStreamEx);554 555 #ifdef LOG_ENABLED556 char szStreamSts[DRVAUDIO_STATUS_STR_MAX];557 #endif558 LogFlowFunc(("[%s] enmStreamCmd=%s, fStatus=%s\n", pStreamEx->Core.szName, PDMAudioStrmCmdGetName(enmStreamCmd),559 dbgAudioStreamStatusToStr(szStreamSts, pStreamEx->Core.fStatus)));560 561 if (!pThis->pHostDrvAudio) /* If not lower driver is configured, bail out. */562 return VINF_SUCCESS;563 564 565 /*566 * Whether to propagate commands down to the backend.567 *568 * This is needed for critical operations like recording audio if audio input is disabled on a per-driver level.569 *570 * Note that not all commands will be covered by this, such as operations like stopping, draining and droppping,571 * which are considered uncritical and sometimes even are required for certain backends (like DirectSound on Windows).572 *573 * The actual stream state will be untouched to not make the state machine handling more complicated than574 * it already is.575 *576 * See @bugref{9882}.577 */578 const bool fEnabled = ( pStreamEx->Core.enmDir == PDMAUDIODIR_IN579 && pThis->In.fEnabled)580 || ( pStreamEx->Core.enmDir == PDMAUDIODIR_OUT581 && pThis->Out.fEnabled);582 583 LogRel2(("Audio: %s stream '%s' in backend (%s is %s)\n", PDMAudioStrmCmdGetName(enmStreamCmd), pStreamEx->Core.szName,584 PDMAudioDirGetName(pStreamEx->Core.enmDir),585 fEnabled ? "enabled" : "disabled"));586 int rc = VINF_SUCCESS;587 switch (enmStreamCmd)588 {589 case PDMAUDIOSTREAMCMD_ENABLE:590 {591 if (fEnabled)592 rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStreamEx->pBackend, PDMAUDIOSTREAMCMD_ENABLE);593 break;594 }595 596 case PDMAUDIOSTREAMCMD_DISABLE:597 {598 rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStreamEx->pBackend, PDMAUDIOSTREAMCMD_DISABLE);599 break;600 }601 602 case PDMAUDIOSTREAMCMD_PAUSE:603 {604 if (fEnabled) /* Needed, as resume below also is being checked for. */605 rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStreamEx->pBackend, PDMAUDIOSTREAMCMD_PAUSE);606 break;607 }608 609 case PDMAUDIOSTREAMCMD_RESUME:610 {611 if (fEnabled)612 rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStreamEx->pBackend, PDMAUDIOSTREAMCMD_RESUME);613 break;614 }615 616 case PDMAUDIOSTREAMCMD_DRAIN:617 {618 rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStreamEx->pBackend, PDMAUDIOSTREAMCMD_DRAIN);619 break;620 }621 622 default:623 AssertMsgFailedReturn(("Command %RU32 not implemented\n", enmStreamCmd), VERR_INTERNAL_ERROR_2);624 }625 626 if (RT_FAILURE(rc))627 {628 if ( rc != VERR_NOT_IMPLEMENTED629 && rc != VERR_NOT_SUPPORTED630 && rc != VERR_AUDIO_STREAM_NOT_READY)631 {632 LogRel(("Audio: %s stream '%s' failed with %Rrc\n", PDMAudioStrmCmdGetName(enmStreamCmd), pStreamEx->Core.szName, rc));633 }634 635 LogFunc(("[%s] %s failed with %Rrc\n", pStreamEx->Core.szName, PDMAudioStrmCmdGetName(enmStreamCmd), rc));636 }637 638 return rc;639 }640 400 641 401 /** … … 999 759 1000 760 /** 1001 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamWrite}1002 */1003 static DECLCALLBACK(int) drvAudioStreamWrite(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream,1004 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten)1005 {1006 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);1007 AssertPtr(pThis);1008 1009 /*1010 * Check input and sanity.1011 */1012 AssertPtrReturn(pInterface, VERR_INVALID_POINTER);1013 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;1014 AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);1015 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);1016 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);1017 uint32_t uTmp;1018 if (!pcbWritten)1019 pcbWritten = &uTmp;1020 AssertPtrReturn(pcbWritten, VERR_INVALID_PARAMETER);1021 1022 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);1023 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);1024 AssertMsgReturn(pStreamEx->Core.enmDir == PDMAUDIODIR_OUT,1025 ("Stream '%s' is not an output stream and therefore cannot be written to (direction is '%s')\n",1026 pStreamEx->Core.szName, PDMAudioDirGetName(pStreamEx->Core.enmDir)), VERR_ACCESS_DENIED);1027 Assert(pStreamEx->fNoMixBufs);1028 1029 AssertMsg(PDMAudioPropsIsSizeAligned(&pStreamEx->Guest.Cfg.Props, cbBuf),1030 ("Stream '%s' got a non-frame-aligned write (%RU32 bytes)\n", pStreamEx->Core.szName, cbBuf));1031 1032 /// @todo STAM_PROFILE_ADV_START(&pThis->Stats.DelayOut, out); /* (stopped in drvAudioStreamPlayLocked) */1033 1034 int rc = RTCritSectEnter(&pThis->CritSect);1035 AssertRCReturn(rc, rc);1036 1037 /*1038 * First check that we can write to the stream, and if not,1039 * whether to just drop the input into the bit bucket.1040 */1041 if (PDMAudioStrmStatusIsReady(pStreamEx->Core.fStatus))1042 {1043 if ( !pThis->Out.fEnabled /* (see @bugref{9882}) */1044 || pThis->pHostDrvAudio == NULL /* (we used to work this condition differently) */1045 || !PDMAudioStrmStatusCanWrite(pThis->pHostDrvAudio->pfnStreamGetStatus(pThis->pHostDrvAudio, pStreamEx->pBackend)))1046 {1047 Log3Func(("[%s] Backend stream %s, discarding the data\n", pStreamEx->Core.szName,1048 !pThis->Out.fEnabled ? "disabled" : !pThis->pHostDrvAudio ? "not attached" : "not ready yet"));1049 *pcbWritten = cbBuf;1050 pStreamEx->offInternal += cbBuf;1051 }1052 /*1053 * No-mixing buffer mode: Write the data directly to the backend, unless1054 * we're prebuffering. There will be no pfnStreamPlay call in this mode.1055 */1056 else1057 {1058 uint64_t offInternalBefore = pStreamEx->offInternal; RT_NOREF(offInternalBefore);1059 rc = drvAudioStreamWriteNoMixBufs(pThis, pStreamEx, (uint8_t const *)pvBuf, cbBuf, pcbWritten);1060 Assert(offInternalBefore + *pcbWritten == pStreamEx->offInternal);1061 if (!pThis->Out.Cfg.Dbg.fEnabled || RT_FAILURE(rc))1062 { /* likely */ }1063 else1064 AudioHlpFileWrite(pStreamEx->Out.Dbg.pFilePlayNonInterleaved, pvBuf, *pcbWritten, 0 /* fFlags */);1065 }1066 }1067 else1068 rc = VERR_AUDIO_STREAM_NOT_READY;1069 1070 RTCritSectLeave(&pThis->CritSect);1071 return rc;1072 }1073 1074 /**1075 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRetain}1076 */1077 static DECLCALLBACK(uint32_t) drvAudioStreamRetain(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)1078 {1079 AssertPtrReturn(pInterface, UINT32_MAX);1080 AssertPtrReturn(pStream, UINT32_MAX);1081 AssertReturn(pStream->uMagic == PDMAUDIOSTREAM_MAGIC, UINT32_MAX);1082 AssertReturn(((PDRVAUDIOSTREAM)pStream)->uMagic == DRVAUDIOSTREAM_MAGIC, UINT32_MAX);1083 RT_NOREF(pInterface);1084 1085 uint32_t const cRefs = ASMAtomicIncU32(&pStream->cRefs);1086 Assert(cRefs > 1);1087 Assert(cRefs < _1K);1088 1089 return cRefs;1090 }1091 1092 /**1093 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRelease}1094 */1095 static DECLCALLBACK(uint32_t) drvAudioStreamRelease(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)1096 {1097 AssertPtrReturn(pInterface, UINT32_MAX);1098 AssertPtrReturn(pStream, UINT32_MAX);1099 AssertReturn(pStream->uMagic == PDMAUDIOSTREAM_MAGIC, UINT32_MAX);1100 AssertReturn(((PDRVAUDIOSTREAM)pStream)->uMagic == DRVAUDIOSTREAM_MAGIC, UINT32_MAX);1101 RT_NOREF(pInterface);1102 1103 uint32_t cRefs = ASMAtomicDecU32(&pStream->cRefs);1104 AssertStmt(cRefs >= 1, cRefs = ASMAtomicIncU32(&pStream->cRefs));1105 Assert(cRefs < _1K);1106 1107 return cRefs;1108 }1109 1110 /**1111 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamIterate}1112 */1113 static DECLCALLBACK(int) drvAudioStreamIterate(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream)1114 {1115 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);1116 AssertPtr(pThis);1117 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;1118 AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);1119 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);1120 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);1121 1122 int rc = RTCritSectEnter(&pThis->CritSect);1123 AssertRCReturn(rc, rc);1124 1125 rc = drvAudioStreamIterateInternal(pThis, pStreamEx);1126 1127 RTCritSectLeave(&pThis->CritSect);1128 1129 if (RT_FAILURE(rc))1130 LogFlowFuncLeaveRC(rc);1131 return rc;1132 }1133 1134 /**1135 761 * Re-initializes the given stream if it is scheduled for this operation. 1136 762 * … … 1204 830 } 1205 831 } 832 833 834 /** 835 * @callback_method_impl{FNTMTIMERDRV} 836 */ 837 static DECLCALLBACK(void) drvAudioEmergencyIterateTimer(PPDMDRVINS pDrvIns, TMTIMERHANDLE hTimer, void *pvUser) 838 { 839 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO); 840 RT_NOREF(hTimer, pvUser); 841 RTCritSectEnter(&pThis->CritSect); 842 843 /* 844 * Iterate any stream with the pending-disable flag set. 845 */ 846 uint32_t cMilliesToNext = 0; 847 PDRVAUDIOSTREAM pStreamEx, pStreamExNext; 848 RTListForEachSafe(&pThis->lstStreams, pStreamEx, pStreamExNext, DRVAUDIOSTREAM, ListEntry) 849 { 850 if ( pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC 851 && pStreamEx->Core.cRefs >= 1) 852 { 853 if (pStreamEx->Core.fStatus & PDMAUDIOSTREAMSTS_FLAGS_PENDING_DISABLE) 854 { 855 drvAudioStreamIterateInternal(pThis, pStreamEx); 856 857 if (pStreamEx->Core.fStatus & PDMAUDIOSTREAMSTS_FLAGS_PENDING_DISABLE) 858 cMilliesToNext = 10; 859 } 860 } 861 } 862 863 /* 864 * Re-arm the timer if we still got streams in the pending state. 865 */ 866 if (cMilliesToNext) 867 { 868 pThis->fTimerArmed = true; 869 PDMDrvHlpTimerSetMillies(pDrvIns, pThis->hTimer, cMilliesToNext); 870 } 871 else 872 pThis->fTimerArmed = false; 873 874 RTCritSectLeave(&pThis->CritSect); 875 } 876 877 878 #ifdef VBOX_WITH_AUDIO_ENUM 879 /** 880 * Enumerates all host audio devices. 881 * 882 * This functionality might not be implemented by all backends and will return 883 * VERR_NOT_SUPPORTED if not being supported. 884 * 885 * @note Must not hold the driver's critical section! 886 * 887 * @returns VBox status code. 888 * @param pThis Driver instance to be called. 889 * @param fLog Whether to print the enumerated device to the release log or not. 890 * @param pDevEnum Where to store the device enumeration. 891 * 892 * @remarks This is currently ONLY used for release logging. 893 */ 894 static int drvAudioDevicesEnumerateInternal(PDRVAUDIO pThis, bool fLog, PPDMAUDIOHOSTENUM pDevEnum) 895 { 896 AssertReturn(!RTCritSectIsOwner(&pThis->CritSect), VERR_WRONG_ORDER); 897 898 int rc; 899 900 /* 901 * If the backend supports it, do a device enumeration. 902 */ 903 if (pThis->pHostDrvAudio->pfnGetDevices) 904 { 905 PDMAUDIOHOSTENUM DevEnum; 906 rc = pThis->pHostDrvAudio->pfnGetDevices(pThis->pHostDrvAudio, &DevEnum); 907 if (RT_SUCCESS(rc)) 908 { 909 if (fLog) 910 LogRel(("Audio: Found %RU16 devices for driver '%s'\n", DevEnum.cDevices, pThis->szName)); 911 912 PPDMAUDIOHOSTDEV pDev; 913 RTListForEach(&DevEnum.LstDevices, pDev, PDMAUDIOHOSTDEV, ListEntry) 914 { 915 if (fLog) 916 { 917 char szFlags[PDMAUDIOHOSTDEV_MAX_FLAGS_STRING_LEN]; 918 LogRel(("Audio: Device '%s':\n", pDev->szName)); 919 LogRel(("Audio: Usage = %s\n", PDMAudioDirGetName(pDev->enmUsage))); 920 LogRel(("Audio: Flags = %s\n", PDMAudioHostDevFlagsToString(szFlags, pDev->fFlags))); 921 LogRel(("Audio: Input channels = %RU8\n", pDev->cMaxInputChannels)); 922 LogRel(("Audio: Output channels = %RU8\n", pDev->cMaxOutputChannels)); 923 } 924 } 925 926 if (pDevEnum) 927 rc = PDMAudioHostEnumCopy(pDevEnum, &DevEnum, PDMAUDIODIR_INVALID /*all*/, true /*fOnlyCoreData*/); 928 929 PDMAudioHostEnumDelete(&DevEnum); 930 } 931 else 932 { 933 if (fLog) 934 LogRel(("Audio: Device enumeration for driver '%s' failed with %Rrc\n", pThis->szName, rc)); 935 /* Not fatal. */ 936 } 937 } 938 else 939 { 940 rc = VERR_NOT_SUPPORTED; 941 942 if (fLog) 943 LogRel2(("Audio: Host driver '%s' does not support audio device enumeration, skipping\n", pThis->szName)); 944 } 945 946 LogFunc(("Returning %Rrc\n", rc)); 947 return rc; 948 } 949 #endif /* VBOX_WITH_AUDIO_ENUM */ 950 951 /** 952 * Initializes the host backend and queries its initial configuration. 953 * 954 * @returns VBox status code. 955 * @param pThis Driver instance to be called. 956 */ 957 static int drvAudioHostInit(PDRVAUDIO pThis) 958 { 959 LogFlowFuncEnter(); 960 961 /* 962 * Check the function pointers, make sure the ones we define as 963 * mandatory are present. 964 */ 965 PPDMIHOSTAUDIO pHostDrvAudio = pThis->pHostDrvAudio; 966 AssertPtrReturn(pHostDrvAudio, VERR_INVALID_POINTER); 967 AssertPtrReturn(pHostDrvAudio->pfnGetConfig, VERR_INVALID_POINTER); 968 AssertPtrNullReturn(pHostDrvAudio->pfnGetDevices, VERR_INVALID_POINTER); 969 AssertPtrNullReturn(pHostDrvAudio->pfnGetStatus, VERR_INVALID_POINTER); 970 AssertPtrNullReturn(pHostDrvAudio->pfnStreamConfigHint, VERR_INVALID_POINTER); 971 AssertPtrReturn(pHostDrvAudio->pfnStreamCreate, VERR_INVALID_POINTER); 972 AssertPtrReturn(pHostDrvAudio->pfnStreamDestroy, VERR_INVALID_POINTER); 973 AssertPtrReturn(pHostDrvAudio->pfnStreamControl, VERR_INVALID_POINTER); 974 AssertPtrReturn(pHostDrvAudio->pfnStreamGetReadable, VERR_INVALID_POINTER); 975 AssertPtrReturn(pHostDrvAudio->pfnStreamGetWritable, VERR_INVALID_POINTER); 976 AssertPtrNullReturn(pHostDrvAudio->pfnStreamGetPending, VERR_INVALID_POINTER); 977 AssertPtrReturn(pHostDrvAudio->pfnStreamGetStatus, VERR_INVALID_POINTER); 978 AssertPtrReturn(pHostDrvAudio->pfnStreamPlay, VERR_INVALID_POINTER); 979 AssertPtrReturn(pHostDrvAudio->pfnStreamCapture, VERR_INVALID_POINTER); 980 981 /* 982 * Get the backend configuration. 983 */ 984 int rc = pThis->pHostDrvAudio->pfnGetConfig(pThis->pHostDrvAudio, &pThis->BackendCfg); 985 if (RT_FAILURE(rc)) 986 { 987 LogRel(("Audio: Getting configuration for driver '%s' failed with %Rrc\n", pThis->szName, rc)); 988 return VERR_AUDIO_BACKEND_INIT_FAILED; 989 } 990 991 pThis->In.cStreamsFree = pThis->BackendCfg.cMaxStreamsIn; 992 pThis->Out.cStreamsFree = pThis->BackendCfg.cMaxStreamsOut; 993 994 LogFlowFunc(("cStreamsFreeIn=%RU8, cStreamsFreeOut=%RU8\n", pThis->In.cStreamsFree, pThis->Out.cStreamsFree)); 995 996 LogRel2(("Audio: Host driver '%s' supports %RU32 input streams and %RU32 output streams at once.\n", 997 pThis->szName, pThis->In.cStreamsFree, pThis->Out.cStreamsFree)); 998 999 #ifdef VBOX_WITH_AUDIO_ENUM 1000 int rc2 = drvAudioDevicesEnumerateInternal(pThis, true /* fLog */, NULL /* pDevEnum */); 1001 if (rc2 != VERR_NOT_SUPPORTED) /* Some backends don't implement device enumeration. */ 1002 AssertRC(rc2); 1003 1004 RT_NOREF(rc2); 1005 /* Ignore rc. */ 1006 #endif 1007 1008 LogFlowFuncLeave(); 1009 return VINF_SUCCESS; 1010 } 1011 1012 1013 /********************************************************************************************************************************* 1014 * PDMIAUDIOCONNECTOR * 1015 *********************************************************************************************************************************/ 1016 1017 /** 1018 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnEnable} 1019 */ 1020 static DECLCALLBACK(int) drvAudioEnable(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir, bool fEnable) 1021 { 1022 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector); 1023 AssertPtr(pThis); 1024 1025 bool *pfEnabled; 1026 if (enmDir == PDMAUDIODIR_IN) 1027 pfEnabled = &pThis->In.fEnabled; 1028 else if (enmDir == PDMAUDIODIR_OUT) 1029 pfEnabled = &pThis->Out.fEnabled; 1030 else 1031 AssertFailedReturn(VERR_INVALID_PARAMETER); 1032 1033 int rc = RTCritSectEnter(&pThis->CritSect); 1034 AssertRCReturn(rc, rc); 1035 1036 if (fEnable != *pfEnabled) 1037 { 1038 LogRel(("Audio: %s %s for driver '%s'\n", 1039 fEnable ? "Enabling" : "Disabling", enmDir == PDMAUDIODIR_IN ? "input" : "output", pThis->szName)); 1040 1041 /* Update the status first, as this will be checked for in drvAudioStreamControlInternalBackend() below. */ 1042 *pfEnabled = fEnable; 1043 1044 PDRVAUDIOSTREAM pStreamEx; 1045 RTListForEach(&pThis->lstStreams, pStreamEx, DRVAUDIOSTREAM, ListEntry) 1046 { 1047 if (pStreamEx->Core.enmDir != enmDir) /* Skip unwanted streams. */ 1048 continue; 1049 1050 /* Note: Only enable / disable the backend, do *not* change the stream's internal status. 1051 * Callers (device emulation, mixer, ...) from outside will not see any status or behavior change, 1052 * to not confuse the rest of the state machine. 1053 * 1054 * When disabling: 1055 * - playing back audo data would go to /dev/null 1056 * - recording audio data would return silence instead 1057 * 1058 * See @bugref{9882}. 1059 */ 1060 int rc2 = drvAudioStreamControlInternalBackend(pThis, pStreamEx, 1061 fEnable ? PDMAUDIOSTREAMCMD_ENABLE : PDMAUDIOSTREAMCMD_DISABLE); 1062 if (RT_FAILURE(rc2)) 1063 { 1064 if (rc2 == VERR_AUDIO_STREAM_NOT_READY) 1065 LogRel(("Audio: Stream '%s' not available\n", pStreamEx->Core.szName)); 1066 else 1067 LogRel(("Audio: Failed to %s %s stream '%s', rc=%Rrc\n", fEnable ? "enable" : "disable", 1068 enmDir == PDMAUDIODIR_IN ? "input" : "output", pStreamEx->Core.szName, rc2)); 1069 } 1070 else 1071 { 1072 /* When (re-)enabling a stream, clear the disabled warning bit again. */ 1073 if (fEnable) 1074 pStreamEx->Core.fWarningsShown &= ~PDMAUDIOSTREAM_WARN_FLAGS_DISABLED; 1075 } 1076 1077 if (RT_SUCCESS(rc)) 1078 rc = rc2; 1079 1080 /* Keep going. */ 1081 } 1082 } 1083 1084 RTCritSectLeave(&pThis->CritSect); 1085 LogFlowFuncLeaveRC(rc); 1086 return rc; 1087 } 1088 1089 1090 /** 1091 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnIsEnabled} 1092 */ 1093 static DECLCALLBACK(bool) drvAudioIsEnabled(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir) 1094 { 1095 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector); 1096 AssertPtr(pThis); 1097 int rc = RTCritSectEnter(&pThis->CritSect); 1098 AssertRCReturn(rc, false); 1099 1100 bool fEnabled; 1101 if (enmDir == PDMAUDIODIR_IN) 1102 fEnabled = pThis->In.fEnabled; 1103 else if (enmDir == PDMAUDIODIR_OUT) 1104 fEnabled = pThis->Out.fEnabled; 1105 else 1106 AssertFailedStmt(fEnabled = false); 1107 1108 RTCritSectLeave(&pThis->CritSect); 1109 return fEnabled; 1110 } 1111 1112 1113 /** 1114 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnGetConfig} 1115 */ 1116 static DECLCALLBACK(int) drvAudioGetConfig(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOBACKENDCFG pCfg) 1117 { 1118 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector); 1119 AssertPtr(pThis); 1120 AssertPtrReturn(pCfg, VERR_INVALID_POINTER); 1121 int rc = RTCritSectEnter(&pThis->CritSect); 1122 AssertRCReturn(rc, rc); 1123 1124 if (pThis->pHostDrvAudio) 1125 { 1126 if (pThis->pHostDrvAudio->pfnGetConfig) 1127 rc = pThis->pHostDrvAudio->pfnGetConfig(pThis->pHostDrvAudio, pCfg); 1128 else 1129 rc = VERR_NOT_SUPPORTED; 1130 } 1131 else 1132 rc = VERR_PDM_NO_ATTACHED_DRIVER; 1133 1134 RTCritSectLeave(&pThis->CritSect); 1135 LogFlowFuncLeaveRC(rc); 1136 return rc; 1137 } 1138 1139 1140 /** 1141 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnGetStatus} 1142 */ 1143 static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvAudioGetStatus(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir) 1144 { 1145 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector); 1146 AssertPtr(pThis); 1147 int rc = RTCritSectEnter(&pThis->CritSect); 1148 AssertRCReturn(rc, PDMAUDIOBACKENDSTS_UNKNOWN); 1149 1150 PDMAUDIOBACKENDSTS fBackendStatus; 1151 if (pThis->pHostDrvAudio) 1152 { 1153 if (pThis->pHostDrvAudio->pfnGetStatus) 1154 fBackendStatus = pThis->pHostDrvAudio->pfnGetStatus(pThis->pHostDrvAudio, enmDir); 1155 else 1156 fBackendStatus = PDMAUDIOBACKENDSTS_UNKNOWN; 1157 } 1158 else 1159 fBackendStatus = PDMAUDIOBACKENDSTS_NOT_ATTACHED; 1160 1161 RTCritSectLeave(&pThis->CritSect); 1162 LogFlowFunc(("LEAVE - %#x\n", fBackendStatus)); 1163 return fBackendStatus; 1164 } 1165 1166 1167 /** 1168 * Adjusts the request stream configuration, applying our settings. 1169 * 1170 * This also does some basic validations. 1171 * 1172 * Used by both the stream creation and stream configuration hinting code. 1173 * 1174 * @returns VBox status code. 1175 * @param pThis Pointer to the DrvAudio instance data. 1176 * @param pCfgReq The request configuration that should be adjusted. 1177 * @param pszName Stream name to use when logging warnings and errors. 1178 */ 1179 static int drvAudioStreamAdjustConfig(PDRVAUDIO pThis, PPDMAUDIOSTREAMCFG pCfgReq, const char *pszName) 1180 { 1181 /* Get the right configuration for the stream to be created. */ 1182 PDRVAUDIOCFG pDrvCfg = pCfgReq->enmDir == PDMAUDIODIR_IN ? &pThis->In.Cfg : &pThis->Out.Cfg; 1183 1184 /* Fill in the tweakable parameters into the requested host configuration. 1185 * All parameters in principle can be changed and returned by the backend via the acquired configuration. */ 1186 1187 /* 1188 * PCM 1189 */ 1190 if (PDMAudioPropsSampleSize(&pDrvCfg->Props) != 0) /* Anything set via custom extra-data? */ 1191 { 1192 PDMAudioPropsSetSampleSize(&pCfgReq->Props, PDMAudioPropsSampleSize(&pDrvCfg->Props)); 1193 LogRel2(("Audio: Using custom sample size of %RU8 bytes for stream '%s'\n", 1194 PDMAudioPropsSampleSize(&pCfgReq->Props), pszName)); 1195 } 1196 1197 if (pDrvCfg->Props.uHz) /* Anything set via custom extra-data? */ 1198 { 1199 pCfgReq->Props.uHz = pDrvCfg->Props.uHz; 1200 LogRel2(("Audio: Using custom Hz rate %RU32 for stream '%s'\n", pCfgReq->Props.uHz, pszName)); 1201 } 1202 1203 if (pDrvCfg->uSigned != UINT8_MAX) /* Anything set via custom extra-data? */ 1204 { 1205 pCfgReq->Props.fSigned = RT_BOOL(pDrvCfg->uSigned); 1206 LogRel2(("Audio: Using custom %s sample format for stream '%s'\n", 1207 pCfgReq->Props.fSigned ? "signed" : "unsigned", pszName)); 1208 } 1209 1210 if (pDrvCfg->uSwapEndian != UINT8_MAX) /* Anything set via custom extra-data? */ 1211 { 1212 pCfgReq->Props.fSwapEndian = RT_BOOL(pDrvCfg->uSwapEndian); 1213 LogRel2(("Audio: Using custom %s endianess for samples of stream '%s'\n", 1214 pCfgReq->Props.fSwapEndian ? "swapped" : "original", pszName)); 1215 } 1216 1217 if (PDMAudioPropsChannels(&pDrvCfg->Props) != 0) /* Anything set via custom extra-data? */ 1218 { 1219 PDMAudioPropsSetChannels(&pCfgReq->Props, PDMAudioPropsChannels(&pDrvCfg->Props)); 1220 LogRel2(("Audio: Using custom %RU8 channel(s) for stream '%s'\n", PDMAudioPropsChannels(&pDrvCfg->Props), pszName)); 1221 } 1222 1223 /* Validate PCM properties. */ 1224 if (!AudioHlpPcmPropsAreValid(&pCfgReq->Props)) 1225 { 1226 LogRel(("Audio: Invalid custom PCM properties set for stream '%s', cannot create stream\n", pszName)); 1227 return VERR_INVALID_PARAMETER; 1228 } 1229 1230 /* 1231 * Period size 1232 */ 1233 const char *pszWhat = "device-specific"; 1234 if (pDrvCfg->uPeriodSizeMs) 1235 { 1236 pCfgReq->Backend.cFramesPeriod = PDMAudioPropsMilliToFrames(&pCfgReq->Props, pDrvCfg->uPeriodSizeMs); 1237 pszWhat = "custom"; 1238 } 1239 1240 if (!pCfgReq->Backend.cFramesPeriod) /* Set default period size if nothing explicitly is set. */ 1241 { 1242 pCfgReq->Backend.cFramesPeriod = PDMAudioPropsMilliToFrames(&pCfgReq->Props, 150 /*ms*/); 1243 pszWhat = "default"; 1244 } 1245 1246 LogRel2(("Audio: Using %s period size %RU64 ms / %RU32 frames for stream '%s'\n", 1247 pszWhat, PDMAudioPropsFramesToMilli(&pCfgReq->Props, pCfgReq->Backend.cFramesPeriod), 1248 pCfgReq->Backend.cFramesPeriod, pszName)); 1249 1250 /* 1251 * Buffer size 1252 */ 1253 pszWhat = "device-specific"; 1254 if (pDrvCfg->uBufferSizeMs) 1255 { 1256 pCfgReq->Backend.cFramesBufferSize = PDMAudioPropsMilliToFrames(&pCfgReq->Props, pDrvCfg->uBufferSizeMs); 1257 pszWhat = "custom"; 1258 } 1259 1260 if (!pCfgReq->Backend.cFramesBufferSize) /* Set default buffer size if nothing explicitly is set. */ 1261 { 1262 pCfgReq->Backend.cFramesBufferSize = PDMAudioPropsMilliToFrames(&pCfgReq->Props, 300 /*ms*/); 1263 pszWhat = "default"; 1264 } 1265 1266 LogRel2(("Audio: Using %s buffer size %RU64 ms / %RU32 frames for stream '%s'\n", 1267 pszWhat, PDMAudioPropsFramesToMilli(&pCfgReq->Props, pCfgReq->Backend.cFramesBufferSize), 1268 pCfgReq->Backend.cFramesBufferSize, pszName)); 1269 1270 /* 1271 * Pre-buffering size 1272 */ 1273 pszWhat = "device-specific"; 1274 if (pDrvCfg->uPreBufSizeMs != UINT32_MAX) /* Anything set via global / per-VM extra-data? */ 1275 { 1276 pCfgReq->Backend.cFramesPreBuffering = PDMAudioPropsMilliToFrames(&pCfgReq->Props, pDrvCfg->uPreBufSizeMs); 1277 pszWhat = "custom"; 1278 } 1279 else /* No, then either use the default or device-specific settings (if any). */ 1280 { 1281 if (pCfgReq->Backend.cFramesPreBuffering == UINT32_MAX) /* Set default pre-buffering size if nothing explicitly is set. */ 1282 { 1283 /* Pre-buffer 66% of the buffer. */ 1284 pCfgReq->Backend.cFramesPreBuffering = pCfgReq->Backend.cFramesBufferSize * 2 / 3; 1285 pszWhat = "default"; 1286 } 1287 } 1288 1289 LogRel2(("Audio: Using %s pre-buffering size %RU64 ms / %RU32 frames for stream '%s'\n", 1290 pszWhat, PDMAudioPropsFramesToMilli(&pCfgReq->Props, pCfgReq->Backend.cFramesPreBuffering), 1291 pCfgReq->Backend.cFramesPreBuffering, pszName)); 1292 1293 /* 1294 * Validate input. 1295 */ 1296 if (pCfgReq->Backend.cFramesBufferSize < pCfgReq->Backend.cFramesPeriod) 1297 { 1298 LogRel(("Audio: Error for stream '%s': Buffering size (%RU64ms) must not be smaller than the period size (%RU64ms)\n", 1299 pszName, PDMAudioPropsFramesToMilli(&pCfgReq->Props, pCfgReq->Backend.cFramesBufferSize), 1300 PDMAudioPropsFramesToMilli(&pCfgReq->Props, pCfgReq->Backend.cFramesPeriod))); 1301 return VERR_INVALID_PARAMETER; 1302 } 1303 1304 if ( pCfgReq->Backend.cFramesPreBuffering != UINT32_MAX /* Custom pre-buffering set? */ 1305 && pCfgReq->Backend.cFramesPreBuffering) 1306 { 1307 if (pCfgReq->Backend.cFramesBufferSize < pCfgReq->Backend.cFramesPreBuffering) 1308 { 1309 LogRel(("Audio: Error for stream '%s': Buffering size (%RU64ms) must not be smaller than the pre-buffering size (%RU64ms)\n", 1310 pszName, PDMAudioPropsFramesToMilli(&pCfgReq->Props, pCfgReq->Backend.cFramesPreBuffering), 1311 PDMAudioPropsFramesToMilli(&pCfgReq->Props, pCfgReq->Backend.cFramesBufferSize))); 1312 return VERR_INVALID_PARAMETER; 1313 } 1314 } 1315 1316 return VINF_SUCCESS; 1317 } 1318 1319 1320 /** 1321 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamConfigHint} 1322 */ 1323 static DECLCALLBACK(void) drvAudioStreamConfigHint(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAMCFG pCfg) 1324 { 1325 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector); 1326 AssertReturnVoid(pCfg->enmDir == PDMAUDIODIR_IN || pCfg->enmDir == PDMAUDIODIR_OUT); 1327 1328 int rc = RTCritSectEnter(&pThis->CritSect); /** @todo Reconsider the locking for DrvAudio */ 1329 AssertRCReturnVoid(rc); 1330 1331 /* 1332 * Don't do anything unless the backend has a pfnStreamConfigHint method 1333 * and the direction is currently enabled. 1334 */ 1335 if ( pThis->pHostDrvAudio 1336 && pThis->pHostDrvAudio->pfnStreamConfigHint) 1337 { 1338 if (pCfg->enmDir == PDMAUDIODIR_OUT ? pThis->Out.fEnabled : pThis->In.fEnabled) 1339 { 1340 /* 1341 * Adjust the configuration (applying out settings) then call the backend driver. 1342 */ 1343 rc = drvAudioStreamAdjustConfig(pThis, pCfg, pCfg->szName); 1344 AssertLogRelRC(rc); 1345 if (RT_SUCCESS(rc)) 1346 pThis->pHostDrvAudio->pfnStreamConfigHint(pThis->pHostDrvAudio, pCfg); 1347 } 1348 else 1349 LogFunc(("Ignoring hint because direction is not currently enabled\n")); 1350 } 1351 else 1352 LogFlowFunc(("Ignoring hint because backend has no pfnStreamConfigHint method.\n")); 1353 1354 RTCritSectLeave(&pThis->CritSect); 1355 } 1356 1357 1358 /** 1359 * Worker for drvAudioStreamInitInternal and drvAudioStreamReInitInternal that 1360 * creates the backend (host driver) side of an audio stream. 1361 * 1362 * @returns VBox status code. 1363 * @param pThis Pointer to driver instance. 1364 * @param pStreamEx Audio stream to create the backend side for. 1365 * @param pCfgReq Requested audio stream configuration to use for 1366 * stream creation. 1367 * @param pCfgAcq Acquired audio stream configuration returned by 1368 * the backend. 1369 * 1370 * @note Configuration precedence for requested audio stream configuration (first has highest priority, if set): 1371 * - per global extra-data 1372 * - per-VM extra-data 1373 * - requested configuration (by pCfgReq) 1374 * - default value 1375 */ 1376 static int drvAudioStreamCreateInternalBackend(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, 1377 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq) 1378 { 1379 AssertMsg((pStreamEx->Core.fStatus & PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED) == 0, 1380 ("Stream '%s' already initialized in backend\n", pStreamEx->Core.szName)); 1381 1382 /* 1383 * Adjust the requested stream config, applying our settings. 1384 */ 1385 int rc = drvAudioStreamAdjustConfig(pThis, pCfgReq, pStreamEx->Core.szName); 1386 if (RT_FAILURE(rc)) 1387 return rc; 1388 1389 /* 1390 * Make the acquired host configuration the requested host configuration initially, 1391 * in case the backend does not report back an acquired configuration. 1392 */ 1393 /** @todo r=bird: This is conveniently not documented in the interface... */ 1394 rc = PDMAudioStrmCfgCopy(pCfgAcq, pCfgReq); 1395 if (RT_FAILURE(rc)) 1396 { 1397 LogRel(("Audio: Creating stream '%s' with an invalid backend configuration not possible, skipping\n", 1398 pStreamEx->Core.szName)); 1399 return rc; 1400 } 1401 1402 /* 1403 * Call the host driver to create the stream. 1404 */ 1405 AssertPtr(pThis->pHostDrvAudio); 1406 if (pThis->pHostDrvAudio) 1407 rc = pThis->pHostDrvAudio->pfnStreamCreate(pThis->pHostDrvAudio, pStreamEx->pBackend, pCfgReq, pCfgAcq); 1408 else 1409 rc = VERR_PDM_NO_ATTACHED_DRIVER; 1410 if (RT_FAILURE(rc)) 1411 { 1412 if (rc == VERR_NOT_SUPPORTED) 1413 LogRel2(("Audio: Creating stream '%s' in backend not supported\n", pStreamEx->Core.szName)); 1414 else if (rc == VERR_AUDIO_STREAM_COULD_NOT_CREATE) 1415 LogRel2(("Audio: Stream '%s' could not be created in backend because of missing hardware / drivers\n", pStreamEx->Core.szName)); 1416 else 1417 LogRel(("Audio: Creating stream '%s' in backend failed with %Rrc\n", pStreamEx->Core.szName, rc)); 1418 return rc; 1419 } 1420 1421 /* Validate acquired configuration. */ 1422 char szTmp[PDMAUDIOPROPSTOSTRING_MAX]; 1423 AssertLogRelMsgReturn(AudioHlpStreamCfgIsValid(pCfgAcq), 1424 ("Audio: Creating stream '%s' returned an invalid backend configuration (%s), skipping\n", 1425 pStreamEx->Core.szName, PDMAudioPropsToString(&pCfgAcq->Props, szTmp, sizeof(szTmp))), 1426 VERR_INVALID_PARAMETER); 1427 1428 /* Let the user know that the backend changed one of the values requested above. */ 1429 if (pCfgAcq->Backend.cFramesBufferSize != pCfgReq->Backend.cFramesBufferSize) 1430 LogRel2(("Audio: Buffer size overwritten by backend for stream '%s' (now %RU64ms, %RU32 frames)\n", 1431 pStreamEx->Core.szName, PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesBufferSize), pCfgAcq->Backend.cFramesBufferSize)); 1432 1433 if (pCfgAcq->Backend.cFramesPeriod != pCfgReq->Backend.cFramesPeriod) 1434 LogRel2(("Audio: Period size overwritten by backend for stream '%s' (now %RU64ms, %RU32 frames)\n", 1435 pStreamEx->Core.szName, PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesPeriod), pCfgAcq->Backend.cFramesPeriod)); 1436 1437 /* Was pre-buffering requested, but the acquired configuration from the backend told us something else? */ 1438 if (pCfgReq->Backend.cFramesPreBuffering) 1439 { 1440 if (pCfgAcq->Backend.cFramesPreBuffering != pCfgReq->Backend.cFramesPreBuffering) 1441 LogRel2(("Audio: Pre-buffering size overwritten by backend for stream '%s' (now %RU64ms, %RU32 frames)\n", 1442 pStreamEx->Core.szName, PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesPreBuffering), pCfgAcq->Backend.cFramesPreBuffering)); 1443 1444 if (pCfgAcq->Backend.cFramesPreBuffering > pCfgAcq->Backend.cFramesBufferSize) 1445 { 1446 pCfgAcq->Backend.cFramesPreBuffering = pCfgAcq->Backend.cFramesBufferSize; 1447 LogRel2(("Audio: Pre-buffering size bigger than buffer size for stream '%s', adjusting to %RU64ms (%RU32 frames)\n", 1448 pStreamEx->Core.szName, PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesPreBuffering), pCfgAcq->Backend.cFramesPreBuffering)); 1449 } 1450 } 1451 else if (pCfgReq->Backend.cFramesPreBuffering == 0) /* Was the pre-buffering requested as being disabeld? Tell the users. */ 1452 { 1453 LogRel2(("Audio: Pre-buffering is disabled for stream '%s'\n", pStreamEx->Core.szName)); 1454 pCfgAcq->Backend.cFramesPreBuffering = 0; 1455 } 1456 1457 /* Sanity for detecting buggy backends. */ 1458 AssertMsgReturn(pCfgAcq->Backend.cFramesPeriod < pCfgAcq->Backend.cFramesBufferSize, 1459 ("Acquired period size must be smaller than buffer size\n"), 1460 VERR_INVALID_PARAMETER); 1461 AssertMsgReturn(pCfgAcq->Backend.cFramesPreBuffering <= pCfgAcq->Backend.cFramesBufferSize, 1462 ("Acquired pre-buffering size must be smaller or as big as the buffer size\n"), 1463 VERR_INVALID_PARAMETER); 1464 1465 pStreamEx->Core.fStatus |= PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED; 1466 1467 return VINF_SUCCESS; 1468 } 1469 1470 1471 /** 1472 * Worker for drvAudioStreamCreate that initializes the audio stream. 1473 * 1474 * @returns VBox status code. 1475 * @param pThis Pointer to driver instance. 1476 * @param pStreamEx Stream to initialize. 1477 * @param fFlags PDMAUDIOSTREAM_CREATE_F_XXX. 1478 * @param pCfgHost Stream configuration to use for the host side (backend). 1479 * @param pCfgGuest Stream configuration to use for the guest side. 1480 */ 1481 static int drvAudioStreamInitInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, uint32_t fFlags, 1482 PPDMAUDIOSTREAMCFG pCfgHost, PPDMAUDIOSTREAMCFG pCfgGuest) 1483 { 1484 /* 1485 * Init host stream. 1486 */ 1487 pStreamEx->Core.uMagic = PDMAUDIOSTREAM_MAGIC; 1488 1489 /* Set the host's default audio data layout. */ 1490 /** @todo r=bird: Why, oh why? OTOH, the layout stuff is non-sense anyway. */ 1491 pCfgHost->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED; 1492 1493 #ifdef LOG_ENABLED 1494 LogFunc(("[%s] Requested host format:\n", pStreamEx->Core.szName)); 1495 PDMAudioStrmCfgLog(pCfgHost); 1496 #endif 1497 1498 LogRel2(("Audio: Creating stream '%s'\n", pStreamEx->Core.szName)); 1499 LogRel2(("Audio: Guest %s format for '%s': %RU32Hz, %u%s, %RU8 channel%s\n", 1500 pCfgGuest->enmDir == PDMAUDIODIR_IN ? "recording" : "playback", pStreamEx->Core.szName, 1501 pCfgGuest->Props.uHz, PDMAudioPropsSampleBits(&pCfgGuest->Props), pCfgGuest->Props.fSigned ? "S" : "U", 1502 PDMAudioPropsChannels(&pCfgGuest->Props), PDMAudioPropsChannels(&pCfgGuest->Props) == 1 ? "" : "s")); 1503 LogRel2(("Audio: Requested host %s format for '%s': %RU32Hz, %u%s, %RU8 channel%s\n", 1504 pCfgHost->enmDir == PDMAUDIODIR_IN ? "recording" : "playback", pStreamEx->Core.szName, 1505 pCfgHost->Props.uHz, PDMAudioPropsSampleBits(&pCfgHost->Props), pCfgHost->Props.fSigned ? "S" : "U", 1506 PDMAudioPropsChannels(&pCfgHost->Props), PDMAudioPropsChannels(&pCfgHost->Props) == 1 ? "" : "s")); 1507 1508 PDMAUDIOSTREAMCFG CfgHostAcq; 1509 int rc = drvAudioStreamCreateInternalBackend(pThis, pStreamEx, pCfgHost, &CfgHostAcq); 1510 if (RT_FAILURE(rc)) 1511 return rc; 1512 1513 LogFunc(("[%s] Acquired host format:\n", pStreamEx->Core.szName)); 1514 PDMAudioStrmCfgLog(&CfgHostAcq); 1515 LogRel2(("Audio: Acquired host %s format for '%s': %RU32Hz, %u%s, %RU8 channel%s\n", 1516 CfgHostAcq.enmDir == PDMAUDIODIR_IN ? "recording" : "playback", pStreamEx->Core.szName, 1517 CfgHostAcq.Props.uHz, PDMAudioPropsSampleBits(&CfgHostAcq.Props), CfgHostAcq.Props.fSigned ? "S" : "U", 1518 PDMAudioPropsChannels(&CfgHostAcq.Props), PDMAudioPropsChannels(&CfgHostAcq.Props) == 1 ? "" : "s")); 1519 Assert(PDMAudioPropsAreValid(&CfgHostAcq.Props)); 1520 1521 /* Set the stream properties (currently guest side, when DevSB16 is 1522 converted to mixer and PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF becomes 1523 default, this will just be the stream properties). */ 1524 if (fFlags & PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF) 1525 pStreamEx->Core.Props = CfgHostAcq.Props; 1526 else 1527 pStreamEx->Core.Props = pCfgGuest->Props; 1528 1529 /* Let the user know if the backend changed some of the tweakable values. */ 1530 if (CfgHostAcq.Backend.cFramesBufferSize != pCfgHost->Backend.cFramesBufferSize) 1531 LogRel2(("Audio: Backend changed buffer size from %RU64ms (%RU32 frames) to %RU64ms (%RU32 frames)\n", 1532 PDMAudioPropsFramesToMilli(&pCfgHost->Props, pCfgHost->Backend.cFramesBufferSize), pCfgHost->Backend.cFramesBufferSize, 1533 PDMAudioPropsFramesToMilli(&CfgHostAcq.Props, CfgHostAcq.Backend.cFramesBufferSize), CfgHostAcq.Backend.cFramesBufferSize)); 1534 1535 if (CfgHostAcq.Backend.cFramesPeriod != pCfgHost->Backend.cFramesPeriod) 1536 LogRel2(("Audio: Backend changed period size from %RU64ms (%RU32 frames) to %RU64ms (%RU32 frames)\n", 1537 PDMAudioPropsFramesToMilli(&pCfgHost->Props, pCfgHost->Backend.cFramesPeriod), pCfgHost->Backend.cFramesPeriod, 1538 PDMAudioPropsFramesToMilli(&CfgHostAcq.Props, CfgHostAcq.Backend.cFramesPeriod), CfgHostAcq.Backend.cFramesPeriod)); 1539 1540 if (CfgHostAcq.Backend.cFramesPreBuffering != pCfgHost->Backend.cFramesPreBuffering) 1541 LogRel2(("Audio: Backend changed pre-buffering size from %RU64ms (%RU32 frames) to %RU64ms (%RU32 frames)\n", 1542 PDMAudioPropsFramesToMilli(&pCfgHost->Props, pCfgHost->Backend.cFramesPreBuffering), pCfgHost->Backend.cFramesPreBuffering, 1543 PDMAudioPropsFramesToMilli(&CfgHostAcq.Props, CfgHostAcq.Backend.cFramesPreBuffering), CfgHostAcq.Backend.cFramesPreBuffering)); 1544 1545 /* 1546 * Check if the backend did return sane values and correct if necessary. 1547 * Should never happen with our own backends, but you never know ... 1548 */ 1549 uint32_t const cFramesPreBufferingMax = CfgHostAcq.Backend.cFramesBufferSize - RT_MIN(16, CfgHostAcq.Backend.cFramesBufferSize); 1550 if (CfgHostAcq.Backend.cFramesPreBuffering > cFramesPreBufferingMax) 1551 { 1552 LogRel2(("Audio: Warning: Pre-buffering size of %RU32 frames for stream '%s' is too close to or larger than the %RU32 frames buffer size, reducing it to %RU32 frames!\n", 1553 CfgHostAcq.Backend.cFramesPreBuffering, pStreamEx->Core.szName, CfgHostAcq.Backend.cFramesBufferSize, cFramesPreBufferingMax)); 1554 AssertFailed(); 1555 CfgHostAcq.Backend.cFramesPreBuffering = cFramesPreBufferingMax; 1556 } 1557 1558 if (CfgHostAcq.Backend.cFramesPeriod > CfgHostAcq.Backend.cFramesBufferSize) 1559 { 1560 LogRel2(("Audio: Warning: Period size of %RU32 frames for stream '%s' is larger than the %RU32 frames buffer size, reducing it to %RU32 frames!\n", 1561 CfgHostAcq.Backend.cFramesPeriod, pStreamEx->Core.szName, CfgHostAcq.Backend.cFramesBufferSize, CfgHostAcq.Backend.cFramesBufferSize / 2)); 1562 AssertFailed(); 1563 CfgHostAcq.Backend.cFramesPeriod = CfgHostAcq.Backend.cFramesBufferSize / 2; 1564 } 1565 1566 LogRel2(("Audio: Buffer size of stream '%s' is %RU64 ms / %RU32 frames\n", pStreamEx->Core.szName, 1567 PDMAudioPropsFramesToMilli(&CfgHostAcq.Props, CfgHostAcq.Backend.cFramesBufferSize), CfgHostAcq.Backend.cFramesBufferSize)); 1568 LogRel2(("Audio: Pre-buffering size of stream '%s' is %RU64 ms / %RU32 frames\n", pStreamEx->Core.szName, 1569 PDMAudioPropsFramesToMilli(&CfgHostAcq.Props, CfgHostAcq.Backend.cFramesPreBuffering), CfgHostAcq.Backend.cFramesPreBuffering)); 1570 1571 /* Make sure the configured buffer size by the backend at least can hold the configured latency. */ 1572 const uint32_t msPeriod = PDMAudioPropsFramesToMilli(&CfgHostAcq.Props, CfgHostAcq.Backend.cFramesPeriod); 1573 LogRel2(("Audio: Period size of stream '%s' is %RU64 ms / %RU32 frames\n", 1574 pStreamEx->Core.szName, msPeriod, CfgHostAcq.Backend.cFramesPeriod)); 1575 1576 if ( pCfgGuest->Device.cMsSchedulingHint /* Any scheduling hint set? */ 1577 && pCfgGuest->Device.cMsSchedulingHint > msPeriod) /* This might lead to buffer underflows. */ 1578 LogRel(("Audio: Warning: Scheduling hint of stream '%s' is bigger (%RU64ms) than used period size (%RU64ms)\n", 1579 pStreamEx->Core.szName, pCfgGuest->Device.cMsSchedulingHint, msPeriod)); 1580 1581 /* 1582 * Make a copy of the acquired host stream configuration and the guest side one. 1583 */ 1584 rc = PDMAudioStrmCfgCopy(&pStreamEx->Host.Cfg, &CfgHostAcq); 1585 AssertRC(rc); 1586 1587 /* Set the guests's default audio data layout. */ 1588 pCfgGuest->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED; /** @todo r=bird: WTF DO WE DO THIS? It's input and probably should've been const... */ 1589 rc = PDMAudioStrmCfgCopy(&pStreamEx->Guest.Cfg, pCfgGuest); 1590 AssertRC(rc); 1591 1592 /* 1593 * Configure host buffers. 1594 */ 1595 1596 /* Destroy any former mixing buffer. */ 1597 AudioMixBufDestroy(&pStreamEx->Host.MixBuf); 1598 1599 if (!(fFlags & PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF)) 1600 { 1601 Assert(pCfgHost->enmDir == PDMAUDIODIR_IN); 1602 rc = AudioMixBufInit(&pStreamEx->Host.MixBuf, pStreamEx->Core.szName, &CfgHostAcq.Props, CfgHostAcq.Backend.cFramesBufferSize); 1603 AssertRCReturn(rc, rc); 1604 } 1605 /* Allocate space for pre-buffering of output stream w/o mixing buffers. */ 1606 else if (pCfgHost->enmDir == PDMAUDIODIR_OUT) 1607 { 1608 Assert(pStreamEx->Out.cbPreBufAlloc == 0); 1609 Assert(pStreamEx->Out.cbPreBufThreshold == 0); 1610 Assert(pStreamEx->Out.cbPreBuffered == 0); 1611 if (CfgHostAcq.Backend.cFramesPreBuffering != 0) 1612 { 1613 pStreamEx->Out.cbPreBufThreshold = PDMAudioPropsFramesToBytes(&CfgHostAcq.Props, CfgHostAcq.Backend.cFramesPreBuffering); 1614 pStreamEx->Out.cbPreBufAlloc = PDMAudioPropsFramesToBytes(&CfgHostAcq.Props, CfgHostAcq.Backend.cFramesBufferSize - 2); 1615 pStreamEx->Out.cbPreBufAlloc = RT_MIN(RT_ALIGN_32(pStreamEx->Out.cbPreBufThreshold + _8K, _4K), 1616 pStreamEx->Out.cbPreBufAlloc); 1617 pStreamEx->Out.pbPreBuf = (uint8_t *)RTMemAllocZ(pStreamEx->Out.cbPreBufAlloc); 1618 AssertReturn(pStreamEx->Out.pbPreBuf, VERR_NO_MEMORY); 1619 } 1620 } 1621 1622 /* 1623 * Init guest stream. 1624 */ 1625 if (pCfgGuest->Device.cMsSchedulingHint) 1626 LogRel2(("Audio: Stream '%s' got a scheduling hint of %RU32ms (%RU32 bytes)\n", 1627 pStreamEx->Core.szName, pCfgGuest->Device.cMsSchedulingHint, 1628 PDMAudioPropsMilliToBytes(&pCfgGuest->Props, pCfgGuest->Device.cMsSchedulingHint))); 1629 1630 /* Destroy any former mixing buffer. */ 1631 AudioMixBufDestroy(&pStreamEx->Guest.MixBuf); 1632 1633 if (!(fFlags & PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF)) 1634 { 1635 Assert(pCfgHost->enmDir == PDMAUDIODIR_IN); 1636 rc = AudioMixBufInit(&pStreamEx->Guest.MixBuf, pStreamEx->Core.szName, &pCfgGuest->Props, CfgHostAcq.Backend.cFramesBufferSize); 1637 AssertRCReturn(rc, rc); 1638 } 1639 1640 if (RT_FAILURE(rc)) 1641 LogRel(("Audio: Creating stream '%s' failed with %Rrc\n", pStreamEx->Core.szName, rc)); 1642 1643 if (!(fFlags & PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF)) 1644 { 1645 Assert(pCfgHost->enmDir == PDMAUDIODIR_IN); 1646 /* Host (Parent) -> Guest (Child). */ 1647 rc = AudioMixBufLinkTo(&pStreamEx->Host.MixBuf, &pStreamEx->Guest.MixBuf); 1648 AssertRC(rc); 1649 } 1650 1651 /* 1652 * Register statistics. 1653 */ 1654 PPDMDRVINS const pDrvIns = pThis->pDrvIns; 1655 /** @todo expose config and more. */ 1656 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Host.Cfg.Backend.cFramesBufferSize, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE, 1657 "Host side: The size of the backend buffer (in frames)", "%s/0-HostBackendBufSize", pStreamEx->Core.szName); 1658 if (!(fFlags & PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF)) 1659 { 1660 Assert(pCfgHost->enmDir == PDMAUDIODIR_IN); 1661 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Host.MixBuf.cFrames, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE, 1662 "Host side: The size of the mixer buffer (in frames)", "%s/1-HostMixBufSize", pStreamEx->Core.szName); 1663 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Guest.MixBuf.cFrames, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE, 1664 "Guest side: The size of the mixer buffer (in frames)", "%s/2-GuestMixBufSize", pStreamEx->Core.szName); 1665 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Host.MixBuf.cMixed, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE, 1666 "Host side: Number of frames in the mixer buffer", "%s/1-HostMixBufUsed", pStreamEx->Core.szName); 1667 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Guest.MixBuf.cUsed, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE, 1668 "Guest side: Number of frames in the mixer buffer", "%s/2-GuestMixBufUsed", pStreamEx->Core.szName); 1669 } 1670 if (pCfgGuest->enmDir == PDMAUDIODIR_IN) 1671 { 1672 /** @todo later? */ 1673 } 1674 else 1675 { 1676 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Out.Stats.cbBackendWritableBefore, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE, 1677 "Host side: Free space in backend buffer before play", "%s/0-HostBackendBufFreeBefore", pStreamEx->Core.szName); 1678 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Out.Stats.cbBackendWritableAfter, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE, 1679 "Host side: Free space in backend buffer after play", "%s/0-HostBackendBufFreeAfter", pStreamEx->Core.szName); 1680 } 1681 1682 #ifdef VBOX_WITH_STATISTICS 1683 if (pCfgGuest->enmDir == PDMAUDIODIR_IN) 1684 { 1685 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->In.Stats.TotalFramesCaptured, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_NONE, 1686 "Total frames played.", "%s/TotalFramesCaptured", pStreamEx->Core.szName); 1687 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->In.Stats.TotalTimesCaptured, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_NONE, 1688 "Total number of playbacks.", "%s/TotalTimesCaptured", pStreamEx->Core.szName); 1689 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->In.Stats.TotalFramesRead, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_NONE, 1690 "Total frames read.", "%s/TotalFramesRead", pStreamEx->Core.szName); 1691 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->In.Stats.TotalTimesRead, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_NONE, 1692 "Total number of reads.", "%s/TotalTimesRead", pStreamEx->Core.szName); 1693 } 1694 else 1695 { 1696 Assert(pCfgGuest->enmDir == PDMAUDIODIR_OUT); 1697 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Out.Stats.TotalFramesPlayed, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_NONE, 1698 "Total frames played.", "%s/TotalFramesPlayed", pStreamEx->Core.szName); 1699 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Out.Stats.TotalTimesPlayed, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_NONE, 1700 "Total number of playbacks.", "%s/TotalTimesPlayed", pStreamEx->Core.szName); 1701 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Out.Stats.TotalFramesWritten, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_NONE, 1702 "Total frames written.", "%s/TotalFramesWritten", pStreamEx->Core.szName); 1703 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Out.Stats.TotalTimesWritten, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_NONE, 1704 "Total number of writes.", "%s/TotalTimesWritten", pStreamEx->Core.szName); 1705 } 1706 #endif /* VBOX_WITH_STATISTICS */ 1707 1708 LogFlowFunc(("[%s] Returning %Rrc\n", pStreamEx->Core.szName, rc)); 1709 return rc; 1710 } 1711 1712 1713 /** 1714 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamCreate} 1715 */ 1716 static DECLCALLBACK(int) drvAudioStreamCreate(PPDMIAUDIOCONNECTOR pInterface, uint32_t fFlags, PPDMAUDIOSTREAMCFG pCfgHost, 1717 PPDMAUDIOSTREAMCFG pCfgGuest, PPDMAUDIOSTREAM *ppStream) 1718 { 1719 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector); 1720 AssertPtr(pThis); 1721 1722 /* 1723 * Assert sanity. 1724 */ 1725 AssertReturn(!(fFlags & ~PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF), VERR_INVALID_FLAGS); 1726 AssertPtrReturn(pCfgHost, VERR_INVALID_POINTER); 1727 AssertPtrReturn(pCfgGuest, VERR_INVALID_POINTER); 1728 AssertPtrReturn(ppStream, VERR_INVALID_POINTER); 1729 LogFlowFunc(("Host=%s, Guest=%s\n", pCfgHost->szName, pCfgGuest->szName)); 1730 #ifdef LOG_ENABLED 1731 PDMAudioStrmCfgLog(pCfgHost); 1732 PDMAudioStrmCfgLog(pCfgGuest); 1733 #endif 1734 AssertReturn(AudioHlpStreamCfgIsValid(pCfgHost), VERR_INVALID_PARAMETER); 1735 AssertReturn(AudioHlpStreamCfgIsValid(pCfgGuest), VERR_INVALID_PARAMETER); 1736 AssertReturn(pCfgHost->enmDir == pCfgGuest->enmDir, VERR_MISMATCH); 1737 AssertReturn(pCfgHost->enmDir == PDMAUDIODIR_IN || pCfgHost->enmDir == PDMAUDIODIR_OUT, VERR_NOT_SUPPORTED); 1738 /* Require PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF for output streams: */ 1739 AssertReturn((fFlags & PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF) || pCfgHost->enmDir == PDMAUDIODIR_IN, VERR_INVALID_FLAGS); 1740 1741 /* 1742 * Lock the whole driver instance. 1743 */ 1744 int rc = RTCritSectEnter(&pThis->CritSect); 1745 AssertRCReturn(rc, rc); 1746 1747 /* 1748 * Check that we have free streams in the backend and get the 1749 * size of the backend specific stream data. 1750 */ 1751 uint32_t *pcFreeStreams; 1752 if (pCfgHost->enmDir == PDMAUDIODIR_IN) 1753 { 1754 if (!pThis->In.cStreamsFree) 1755 { 1756 LogFlowFunc(("Maximum number of host input streams reached\n")); 1757 rc = VERR_AUDIO_NO_FREE_INPUT_STREAMS; 1758 } 1759 pcFreeStreams = &pThis->In.cStreamsFree; 1760 } 1761 else /* Out */ 1762 { 1763 if (!pThis->Out.cStreamsFree) 1764 { 1765 LogFlowFunc(("Maximum number of host output streams reached\n")); 1766 rc = VERR_AUDIO_NO_FREE_OUTPUT_STREAMS; 1767 } 1768 pcFreeStreams = &pThis->Out.cStreamsFree; 1769 } 1770 size_t const cbHstStrm = pThis->BackendCfg.cbStream; 1771 AssertStmt(cbHstStrm < _16M, rc = VERR_OUT_OF_RANGE); 1772 if (RT_SUCCESS(rc)) 1773 { 1774 /* 1775 * Allocate and initialize common state. 1776 */ 1777 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)RTMemAllocZ(sizeof(DRVAUDIOSTREAM) + RT_ALIGN_Z(cbHstStrm, 64)); 1778 if (pStreamEx) 1779 { 1780 /* Retrieve host driver name for easier identification. */ 1781 AssertPtr(pThis->pHostDrvAudio); 1782 RTStrPrintf(pStreamEx->Core.szName, RT_ELEMENTS(pStreamEx->Core.szName), "[%s] %s", 1783 pThis->BackendCfg.szName, pCfgHost->szName[0] != '\0' ? pCfgHost->szName : "<Untitled>"); 1784 1785 pStreamEx->Core.enmDir = pCfgHost->enmDir; 1786 pStreamEx->Core.cbBackend = (uint32_t)cbHstStrm; 1787 if (cbHstStrm) 1788 pStreamEx->pBackend = (PPDMAUDIOBACKENDSTREAM)(pStreamEx + 1); 1789 pStreamEx->fNoMixBufs = RT_BOOL(fFlags & PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF); 1790 pStreamEx->uMagic = DRVAUDIOSTREAM_MAGIC; 1791 1792 /* 1793 * Try to init the rest. 1794 */ 1795 rc = drvAudioStreamInitInternal(pThis, pStreamEx, fFlags, pCfgHost, pCfgGuest); 1796 if (RT_SUCCESS(rc)) 1797 { 1798 /* Set initial reference counts. */ 1799 pStreamEx->Core.cRefs = 1; 1800 1801 /* Decrement the free stream counter. */ 1802 Assert(*pcFreeStreams > 0); 1803 *pcFreeStreams -= 1; 1804 1805 /* 1806 * We're good. 1807 */ 1808 RTListAppend(&pThis->lstStreams, &pStreamEx->ListEntry); 1809 STAM_COUNTER_INC(&pThis->Stats.TotalStreamsCreated); 1810 *ppStream = &pStreamEx->Core; 1811 1812 /* 1813 * Init debug stuff if enabled (ignore failures). 1814 */ 1815 if (pCfgHost->enmDir == PDMAUDIODIR_IN) 1816 { 1817 if (pThis->In.Cfg.Dbg.fEnabled) 1818 { 1819 AudioHlpFileCreateAndOpen(&pStreamEx->In.Dbg.pFileCaptureNonInterleaved, pThis->In.Cfg.Dbg.szPathOut, 1820 "DrvAudioCapNonInt", pThis->pDrvIns->iInstance, &pStreamEx->Host.Cfg.Props); 1821 AudioHlpFileCreateAndOpen(&pStreamEx->In.Dbg.pFileStreamRead, pThis->In.Cfg.Dbg.szPathOut, 1822 "DrvAudioRead", pThis->pDrvIns->iInstance, &pStreamEx->Host.Cfg.Props); 1823 } 1824 } 1825 else /* Out */ 1826 { 1827 if (pThis->Out.Cfg.Dbg.fEnabled) 1828 { 1829 AudioHlpFileCreateAndOpen(&pStreamEx->Out.Dbg.pFilePlayNonInterleaved, pThis->Out.Cfg.Dbg.szPathOut, 1830 "DrvAudioPlayNonInt", pThis->pDrvIns->iInstance, &pStreamEx->Host.Cfg.Props); 1831 AudioHlpFileCreateAndOpen(&pStreamEx->Out.Dbg.pFileStreamWrite, pThis->Out.Cfg.Dbg.szPathOut, 1832 "DrvAudioWrite", pThis->pDrvIns->iInstance, &pStreamEx->Host.Cfg.Props); 1833 } 1834 } 1835 } 1836 else 1837 { 1838 LogFunc(("drvAudioStreamInitInternal failed: %Rrc\n", rc)); 1839 int rc2 = drvAudioStreamUninitInternal(pThis, pStreamEx); 1840 AssertRC(rc2); 1841 drvAudioStreamFree(pStreamEx); 1842 } 1843 } 1844 else 1845 rc = VERR_NO_MEMORY; 1846 } 1847 1848 RTCritSectLeave(&pThis->CritSect); 1849 LogFlowFuncLeaveRC(rc); 1850 return rc; 1851 } 1852 1853 1854 /** 1855 * Calls the backend to give it the chance to destroy its part of the audio stream. 1856 * 1857 * Called from drvAudioPowerOff, drvAudioStreamUninitInternal and 1858 * drvAudioStreamReInitInternal. 1859 * 1860 * @returns VBox status code. 1861 * @param pThis Pointer to driver instance. 1862 * @param pStreamEx Audio stream destruct backend for. 1863 */ 1864 static int drvAudioStreamDestroyInternalBackend(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx) 1865 { 1866 AssertPtr(pThis); 1867 AssertPtr(pStreamEx); 1868 1869 int rc = VINF_SUCCESS; 1870 1871 #ifdef LOG_ENABLED 1872 char szStreamSts[DRVAUDIO_STATUS_STR_MAX]; 1873 #endif 1874 LogFunc(("[%s] fStatus=%s\n", pStreamEx->Core.szName, dbgAudioStreamStatusToStr(szStreamSts, pStreamEx->Core.fStatus))); 1875 1876 if (pStreamEx->Core.fStatus & PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED) 1877 { 1878 AssertPtr(pStreamEx->pBackend); 1879 1880 /* Check if the pointer to the host audio driver is still valid. 1881 * It can be NULL if we were called in drvAudioDestruct, for example. */ 1882 if (pThis->pHostDrvAudio) 1883 rc = pThis->pHostDrvAudio->pfnStreamDestroy(pThis->pHostDrvAudio, pStreamEx->pBackend); 1884 1885 pStreamEx->Core.fStatus &= ~PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED; 1886 } 1887 1888 LogFlowFunc(("[%s] Returning %Rrc\n", pStreamEx->Core.szName, rc)); 1889 return rc; 1890 } 1891 1892 1893 /** 1894 * Uninitializes an audio stream - worker for drvAudioStreamDestroy, 1895 * drvAudioDestruct and drvAudioStreamCreate. 1896 * 1897 * @returns VBox status code. 1898 * @param pThis Pointer to driver instance. 1899 * @param pStreamEx Pointer to audio stream to uninitialize. 1900 * 1901 * @note Caller owns the critical section. 1902 */ 1903 static int drvAudioStreamUninitInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx) 1904 { 1905 AssertPtrReturn(pThis, VERR_INVALID_POINTER); 1906 AssertMsgReturn(pStreamEx->Core.cRefs <= 1, 1907 ("Stream '%s' still has %RU32 references held when uninitializing\n", pStreamEx->Core.szName, pStreamEx->Core.cRefs), 1908 VERR_WRONG_ORDER); 1909 LogFlowFunc(("[%s] cRefs=%RU32\n", pStreamEx->Core.szName, pStreamEx->Core.cRefs)); 1910 1911 /* 1912 * ... 1913 */ 1914 int rc = drvAudioStreamControlInternal(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE); 1915 if (RT_SUCCESS(rc)) 1916 rc = drvAudioStreamDestroyInternalBackend(pThis, pStreamEx); 1917 1918 /* Destroy mixing buffers. */ 1919 AudioMixBufDestroy(&pStreamEx->Guest.MixBuf); 1920 AudioMixBufDestroy(&pStreamEx->Host.MixBuf); 1921 1922 /* Free pre-buffer space. */ 1923 if ( pStreamEx->Core.enmDir == PDMAUDIODIR_OUT 1924 && pStreamEx->Out.pbPreBuf) 1925 { 1926 RTMemFree(pStreamEx->Out.pbPreBuf); 1927 pStreamEx->Out.pbPreBuf = NULL; 1928 pStreamEx->Out.cbPreBufAlloc = 0; 1929 pStreamEx->Out.cbPreBuffered = 0; 1930 } 1931 1932 if (RT_SUCCESS(rc)) 1933 { 1934 #ifdef LOG_ENABLED 1935 if (pStreamEx->Core.fStatus != PDMAUDIOSTREAMSTS_FLAGS_NONE) 1936 { 1937 char szStreamSts[DRVAUDIO_STATUS_STR_MAX]; 1938 LogFunc(("[%s] Warning: Still has %s set when uninitializing\n", 1939 pStreamEx->Core.szName, dbgAudioStreamStatusToStr(szStreamSts, pStreamEx->Core.fStatus))); 1940 } 1941 #endif 1942 pStreamEx->Core.fStatus = PDMAUDIOSTREAMSTS_FLAGS_NONE; 1943 } 1944 1945 PPDMDRVINS const pDrvIns = pThis->pDrvIns; 1946 PDMDrvHlpSTAMDeregisterByPrefix(pDrvIns, pStreamEx->Core.szName); 1947 1948 if (pStreamEx->Core.enmDir == PDMAUDIODIR_IN) 1949 { 1950 if (pThis->In.Cfg.Dbg.fEnabled) 1951 { 1952 AudioHlpFileDestroy(pStreamEx->In.Dbg.pFileCaptureNonInterleaved); 1953 pStreamEx->In.Dbg.pFileCaptureNonInterleaved = NULL; 1954 1955 AudioHlpFileDestroy(pStreamEx->In.Dbg.pFileStreamRead); 1956 pStreamEx->In.Dbg.pFileStreamRead = NULL; 1957 } 1958 } 1959 else 1960 { 1961 Assert(pStreamEx->Core.enmDir == PDMAUDIODIR_OUT); 1962 if (pThis->Out.Cfg.Dbg.fEnabled) 1963 { 1964 AudioHlpFileDestroy(pStreamEx->Out.Dbg.pFilePlayNonInterleaved); 1965 pStreamEx->Out.Dbg.pFilePlayNonInterleaved = NULL; 1966 1967 AudioHlpFileDestroy(pStreamEx->Out.Dbg.pFileStreamWrite); 1968 pStreamEx->Out.Dbg.pFileStreamWrite = NULL; 1969 } 1970 } 1971 LogFlowFunc(("Returning %Rrc\n", rc)); 1972 return rc; 1973 } 1974 1975 1976 /** 1977 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamDestroy} 1978 */ 1979 static DECLCALLBACK(int) drvAudioStreamDestroy(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream) 1980 { 1981 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector); 1982 AssertPtr(pThis); 1983 1984 if (!pStream) 1985 return VINF_SUCCESS; 1986 AssertPtrReturn(pStream, VERR_INVALID_POINTER); 1987 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream; /* Note! Do not touch pStream after this! */ 1988 Assert(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC); 1989 Assert(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC); 1990 1991 int rc = RTCritSectEnter(&pThis->CritSect); 1992 AssertRCReturn(rc, rc); 1993 1994 LogRel2(("Audio: Destroying stream '%s'\n", pStreamEx->Core.szName)); 1995 1996 LogFlowFunc(("[%s] cRefs=%RU32\n", pStreamEx->Core.szName, pStreamEx->Core.cRefs)); 1997 AssertMsg(pStreamEx->Core.cRefs <= 1, ("%u %s\n", pStreamEx->Core.cRefs, pStreamEx->Core.szName)); 1998 if (pStreamEx->Core.cRefs <= 1) 1999 { 2000 rc = drvAudioStreamUninitInternal(pThis, pStreamEx); 2001 if (RT_SUCCESS(rc)) 2002 { 2003 if (pStreamEx->Core.enmDir == PDMAUDIODIR_IN) 2004 pThis->In.cStreamsFree++; 2005 else /* Out */ 2006 pThis->Out.cStreamsFree++; 2007 2008 RTListNodeRemove(&pStreamEx->ListEntry); 2009 2010 drvAudioStreamFree(pStreamEx); 2011 pStreamEx = NULL; 2012 pStream = NULL; 2013 } 2014 else 2015 LogRel(("Audio: Uninitializing stream '%s' failed with %Rrc\n", pStreamEx->Core.szName, rc)); 2016 } 2017 else 2018 rc = VERR_WRONG_ORDER; 2019 2020 RTCritSectLeave(&pThis->CritSect); 2021 LogFlowFuncLeaveRC(rc); 2022 return rc; 2023 } 2024 2025 2026 /** 2027 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRetain} 2028 */ 2029 static DECLCALLBACK(uint32_t) drvAudioStreamRetain(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream) 2030 { 2031 AssertPtrReturn(pInterface, UINT32_MAX); 2032 AssertPtrReturn(pStream, UINT32_MAX); 2033 AssertReturn(pStream->uMagic == PDMAUDIOSTREAM_MAGIC, UINT32_MAX); 2034 AssertReturn(((PDRVAUDIOSTREAM)pStream)->uMagic == DRVAUDIOSTREAM_MAGIC, UINT32_MAX); 2035 RT_NOREF(pInterface); 2036 2037 uint32_t const cRefs = ASMAtomicIncU32(&pStream->cRefs); 2038 Assert(cRefs > 1); 2039 Assert(cRefs < _1K); 2040 2041 return cRefs; 2042 } 2043 2044 2045 /** 2046 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRelease} 2047 */ 2048 static DECLCALLBACK(uint32_t) drvAudioStreamRelease(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream) 2049 { 2050 AssertPtrReturn(pInterface, UINT32_MAX); 2051 AssertPtrReturn(pStream, UINT32_MAX); 2052 AssertReturn(pStream->uMagic == PDMAUDIOSTREAM_MAGIC, UINT32_MAX); 2053 AssertReturn(((PDRVAUDIOSTREAM)pStream)->uMagic == DRVAUDIOSTREAM_MAGIC, UINT32_MAX); 2054 RT_NOREF(pInterface); 2055 2056 uint32_t cRefs = ASMAtomicDecU32(&pStream->cRefs); 2057 AssertStmt(cRefs >= 1, cRefs = ASMAtomicIncU32(&pStream->cRefs)); 2058 Assert(cRefs < _1K); 2059 2060 return cRefs; 2061 } 2062 2063 2064 /** 2065 * Controls a stream's backend. 2066 * 2067 * If the stream has no backend available, VERR_NOT_FOUND is returned 2068 * (bird: actually the code returns VINF_SUCCESS). 2069 * 2070 * @returns VBox status code. 2071 * @param pThis Pointer to driver instance. 2072 * @param pStreamEx Stream to control. 2073 * @param enmStreamCmd Control command. 2074 */ 2075 static int drvAudioStreamControlInternalBackend(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, PDMAUDIOSTREAMCMD enmStreamCmd) 2076 { 2077 AssertPtr(pThis); 2078 AssertPtr(pStreamEx); 2079 2080 #ifdef LOG_ENABLED 2081 char szStreamSts[DRVAUDIO_STATUS_STR_MAX]; 2082 #endif 2083 LogFlowFunc(("[%s] enmStreamCmd=%s, fStatus=%s\n", pStreamEx->Core.szName, PDMAudioStrmCmdGetName(enmStreamCmd), 2084 dbgAudioStreamStatusToStr(szStreamSts, pStreamEx->Core.fStatus))); 2085 2086 if (!pThis->pHostDrvAudio) /* If not lower driver is configured, bail out. */ 2087 return VINF_SUCCESS; 2088 2089 2090 /* 2091 * Whether to propagate commands down to the backend. 2092 * 2093 * This is needed for critical operations like recording audio if audio input is disabled on a per-driver level. 2094 * 2095 * Note that not all commands will be covered by this, such as operations like stopping, draining and droppping, 2096 * which are considered uncritical and sometimes even are required for certain backends (like DirectSound on Windows). 2097 * 2098 * The actual stream state will be untouched to not make the state machine handling more complicated than 2099 * it already is. 2100 * 2101 * See @bugref{9882}. 2102 */ 2103 const bool fEnabled = ( pStreamEx->Core.enmDir == PDMAUDIODIR_IN 2104 && pThis->In.fEnabled) 2105 || ( pStreamEx->Core.enmDir == PDMAUDIODIR_OUT 2106 && pThis->Out.fEnabled); 2107 2108 LogRel2(("Audio: %s stream '%s' in backend (%s is %s)\n", PDMAudioStrmCmdGetName(enmStreamCmd), pStreamEx->Core.szName, 2109 PDMAudioDirGetName(pStreamEx->Core.enmDir), 2110 fEnabled ? "enabled" : "disabled")); 2111 int rc = VINF_SUCCESS; 2112 switch (enmStreamCmd) 2113 { 2114 case PDMAUDIOSTREAMCMD_ENABLE: 2115 { 2116 if (fEnabled) 2117 rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStreamEx->pBackend, PDMAUDIOSTREAMCMD_ENABLE); 2118 break; 2119 } 2120 2121 case PDMAUDIOSTREAMCMD_DISABLE: 2122 { 2123 rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStreamEx->pBackend, PDMAUDIOSTREAMCMD_DISABLE); 2124 break; 2125 } 2126 2127 case PDMAUDIOSTREAMCMD_PAUSE: 2128 { 2129 if (fEnabled) /* Needed, as resume below also is being checked for. */ 2130 rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStreamEx->pBackend, PDMAUDIOSTREAMCMD_PAUSE); 2131 break; 2132 } 2133 2134 case PDMAUDIOSTREAMCMD_RESUME: 2135 { 2136 if (fEnabled) 2137 rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStreamEx->pBackend, PDMAUDIOSTREAMCMD_RESUME); 2138 break; 2139 } 2140 2141 case PDMAUDIOSTREAMCMD_DRAIN: 2142 { 2143 rc = pThis->pHostDrvAudio->pfnStreamControl(pThis->pHostDrvAudio, pStreamEx->pBackend, PDMAUDIOSTREAMCMD_DRAIN); 2144 break; 2145 } 2146 2147 default: 2148 AssertMsgFailedReturn(("Command %RU32 not implemented\n", enmStreamCmd), VERR_INTERNAL_ERROR_2); 2149 } 2150 2151 if (RT_FAILURE(rc)) 2152 { 2153 if ( rc != VERR_NOT_IMPLEMENTED 2154 && rc != VERR_NOT_SUPPORTED 2155 && rc != VERR_AUDIO_STREAM_NOT_READY) 2156 { 2157 LogRel(("Audio: %s stream '%s' failed with %Rrc\n", PDMAudioStrmCmdGetName(enmStreamCmd), pStreamEx->Core.szName, rc)); 2158 } 2159 2160 LogFunc(("[%s] %s failed with %Rrc\n", pStreamEx->Core.szName, PDMAudioStrmCmdGetName(enmStreamCmd), rc)); 2161 } 2162 2163 return rc; 2164 } 2165 2166 /** 2167 * Controls an audio stream. 2168 * 2169 * @returns VBox status code. 2170 * @param pThis Pointer to driver instance. 2171 * @param pStreamEx Stream to control. 2172 * @param enmStreamCmd Control command. 2173 */ 2174 static int drvAudioStreamControlInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, PDMAUDIOSTREAMCMD enmStreamCmd) 2175 { 2176 AssertPtr(pThis); 2177 AssertPtr(pStreamEx); 2178 2179 #ifdef LOG_ENABLED 2180 char szStreamSts[DRVAUDIO_STATUS_STR_MAX]; 2181 #endif 2182 LogFunc(("[%s] enmStreamCmd=%s fStatus=%s\n", pStreamEx->Core.szName, PDMAudioStrmCmdGetName(enmStreamCmd), 2183 dbgAudioStreamStatusToStr(szStreamSts, pStreamEx->Core.fStatus))); 2184 2185 int rc = VINF_SUCCESS; 2186 2187 switch (enmStreamCmd) 2188 { 2189 case PDMAUDIOSTREAMCMD_ENABLE: 2190 { 2191 if (!(pStreamEx->Core.fStatus & PDMAUDIOSTREAMSTS_FLAGS_ENABLED)) 2192 { 2193 /* Is a pending disable outstanding? Then disable first. */ 2194 if (pStreamEx->Core.fStatus & PDMAUDIOSTREAMSTS_FLAGS_PENDING_DISABLE) 2195 rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE); 2196 2197 if (RT_SUCCESS(rc)) 2198 rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_ENABLE); 2199 2200 if (RT_SUCCESS(rc)) 2201 pStreamEx->Core.fStatus |= PDMAUDIOSTREAMSTS_FLAGS_ENABLED; 2202 } 2203 break; 2204 } 2205 2206 case PDMAUDIOSTREAMCMD_DISABLE: 2207 { 2208 if (pStreamEx->Core.fStatus & PDMAUDIOSTREAMSTS_FLAGS_ENABLED) 2209 { 2210 /* 2211 * For playback (output) streams first mark the host stream as pending disable, 2212 * so that the rest of the remaining audio data will be played first before 2213 * closing the stream. 2214 */ 2215 if (pStreamEx->Core.enmDir == PDMAUDIODIR_OUT) 2216 { 2217 LogFunc(("[%s] Pending disable/pause\n", pStreamEx->Core.szName)); 2218 pStreamEx->Core.fStatus |= PDMAUDIOSTREAMSTS_FLAGS_PENDING_DISABLE; 2219 2220 /* Schedule a follow up timer to the pending-disable state. We cannot rely 2221 on the device to provide further callouts to finish the state transition. 2222 10ms is taking out of thin air and may be too course grained, we should 2223 really consider the amount of unplayed buffer in the backend and what not... */ 2224 if (!pThis->fTimerArmed) 2225 { 2226 LogFlowFunc(("Arming emergency pending-disable hack...\n")); 2227 int rc2 = PDMDrvHlpTimerSetMillies(pThis->pDrvIns, pThis->hTimer, 10 /*ms*/); 2228 AssertRC(rc2); 2229 pThis->fTimerArmed = true; 2230 } 2231 } 2232 2233 /* Can we close the host stream as well (not in pending disable mode)? */ 2234 if (!(pStreamEx->Core.fStatus & PDMAUDIOSTREAMSTS_FLAGS_PENDING_DISABLE)) 2235 { 2236 rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE); 2237 if (RT_SUCCESS(rc)) 2238 drvAudioStreamResetInternal(pThis, pStreamEx); 2239 } 2240 } 2241 break; 2242 } 2243 2244 case PDMAUDIOSTREAMCMD_PAUSE: 2245 { 2246 if (!(pStreamEx->Core.fStatus & PDMAUDIOSTREAMSTS_FLAGS_PAUSED)) 2247 { 2248 rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_PAUSE); 2249 if (RT_SUCCESS(rc)) 2250 pStreamEx->Core.fStatus |= PDMAUDIOSTREAMSTS_FLAGS_PAUSED; 2251 } 2252 break; 2253 } 2254 2255 case PDMAUDIOSTREAMCMD_RESUME: 2256 { 2257 if (pStreamEx->Core.fStatus & PDMAUDIOSTREAMSTS_FLAGS_PAUSED) 2258 { 2259 rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_RESUME); 2260 if (RT_SUCCESS(rc)) 2261 pStreamEx->Core.fStatus &= ~PDMAUDIOSTREAMSTS_FLAGS_PAUSED; 2262 } 2263 break; 2264 } 2265 2266 default: 2267 rc = VERR_NOT_IMPLEMENTED; 2268 break; 2269 } 2270 2271 if (RT_FAILURE(rc)) 2272 LogFunc(("[%s] Failed with %Rrc\n", pStreamEx->Core.szName, rc)); 2273 2274 return rc; 2275 } 2276 2277 2278 /** 2279 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamControl} 2280 */ 2281 static DECLCALLBACK(int) drvAudioStreamControl(PPDMIAUDIOCONNECTOR pInterface, 2282 PPDMAUDIOSTREAM pStream, PDMAUDIOSTREAMCMD enmStreamCmd) 2283 { 2284 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector); 2285 AssertPtr(pThis); 2286 2287 /** @todo r=bird: why? It's not documented to ignore NULL streams. */ 2288 if (!pStream) 2289 return VINF_SUCCESS; 2290 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream; 2291 AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER); 2292 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC); 2293 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC); 2294 2295 int rc = RTCritSectEnter(&pThis->CritSect); 2296 AssertRCReturn(rc, rc); 2297 2298 LogFlowFunc(("[%s] enmStreamCmd=%s\n", pStream->szName, PDMAudioStrmCmdGetName(enmStreamCmd))); 2299 2300 rc = drvAudioStreamControlInternal(pThis, pStreamEx, enmStreamCmd); 2301 2302 RTCritSectLeave(&pThis->CritSect); 2303 return rc; 2304 } 2305 2306 2307 /** 2308 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRead} 2309 */ 2310 static DECLCALLBACK(int) drvAudioStreamRead(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream, 2311 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead) 2312 { 2313 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector); 2314 AssertPtr(pThis); 2315 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream; 2316 AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER); 2317 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); 2318 AssertReturn(cbBuf, VERR_INVALID_PARAMETER); 2319 AssertPtrNullReturn(pcbRead, VERR_INVALID_POINTER); 2320 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC); 2321 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC); 2322 AssertMsg(pStreamEx->Core.enmDir == PDMAUDIODIR_IN, 2323 ("Stream '%s' is not an input stream and therefore cannot be read from (direction is 0x%x)\n", 2324 pStreamEx->Core.szName, pStreamEx->Core.enmDir)); 2325 2326 int rc = RTCritSectEnter(&pThis->CritSect); 2327 AssertRCReturn(rc, rc); 2328 2329 /* 2330 * ... 2331 */ 2332 uint32_t cbReadTotal = 0; 2333 2334 do 2335 { 2336 uint32_t cfReadTotal = 0; 2337 2338 const uint32_t cfBuf = AUDIOMIXBUF_B2F(&pStreamEx->Guest.MixBuf, cbBuf); 2339 2340 if (pThis->In.fEnabled) /* Input for this audio driver enabled? See #9822. */ 2341 { 2342 if (!PDMAudioStrmStatusCanRead(pStream->fStatus)) 2343 { 2344 rc = VERR_AUDIO_STREAM_NOT_READY; 2345 break; 2346 } 2347 2348 /* 2349 * Read from the parent buffer (that is, the guest buffer) which 2350 * should have the audio data in the format the guest needs. 2351 */ 2352 uint32_t cfToRead = RT_MIN(cfBuf, AudioMixBufLive(&pStreamEx->Guest.MixBuf)); 2353 while (cfToRead) 2354 { 2355 uint32_t cfRead; 2356 rc = AudioMixBufAcquireReadBlock(&pStreamEx->Guest.MixBuf, 2357 (uint8_t *)pvBuf + AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfReadTotal), 2358 AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfToRead), &cfRead); 2359 if (RT_FAILURE(rc)) 2360 break; 2361 2362 #ifdef VBOX_WITH_STATISTICS 2363 const uint32_t cbRead = AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfRead); 2364 STAM_COUNTER_ADD(&pThis->Stats.TotalBytesRead, cbRead); 2365 STAM_COUNTER_ADD(&pStreamEx->In.Stats.TotalFramesRead, cfRead); 2366 STAM_COUNTER_INC(&pStreamEx->In.Stats.TotalTimesRead); 2367 #endif 2368 Assert(cfToRead >= cfRead); 2369 cfToRead -= cfRead; 2370 2371 cfReadTotal += cfRead; 2372 2373 AudioMixBufReleaseReadBlock(&pStreamEx->Guest.MixBuf, cfRead); 2374 } 2375 2376 if (cfReadTotal) 2377 { 2378 if (pThis->In.Cfg.Dbg.fEnabled) 2379 AudioHlpFileWrite(pStreamEx->In.Dbg.pFileStreamRead, 2380 pvBuf, AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfReadTotal), 0 /* fFlags */); 2381 2382 AudioMixBufFinish(&pStreamEx->Guest.MixBuf, cfReadTotal); 2383 } 2384 } 2385 2386 /* If we were not able to read as much data as requested, fill up the returned 2387 * data with silence. 2388 * 2389 * This is needed to keep the device emulation DMA transfers up and running at a constant rate. */ 2390 if (cfReadTotal < cfBuf) 2391 { 2392 Log3Func(("[%s] Filling in silence (%RU64ms / %RU64ms)\n", pStream->szName, 2393 PDMAudioPropsFramesToMilli(&pStreamEx->Guest.Cfg.Props, cfBuf - cfReadTotal), 2394 PDMAudioPropsFramesToMilli(&pStreamEx->Guest.Cfg.Props, cfBuf))); 2395 2396 PDMAudioPropsClearBuffer(&pStreamEx->Guest.Cfg.Props, 2397 (uint8_t *)pvBuf + AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfReadTotal), 2398 AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfBuf - cfReadTotal), 2399 cfBuf - cfReadTotal); 2400 2401 cfReadTotal = cfBuf; 2402 } 2403 2404 cbReadTotal = AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfReadTotal); 2405 2406 pStreamEx->nsLastReadWritten = RTTimeNanoTS(); 2407 2408 Log3Func(("[%s] fEnabled=%RTbool, cbReadTotal=%RU32, rc=%Rrc\n", pStream->szName, pThis->In.fEnabled, cbReadTotal, rc)); 2409 2410 } while (0); 2411 2412 RTCritSectLeave(&pThis->CritSect); 2413 2414 if (RT_SUCCESS(rc) && pcbRead) 2415 *pcbRead = cbReadTotal; 2416 return rc; 2417 } 2418 2419 2420 /** 2421 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamWrite} 2422 */ 2423 static DECLCALLBACK(int) drvAudioStreamWrite(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream, 2424 const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten) 2425 { 2426 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector); 2427 AssertPtr(pThis); 2428 2429 /* 2430 * Check input and sanity. 2431 */ 2432 AssertPtrReturn(pInterface, VERR_INVALID_POINTER); 2433 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream; 2434 AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER); 2435 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); 2436 AssertReturn(cbBuf, VERR_INVALID_PARAMETER); 2437 uint32_t uTmp; 2438 if (!pcbWritten) 2439 pcbWritten = &uTmp; 2440 AssertPtrReturn(pcbWritten, VERR_INVALID_PARAMETER); 2441 2442 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC); 2443 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC); 2444 AssertMsgReturn(pStreamEx->Core.enmDir == PDMAUDIODIR_OUT, 2445 ("Stream '%s' is not an output stream and therefore cannot be written to (direction is '%s')\n", 2446 pStreamEx->Core.szName, PDMAudioDirGetName(pStreamEx->Core.enmDir)), VERR_ACCESS_DENIED); 2447 Assert(pStreamEx->fNoMixBufs); 2448 2449 AssertMsg(PDMAudioPropsIsSizeAligned(&pStreamEx->Guest.Cfg.Props, cbBuf), 2450 ("Stream '%s' got a non-frame-aligned write (%RU32 bytes)\n", pStreamEx->Core.szName, cbBuf)); 2451 2452 /// @todo STAM_PROFILE_ADV_START(&pThis->Stats.DelayOut, out); /* (stopped in drvAudioStreamPlayLocked) */ 2453 2454 int rc = RTCritSectEnter(&pThis->CritSect); 2455 AssertRCReturn(rc, rc); 2456 2457 /* 2458 * First check that we can write to the stream, and if not, 2459 * whether to just drop the input into the bit bucket. 2460 */ 2461 if (PDMAudioStrmStatusIsReady(pStreamEx->Core.fStatus)) 2462 { 2463 if ( !pThis->Out.fEnabled /* (see @bugref{9882}) */ 2464 || pThis->pHostDrvAudio == NULL /* (we used to work this condition differently) */ 2465 || !PDMAudioStrmStatusCanWrite(pThis->pHostDrvAudio->pfnStreamGetStatus(pThis->pHostDrvAudio, pStreamEx->pBackend))) 2466 { 2467 Log3Func(("[%s] Backend stream %s, discarding the data\n", pStreamEx->Core.szName, 2468 !pThis->Out.fEnabled ? "disabled" : !pThis->pHostDrvAudio ? "not attached" : "not ready yet")); 2469 *pcbWritten = cbBuf; 2470 pStreamEx->offInternal += cbBuf; 2471 } 2472 /* 2473 * No-mixing buffer mode: Write the data directly to the backend, unless 2474 * we're prebuffering. There will be no pfnStreamPlay call in this mode. 2475 */ 2476 else 2477 { 2478 uint64_t offInternalBefore = pStreamEx->offInternal; RT_NOREF(offInternalBefore); 2479 rc = drvAudioStreamWriteNoMixBufs(pThis, pStreamEx, (uint8_t const *)pvBuf, cbBuf, pcbWritten); 2480 Assert(offInternalBefore + *pcbWritten == pStreamEx->offInternal); 2481 if (!pThis->Out.Cfg.Dbg.fEnabled || RT_FAILURE(rc)) 2482 { /* likely */ } 2483 else 2484 AudioHlpFileWrite(pStreamEx->Out.Dbg.pFilePlayNonInterleaved, pvBuf, *pcbWritten, 0 /* fFlags */); 2485 } 2486 } 2487 else 2488 rc = VERR_AUDIO_STREAM_NOT_READY; 2489 2490 RTCritSectLeave(&pThis->CritSect); 2491 return rc; 2492 } 2493 1206 2494 1207 2495 /** … … 1325 2613 } 1326 2614 1327 /** 1328 * @callback_method_impl{FNTMTIMERDRV} 1329 */ 1330 static DECLCALLBACK(void) drvAudioEmergencyIterateTimer(PPDMDRVINS pDrvIns, TMTIMERHANDLE hTimer, void *pvUser) 1331 { 1332 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO); 1333 RT_NOREF(hTimer, pvUser); 1334 RTCritSectEnter(&pThis->CritSect); 1335 1336 /* 1337 * Iterate any stream with the pending-disable flag set. 1338 */ 1339 uint32_t cMilliesToNext = 0; 1340 PDRVAUDIOSTREAM pStreamEx, pStreamExNext; 1341 RTListForEachSafe(&pThis->lstStreams, pStreamEx, pStreamExNext, DRVAUDIOSTREAM, ListEntry) 1342 { 1343 if ( pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC 1344 && pStreamEx->Core.cRefs >= 1) 1345 { 1346 if (pStreamEx->Core.fStatus & PDMAUDIOSTREAMSTS_FLAGS_PENDING_DISABLE) 1347 { 1348 drvAudioStreamIterateInternal(pThis, pStreamEx); 1349 1350 if (pStreamEx->Core.fStatus & PDMAUDIOSTREAMSTS_FLAGS_PENDING_DISABLE) 1351 cMilliesToNext = 10; 1352 } 1353 } 1354 } 1355 1356 /* 1357 * Re-arm the timer if we still got streams in the pending state. 1358 */ 1359 if (cMilliesToNext) 1360 { 1361 pThis->fTimerArmed = true; 1362 PDMDrvHlpTimerSetMillies(pDrvIns, pThis->hTimer, cMilliesToNext); 1363 } 1364 else 1365 pThis->fTimerArmed = false; 1366 1367 RTCritSectLeave(&pThis->CritSect); 1368 } 1369 1370 1371 /** 1372 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamPlay} 1373 */ 1374 static DECLCALLBACK(int) drvAudioStreamPlay(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream, uint32_t *pcFramesPlayed) 1375 { 1376 RT_NOREF(pInterface, pStream, pcFramesPlayed); 1377 AssertFailed(/* OBSOLETE! */); 1378 return VERR_NOT_SUPPORTED; 1379 } 1380 1381 1382 /** 1383 * Captures non-interleaved input from a host stream. 1384 * 1385 * @returns VBox status code. 1386 * @param pThis Driver instance. 1387 * @param pStreamEx Stream to capture from. 1388 * @param pcfCaptured Number of (host) audio frames captured. 1389 */ 1390 static int drvAudioStreamCaptureNonInterleaved(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, uint32_t *pcfCaptured) 1391 { 1392 Assert(pStreamEx->Core.enmDir == PDMAUDIODIR_IN); 1393 Assert(pStreamEx->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED); 1394 1395 /* 1396 * ... 1397 */ 1398 AssertPtr(pThis->pHostDrvAudio->pfnStreamGetReadable); 1399 uint32_t cbReadable = pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio, pStreamEx->pBackend); 1400 if (!cbReadable) 1401 Log2Func(("[%s] No readable data available\n", pStreamEx->Core.szName)); 1402 1403 uint32_t cbFree = AudioMixBufFreeBytes(&pStreamEx->Guest.MixBuf); /* Parent */ 1404 if (!cbFree) 1405 Log2Func(("[%s] Buffer full\n", pStreamEx->Core.szName)); 1406 1407 if (cbReadable > cbFree) /* More data readable than we can store at the moment? Limit. */ 1408 cbReadable = cbFree; 1409 1410 /* 1411 * ... 1412 */ 1413 int rc = VINF_SUCCESS; 1414 uint32_t cfCapturedTotal = 0; 1415 while (cbReadable) 1416 { 1417 uint8_t abChunk[_4K]; 1418 uint32_t cbCaptured; 1419 rc = pThis->pHostDrvAudio->pfnStreamCapture(pThis->pHostDrvAudio, pStreamEx->pBackend, 1420 abChunk, RT_MIN(cbReadable, (uint32_t)sizeof(abChunk)), &cbCaptured); 1421 if (RT_FAILURE(rc)) 1422 { 1423 int rc2 = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE); 1424 AssertRC(rc2); 1425 break; 1426 } 1427 1428 Assert(cbCaptured <= sizeof(abChunk)); 1429 if (cbCaptured > sizeof(abChunk)) /* Paranoia. */ 1430 cbCaptured = (uint32_t)sizeof(abChunk); 1431 1432 if (!cbCaptured) /* Nothing captured? Take a shortcut. */ 1433 break; 1434 1435 /* We use the host side mixing buffer as an intermediate buffer to do some 1436 * (first) processing (if needed), so always write the incoming data at offset 0. */ 1437 uint32_t cfHstWritten = 0; 1438 rc = AudioMixBufWriteAt(&pStreamEx->Host.MixBuf, 0 /* offFrames */, abChunk, cbCaptured, &cfHstWritten); 1439 if ( RT_FAILURE(rc) 1440 || !cfHstWritten) 1441 { 1442 AssertMsgFailed(("[%s] Write failed: cbCaptured=%RU32, cfHstWritten=%RU32, rc=%Rrc\n", 1443 pStreamEx->Core.szName, cbCaptured, cfHstWritten, rc)); 1444 break; 1445 } 1446 1447 if (pThis->In.Cfg.Dbg.fEnabled) 1448 AudioHlpFileWrite(pStreamEx->In.Dbg.pFileCaptureNonInterleaved, abChunk, cbCaptured, 0 /* fFlags */); 1449 1450 uint32_t cfHstMixed = 0; 1451 if (cfHstWritten) 1452 { 1453 int rc2 = AudioMixBufMixToParentEx(&pStreamEx->Host.MixBuf, 0 /* cSrcOffset */, cfHstWritten /* cSrcFrames */, 1454 &cfHstMixed /* pcSrcMixed */); 1455 Log3Func(("[%s] cbCaptured=%RU32, cfWritten=%RU32, cfMixed=%RU32, rc=%Rrc\n", 1456 pStreamEx->Core.szName, cbCaptured, cfHstWritten, cfHstMixed, rc2)); 1457 AssertRC(rc2); 1458 } 1459 1460 Assert(cbReadable >= cbCaptured); 1461 cbReadable -= cbCaptured; 1462 cfCapturedTotal += cfHstMixed; 1463 } 1464 1465 if (RT_SUCCESS(rc)) 1466 { 1467 if (cfCapturedTotal) 1468 Log2Func(("[%s] %RU32 frames captured, rc=%Rrc\n", pStreamEx->Core.szName, cfCapturedTotal, rc)); 1469 } 1470 else 1471 LogFunc(("[%s] Capturing failed with rc=%Rrc\n", pStreamEx->Core.szName, rc)); 1472 1473 if (pcfCaptured) 1474 *pcfCaptured = cfCapturedTotal; 1475 1476 return rc; 1477 } 1478 1479 /** 1480 * Captures raw input from a host stream. 1481 * Raw input means that the backend directly operates on PDMAUDIOFRAME structs without 1482 * no data layout processing done in between. 1483 * 1484 * Needed for e.g. the VRDP audio backend (in Main). 1485 * 1486 * @returns VBox status code. 1487 * @param pThis Driver instance. 1488 * @param pStreamEx Stream to capture from. 1489 * @param pcfCaptured Number of (host) audio frames captured. 1490 */ 1491 static int drvAudioStreamCaptureRaw(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, uint32_t *pcfCaptured) 1492 { 1493 Assert(pStreamEx->Core.enmDir == PDMAUDIODIR_IN); 1494 Assert(pStreamEx->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_RAW); 1495 AssertPtr(pThis->pHostDrvAudio->pfnStreamGetReadable); 1496 1497 /* 1498 * ... 1499 */ 1500 /* Note: Raw means *audio frames*, not bytes! */ 1501 uint32_t cfReadable = pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio, pStreamEx->pBackend); 1502 if (!cfReadable) 1503 Log2Func(("[%s] No readable data available\n", pStreamEx->Core.szName)); 1504 1505 const uint32_t cfFree = AudioMixBufFree(&pStreamEx->Guest.MixBuf); /* Parent */ 1506 if (!cfFree) 1507 Log2Func(("[%s] Buffer full\n", pStreamEx->Core.szName)); 1508 1509 if (cfReadable > cfFree) /* More data readable than we can store at the moment? Limit. */ 1510 cfReadable = cfFree; 1511 1512 /* 1513 * ... 1514 */ 1515 int rc = VINF_SUCCESS; 1516 uint32_t cfCapturedTotal = 0; 1517 while (cfReadable) 1518 { 1519 PPDMAUDIOFRAME paFrames; 1520 uint32_t cfWritable; 1521 rc = AudioMixBufPeekMutable(&pStreamEx->Host.MixBuf, cfReadable, &paFrames, &cfWritable); 1522 if ( RT_FAILURE(rc) 1523 || !cfWritable) 1524 break; 1525 1526 uint32_t cfCaptured; 1527 rc = pThis->pHostDrvAudio->pfnStreamCapture(pThis->pHostDrvAudio, pStreamEx->pBackend, 1528 paFrames, cfWritable, &cfCaptured); 1529 if (RT_FAILURE(rc)) 1530 { 1531 int rc2 = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE); 1532 AssertRC(rc2); 1533 break; 1534 } 1535 1536 Assert(cfCaptured <= cfWritable); 1537 if (cfCaptured > cfWritable) /* Paranoia. */ 1538 cfCaptured = cfWritable; 1539 1540 Assert(cfReadable >= cfCaptured); 1541 cfReadable -= cfCaptured; 1542 cfCapturedTotal += cfCaptured; 1543 } 1544 1545 if (pcfCaptured) 1546 *pcfCaptured = cfCapturedTotal; 1547 Log2Func(("[%s] %RU32 frames captured, rc=%Rrc\n", pStreamEx->Core.szName, cfCapturedTotal, rc)); 1548 return rc; 1549 } 1550 1551 /** 1552 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamCapture} 1553 */ 1554 static DECLCALLBACK(int) drvAudioStreamCapture(PPDMIAUDIOCONNECTOR pInterface, 1555 PPDMAUDIOSTREAM pStream, uint32_t *pcFramesCaptured) 2615 2616 /** 2617 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamIterate} 2618 */ 2619 static DECLCALLBACK(int) drvAudioStreamIterate(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream) 1556 2620 { 1557 2621 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector); … … 1559 2623 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream; 1560 2624 AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER); 1561 AssertPtrNull(pcFramesCaptured);1562 2625 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC); 1563 2626 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC); 1564 AssertMsg(pStreamEx->Core.enmDir == PDMAUDIODIR_IN, 1565 ("Stream '%s' is not an input stream and therefore cannot be captured (direction is 0x%x)\n", 1566 pStreamEx->Core.szName, pStreamEx->Core.enmDir)); 2627 1567 2628 int rc = RTCritSectEnter(&pThis->CritSect); 1568 2629 AssertRCReturn(rc, rc); 1569 2630 1570 #ifdef LOG_ENABLED 1571 char szStreamSts[DRVAUDIO_STATUS_STR_MAX]; 1572 #endif 1573 Log3Func(("[%s] fStatus=%s\n", pStreamEx->Core.szName, dbgAudioStreamStatusToStr(szStreamSts, pStreamEx->Core.fStatus))); 1574 1575 /* 1576 * ... 1577 */ 1578 uint32_t cfCaptured = 0; 1579 do 1580 { 1581 if (!pThis->pHostDrvAudio) 1582 { 1583 rc = VERR_PDM_NO_ATTACHED_DRIVER; 1584 break; 1585 } 1586 1587 if ( !pThis->In.fEnabled 1588 || !PDMAudioStrmStatusCanRead(pStreamEx->Core.fStatus)) 1589 { 1590 rc = VERR_AUDIO_STREAM_NOT_READY; 1591 break; 1592 } 1593 1594 /* 1595 * Do the actual capturing. 1596 */ 1597 if (RT_LIKELY(pStreamEx->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED)) 1598 rc = drvAudioStreamCaptureNonInterleaved(pThis, pStreamEx, &cfCaptured); 1599 else if (pStreamEx->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_RAW) 1600 rc = drvAudioStreamCaptureRaw(pThis, pStreamEx, &cfCaptured); 1601 else 1602 AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED); 1603 1604 if (RT_SUCCESS(rc)) 1605 { 1606 Log3Func(("[%s] %RU32 frames captured, rc=%Rrc\n", pStreamEx->Core.szName, cfCaptured, rc)); 1607 1608 STAM_COUNTER_ADD(&pThis->Stats.TotalFramesIn, cfCaptured); 1609 STAM_COUNTER_ADD(&pStreamEx->In.Stats.TotalFramesCaptured, cfCaptured); 1610 } 1611 else if (RT_UNLIKELY(RT_FAILURE(rc))) 1612 LogRel(("Audio: Capturing stream '%s' failed with %Rrc\n", pStreamEx->Core.szName, rc)); 1613 } while (0); 2631 rc = drvAudioStreamIterateInternal(pThis, pStreamEx); 1614 2632 1615 2633 RTCritSectLeave(&pThis->CritSect); 1616 1617 if (pcFramesCaptured)1618 *pcFramesCaptured = cfCaptured;1619 2634 1620 2635 if (RT_FAILURE(rc)) … … 1623 2638 } 1624 2639 1625 #ifdef VBOX_WITH_AUDIO_ENUM1626 /**1627 * Enumerates all host audio devices.1628 *1629 * This functionality might not be implemented by all backends and will return1630 * VERR_NOT_SUPPORTED if not being supported.1631 *1632 * @note Must not hold the driver's critical section!1633 *1634 * @returns VBox status code.1635 * @param pThis Driver instance to be called.1636 * @param fLog Whether to print the enumerated device to the release log or not.1637 * @param pDevEnum Where to store the device enumeration.1638 *1639 * @remarks This is currently ONLY used for release logging.1640 */1641 static int drvAudioDevicesEnumerateInternal(PDRVAUDIO pThis, bool fLog, PPDMAUDIOHOSTENUM pDevEnum)1642 {1643 AssertReturn(!RTCritSectIsOwner(&pThis->CritSect), VERR_WRONG_ORDER);1644 1645 int rc;1646 1647 /*1648 * If the backend supports it, do a device enumeration.1649 */1650 if (pThis->pHostDrvAudio->pfnGetDevices)1651 {1652 PDMAUDIOHOSTENUM DevEnum;1653 rc = pThis->pHostDrvAudio->pfnGetDevices(pThis->pHostDrvAudio, &DevEnum);1654 if (RT_SUCCESS(rc))1655 {1656 if (fLog)1657 LogRel(("Audio: Found %RU16 devices for driver '%s'\n", DevEnum.cDevices, pThis->szName));1658 1659 PPDMAUDIOHOSTDEV pDev;1660 RTListForEach(&DevEnum.LstDevices, pDev, PDMAUDIOHOSTDEV, ListEntry)1661 {1662 if (fLog)1663 {1664 char szFlags[PDMAUDIOHOSTDEV_MAX_FLAGS_STRING_LEN];1665 LogRel(("Audio: Device '%s':\n", pDev->szName));1666 LogRel(("Audio: Usage = %s\n", PDMAudioDirGetName(pDev->enmUsage)));1667 LogRel(("Audio: Flags = %s\n", PDMAudioHostDevFlagsToString(szFlags, pDev->fFlags)));1668 LogRel(("Audio: Input channels = %RU8\n", pDev->cMaxInputChannels));1669 LogRel(("Audio: Output channels = %RU8\n", pDev->cMaxOutputChannels));1670 }1671 }1672 1673 if (pDevEnum)1674 rc = PDMAudioHostEnumCopy(pDevEnum, &DevEnum, PDMAUDIODIR_INVALID /*all*/, true /*fOnlyCoreData*/);1675 1676 PDMAudioHostEnumDelete(&DevEnum);1677 }1678 else1679 {1680 if (fLog)1681 LogRel(("Audio: Device enumeration for driver '%s' failed with %Rrc\n", pThis->szName, rc));1682 /* Not fatal. */1683 }1684 }1685 else1686 {1687 rc = VERR_NOT_SUPPORTED;1688 1689 if (fLog)1690 LogRel2(("Audio: Host driver '%s' does not support audio device enumeration, skipping\n", pThis->szName));1691 }1692 1693 LogFunc(("Returning %Rrc\n", rc));1694 return rc;1695 }1696 #endif /* VBOX_WITH_AUDIO_ENUM */1697 1698 /**1699 * Initializes the host backend and queries its initial configuration.1700 *1701 * @returns VBox status code.1702 * @param pThis Driver instance to be called.1703 */1704 static int drvAudioHostInit(PDRVAUDIO pThis)1705 {1706 LogFlowFuncEnter();1707 1708 /*1709 * Check the function pointers, make sure the ones we define as1710 * mandatory are present.1711 */1712 PPDMIHOSTAUDIO pHostDrvAudio = pThis->pHostDrvAudio;1713 AssertPtrReturn(pHostDrvAudio, VERR_INVALID_POINTER);1714 AssertPtrReturn(pHostDrvAudio->pfnGetConfig, VERR_INVALID_POINTER);1715 AssertPtrNullReturn(pHostDrvAudio->pfnGetDevices, VERR_INVALID_POINTER);1716 AssertPtrNullReturn(pHostDrvAudio->pfnGetStatus, VERR_INVALID_POINTER);1717 AssertPtrNullReturn(pHostDrvAudio->pfnStreamConfigHint, VERR_INVALID_POINTER);1718 AssertPtrReturn(pHostDrvAudio->pfnStreamCreate, VERR_INVALID_POINTER);1719 AssertPtrReturn(pHostDrvAudio->pfnStreamDestroy, VERR_INVALID_POINTER);1720 AssertPtrReturn(pHostDrvAudio->pfnStreamControl, VERR_INVALID_POINTER);1721 AssertPtrReturn(pHostDrvAudio->pfnStreamGetReadable, VERR_INVALID_POINTER);1722 AssertPtrReturn(pHostDrvAudio->pfnStreamGetWritable, VERR_INVALID_POINTER);1723 AssertPtrNullReturn(pHostDrvAudio->pfnStreamGetPending, VERR_INVALID_POINTER);1724 AssertPtrReturn(pHostDrvAudio->pfnStreamGetStatus, VERR_INVALID_POINTER);1725 AssertPtrReturn(pHostDrvAudio->pfnStreamPlay, VERR_INVALID_POINTER);1726 AssertPtrReturn(pHostDrvAudio->pfnStreamCapture, VERR_INVALID_POINTER);1727 1728 /*1729 * Get the backend configuration.1730 */1731 int rc = pThis->pHostDrvAudio->pfnGetConfig(pThis->pHostDrvAudio, &pThis->BackendCfg);1732 if (RT_FAILURE(rc))1733 {1734 LogRel(("Audio: Getting configuration for driver '%s' failed with %Rrc\n", pThis->szName, rc));1735 return VERR_AUDIO_BACKEND_INIT_FAILED;1736 }1737 1738 pThis->In.cStreamsFree = pThis->BackendCfg.cMaxStreamsIn;1739 pThis->Out.cStreamsFree = pThis->BackendCfg.cMaxStreamsOut;1740 1741 LogFlowFunc(("cStreamsFreeIn=%RU8, cStreamsFreeOut=%RU8\n", pThis->In.cStreamsFree, pThis->Out.cStreamsFree));1742 1743 LogRel2(("Audio: Host driver '%s' supports %RU32 input streams and %RU32 output streams at once.\n",1744 pThis->szName, pThis->In.cStreamsFree, pThis->Out.cStreamsFree));1745 1746 #ifdef VBOX_WITH_AUDIO_ENUM1747 int rc2 = drvAudioDevicesEnumerateInternal(pThis, true /* fLog */, NULL /* pDevEnum */);1748 if (rc2 != VERR_NOT_SUPPORTED) /* Some backends don't implement device enumeration. */1749 AssertRC(rc2);1750 1751 RT_NOREF(rc2);1752 /* Ignore rc. */1753 #endif1754 1755 LogFlowFuncLeave();1756 return VINF_SUCCESS;1757 }1758 1759 /**1760 * Handles state changes for all audio streams.1761 *1762 * @param pDrvIns Pointer to driver instance.1763 * @param enmCmd Stream command to set for all streams.1764 */1765 static void drvAudioStateHandler(PPDMDRVINS pDrvIns, PDMAUDIOSTREAMCMD enmCmd)1766 {1767 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);1768 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);1769 LogFlowFunc(("enmCmd=%s\n", PDMAudioStrmCmdGetName(enmCmd)));1770 1771 int rc2 = RTCritSectEnter(&pThis->CritSect);1772 AssertRCReturnVoid(rc2);1773 1774 if (pThis->pHostDrvAudio)1775 {1776 PDRVAUDIOSTREAM pStreamEx;1777 RTListForEach(&pThis->lstStreams, pStreamEx, DRVAUDIOSTREAM, ListEntry)1778 {1779 drvAudioStreamControlInternal(pThis, pStreamEx, enmCmd);1780 }1781 }1782 1783 rc2 = RTCritSectLeave(&pThis->CritSect);1784 AssertRC(rc2);1785 }1786 1787 /**1788 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamRead}1789 */1790 static DECLCALLBACK(int) drvAudioStreamRead(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream,1791 void *pvBuf, uint32_t cbBuf, uint32_t *pcbRead)1792 {1793 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);1794 AssertPtr(pThis);1795 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream;1796 AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER);1797 AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);1798 AssertReturn(cbBuf, VERR_INVALID_PARAMETER);1799 AssertPtrNullReturn(pcbRead, VERR_INVALID_POINTER);1800 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);1801 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC);1802 AssertMsg(pStreamEx->Core.enmDir == PDMAUDIODIR_IN,1803 ("Stream '%s' is not an input stream and therefore cannot be read from (direction is 0x%x)\n",1804 pStreamEx->Core.szName, pStreamEx->Core.enmDir));1805 1806 int rc = RTCritSectEnter(&pThis->CritSect);1807 AssertRCReturn(rc, rc);1808 1809 /*1810 * ...1811 */1812 uint32_t cbReadTotal = 0;1813 1814 do1815 {1816 uint32_t cfReadTotal = 0;1817 1818 const uint32_t cfBuf = AUDIOMIXBUF_B2F(&pStreamEx->Guest.MixBuf, cbBuf);1819 1820 if (pThis->In.fEnabled) /* Input for this audio driver enabled? See #9822. */1821 {1822 if (!PDMAudioStrmStatusCanRead(pStream->fStatus))1823 {1824 rc = VERR_AUDIO_STREAM_NOT_READY;1825 break;1826 }1827 1828 /*1829 * Read from the parent buffer (that is, the guest buffer) which1830 * should have the audio data in the format the guest needs.1831 */1832 uint32_t cfToRead = RT_MIN(cfBuf, AudioMixBufLive(&pStreamEx->Guest.MixBuf));1833 while (cfToRead)1834 {1835 uint32_t cfRead;1836 rc = AudioMixBufAcquireReadBlock(&pStreamEx->Guest.MixBuf,1837 (uint8_t *)pvBuf + AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfReadTotal),1838 AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfToRead), &cfRead);1839 if (RT_FAILURE(rc))1840 break;1841 1842 #ifdef VBOX_WITH_STATISTICS1843 const uint32_t cbRead = AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfRead);1844 STAM_COUNTER_ADD(&pThis->Stats.TotalBytesRead, cbRead);1845 STAM_COUNTER_ADD(&pStreamEx->In.Stats.TotalFramesRead, cfRead);1846 STAM_COUNTER_INC(&pStreamEx->In.Stats.TotalTimesRead);1847 #endif1848 Assert(cfToRead >= cfRead);1849 cfToRead -= cfRead;1850 1851 cfReadTotal += cfRead;1852 1853 AudioMixBufReleaseReadBlock(&pStreamEx->Guest.MixBuf, cfRead);1854 }1855 1856 if (cfReadTotal)1857 {1858 if (pThis->In.Cfg.Dbg.fEnabled)1859 AudioHlpFileWrite(pStreamEx->In.Dbg.pFileStreamRead,1860 pvBuf, AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfReadTotal), 0 /* fFlags */);1861 1862 AudioMixBufFinish(&pStreamEx->Guest.MixBuf, cfReadTotal);1863 }1864 }1865 1866 /* If we were not able to read as much data as requested, fill up the returned1867 * data with silence.1868 *1869 * This is needed to keep the device emulation DMA transfers up and running at a constant rate. */1870 if (cfReadTotal < cfBuf)1871 {1872 Log3Func(("[%s] Filling in silence (%RU64ms / %RU64ms)\n", pStream->szName,1873 PDMAudioPropsFramesToMilli(&pStreamEx->Guest.Cfg.Props, cfBuf - cfReadTotal),1874 PDMAudioPropsFramesToMilli(&pStreamEx->Guest.Cfg.Props, cfBuf)));1875 1876 PDMAudioPropsClearBuffer(&pStreamEx->Guest.Cfg.Props,1877 (uint8_t *)pvBuf + AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfReadTotal),1878 AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfBuf - cfReadTotal),1879 cfBuf - cfReadTotal);1880 1881 cfReadTotal = cfBuf;1882 }1883 1884 cbReadTotal = AUDIOMIXBUF_F2B(&pStreamEx->Guest.MixBuf, cfReadTotal);1885 1886 pStreamEx->nsLastReadWritten = RTTimeNanoTS();1887 1888 Log3Func(("[%s] fEnabled=%RTbool, cbReadTotal=%RU32, rc=%Rrc\n", pStream->szName, pThis->In.fEnabled, cbReadTotal, rc));1889 1890 } while (0);1891 1892 RTCritSectLeave(&pThis->CritSect);1893 1894 if (RT_SUCCESS(rc) && pcbRead)1895 *pcbRead = cbReadTotal;1896 return rc;1897 }1898 1899 /**1900 * Adjusts the request stream configuration, applying our settings.1901 *1902 * This also does some basic validations.1903 *1904 * Used by both the stream creation and stream configuration hinting code.1905 *1906 * @returns VBox status code.1907 * @param pThis Pointer to the DrvAudio instance data.1908 * @param pCfgReq The request configuration that should be adjusted.1909 * @param pszName Stream name to use when logging warnings and errors.1910 */1911 static int drvAudioStreamAdjustConfig(PDRVAUDIO pThis, PPDMAUDIOSTREAMCFG pCfgReq, const char *pszName)1912 {1913 /* Get the right configuration for the stream to be created. */1914 PDRVAUDIOCFG pDrvCfg = pCfgReq->enmDir == PDMAUDIODIR_IN ? &pThis->In.Cfg : &pThis->Out.Cfg;1915 1916 /* Fill in the tweakable parameters into the requested host configuration.1917 * All parameters in principle can be changed and returned by the backend via the acquired configuration. */1918 1919 /*1920 * PCM1921 */1922 if (PDMAudioPropsSampleSize(&pDrvCfg->Props) != 0) /* Anything set via custom extra-data? */1923 {1924 PDMAudioPropsSetSampleSize(&pCfgReq->Props, PDMAudioPropsSampleSize(&pDrvCfg->Props));1925 LogRel2(("Audio: Using custom sample size of %RU8 bytes for stream '%s'\n",1926 PDMAudioPropsSampleSize(&pCfgReq->Props), pszName));1927 }1928 1929 if (pDrvCfg->Props.uHz) /* Anything set via custom extra-data? */1930 {1931 pCfgReq->Props.uHz = pDrvCfg->Props.uHz;1932 LogRel2(("Audio: Using custom Hz rate %RU32 for stream '%s'\n", pCfgReq->Props.uHz, pszName));1933 }1934 1935 if (pDrvCfg->uSigned != UINT8_MAX) /* Anything set via custom extra-data? */1936 {1937 pCfgReq->Props.fSigned = RT_BOOL(pDrvCfg->uSigned);1938 LogRel2(("Audio: Using custom %s sample format for stream '%s'\n",1939 pCfgReq->Props.fSigned ? "signed" : "unsigned", pszName));1940 }1941 1942 if (pDrvCfg->uSwapEndian != UINT8_MAX) /* Anything set via custom extra-data? */1943 {1944 pCfgReq->Props.fSwapEndian = RT_BOOL(pDrvCfg->uSwapEndian);1945 LogRel2(("Audio: Using custom %s endianess for samples of stream '%s'\n",1946 pCfgReq->Props.fSwapEndian ? "swapped" : "original", pszName));1947 }1948 1949 if (PDMAudioPropsChannels(&pDrvCfg->Props) != 0) /* Anything set via custom extra-data? */1950 {1951 PDMAudioPropsSetChannels(&pCfgReq->Props, PDMAudioPropsChannels(&pDrvCfg->Props));1952 LogRel2(("Audio: Using custom %RU8 channel(s) for stream '%s'\n", PDMAudioPropsChannels(&pDrvCfg->Props), pszName));1953 }1954 1955 /* Validate PCM properties. */1956 if (!AudioHlpPcmPropsAreValid(&pCfgReq->Props))1957 {1958 LogRel(("Audio: Invalid custom PCM properties set for stream '%s', cannot create stream\n", pszName));1959 return VERR_INVALID_PARAMETER;1960 }1961 1962 /*1963 * Period size1964 */1965 const char *pszWhat = "device-specific";1966 if (pDrvCfg->uPeriodSizeMs)1967 {1968 pCfgReq->Backend.cFramesPeriod = PDMAudioPropsMilliToFrames(&pCfgReq->Props, pDrvCfg->uPeriodSizeMs);1969 pszWhat = "custom";1970 }1971 1972 if (!pCfgReq->Backend.cFramesPeriod) /* Set default period size if nothing explicitly is set. */1973 {1974 pCfgReq->Backend.cFramesPeriod = PDMAudioPropsMilliToFrames(&pCfgReq->Props, 150 /*ms*/);1975 pszWhat = "default";1976 }1977 1978 LogRel2(("Audio: Using %s period size %RU64 ms / %RU32 frames for stream '%s'\n",1979 pszWhat, PDMAudioPropsFramesToMilli(&pCfgReq->Props, pCfgReq->Backend.cFramesPeriod),1980 pCfgReq->Backend.cFramesPeriod, pszName));1981 1982 /*1983 * Buffer size1984 */1985 pszWhat = "device-specific";1986 if (pDrvCfg->uBufferSizeMs)1987 {1988 pCfgReq->Backend.cFramesBufferSize = PDMAudioPropsMilliToFrames(&pCfgReq->Props, pDrvCfg->uBufferSizeMs);1989 pszWhat = "custom";1990 }1991 1992 if (!pCfgReq->Backend.cFramesBufferSize) /* Set default buffer size if nothing explicitly is set. */1993 {1994 pCfgReq->Backend.cFramesBufferSize = PDMAudioPropsMilliToFrames(&pCfgReq->Props, 300 /*ms*/);1995 pszWhat = "default";1996 }1997 1998 LogRel2(("Audio: Using %s buffer size %RU64 ms / %RU32 frames for stream '%s'\n",1999 pszWhat, PDMAudioPropsFramesToMilli(&pCfgReq->Props, pCfgReq->Backend.cFramesBufferSize),2000 pCfgReq->Backend.cFramesBufferSize, pszName));2001 2002 /*2003 * Pre-buffering size2004 */2005 pszWhat = "device-specific";2006 if (pDrvCfg->uPreBufSizeMs != UINT32_MAX) /* Anything set via global / per-VM extra-data? */2007 {2008 pCfgReq->Backend.cFramesPreBuffering = PDMAudioPropsMilliToFrames(&pCfgReq->Props, pDrvCfg->uPreBufSizeMs);2009 pszWhat = "custom";2010 }2011 else /* No, then either use the default or device-specific settings (if any). */2012 {2013 if (pCfgReq->Backend.cFramesPreBuffering == UINT32_MAX) /* Set default pre-buffering size if nothing explicitly is set. */2014 {2015 /* Pre-buffer 66% of the buffer. */2016 pCfgReq->Backend.cFramesPreBuffering = pCfgReq->Backend.cFramesBufferSize * 2 / 3;2017 pszWhat = "default";2018 }2019 }2020 2021 LogRel2(("Audio: Using %s pre-buffering size %RU64 ms / %RU32 frames for stream '%s'\n",2022 pszWhat, PDMAudioPropsFramesToMilli(&pCfgReq->Props, pCfgReq->Backend.cFramesPreBuffering),2023 pCfgReq->Backend.cFramesPreBuffering, pszName));2024 2025 /*2026 * Validate input.2027 */2028 if (pCfgReq->Backend.cFramesBufferSize < pCfgReq->Backend.cFramesPeriod)2029 {2030 LogRel(("Audio: Error for stream '%s': Buffering size (%RU64ms) must not be smaller than the period size (%RU64ms)\n",2031 pszName, PDMAudioPropsFramesToMilli(&pCfgReq->Props, pCfgReq->Backend.cFramesBufferSize),2032 PDMAudioPropsFramesToMilli(&pCfgReq->Props, pCfgReq->Backend.cFramesPeriod)));2033 return VERR_INVALID_PARAMETER;2034 }2035 2036 if ( pCfgReq->Backend.cFramesPreBuffering != UINT32_MAX /* Custom pre-buffering set? */2037 && pCfgReq->Backend.cFramesPreBuffering)2038 {2039 if (pCfgReq->Backend.cFramesBufferSize < pCfgReq->Backend.cFramesPreBuffering)2040 {2041 LogRel(("Audio: Error for stream '%s': Buffering size (%RU64ms) must not be smaller than the pre-buffering size (%RU64ms)\n",2042 pszName, PDMAudioPropsFramesToMilli(&pCfgReq->Props, pCfgReq->Backend.cFramesPreBuffering),2043 PDMAudioPropsFramesToMilli(&pCfgReq->Props, pCfgReq->Backend.cFramesBufferSize)));2044 return VERR_INVALID_PARAMETER;2045 }2046 }2047 2048 return VINF_SUCCESS;2049 }2050 2051 /**2052 * Worker for drvAudioStreamInitInternal and drvAudioStreamReInitInternal that2053 * creates the backend (host driver) side of an audio stream.2054 *2055 * @returns VBox status code.2056 * @param pThis Pointer to driver instance.2057 * @param pStreamEx Audio stream to create the backend side for.2058 * @param pCfgReq Requested audio stream configuration to use for2059 * stream creation.2060 * @param pCfgAcq Acquired audio stream configuration returned by2061 * the backend.2062 *2063 * @note Configuration precedence for requested audio stream configuration (first has highest priority, if set):2064 * - per global extra-data2065 * - per-VM extra-data2066 * - requested configuration (by pCfgReq)2067 * - default value2068 */2069 static int drvAudioStreamCreateInternalBackend(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx,2070 PPDMAUDIOSTREAMCFG pCfgReq, PPDMAUDIOSTREAMCFG pCfgAcq)2071 {2072 AssertMsg((pStreamEx->Core.fStatus & PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED) == 0,2073 ("Stream '%s' already initialized in backend\n", pStreamEx->Core.szName));2074 2075 /*2076 * Adjust the requested stream config, applying our settings.2077 */2078 int rc = drvAudioStreamAdjustConfig(pThis, pCfgReq, pStreamEx->Core.szName);2079 if (RT_FAILURE(rc))2080 return rc;2081 2082 /*2083 * Make the acquired host configuration the requested host configuration initially,2084 * in case the backend does not report back an acquired configuration.2085 */2086 /** @todo r=bird: This is conveniently not documented in the interface... */2087 rc = PDMAudioStrmCfgCopy(pCfgAcq, pCfgReq);2088 if (RT_FAILURE(rc))2089 {2090 LogRel(("Audio: Creating stream '%s' with an invalid backend configuration not possible, skipping\n",2091 pStreamEx->Core.szName));2092 return rc;2093 }2094 2095 /*2096 * Call the host driver to create the stream.2097 */2098 AssertPtr(pThis->pHostDrvAudio);2099 if (pThis->pHostDrvAudio)2100 rc = pThis->pHostDrvAudio->pfnStreamCreate(pThis->pHostDrvAudio, pStreamEx->pBackend, pCfgReq, pCfgAcq);2101 else2102 rc = VERR_PDM_NO_ATTACHED_DRIVER;2103 if (RT_FAILURE(rc))2104 {2105 if (rc == VERR_NOT_SUPPORTED)2106 LogRel2(("Audio: Creating stream '%s' in backend not supported\n", pStreamEx->Core.szName));2107 else if (rc == VERR_AUDIO_STREAM_COULD_NOT_CREATE)2108 LogRel2(("Audio: Stream '%s' could not be created in backend because of missing hardware / drivers\n", pStreamEx->Core.szName));2109 else2110 LogRel(("Audio: Creating stream '%s' in backend failed with %Rrc\n", pStreamEx->Core.szName, rc));2111 return rc;2112 }2113 2114 /* Validate acquired configuration. */2115 char szTmp[PDMAUDIOPROPSTOSTRING_MAX];2116 AssertLogRelMsgReturn(AudioHlpStreamCfgIsValid(pCfgAcq),2117 ("Audio: Creating stream '%s' returned an invalid backend configuration (%s), skipping\n",2118 pStreamEx->Core.szName, PDMAudioPropsToString(&pCfgAcq->Props, szTmp, sizeof(szTmp))),2119 VERR_INVALID_PARAMETER);2120 2121 /* Let the user know that the backend changed one of the values requested above. */2122 if (pCfgAcq->Backend.cFramesBufferSize != pCfgReq->Backend.cFramesBufferSize)2123 LogRel2(("Audio: Buffer size overwritten by backend for stream '%s' (now %RU64ms, %RU32 frames)\n",2124 pStreamEx->Core.szName, PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesBufferSize), pCfgAcq->Backend.cFramesBufferSize));2125 2126 if (pCfgAcq->Backend.cFramesPeriod != pCfgReq->Backend.cFramesPeriod)2127 LogRel2(("Audio: Period size overwritten by backend for stream '%s' (now %RU64ms, %RU32 frames)\n",2128 pStreamEx->Core.szName, PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesPeriod), pCfgAcq->Backend.cFramesPeriod));2129 2130 /* Was pre-buffering requested, but the acquired configuration from the backend told us something else? */2131 if (pCfgReq->Backend.cFramesPreBuffering)2132 {2133 if (pCfgAcq->Backend.cFramesPreBuffering != pCfgReq->Backend.cFramesPreBuffering)2134 LogRel2(("Audio: Pre-buffering size overwritten by backend for stream '%s' (now %RU64ms, %RU32 frames)\n",2135 pStreamEx->Core.szName, PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesPreBuffering), pCfgAcq->Backend.cFramesPreBuffering));2136 2137 if (pCfgAcq->Backend.cFramesPreBuffering > pCfgAcq->Backend.cFramesBufferSize)2138 {2139 pCfgAcq->Backend.cFramesPreBuffering = pCfgAcq->Backend.cFramesBufferSize;2140 LogRel2(("Audio: Pre-buffering size bigger than buffer size for stream '%s', adjusting to %RU64ms (%RU32 frames)\n",2141 pStreamEx->Core.szName, PDMAudioPropsFramesToMilli(&pCfgAcq->Props, pCfgAcq->Backend.cFramesPreBuffering), pCfgAcq->Backend.cFramesPreBuffering));2142 }2143 }2144 else if (pCfgReq->Backend.cFramesPreBuffering == 0) /* Was the pre-buffering requested as being disabeld? Tell the users. */2145 {2146 LogRel2(("Audio: Pre-buffering is disabled for stream '%s'\n", pStreamEx->Core.szName));2147 pCfgAcq->Backend.cFramesPreBuffering = 0;2148 }2149 2150 /* Sanity for detecting buggy backends. */2151 AssertMsgReturn(pCfgAcq->Backend.cFramesPeriod < pCfgAcq->Backend.cFramesBufferSize,2152 ("Acquired period size must be smaller than buffer size\n"),2153 VERR_INVALID_PARAMETER);2154 AssertMsgReturn(pCfgAcq->Backend.cFramesPreBuffering <= pCfgAcq->Backend.cFramesBufferSize,2155 ("Acquired pre-buffering size must be smaller or as big as the buffer size\n"),2156 VERR_INVALID_PARAMETER);2157 2158 pStreamEx->Core.fStatus |= PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED;2159 2160 return VINF_SUCCESS;2161 }2162 2163 2164 /**2165 * Worker for drvAudioStreamCreate that initializes the audio stream.2166 *2167 * @returns VBox status code.2168 * @param pThis Pointer to driver instance.2169 * @param pStreamEx Stream to initialize.2170 * @param fFlags PDMAUDIOSTREAM_CREATE_F_XXX.2171 * @param pCfgHost Stream configuration to use for the host side (backend).2172 * @param pCfgGuest Stream configuration to use for the guest side.2173 */2174 static int drvAudioStreamInitInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, uint32_t fFlags,2175 PPDMAUDIOSTREAMCFG pCfgHost, PPDMAUDIOSTREAMCFG pCfgGuest)2176 {2177 /*2178 * Init host stream.2179 */2180 pStreamEx->Core.uMagic = PDMAUDIOSTREAM_MAGIC;2181 2182 /* Set the host's default audio data layout. */2183 /** @todo r=bird: Why, oh why? OTOH, the layout stuff is non-sense anyway. */2184 pCfgHost->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED;2185 2186 #ifdef LOG_ENABLED2187 LogFunc(("[%s] Requested host format:\n", pStreamEx->Core.szName));2188 PDMAudioStrmCfgLog(pCfgHost);2189 #endif2190 2191 LogRel2(("Audio: Creating stream '%s'\n", pStreamEx->Core.szName));2192 LogRel2(("Audio: Guest %s format for '%s': %RU32Hz, %u%s, %RU8 channel%s\n",2193 pCfgGuest->enmDir == PDMAUDIODIR_IN ? "recording" : "playback", pStreamEx->Core.szName,2194 pCfgGuest->Props.uHz, PDMAudioPropsSampleBits(&pCfgGuest->Props), pCfgGuest->Props.fSigned ? "S" : "U",2195 PDMAudioPropsChannels(&pCfgGuest->Props), PDMAudioPropsChannels(&pCfgGuest->Props) == 1 ? "" : "s"));2196 LogRel2(("Audio: Requested host %s format for '%s': %RU32Hz, %u%s, %RU8 channel%s\n",2197 pCfgHost->enmDir == PDMAUDIODIR_IN ? "recording" : "playback", pStreamEx->Core.szName,2198 pCfgHost->Props.uHz, PDMAudioPropsSampleBits(&pCfgHost->Props), pCfgHost->Props.fSigned ? "S" : "U",2199 PDMAudioPropsChannels(&pCfgHost->Props), PDMAudioPropsChannels(&pCfgHost->Props) == 1 ? "" : "s"));2200 2201 PDMAUDIOSTREAMCFG CfgHostAcq;2202 int rc = drvAudioStreamCreateInternalBackend(pThis, pStreamEx, pCfgHost, &CfgHostAcq);2203 if (RT_FAILURE(rc))2204 return rc;2205 2206 LogFunc(("[%s] Acquired host format:\n", pStreamEx->Core.szName));2207 PDMAudioStrmCfgLog(&CfgHostAcq);2208 LogRel2(("Audio: Acquired host %s format for '%s': %RU32Hz, %u%s, %RU8 channel%s\n",2209 CfgHostAcq.enmDir == PDMAUDIODIR_IN ? "recording" : "playback", pStreamEx->Core.szName,2210 CfgHostAcq.Props.uHz, PDMAudioPropsSampleBits(&CfgHostAcq.Props), CfgHostAcq.Props.fSigned ? "S" : "U",2211 PDMAudioPropsChannels(&CfgHostAcq.Props), PDMAudioPropsChannels(&CfgHostAcq.Props) == 1 ? "" : "s"));2212 Assert(PDMAudioPropsAreValid(&CfgHostAcq.Props));2213 2214 /* Set the stream properties (currently guest side, when DevSB16 is2215 converted to mixer and PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF becomes2216 default, this will just be the stream properties). */2217 if (fFlags & PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF)2218 pStreamEx->Core.Props = CfgHostAcq.Props;2219 else2220 pStreamEx->Core.Props = pCfgGuest->Props;2221 2222 /* Let the user know if the backend changed some of the tweakable values. */2223 if (CfgHostAcq.Backend.cFramesBufferSize != pCfgHost->Backend.cFramesBufferSize)2224 LogRel2(("Audio: Backend changed buffer size from %RU64ms (%RU32 frames) to %RU64ms (%RU32 frames)\n",2225 PDMAudioPropsFramesToMilli(&pCfgHost->Props, pCfgHost->Backend.cFramesBufferSize), pCfgHost->Backend.cFramesBufferSize,2226 PDMAudioPropsFramesToMilli(&CfgHostAcq.Props, CfgHostAcq.Backend.cFramesBufferSize), CfgHostAcq.Backend.cFramesBufferSize));2227 2228 if (CfgHostAcq.Backend.cFramesPeriod != pCfgHost->Backend.cFramesPeriod)2229 LogRel2(("Audio: Backend changed period size from %RU64ms (%RU32 frames) to %RU64ms (%RU32 frames)\n",2230 PDMAudioPropsFramesToMilli(&pCfgHost->Props, pCfgHost->Backend.cFramesPeriod), pCfgHost->Backend.cFramesPeriod,2231 PDMAudioPropsFramesToMilli(&CfgHostAcq.Props, CfgHostAcq.Backend.cFramesPeriod), CfgHostAcq.Backend.cFramesPeriod));2232 2233 if (CfgHostAcq.Backend.cFramesPreBuffering != pCfgHost->Backend.cFramesPreBuffering)2234 LogRel2(("Audio: Backend changed pre-buffering size from %RU64ms (%RU32 frames) to %RU64ms (%RU32 frames)\n",2235 PDMAudioPropsFramesToMilli(&pCfgHost->Props, pCfgHost->Backend.cFramesPreBuffering), pCfgHost->Backend.cFramesPreBuffering,2236 PDMAudioPropsFramesToMilli(&CfgHostAcq.Props, CfgHostAcq.Backend.cFramesPreBuffering), CfgHostAcq.Backend.cFramesPreBuffering));2237 2238 /*2239 * Check if the backend did return sane values and correct if necessary.2240 * Should never happen with our own backends, but you never know ...2241 */2242 uint32_t const cFramesPreBufferingMax = CfgHostAcq.Backend.cFramesBufferSize - RT_MIN(16, CfgHostAcq.Backend.cFramesBufferSize);2243 if (CfgHostAcq.Backend.cFramesPreBuffering > cFramesPreBufferingMax)2244 {2245 LogRel2(("Audio: Warning: Pre-buffering size of %RU32 frames for stream '%s' is too close to or larger than the %RU32 frames buffer size, reducing it to %RU32 frames!\n",2246 CfgHostAcq.Backend.cFramesPreBuffering, pStreamEx->Core.szName, CfgHostAcq.Backend.cFramesBufferSize, cFramesPreBufferingMax));2247 AssertFailed();2248 CfgHostAcq.Backend.cFramesPreBuffering = cFramesPreBufferingMax;2249 }2250 2251 if (CfgHostAcq.Backend.cFramesPeriod > CfgHostAcq.Backend.cFramesBufferSize)2252 {2253 LogRel2(("Audio: Warning: Period size of %RU32 frames for stream '%s' is larger than the %RU32 frames buffer size, reducing it to %RU32 frames!\n",2254 CfgHostAcq.Backend.cFramesPeriod, pStreamEx->Core.szName, CfgHostAcq.Backend.cFramesBufferSize, CfgHostAcq.Backend.cFramesBufferSize / 2));2255 AssertFailed();2256 CfgHostAcq.Backend.cFramesPeriod = CfgHostAcq.Backend.cFramesBufferSize / 2;2257 }2258 2259 LogRel2(("Audio: Buffer size of stream '%s' is %RU64 ms / %RU32 frames\n", pStreamEx->Core.szName,2260 PDMAudioPropsFramesToMilli(&CfgHostAcq.Props, CfgHostAcq.Backend.cFramesBufferSize), CfgHostAcq.Backend.cFramesBufferSize));2261 LogRel2(("Audio: Pre-buffering size of stream '%s' is %RU64 ms / %RU32 frames\n", pStreamEx->Core.szName,2262 PDMAudioPropsFramesToMilli(&CfgHostAcq.Props, CfgHostAcq.Backend.cFramesPreBuffering), CfgHostAcq.Backend.cFramesPreBuffering));2263 2264 /* Make sure the configured buffer size by the backend at least can hold the configured latency. */2265 const uint32_t msPeriod = PDMAudioPropsFramesToMilli(&CfgHostAcq.Props, CfgHostAcq.Backend.cFramesPeriod);2266 LogRel2(("Audio: Period size of stream '%s' is %RU64 ms / %RU32 frames\n",2267 pStreamEx->Core.szName, msPeriod, CfgHostAcq.Backend.cFramesPeriod));2268 2269 if ( pCfgGuest->Device.cMsSchedulingHint /* Any scheduling hint set? */2270 && pCfgGuest->Device.cMsSchedulingHint > msPeriod) /* This might lead to buffer underflows. */2271 LogRel(("Audio: Warning: Scheduling hint of stream '%s' is bigger (%RU64ms) than used period size (%RU64ms)\n",2272 pStreamEx->Core.szName, pCfgGuest->Device.cMsSchedulingHint, msPeriod));2273 2274 /*2275 * Make a copy of the acquired host stream configuration and the guest side one.2276 */2277 rc = PDMAudioStrmCfgCopy(&pStreamEx->Host.Cfg, &CfgHostAcq);2278 AssertRC(rc);2279 2280 /* Set the guests's default audio data layout. */2281 pCfgGuest->enmLayout = PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED; /** @todo r=bird: WTF DO WE DO THIS? It's input and probably should've been const... */2282 rc = PDMAudioStrmCfgCopy(&pStreamEx->Guest.Cfg, pCfgGuest);2283 AssertRC(rc);2284 2285 /*2286 * Configure host buffers.2287 */2288 2289 /* Destroy any former mixing buffer. */2290 AudioMixBufDestroy(&pStreamEx->Host.MixBuf);2291 2292 if (!(fFlags & PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF))2293 {2294 Assert(pCfgHost->enmDir == PDMAUDIODIR_IN);2295 rc = AudioMixBufInit(&pStreamEx->Host.MixBuf, pStreamEx->Core.szName, &CfgHostAcq.Props, CfgHostAcq.Backend.cFramesBufferSize);2296 AssertRCReturn(rc, rc);2297 }2298 /* Allocate space for pre-buffering of output stream w/o mixing buffers. */2299 else if (pCfgHost->enmDir == PDMAUDIODIR_OUT)2300 {2301 Assert(pStreamEx->Out.cbPreBufAlloc == 0);2302 Assert(pStreamEx->Out.cbPreBufThreshold == 0);2303 Assert(pStreamEx->Out.cbPreBuffered == 0);2304 if (CfgHostAcq.Backend.cFramesPreBuffering != 0)2305 {2306 pStreamEx->Out.cbPreBufThreshold = PDMAudioPropsFramesToBytes(&CfgHostAcq.Props, CfgHostAcq.Backend.cFramesPreBuffering);2307 pStreamEx->Out.cbPreBufAlloc = PDMAudioPropsFramesToBytes(&CfgHostAcq.Props, CfgHostAcq.Backend.cFramesBufferSize - 2);2308 pStreamEx->Out.cbPreBufAlloc = RT_MIN(RT_ALIGN_32(pStreamEx->Out.cbPreBufThreshold + _8K, _4K),2309 pStreamEx->Out.cbPreBufAlloc);2310 pStreamEx->Out.pbPreBuf = (uint8_t *)RTMemAllocZ(pStreamEx->Out.cbPreBufAlloc);2311 AssertReturn(pStreamEx->Out.pbPreBuf, VERR_NO_MEMORY);2312 }2313 }2314 2315 /*2316 * Init guest stream.2317 */2318 if (pCfgGuest->Device.cMsSchedulingHint)2319 LogRel2(("Audio: Stream '%s' got a scheduling hint of %RU32ms (%RU32 bytes)\n",2320 pStreamEx->Core.szName, pCfgGuest->Device.cMsSchedulingHint,2321 PDMAudioPropsMilliToBytes(&pCfgGuest->Props, pCfgGuest->Device.cMsSchedulingHint)));2322 2323 /* Destroy any former mixing buffer. */2324 AudioMixBufDestroy(&pStreamEx->Guest.MixBuf);2325 2326 if (!(fFlags & PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF))2327 {2328 Assert(pCfgHost->enmDir == PDMAUDIODIR_IN);2329 rc = AudioMixBufInit(&pStreamEx->Guest.MixBuf, pStreamEx->Core.szName, &pCfgGuest->Props, CfgHostAcq.Backend.cFramesBufferSize);2330 AssertRCReturn(rc, rc);2331 }2332 2333 if (RT_FAILURE(rc))2334 LogRel(("Audio: Creating stream '%s' failed with %Rrc\n", pStreamEx->Core.szName, rc));2335 2336 if (!(fFlags & PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF))2337 {2338 Assert(pCfgHost->enmDir == PDMAUDIODIR_IN);2339 /* Host (Parent) -> Guest (Child). */2340 rc = AudioMixBufLinkTo(&pStreamEx->Host.MixBuf, &pStreamEx->Guest.MixBuf);2341 AssertRC(rc);2342 }2343 2344 /*2345 * Register statistics.2346 */2347 PPDMDRVINS const pDrvIns = pThis->pDrvIns;2348 /** @todo expose config and more. */2349 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Host.Cfg.Backend.cFramesBufferSize, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,2350 "Host side: The size of the backend buffer (in frames)", "%s/0-HostBackendBufSize", pStreamEx->Core.szName);2351 if (!(fFlags & PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF))2352 {2353 Assert(pCfgHost->enmDir == PDMAUDIODIR_IN);2354 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Host.MixBuf.cFrames, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,2355 "Host side: The size of the mixer buffer (in frames)", "%s/1-HostMixBufSize", pStreamEx->Core.szName);2356 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Guest.MixBuf.cFrames, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,2357 "Guest side: The size of the mixer buffer (in frames)", "%s/2-GuestMixBufSize", pStreamEx->Core.szName);2358 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Host.MixBuf.cMixed, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,2359 "Host side: Number of frames in the mixer buffer", "%s/1-HostMixBufUsed", pStreamEx->Core.szName);2360 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Guest.MixBuf.cUsed, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,2361 "Guest side: Number of frames in the mixer buffer", "%s/2-GuestMixBufUsed", pStreamEx->Core.szName);2362 }2363 if (pCfgGuest->enmDir == PDMAUDIODIR_IN)2364 {2365 /** @todo later? */2366 }2367 else2368 {2369 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Out.Stats.cbBackendWritableBefore, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,2370 "Host side: Free space in backend buffer before play", "%s/0-HostBackendBufFreeBefore", pStreamEx->Core.szName);2371 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Out.Stats.cbBackendWritableAfter, STAMTYPE_U32, STAMVISIBILITY_USED, STAMUNIT_NONE,2372 "Host side: Free space in backend buffer after play", "%s/0-HostBackendBufFreeAfter", pStreamEx->Core.szName);2373 }2374 2375 #ifdef VBOX_WITH_STATISTICS2376 if (pCfgGuest->enmDir == PDMAUDIODIR_IN)2377 {2378 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->In.Stats.TotalFramesCaptured, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_NONE,2379 "Total frames played.", "%s/TotalFramesCaptured", pStreamEx->Core.szName);2380 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->In.Stats.TotalTimesCaptured, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_NONE,2381 "Total number of playbacks.", "%s/TotalTimesCaptured", pStreamEx->Core.szName);2382 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->In.Stats.TotalFramesRead, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_NONE,2383 "Total frames read.", "%s/TotalFramesRead", pStreamEx->Core.szName);2384 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->In.Stats.TotalTimesRead, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_NONE,2385 "Total number of reads.", "%s/TotalTimesRead", pStreamEx->Core.szName);2386 }2387 else2388 {2389 Assert(pCfgGuest->enmDir == PDMAUDIODIR_OUT);2390 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Out.Stats.TotalFramesPlayed, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_NONE,2391 "Total frames played.", "%s/TotalFramesPlayed", pStreamEx->Core.szName);2392 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Out.Stats.TotalTimesPlayed, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_NONE,2393 "Total number of playbacks.", "%s/TotalTimesPlayed", pStreamEx->Core.szName);2394 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Out.Stats.TotalFramesWritten, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_NONE,2395 "Total frames written.", "%s/TotalFramesWritten", pStreamEx->Core.szName);2396 PDMDrvHlpSTAMRegisterF(pDrvIns, &pStreamEx->Out.Stats.TotalTimesWritten, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_NONE,2397 "Total number of writes.", "%s/TotalTimesWritten", pStreamEx->Core.szName);2398 }2399 #endif /* VBOX_WITH_STATISTICS */2400 2401 LogFlowFunc(("[%s] Returning %Rrc\n", pStreamEx->Core.szName, rc));2402 return rc;2403 }2404 2405 /**2406 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamCreate}2407 */2408 static DECLCALLBACK(int) drvAudioStreamCreate(PPDMIAUDIOCONNECTOR pInterface, uint32_t fFlags, PPDMAUDIOSTREAMCFG pCfgHost,2409 PPDMAUDIOSTREAMCFG pCfgGuest, PPDMAUDIOSTREAM *ppStream)2410 {2411 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);2412 AssertPtr(pThis);2413 2414 /*2415 * Assert sanity.2416 */2417 AssertReturn(!(fFlags & ~PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF), VERR_INVALID_FLAGS);2418 AssertPtrReturn(pCfgHost, VERR_INVALID_POINTER);2419 AssertPtrReturn(pCfgGuest, VERR_INVALID_POINTER);2420 AssertPtrReturn(ppStream, VERR_INVALID_POINTER);2421 LogFlowFunc(("Host=%s, Guest=%s\n", pCfgHost->szName, pCfgGuest->szName));2422 #ifdef LOG_ENABLED2423 PDMAudioStrmCfgLog(pCfgHost);2424 PDMAudioStrmCfgLog(pCfgGuest);2425 #endif2426 AssertReturn(AudioHlpStreamCfgIsValid(pCfgHost), VERR_INVALID_PARAMETER);2427 AssertReturn(AudioHlpStreamCfgIsValid(pCfgGuest), VERR_INVALID_PARAMETER);2428 AssertReturn(pCfgHost->enmDir == pCfgGuest->enmDir, VERR_MISMATCH);2429 AssertReturn(pCfgHost->enmDir == PDMAUDIODIR_IN || pCfgHost->enmDir == PDMAUDIODIR_OUT, VERR_NOT_SUPPORTED);2430 /* Require PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF for output streams: */2431 AssertReturn((fFlags & PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF) || pCfgHost->enmDir == PDMAUDIODIR_IN, VERR_INVALID_FLAGS);2432 2433 /*2434 * Lock the whole driver instance.2435 */2436 int rc = RTCritSectEnter(&pThis->CritSect);2437 AssertRCReturn(rc, rc);2438 2439 /*2440 * Check that we have free streams in the backend and get the2441 * size of the backend specific stream data.2442 */2443 uint32_t *pcFreeStreams;2444 if (pCfgHost->enmDir == PDMAUDIODIR_IN)2445 {2446 if (!pThis->In.cStreamsFree)2447 {2448 LogFlowFunc(("Maximum number of host input streams reached\n"));2449 rc = VERR_AUDIO_NO_FREE_INPUT_STREAMS;2450 }2451 pcFreeStreams = &pThis->In.cStreamsFree;2452 }2453 else /* Out */2454 {2455 if (!pThis->Out.cStreamsFree)2456 {2457 LogFlowFunc(("Maximum number of host output streams reached\n"));2458 rc = VERR_AUDIO_NO_FREE_OUTPUT_STREAMS;2459 }2460 pcFreeStreams = &pThis->Out.cStreamsFree;2461 }2462 size_t const cbHstStrm = pThis->BackendCfg.cbStream;2463 AssertStmt(cbHstStrm < _16M, rc = VERR_OUT_OF_RANGE);2464 if (RT_SUCCESS(rc))2465 {2466 /*2467 * Allocate and initialize common state.2468 */2469 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)RTMemAllocZ(sizeof(DRVAUDIOSTREAM) + RT_ALIGN_Z(cbHstStrm, 64));2470 if (pStreamEx)2471 {2472 /* Retrieve host driver name for easier identification. */2473 AssertPtr(pThis->pHostDrvAudio);2474 RTStrPrintf(pStreamEx->Core.szName, RT_ELEMENTS(pStreamEx->Core.szName), "[%s] %s",2475 pThis->BackendCfg.szName, pCfgHost->szName[0] != '\0' ? pCfgHost->szName : "<Untitled>");2476 2477 pStreamEx->Core.enmDir = pCfgHost->enmDir;2478 pStreamEx->Core.cbBackend = (uint32_t)cbHstStrm;2479 if (cbHstStrm)2480 pStreamEx->pBackend = (PPDMAUDIOBACKENDSTREAM)(pStreamEx + 1);2481 pStreamEx->fNoMixBufs = RT_BOOL(fFlags & PDMAUDIOSTREAM_CREATE_F_NO_MIXBUF);2482 pStreamEx->uMagic = DRVAUDIOSTREAM_MAGIC;2483 2484 /*2485 * Try to init the rest.2486 */2487 rc = drvAudioStreamInitInternal(pThis, pStreamEx, fFlags, pCfgHost, pCfgGuest);2488 if (RT_SUCCESS(rc))2489 {2490 /* Set initial reference counts. */2491 pStreamEx->Core.cRefs = 1;2492 2493 /* Decrement the free stream counter. */2494 Assert(*pcFreeStreams > 0);2495 *pcFreeStreams -= 1;2496 2497 /*2498 * We're good.2499 */2500 RTListAppend(&pThis->lstStreams, &pStreamEx->ListEntry);2501 STAM_COUNTER_INC(&pThis->Stats.TotalStreamsCreated);2502 *ppStream = &pStreamEx->Core;2503 2504 /*2505 * Init debug stuff if enabled (ignore failures).2506 */2507 if (pCfgHost->enmDir == PDMAUDIODIR_IN)2508 {2509 if (pThis->In.Cfg.Dbg.fEnabled)2510 {2511 AudioHlpFileCreateAndOpen(&pStreamEx->In.Dbg.pFileCaptureNonInterleaved, pThis->In.Cfg.Dbg.szPathOut,2512 "DrvAudioCapNonInt", pThis->pDrvIns->iInstance, &pStreamEx->Host.Cfg.Props);2513 AudioHlpFileCreateAndOpen(&pStreamEx->In.Dbg.pFileStreamRead, pThis->In.Cfg.Dbg.szPathOut,2514 "DrvAudioRead", pThis->pDrvIns->iInstance, &pStreamEx->Host.Cfg.Props);2515 }2516 }2517 else /* Out */2518 {2519 if (pThis->Out.Cfg.Dbg.fEnabled)2520 {2521 AudioHlpFileCreateAndOpen(&pStreamEx->Out.Dbg.pFilePlayNonInterleaved, pThis->Out.Cfg.Dbg.szPathOut,2522 "DrvAudioPlayNonInt", pThis->pDrvIns->iInstance, &pStreamEx->Host.Cfg.Props);2523 AudioHlpFileCreateAndOpen(&pStreamEx->Out.Dbg.pFileStreamWrite, pThis->Out.Cfg.Dbg.szPathOut,2524 "DrvAudioWrite", pThis->pDrvIns->iInstance, &pStreamEx->Host.Cfg.Props);2525 }2526 }2527 }2528 else2529 {2530 LogFunc(("drvAudioStreamInitInternal failed: %Rrc\n", rc));2531 int rc2 = drvAudioStreamUninitInternal(pThis, pStreamEx);2532 AssertRC(rc2);2533 drvAudioStreamFree(pStreamEx);2534 }2535 }2536 else2537 rc = VERR_NO_MEMORY;2538 }2539 2540 RTCritSectLeave(&pThis->CritSect);2541 LogFlowFuncLeaveRC(rc);2542 return rc;2543 }2544 2545 /**2546 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnEnable}2547 */2548 static DECLCALLBACK(int) drvAudioEnable(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir, bool fEnable)2549 {2550 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);2551 AssertPtr(pThis);2552 2553 bool *pfEnabled;2554 if (enmDir == PDMAUDIODIR_IN)2555 pfEnabled = &pThis->In.fEnabled;2556 else if (enmDir == PDMAUDIODIR_OUT)2557 pfEnabled = &pThis->Out.fEnabled;2558 else2559 AssertFailedReturn(VERR_INVALID_PARAMETER);2560 2561 int rc = RTCritSectEnter(&pThis->CritSect);2562 AssertRCReturn(rc, rc);2563 2564 if (fEnable != *pfEnabled)2565 {2566 LogRel(("Audio: %s %s for driver '%s'\n",2567 fEnable ? "Enabling" : "Disabling", enmDir == PDMAUDIODIR_IN ? "input" : "output", pThis->szName));2568 2569 /* Update the status first, as this will be checked for in drvAudioStreamControlInternalBackend() below. */2570 *pfEnabled = fEnable;2571 2572 PDRVAUDIOSTREAM pStreamEx;2573 RTListForEach(&pThis->lstStreams, pStreamEx, DRVAUDIOSTREAM, ListEntry)2574 {2575 if (pStreamEx->Core.enmDir != enmDir) /* Skip unwanted streams. */2576 continue;2577 2578 /* Note: Only enable / disable the backend, do *not* change the stream's internal status.2579 * Callers (device emulation, mixer, ...) from outside will not see any status or behavior change,2580 * to not confuse the rest of the state machine.2581 *2582 * When disabling:2583 * - playing back audo data would go to /dev/null2584 * - recording audio data would return silence instead2585 *2586 * See @bugref{9882}.2587 */2588 int rc2 = drvAudioStreamControlInternalBackend(pThis, pStreamEx,2589 fEnable ? PDMAUDIOSTREAMCMD_ENABLE : PDMAUDIOSTREAMCMD_DISABLE);2590 if (RT_FAILURE(rc2))2591 {2592 if (rc2 == VERR_AUDIO_STREAM_NOT_READY)2593 LogRel(("Audio: Stream '%s' not available\n", pStreamEx->Core.szName));2594 else2595 LogRel(("Audio: Failed to %s %s stream '%s', rc=%Rrc\n", fEnable ? "enable" : "disable",2596 enmDir == PDMAUDIODIR_IN ? "input" : "output", pStreamEx->Core.szName, rc2));2597 }2598 else2599 {2600 /* When (re-)enabling a stream, clear the disabled warning bit again. */2601 if (fEnable)2602 pStreamEx->Core.fWarningsShown &= ~PDMAUDIOSTREAM_WARN_FLAGS_DISABLED;2603 }2604 2605 if (RT_SUCCESS(rc))2606 rc = rc2;2607 2608 /* Keep going. */2609 }2610 }2611 2612 RTCritSectLeave(&pThis->CritSect);2613 LogFlowFuncLeaveRC(rc);2614 return rc;2615 }2616 2617 /**2618 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnIsEnabled}2619 */2620 static DECLCALLBACK(bool) drvAudioIsEnabled(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir)2621 {2622 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);2623 AssertPtr(pThis);2624 int rc = RTCritSectEnter(&pThis->CritSect);2625 AssertRCReturn(rc, false);2626 2627 bool fEnabled;2628 if (enmDir == PDMAUDIODIR_IN)2629 fEnabled = pThis->In.fEnabled;2630 else if (enmDir == PDMAUDIODIR_OUT)2631 fEnabled = pThis->Out.fEnabled;2632 else2633 AssertFailedStmt(fEnabled = false);2634 2635 RTCritSectLeave(&pThis->CritSect);2636 return fEnabled;2637 }2638 2639 /**2640 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnGetConfig}2641 */2642 static DECLCALLBACK(int) drvAudioGetConfig(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOBACKENDCFG pCfg)2643 {2644 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);2645 AssertPtr(pThis);2646 AssertPtrReturn(pCfg, VERR_INVALID_POINTER);2647 int rc = RTCritSectEnter(&pThis->CritSect);2648 AssertRCReturn(rc, rc);2649 2650 if (pThis->pHostDrvAudio)2651 {2652 if (pThis->pHostDrvAudio->pfnGetConfig)2653 rc = pThis->pHostDrvAudio->pfnGetConfig(pThis->pHostDrvAudio, pCfg);2654 else2655 rc = VERR_NOT_SUPPORTED;2656 }2657 else2658 rc = VERR_PDM_NO_ATTACHED_DRIVER;2659 2660 RTCritSectLeave(&pThis->CritSect);2661 LogFlowFuncLeaveRC(rc);2662 return rc;2663 }2664 2665 /**2666 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnGetStatus}2667 */2668 static DECLCALLBACK(PDMAUDIOBACKENDSTS) drvAudioGetStatus(PPDMIAUDIOCONNECTOR pInterface, PDMAUDIODIR enmDir)2669 {2670 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);2671 AssertPtr(pThis);2672 int rc = RTCritSectEnter(&pThis->CritSect);2673 AssertRCReturn(rc, PDMAUDIOBACKENDSTS_UNKNOWN);2674 2675 PDMAUDIOBACKENDSTS fBackendStatus;2676 if (pThis->pHostDrvAudio)2677 {2678 if (pThis->pHostDrvAudio->pfnGetStatus)2679 fBackendStatus = pThis->pHostDrvAudio->pfnGetStatus(pThis->pHostDrvAudio, enmDir);2680 else2681 fBackendStatus = PDMAUDIOBACKENDSTS_UNKNOWN;2682 }2683 else2684 fBackendStatus = PDMAUDIOBACKENDSTS_NOT_ATTACHED;2685 2686 RTCritSectLeave(&pThis->CritSect);2687 LogFlowFunc(("LEAVE - %#x\n", fBackendStatus));2688 return fBackendStatus;2689 }2690 2691 /**2692 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamConfigHint}2693 */2694 static DECLCALLBACK(void) drvAudioStreamConfigHint(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAMCFG pCfg)2695 {2696 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector);2697 AssertReturnVoid(pCfg->enmDir == PDMAUDIODIR_IN || pCfg->enmDir == PDMAUDIODIR_OUT);2698 2699 int rc = RTCritSectEnter(&pThis->CritSect); /** @todo Reconsider the locking for DrvAudio */2700 AssertRCReturnVoid(rc);2701 2702 /*2703 * Don't do anything unless the backend has a pfnStreamConfigHint method2704 * and the direction is currently enabled.2705 */2706 if ( pThis->pHostDrvAudio2707 && pThis->pHostDrvAudio->pfnStreamConfigHint)2708 {2709 if (pCfg->enmDir == PDMAUDIODIR_OUT ? pThis->Out.fEnabled : pThis->In.fEnabled)2710 {2711 /*2712 * Adjust the configuration (applying out settings) then call the backend driver.2713 */2714 rc = drvAudioStreamAdjustConfig(pThis, pCfg, pCfg->szName);2715 AssertLogRelRC(rc);2716 if (RT_SUCCESS(rc))2717 pThis->pHostDrvAudio->pfnStreamConfigHint(pThis->pHostDrvAudio, pCfg);2718 }2719 else2720 LogFunc(("Ignoring hint because direction is not currently enabled\n"));2721 }2722 else2723 LogFlowFunc(("Ignoring hint because backend has no pfnStreamConfigHint method.\n"));2724 2725 RTCritSectLeave(&pThis->CritSect);2726 }2727 2640 2728 2641 /** … … 2804 2717 return cbReadable; 2805 2718 } 2719 2806 2720 2807 2721 /** … … 2872 2786 } 2873 2787 2788 2874 2789 /** 2875 2790 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamGetStatus} … … 2905 2820 } 2906 2821 2822 2907 2823 /** 2908 2824 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamSetVolume} … … 2926 2842 } 2927 2843 2928 /** 2929 * Calls the backend to give it the chance to destroy its part of the audio stream. 2930 * 2931 * Called from drvAudioPowerOff, drvAudioStreamUninitInternal and 2932 * drvAudioStreamReInitInternal. 2844 2845 /** 2846 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamPlay} 2847 */ 2848 static DECLCALLBACK(int) drvAudioStreamPlay(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream, uint32_t *pcFramesPlayed) 2849 { 2850 RT_NOREF(pInterface, pStream, pcFramesPlayed); 2851 AssertFailed(/* OBSOLETE! */); 2852 return VERR_NOT_SUPPORTED; 2853 } 2854 2855 2856 /** 2857 * Captures non-interleaved input from a host stream. 2933 2858 * 2934 2859 * @returns VBox status code. 2935 * @param pThis Pointer to driver instance. 2936 * @param pStreamEx Audio stream destruct backend for. 2937 */ 2938 static int drvAudioStreamDestroyInternalBackend(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx) 2939 { 2860 * @param pThis Driver instance. 2861 * @param pStreamEx Stream to capture from. 2862 * @param pcfCaptured Number of (host) audio frames captured. 2863 */ 2864 static int drvAudioStreamCaptureNonInterleaved(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, uint32_t *pcfCaptured) 2865 { 2866 Assert(pStreamEx->Core.enmDir == PDMAUDIODIR_IN); 2867 Assert(pStreamEx->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED); 2868 2869 /* 2870 * ... 2871 */ 2872 AssertPtr(pThis->pHostDrvAudio->pfnStreamGetReadable); 2873 uint32_t cbReadable = pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio, pStreamEx->pBackend); 2874 if (!cbReadable) 2875 Log2Func(("[%s] No readable data available\n", pStreamEx->Core.szName)); 2876 2877 uint32_t cbFree = AudioMixBufFreeBytes(&pStreamEx->Guest.MixBuf); /* Parent */ 2878 if (!cbFree) 2879 Log2Func(("[%s] Buffer full\n", pStreamEx->Core.szName)); 2880 2881 if (cbReadable > cbFree) /* More data readable than we can store at the moment? Limit. */ 2882 cbReadable = cbFree; 2883 2884 /* 2885 * ... 2886 */ 2887 int rc = VINF_SUCCESS; 2888 uint32_t cfCapturedTotal = 0; 2889 while (cbReadable) 2890 { 2891 uint8_t abChunk[_4K]; 2892 uint32_t cbCaptured; 2893 rc = pThis->pHostDrvAudio->pfnStreamCapture(pThis->pHostDrvAudio, pStreamEx->pBackend, 2894 abChunk, RT_MIN(cbReadable, (uint32_t)sizeof(abChunk)), &cbCaptured); 2895 if (RT_FAILURE(rc)) 2896 { 2897 int rc2 = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE); 2898 AssertRC(rc2); 2899 break; 2900 } 2901 2902 Assert(cbCaptured <= sizeof(abChunk)); 2903 if (cbCaptured > sizeof(abChunk)) /* Paranoia. */ 2904 cbCaptured = (uint32_t)sizeof(abChunk); 2905 2906 if (!cbCaptured) /* Nothing captured? Take a shortcut. */ 2907 break; 2908 2909 /* We use the host side mixing buffer as an intermediate buffer to do some 2910 * (first) processing (if needed), so always write the incoming data at offset 0. */ 2911 uint32_t cfHstWritten = 0; 2912 rc = AudioMixBufWriteAt(&pStreamEx->Host.MixBuf, 0 /* offFrames */, abChunk, cbCaptured, &cfHstWritten); 2913 if ( RT_FAILURE(rc) 2914 || !cfHstWritten) 2915 { 2916 AssertMsgFailed(("[%s] Write failed: cbCaptured=%RU32, cfHstWritten=%RU32, rc=%Rrc\n", 2917 pStreamEx->Core.szName, cbCaptured, cfHstWritten, rc)); 2918 break; 2919 } 2920 2921 if (pThis->In.Cfg.Dbg.fEnabled) 2922 AudioHlpFileWrite(pStreamEx->In.Dbg.pFileCaptureNonInterleaved, abChunk, cbCaptured, 0 /* fFlags */); 2923 2924 uint32_t cfHstMixed = 0; 2925 if (cfHstWritten) 2926 { 2927 int rc2 = AudioMixBufMixToParentEx(&pStreamEx->Host.MixBuf, 0 /* cSrcOffset */, cfHstWritten /* cSrcFrames */, 2928 &cfHstMixed /* pcSrcMixed */); 2929 Log3Func(("[%s] cbCaptured=%RU32, cfWritten=%RU32, cfMixed=%RU32, rc=%Rrc\n", 2930 pStreamEx->Core.szName, cbCaptured, cfHstWritten, cfHstMixed, rc2)); 2931 AssertRC(rc2); 2932 } 2933 2934 Assert(cbReadable >= cbCaptured); 2935 cbReadable -= cbCaptured; 2936 cfCapturedTotal += cfHstMixed; 2937 } 2938 2939 if (RT_SUCCESS(rc)) 2940 { 2941 if (cfCapturedTotal) 2942 Log2Func(("[%s] %RU32 frames captured, rc=%Rrc\n", pStreamEx->Core.szName, cfCapturedTotal, rc)); 2943 } 2944 else 2945 LogFunc(("[%s] Capturing failed with rc=%Rrc\n", pStreamEx->Core.szName, rc)); 2946 2947 if (pcfCaptured) 2948 *pcfCaptured = cfCapturedTotal; 2949 2950 return rc; 2951 } 2952 2953 2954 /** 2955 * Captures raw input from a host stream. 2956 * 2957 * Raw input means that the backend directly operates on PDMAUDIOFRAME structs without 2958 * no data layout processing done in between. 2959 * 2960 * Needed for e.g. the VRDP audio backend (in Main). 2961 * 2962 * @returns VBox status code. 2963 * @param pThis Driver instance. 2964 * @param pStreamEx Stream to capture from. 2965 * @param pcfCaptured Number of (host) audio frames captured. 2966 */ 2967 static int drvAudioStreamCaptureRaw(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx, uint32_t *pcfCaptured) 2968 { 2969 Assert(pStreamEx->Core.enmDir == PDMAUDIODIR_IN); 2970 Assert(pStreamEx->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_RAW); 2971 AssertPtr(pThis->pHostDrvAudio->pfnStreamGetReadable); 2972 2973 /* 2974 * ... 2975 */ 2976 /* Note: Raw means *audio frames*, not bytes! */ 2977 uint32_t cfReadable = pThis->pHostDrvAudio->pfnStreamGetReadable(pThis->pHostDrvAudio, pStreamEx->pBackend); 2978 if (!cfReadable) 2979 Log2Func(("[%s] No readable data available\n", pStreamEx->Core.szName)); 2980 2981 const uint32_t cfFree = AudioMixBufFree(&pStreamEx->Guest.MixBuf); /* Parent */ 2982 if (!cfFree) 2983 Log2Func(("[%s] Buffer full\n", pStreamEx->Core.szName)); 2984 2985 if (cfReadable > cfFree) /* More data readable than we can store at the moment? Limit. */ 2986 cfReadable = cfFree; 2987 2988 /* 2989 * ... 2990 */ 2991 int rc = VINF_SUCCESS; 2992 uint32_t cfCapturedTotal = 0; 2993 while (cfReadable) 2994 { 2995 PPDMAUDIOFRAME paFrames; 2996 uint32_t cfWritable; 2997 rc = AudioMixBufPeekMutable(&pStreamEx->Host.MixBuf, cfReadable, &paFrames, &cfWritable); 2998 if ( RT_FAILURE(rc) 2999 || !cfWritable) 3000 break; 3001 3002 uint32_t cfCaptured; 3003 rc = pThis->pHostDrvAudio->pfnStreamCapture(pThis->pHostDrvAudio, pStreamEx->pBackend, 3004 paFrames, cfWritable, &cfCaptured); 3005 if (RT_FAILURE(rc)) 3006 { 3007 int rc2 = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE); 3008 AssertRC(rc2); 3009 break; 3010 } 3011 3012 Assert(cfCaptured <= cfWritable); 3013 if (cfCaptured > cfWritable) /* Paranoia. */ 3014 cfCaptured = cfWritable; 3015 3016 Assert(cfReadable >= cfCaptured); 3017 cfReadable -= cfCaptured; 3018 cfCapturedTotal += cfCaptured; 3019 } 3020 3021 if (pcfCaptured) 3022 *pcfCaptured = cfCapturedTotal; 3023 Log2Func(("[%s] %RU32 frames captured, rc=%Rrc\n", pStreamEx->Core.szName, cfCapturedTotal, rc)); 3024 return rc; 3025 } 3026 3027 3028 /** 3029 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamCapture} 3030 */ 3031 static DECLCALLBACK(int) drvAudioStreamCapture(PPDMIAUDIOCONNECTOR pInterface, 3032 PPDMAUDIOSTREAM pStream, uint32_t *pcFramesCaptured) 3033 { 3034 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector); 2940 3035 AssertPtr(pThis); 2941 AssertPtr(pStreamEx); 2942 2943 int rc = VINF_SUCCESS; 3036 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream; 3037 AssertPtrReturn(pStreamEx, VERR_INVALID_POINTER); 3038 AssertPtrNull(pcFramesCaptured); 3039 AssertReturn(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC); 3040 AssertReturn(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC, VERR_INVALID_MAGIC); 3041 AssertMsg(pStreamEx->Core.enmDir == PDMAUDIODIR_IN, 3042 ("Stream '%s' is not an input stream and therefore cannot be captured (direction is 0x%x)\n", 3043 pStreamEx->Core.szName, pStreamEx->Core.enmDir)); 3044 int rc = RTCritSectEnter(&pThis->CritSect); 3045 AssertRCReturn(rc, rc); 2944 3046 2945 3047 #ifdef LOG_ENABLED 2946 3048 char szStreamSts[DRVAUDIO_STATUS_STR_MAX]; 2947 3049 #endif 2948 LogFunc(("[%s] fStatus=%s\n", pStreamEx->Core.szName, dbgAudioStreamStatusToStr(szStreamSts, pStreamEx->Core.fStatus))); 2949 2950 if (pStreamEx->Core.fStatus & PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED) 2951 { 2952 AssertPtr(pStreamEx->pBackend); 2953 2954 /* Check if the pointer to the host audio driver is still valid. 2955 * It can be NULL if we were called in drvAudioDestruct, for example. */ 2956 if (pThis->pHostDrvAudio) 2957 rc = pThis->pHostDrvAudio->pfnStreamDestroy(pThis->pHostDrvAudio, pStreamEx->pBackend); 2958 2959 pStreamEx->Core.fStatus &= ~PDMAUDIOSTREAMSTS_FLAGS_INITIALIZED; 2960 } 2961 2962 LogFlowFunc(("[%s] Returning %Rrc\n", pStreamEx->Core.szName, rc)); 2963 return rc; 2964 } 2965 2966 /** 2967 * Uninitializes an audio stream - worker for drvAudioStreamDestroy, 2968 * drvAudioDestruct and drvAudioStreamCreate. 2969 * 2970 * @returns VBox status code. 2971 * @param pThis Pointer to driver instance. 2972 * @param pStreamEx Pointer to audio stream to uninitialize. 2973 * 2974 * @note Caller owns the critical section. 2975 */ 2976 static int drvAudioStreamUninitInternal(PDRVAUDIO pThis, PDRVAUDIOSTREAM pStreamEx) 2977 { 2978 AssertPtrReturn(pThis, VERR_INVALID_POINTER); 2979 AssertMsgReturn(pStreamEx->Core.cRefs <= 1, 2980 ("Stream '%s' still has %RU32 references held when uninitializing\n", pStreamEx->Core.szName, pStreamEx->Core.cRefs), 2981 VERR_WRONG_ORDER); 2982 LogFlowFunc(("[%s] cRefs=%RU32\n", pStreamEx->Core.szName, pStreamEx->Core.cRefs)); 3050 Log3Func(("[%s] fStatus=%s\n", pStreamEx->Core.szName, dbgAudioStreamStatusToStr(szStreamSts, pStreamEx->Core.fStatus))); 2983 3051 2984 3052 /* 2985 3053 * ... 2986 3054 */ 2987 int rc = drvAudioStreamControlInternal(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE); 2988 if (RT_SUCCESS(rc)) 2989 rc = drvAudioStreamDestroyInternalBackend(pThis, pStreamEx); 2990 2991 /* Destroy mixing buffers. */ 2992 AudioMixBufDestroy(&pStreamEx->Guest.MixBuf); 2993 AudioMixBufDestroy(&pStreamEx->Host.MixBuf); 2994 2995 /* Free pre-buffer space. */ 2996 if ( pStreamEx->Core.enmDir == PDMAUDIODIR_OUT 2997 && pStreamEx->Out.pbPreBuf) 2998 { 2999 RTMemFree(pStreamEx->Out.pbPreBuf); 3000 pStreamEx->Out.pbPreBuf = NULL; 3001 pStreamEx->Out.cbPreBufAlloc = 0; 3002 pStreamEx->Out.cbPreBuffered = 0; 3003 } 3004 3005 if (RT_SUCCESS(rc)) 3006 { 3007 #ifdef LOG_ENABLED 3008 if (pStreamEx->Core.fStatus != PDMAUDIOSTREAMSTS_FLAGS_NONE) 3009 { 3010 char szStreamSts[DRVAUDIO_STATUS_STR_MAX]; 3011 LogFunc(("[%s] Warning: Still has %s set when uninitializing\n", 3012 pStreamEx->Core.szName, dbgAudioStreamStatusToStr(szStreamSts, pStreamEx->Core.fStatus))); 3013 } 3014 #endif 3015 pStreamEx->Core.fStatus = PDMAUDIOSTREAMSTS_FLAGS_NONE; 3016 } 3017 3018 PPDMDRVINS const pDrvIns = pThis->pDrvIns; 3019 PDMDrvHlpSTAMDeregisterByPrefix(pDrvIns, pStreamEx->Core.szName); 3020 3021 if (pStreamEx->Core.enmDir == PDMAUDIODIR_IN) 3022 { 3023 if (pThis->In.Cfg.Dbg.fEnabled) 3024 { 3025 AudioHlpFileDestroy(pStreamEx->In.Dbg.pFileCaptureNonInterleaved); 3026 pStreamEx->In.Dbg.pFileCaptureNonInterleaved = NULL; 3027 3028 AudioHlpFileDestroy(pStreamEx->In.Dbg.pFileStreamRead); 3029 pStreamEx->In.Dbg.pFileStreamRead = NULL; 3030 } 3031 } 3032 else 3033 { 3034 Assert(pStreamEx->Core.enmDir == PDMAUDIODIR_OUT); 3035 if (pThis->Out.Cfg.Dbg.fEnabled) 3036 { 3037 AudioHlpFileDestroy(pStreamEx->Out.Dbg.pFilePlayNonInterleaved); 3038 pStreamEx->Out.Dbg.pFilePlayNonInterleaved = NULL; 3039 3040 AudioHlpFileDestroy(pStreamEx->Out.Dbg.pFileStreamWrite); 3041 pStreamEx->Out.Dbg.pFileStreamWrite = NULL; 3042 } 3043 } 3044 LogFlowFunc(("Returning %Rrc\n", rc)); 3045 return rc; 3046 } 3047 3048 /** 3049 * @interface_method_impl{PDMIAUDIOCONNECTOR,pfnStreamDestroy} 3050 */ 3051 static DECLCALLBACK(int) drvAudioStreamDestroy(PPDMIAUDIOCONNECTOR pInterface, PPDMAUDIOSTREAM pStream) 3052 { 3053 PDRVAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVAUDIO, IAudioConnector); 3054 AssertPtr(pThis); 3055 3056 if (!pStream) 3057 return VINF_SUCCESS; 3058 AssertPtrReturn(pStream, VERR_INVALID_POINTER); 3059 PDRVAUDIOSTREAM pStreamEx = (PDRVAUDIOSTREAM)pStream; /* Note! Do not touch pStream after this! */ 3060 Assert(pStreamEx->Core.uMagic == PDMAUDIOSTREAM_MAGIC); 3061 Assert(pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC); 3062 3063 int rc = RTCritSectEnter(&pThis->CritSect); 3064 AssertRCReturn(rc, rc); 3065 3066 LogRel2(("Audio: Destroying stream '%s'\n", pStreamEx->Core.szName)); 3067 3068 LogFlowFunc(("[%s] cRefs=%RU32\n", pStreamEx->Core.szName, pStreamEx->Core.cRefs)); 3069 AssertMsg(pStreamEx->Core.cRefs <= 1, ("%u %s\n", pStreamEx->Core.cRefs, pStreamEx->Core.szName)); 3070 if (pStreamEx->Core.cRefs <= 1) 3071 { 3072 rc = drvAudioStreamUninitInternal(pThis, pStreamEx); 3055 uint32_t cfCaptured = 0; 3056 do 3057 { 3058 if (!pThis->pHostDrvAudio) 3059 { 3060 rc = VERR_PDM_NO_ATTACHED_DRIVER; 3061 break; 3062 } 3063 3064 if ( !pThis->In.fEnabled 3065 || !PDMAudioStrmStatusCanRead(pStreamEx->Core.fStatus)) 3066 { 3067 rc = VERR_AUDIO_STREAM_NOT_READY; 3068 break; 3069 } 3070 3071 /* 3072 * Do the actual capturing. 3073 */ 3074 if (RT_LIKELY(pStreamEx->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_NON_INTERLEAVED)) 3075 rc = drvAudioStreamCaptureNonInterleaved(pThis, pStreamEx, &cfCaptured); 3076 else if (pStreamEx->Host.Cfg.enmLayout == PDMAUDIOSTREAMLAYOUT_RAW) 3077 rc = drvAudioStreamCaptureRaw(pThis, pStreamEx, &cfCaptured); 3078 else 3079 AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED); 3080 3073 3081 if (RT_SUCCESS(rc)) 3074 3082 { 3075 if (pStreamEx->Core.enmDir == PDMAUDIODIR_IN) 3076 pThis->In.cStreamsFree++; 3077 else /* Out */ 3078 pThis->Out.cStreamsFree++; 3079 3080 RTListNodeRemove(&pStreamEx->ListEntry); 3081 3082 drvAudioStreamFree(pStreamEx); 3083 pStreamEx = NULL; 3084 pStream = NULL; 3085 } 3086 else 3087 LogRel(("Audio: Uninitializing stream '%s' failed with %Rrc\n", pStreamEx->Core.szName, rc)); 3088 } 3089 else 3090 rc = VERR_WRONG_ORDER; 3083 Log3Func(("[%s] %RU32 frames captured, rc=%Rrc\n", pStreamEx->Core.szName, cfCaptured, rc)); 3084 3085 STAM_COUNTER_ADD(&pThis->Stats.TotalFramesIn, cfCaptured); 3086 STAM_COUNTER_ADD(&pStreamEx->In.Stats.TotalFramesCaptured, cfCaptured); 3087 } 3088 else if (RT_UNLIKELY(RT_FAILURE(rc))) 3089 LogRel(("Audio: Capturing stream '%s' failed with %Rrc\n", pStreamEx->Core.szName, rc)); 3090 } while (0); 3091 3091 3092 3092 RTCritSectLeave(&pThis->CritSect); 3093 LogFlowFuncLeaveRC(rc); 3093 3094 if (pcFramesCaptured) 3095 *pcFramesCaptured = cfCaptured; 3096 3097 if (RT_FAILURE(rc)) 3098 LogFlowFuncLeaveRC(rc); 3094 3099 return rc; 3095 3100 } … … 3286 3291 RTCritSectLeave(&pThis->CritSect); 3287 3292 return rc; 3293 } 3294 3295 3296 /** 3297 * Handles state changes for all audio streams. 3298 * 3299 * @param pDrvIns Pointer to driver instance. 3300 * @param enmCmd Stream command to set for all streams. 3301 */ 3302 static void drvAudioStateHandler(PPDMDRVINS pDrvIns, PDMAUDIOSTREAMCMD enmCmd) 3303 { 3304 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); 3305 PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO); 3306 LogFlowFunc(("enmCmd=%s\n", PDMAudioStrmCmdGetName(enmCmd))); 3307 3308 int rc2 = RTCritSectEnter(&pThis->CritSect); 3309 AssertRCReturnVoid(rc2); 3310 3311 if (pThis->pHostDrvAudio) 3312 { 3313 PDRVAUDIOSTREAM pStreamEx; 3314 RTListForEach(&pThis->lstStreams, pStreamEx, DRVAUDIOSTREAM, ListEntry) 3315 { 3316 drvAudioStreamControlInternal(pThis, pStreamEx, enmCmd); 3317 } 3318 } 3319 3320 rc2 = RTCritSectLeave(&pThis->CritSect); 3321 AssertRC(rc2); 3288 3322 } 3289 3323
Note:
See TracChangeset
for help on using the changeset viewer.