//--------------------------------------------------------------------------- // // Module: mixer.c // // Description: // Contains the kernel mode portion of the mixer line driver. // // //@@BEGIN_MSINTERNAL // Development Team: // D. Baumberger // // History: Date Author Comment // //@@END_MSINTERNAL // //--------------------------------------------------------------------------- // // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY // KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR // PURPOSE. // // Copyright (C) Microsoft Corporation, 1997 - 1999 All Rights Reserved. // //--------------------------------------------------------------------------- /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// // // // I N C L U D E S // // // /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// #include "WDMSYS.H" typedef struct { VOID *pNext; PMXLCONTROL pControl; #ifdef DEBUG DWORD dwSig; // CONTROLLINK_SIGNATURE #endif } CONTROLLINK, *PCONTROLLINK; // // Data structure for Notification list. // typedef struct { PVOID pNext; // Pointer to next node in linked list. DWORD NodeId; // This is the control's Id as seen by SysAudio DWORD LineID; // Line that this control lives on DWORD ControlID; // ControlID from pControl->Control.dwControlID DWORD ControlType; // // context specific stuff... PWDMACONTEXT pContext; // Pointer to Global Context structure PMIXERDEVICE pmxd; // The mixer device for this control // PFILE_OBJECT pfo; // File Object to SysAudio for this Context PCONTROLLINK pcLink; // points to structure that contains valid // pControl addresses in this context. KDPC NodeEventDPC; // For handling the DPCs KSEVENTDATA NodeEventData; #ifdef DEBUG DWORD dwSig; // NOTIFICATION_SIGNATURE #endif } NOTENODE, *PNOTENODE; #ifdef DEBUG BOOL IsValidNoteNode( IN PNOTENODE pnnode ); BOOL IsValidControlLink( IN PCONTROLLINK pcLink ); ///////////////////////////////////////////////////////////////////////////// // // IsValidNoteNode // // Validates that the pointer is a PNOTENODE type. // BOOL IsValidNoteNode( IN PNOTENODE pnnode) { NTSTATUS Status=STATUS_SUCCESS; try { if(pnnode->dwSig != NOTIFICATION_SIGNATURE) { DPF(DL_ERROR|FA_ASSERT,("Invalid pnnode->dwSig(%08X)",pnnode->dwSig) ); Status=STATUS_UNSUCCESSFUL; } /* if(pnnode->pfo == NULL) { DPF(DL_ERROR|FA_ASSERT,("Invalid pnnode->pfo(%08X)",pnnode->pfo) ); Status=STATUS_UNSUCCESSFUL; } */ if( !IsValidWdmaContext(pnnode->pContext) ) { DPF(DL_ERROR|FA_ASSERT,("Invalid pnnode->pContext(%08X)",pnnode->pContext) ); Status=STATUS_UNSUCCESSFUL; } if( !IsValidMixerDevice(pnnode->pmxd) ) { DPF(DL_ERROR|FA_ASSERT,("Invalid pnnode->pmxd(%08X)",pnnode->pmxd) ); Status=STATUS_UNSUCCESSFUL; } if( !IsValidControlLink(pnnode->pcLink) ) { DPF(DL_ERROR|FA_ASSERT,("Invalid pnnode->pcLink(%08X)",pnnode->pcLink) ); Status=STATUS_UNSUCCESSFUL; } } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); } if( NT_SUCCESS(Status) ) { return TRUE; } else { return FALSE; } } ///////////////////////////////////////////////////////////////////////////// // // IsValidControlLink // // Validates that the pointer is a PNOTENODE type. // BOOL IsValidControlLink( IN PCONTROLLINK pcLink) { NTSTATUS Status=STATUS_SUCCESS; try { if(pcLink->dwSig != CONTROLLINK_SIGNATURE) { DPF(DL_ERROR|FA_ASSERT,("Invalid pcLink->dwSig(%08X)",pcLink->dwSig) ); Status=STATUS_UNSUCCESSFUL; } if( !IsValidControl(pcLink->pControl) ) { Status=STATUS_UNSUCCESSFUL; } // // pcLink->pNext is a pointer to another CONTROLLINK structure. Thus, // if it's not NULL, the structure that it points to should also be valid. // if( pcLink->pNext ) { PCONTROLLINK pTmp=pcLink->pNext; if( pTmp->dwSig != CONTROLLINK_SIGNATURE ) { DPF(DL_ERROR|FA_ASSERT,("Invalid pcLink->pNext->dwSig(%08X)",pTmp->dwSig) ); Status=STATUS_UNSUCCESSFUL; } } } except (EXCEPTION_EXECUTE_HANDLER) { Status = GetExceptionCode(); } if( NT_SUCCESS(Status) ) { return TRUE; } else { return FALSE; } } #endif PNOTENODE kmxlNewNoteNode( ); PCONTROLLINK kmxlNewControlLink( IN PMXLCONTROL pControl ); VOID kmxlFreeControlLink( IN OUT PCONTROLLINK pcLink ); VOID kmxlFreeNoteNode( IN OUT PNOTENODE pnnode ); VOID kmxlAddNoteNodeToList( IN OUT PNOTENODE pnnode ); VOID kmxlRemoveNoteNodeFromList( IN OUT PNOTENODE pnnode ); PNOTENODE kmxlFindControlInNoteList( IN PMXLCONTROL pControl ); NTSTATUS kmxlFindNodeInNoteList( IN PNOTENODE pnlookupnode ); PNOTENODE kmxlFindIdInContextInNoteList( IN PWDMACONTEXT pWdmaContext, IN PMIXERDEVICE pmxd, IN DWORD Id ); PNOTENODE kmxlFindContextInNoteList( IN PWDMACONTEXT pWdmaContext ); NTSTATUS kmxlAddControlToNoteList( IN OUT PNOTENODE pnnode, IN PMXLCONTROL pControl ); PCONTROLLINK kmxlRemoveControlFromNoteList( IN OUT PNOTENODE pnnode, IN PMXLCONTROL pControl ); NTSTATUS kmxlQueryControlChange( IN PFILE_OBJECT pfo, IN ULONG NodeId ); NTSTATUS kmxlEnableControlChange( IN PFILE_OBJECT pfo, IN ULONG NodeId, IN OUT PKSEVENTDATA pksed ); NTSTATUS kmxlDisableControlChange( IN PFILE_OBJECT pfo, IN ULONG NodeId, IN OUT PKSEVENTDATA pksed ); NTSTATUS kmxlEnableControlChangeNotifications( IN PMIXEROBJECT pmxobj, IN PMXLLINE pLine, IN PMXLCONTROL pControl ); VOID kmxlRemoveContextFromNoteList( IN PWDMACONTEXT pWdmaContext ); NTSTATUS kmxlEnableAllControls( IN PMIXEROBJECT pmxobj ); // // Used in persist // extern NTSTATUS kmxlPersistSingleControl( IN PFILE_OBJECT pfo, IN PMIXERDEVICE pmxd, IN PMXLCONTROL pControl, IN PVOID paDetails ); VOID PersistHWControlWorker( IN LPVOID pData ); VOID kmxlGrabNoteMutex( ); VOID kmxlReleaseNoteMutex( ); VOID kmxlCloseMixerDevice( IN OUT PMIXERDEVICE pmxd ); #pragma LOCKED_CODE #pragma LOCKED_DATA /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// // // // F U N C T I O N S // // // /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// // // This is the head of the notification list. mtxNotification is used to // handle the list manipulcation. The allocated memory in the firstnotenode // list will be touched at DPC level. // PNOTENODE firstnotenode=NULL; extern KMUTEX mtxNote; #ifdef DEBUG LONG totalnotificationcount=0; #endif #define CALLBACKARRAYSIZE 128 typedef struct { DWORD dwControlID; DWORD dwLineID; DWORD dwCallbackType; } CBINFO, *PCBINFO; ULONG emptyindex=0; volatile ULONG loadindex=0; CBINFO callbacks[CALLBACKARRAYSIZE]={0}; KSPIN_LOCK HardwareCallbackSpinLock; LIST_ENTRY HardwareCallbackListHead; PKEVENT pHardwareCallbackEvent=NULL; PKSWORKER HardwareCallbackWorkerObject=NULL; WORK_QUEUE_ITEM HardwareCallbackWorkItem; ULONG HardwareCallbackScheduled=0; typedef struct tagHWLink { LIST_ENTRY Next; PNOTENODE pnnode; #ifdef DEBUG DWORD dwSig; #endif } HWLINK, *PHWLINK; ///////////////////////////////////////////////////////////////////////////// // // NodeEventDPCHandler // // This routine is called at DISPATCH_LEVEL, thus you can not touch anything // but pnnode ellements. pnnode is fixed in memory thus it is safe. // VOID NodeEventDPCHandler( IN PKDPC Dpc, IN PVOID DeferredContext, IN PVOID SystemArgument1, IN PVOID SystemArgument2 ) { PHWLINK phwlink=NULL; PNOTENODE pnnode=(PNOTENODE)DeferredContext; PCBINFO pcbinfo; // // WorkItem: Use proper synchronization so that we never go away if there is // a work item scheduled! // DPFASSERT( pnnode->dwSig == NOTIFICATION_SIGNATURE ); DPF(DL_TRACE|FA_HARDWAREEVENT, ("** ++ Event Signaled ++ **") ); DPF(DL_TRACE|FA_HARDWAREEVENT, ("NodeId = %d LineID = %X ControlID = %X ControlType = %X", pnnode->NodeId,pnnode->LineID, pnnode->ControlID,pnnode->ControlType) ); callbacks[loadindex%CALLBACKARRAYSIZE].dwControlID=pnnode->ControlID; callbacks[loadindex%CALLBACKARRAYSIZE].dwLineID=pnnode->LineID; callbacks[loadindex%CALLBACKARRAYSIZE].dwCallbackType=MIXER_CONTROL_CALLBACK; // // Now we want to setup a work item to persist the hardware event. The idea // is that we'll put all the events in a list and then remove them from the list // in the handler. If we already have a workitem outstanding to service the // list, we don't schedule another. if( HardwareCallbackWorkerObject ) { // // Always allocate an entry in our list to service this DPC. We'll free this // event in the worker routine. // if( NT_SUCCESS(AudioAllocateMemory_Fixed(sizeof(HWLINK), TAG_AuDF_HARDWAREEVENT, ZERO_FILL_MEMORY, &phwlink) ) ) { phwlink->pnnode=pnnode; #ifdef DEBUG phwlink->dwSig=HWLINK_SIGNATURE; #endif ExInterlockedInsertTailList(&HardwareCallbackListHead, &phwlink->Next, &HardwareCallbackSpinLock); // // Now, if we haven't scheduled a work item yet, do so to service this // info in the list. HardwareCallbackSchedule will be 0 at // initialization time. This variable will be set to 0 in the // work item handler. If we increment and it's any other value other // then 1, we've already scheduled a work item. // if( InterlockedIncrement(&HardwareCallbackScheduled) == 1 ) { KsQueueWorkItem(HardwareCallbackWorkerObject, &HardwareCallbackWorkItem); } } } // Now if the control was a mute, then send line change as well. if (pnnode->ControlType==MIXERCONTROL_CONTROLTYPE_MUTE) { callbacks[loadindex%CALLBACKARRAYSIZE].dwCallbackType|=MIXER_LINE_CALLBACK; } loadindex++; if (pHardwareCallbackEvent!=NULL) { KeSetEvent(pHardwareCallbackEvent, 0 , FALSE); } } #pragma PAGEABLE_CODE #pragma PAGEABLE_DATA ///////////////////////////////////////////////////////////////////////////// // // kmxlNewNoteNode // // This routine allocates another Notification node. Note that AudioAllocateMemory zeros // all successful memory allocations. // // The return value is a pointer to a Notification Node or NULL. // PNOTENODE kmxlNewNoteNode( ) { PNOTENODE pnnode = NULL; NTSTATUS Status; PAGED_CODE(); Status = AudioAllocateMemory_Fixed(sizeof( NOTENODE ), TAG_AuDN_NOTIFICATION, ZERO_FILL_MEMORY, &pnnode ); if( !NT_SUCCESS( Status ) ) { return( NULL ); } DPF(DL_MAX|FA_NOTE, ("New notification node allocated %08X",pnnode) ); #ifdef DEBUG pnnode->dwSig=NOTIFICATION_SIGNATURE; #endif return pnnode; } ///////////////////////////////////////////////////////////////////////////// // // kmxlNewControlLink // // This routine allocates another CONTROLLINK node. Note that AudioAllocateMemory zeros // all successful memory allocations. // // The return value is a pointer to a Notification Node or NULL. // PCONTROLLINK kmxlNewControlLink( IN PMXLCONTROL pControl ) { PCONTROLLINK pcLink = NULL; NTSTATUS Status; PAGED_CODE(); Status = AudioAllocateMemory_Fixed(sizeof( CONTROLLINK ), TAG_AuDL_LINK, ZERO_FILL_MEMORY, &pcLink ); if( !NT_SUCCESS( Status ) ) { return( NULL ); } DPF(DL_MAX|FA_NOTE, ("New controllink node allocated %08X",pcLink) ); #ifdef DEBUG pcLink->dwSig=CONTROLLINK_SIGNATURE; #endif // // Setup the pcontrol first and then link it in. // pcLink->pControl=pControl; return pcLink; } VOID kmxlFreeControlLink( IN OUT PCONTROLLINK pcLink ) { DPFASSERT(IsValidControlLink(pcLink)); PAGED_CODE(); #ifdef DEBUG pcLink->dwSig=(DWORD)0xDEADBEEF; #endif AudioFreeMemory(sizeof( CONTROLLINK ),&pcLink); } ///////////////////////////////////////////////////////////////////////////// // // kmxlFreeNoteNode // // This routine frees a Notification Node. // VOID kmxlFreeNoteNode( IN OUT PNOTENODE pnnode ) { PCONTROLLINK pcLink,pcTmp; PAGED_CODE(); DPFASSERT( pnnode->dwSig == NOTIFICATION_SIGNATURE ); DPFASSERT( pnnode->pNext == NULL ); DPFASSERT( pnnode->pcLink == NULL ); DPF(DL_MAX|FA_NOTE,("NotificationNode freed %08X",pnnode) ); AudioFreeMemory( sizeof( NOTENODE ),&pnnode ); } ///////////////////////////////////////////////////////////////////////////// // // kmxlAddNoteNodeToList // // This routine adds the NotificationNode to the list. It places the node // at the head of the list. // VOID kmxlAddNoteNodeToList( IN OUT PNOTENODE pnnode ) { PAGED_CODE(); pnnode->pNext=firstnotenode; firstnotenode=pnnode; #ifdef DEBUG totalnotificationcount++; #endif DPF(DL_TRACE|FA_INSTANCE,("New NoteNode head %08X, Total=%d",pnnode,totalnotificationcount) ); } ///////////////////////////////////////////////////////////////////////////// // // kmxlRemoveNoteNodeFromList // // This routine removes a node from the list. // VOID kmxlRemoveNoteNodeFromList( IN OUT PNOTENODE pnnode ) { PNOTENODE pTmp; PAGED_CODE(); DPFASSERT(pnnode->dwSig == NOTIFICATION_SIGNATURE); // are we the head of the list? If so, move the next to the head. if( pnnode == firstnotenode ) { firstnotenode=firstnotenode->pNext; DPF(DL_MAX|FA_NOTE,("removed notenode head") ); } else { // we are somewhere in the list we need to walk the list until we find // our node and clip it out. for (pTmp=firstnotenode;pTmp!=NULL;pTmp=pTmp->pNext) { DPFASSERT(pTmp->dwSig==NOTIFICATION_SIGNATURE); // Did we find our node in the next position? If so, we // need to clip it out. Thus, pTmp.next needs to point to // (our node)'s next. if(pTmp->pNext == pnnode) { pTmp->pNext = pnnode->pNext; DPF(DL_MAX|FA_NOTE,("removed middle") ); break; } } } #ifdef DEBUG totalnotificationcount--; #endif // // to indicate that this node has been removed, pNext gets set to NULL! // pnnode->pNext=NULL; } ///////////////////////////////////////////////////////////////////////////// // // kmxlFindControlInNoteList // // This routine walks the linked list looking for this control. Because all controls // are globally allocated, all pControl addresses will be unique. Thus, all // we really need to do is find the control. If an exact match is found, // pnnode is returned. // PNOTENODE kmxlFindControlInNoteList( IN PMXLCONTROL pControl ) { PNOTENODE pnnode; PCONTROLLINK pcLink; PAGED_CODE(); for (pnnode=firstnotenode;pnnode!=NULL;pnnode=pnnode->pNext) { // // Can't check entire structure because pmxd may have been cleaned out. // DPFASSERT(pnnode->dwSig == NOTIFICATION_SIGNATURE); for(pcLink=pnnode->pcLink;pcLink!=NULL;pcLink=pcLink->pNext) { DPFASSERT(IsValidControlLink(pcLink)); if( pcLink->pControl == pControl ) { // // We found this control in the list! // return pnnode; } } } return NULL; } ///////////////////////////////////////////////////////////////////////////// // // kmxlFindControlTypeInList // // This routine walks // PMXLCONTROL kmxlFindControlTypeInList( IN PNOTENODE pnnode, IN DWORD dwControlType ) { PCONTROLLINK pcLink; PAGED_CODE(); for(pcLink=pnnode->pcLink;pcLink!=NULL;pcLink=pcLink->pNext) { DPFASSERT(IsValidControlLink(pcLink)); if( pcLink->pControl->Control.dwControlType == dwControlType ) { // // We found this control in the list! Types match. // DPF(DL_TRACE|FA_NOTE,("Found Correct pControl %08X",pcLink->pControl) ); return pcLink->pControl; } } return NULL; } #ifdef DEBUG ///////////////////////////////////////////////////////////////////////////// // // kmxlFindAddressInNoteList // // This routine walks the linked list looking for this control within this // context. If an exact match is found, pnnode is returned. // VOID kmxlFindAddressInNoteList( IN PMXLCONTROL pControl ) { PNOTENODE pnnode; PCONTROLLINK pcLink; PAGED_CODE(); for (pnnode=firstnotenode;pnnode!=NULL;pnnode=pnnode->pNext) { DPFASSERT(pnnode->dwSig == NOTIFICATION_SIGNATURE); // // Let's look to see if this address is one of our pControls! // for(pcLink=pnnode->pcLink;pcLink!=NULL;pcLink=pcLink->pNext) { DPFASSERT(pcLink->dwSig == CONTROLLINK_SIGNATURE); if( pcLink->pControl == pControl ) { // // We found this control in the list - in the right context! // DPF(DL_ERROR|FA_NOTE,("Found pControl(%08X) in our list!",pControl) ); return ; } } } return ; } #endif ///////////////////////////////////////////////////////////////////////////// // // kmxlFindNodeInNoteList // // Walks the node list looking for this node. This must be called within the // mtxMutex. // NTSTATUS kmxlFindNodeInNoteList( IN PNOTENODE pnlookupnode ) { PNOTENODE pnnode; PAGED_CODE(); for (pnnode=firstnotenode;pnnode!=NULL;pnnode=pnnode->pNext) { if( pnnode == pnlookupnode ) { // // Only if we find what we're looking for do we actually verify // that it's still good. // DPFASSERT(IsValidNoteNode(pnnode)); // // we found this control in the list // return STATUS_SUCCESS; } } return STATUS_UNSUCCESSFUL; } ///////////////////////////////////////////////////////////////////////////// // // kmxlFindIdInContextInNoteList // // Walks the notification list looking for the node that contains this id in // this context. // PNOTENODE kmxlFindIdInContextInNoteList( IN PWDMACONTEXT pWdmaContext, IN PMIXERDEVICE pmxd, IN DWORD NodeId ) { PNOTENODE pnTmp; PAGED_CODE(); for (pnTmp=firstnotenode;pnTmp!=NULL;pnTmp=pnTmp->pNext) { DPFASSERT(IsValidNoteNode(pnTmp)); if(( pnTmp->NodeId == NodeId ) && ( pnTmp->pmxd == pmxd ) && ( pnTmp->pContext == pWdmaContext ) ) { // // we found this control in the list in this context. // return pnTmp; } } // // We have walked the list. There is no control with this ID in this Context. // return NULL; } ///////////////////////////////////////////////////////////////////////////// // // kmxlFindContextInNoteList // PNOTENODE kmxlFindContextInNoteList( IN PWDMACONTEXT pWdmaContext ) { PNOTENODE pnTmp; PAGED_CODE(); for (pnTmp=firstnotenode;pnTmp!=NULL;pnTmp=pnTmp->pNext) { DPFASSERT(IsValidNoteNode(pnTmp)); if( pnTmp->pContext == pWdmaContext ) { // // Found this context in our list. // DPFBTRAP(); return pnTmp; } } // // We have walked the list. There is no control with this ID in this Context. // return NULL; } ///////////////////////////////////////////////////////////////////////////// // // kmxlAddControlToNoteList // // Adds this pControl to pcLink list. // NTSTATUS kmxlAddControlToNoteList( IN OUT PNOTENODE pnnode, IN PMXLCONTROL pControl ) { NTSTATUS Status=STATUS_SUCCESS; PCONTROLLINK pcLink = NULL; PAGED_CODE(); #ifdef DEBUG // // Let's walk the list and make double sure that this node is not already in // the list. // for(pcLink=pnnode->pcLink;pcLink!=NULL;pcLink=pcLink->pNext) { if( pcLink->pControl == pControl ) { DPF(DL_ERROR|FA_NOTE,("pControl(%08X) already in list!") ); return STATUS_UNSUCCESSFUL; } } #endif Status = AudioAllocateMemory_Fixed(sizeof( CONTROLLINK ), TAG_AuDL_LINK, ZERO_FILL_MEMORY, &pcLink ); if( !NT_SUCCESS( Status ) ) { DPF(DL_ERROR|FA_NOTE,("Wasn't able to add control to list! Status=%08X",Status) ); return Status; } #ifdef DEBUG pcLink->dwSig=CONTROLLINK_SIGNATURE; #endif pcLink->pControl=pControl; // // Make new head. // // if( pnnode->pcLink != NULL ) // _asm int 3 pcLink->pNext=pnnode->pcLink; pnnode->pcLink=pcLink; DPF(DL_TRACE|FA_NOTE,("Added pControl %d to pnnode(%08X)",pControl->Control.dwControlID,pnnode) ); return Status; } PCONTROLLINK kmxlRemoveControlFromNoteList( IN OUT PNOTENODE pnnode, IN PMXLCONTROL pControl ) { PCONTROLLINK pcLink,pcTmp; PAGED_CODE(); // // Does the first node contain our pControl? // DPFASSERT(IsValidControlLink(pnnode->pcLink)); for(pcLink=pnnode->pcLink;pcLink!=NULL;pcLink=pcLink->pNext) { if( pcLink->pControl == pControl ) break; } if( pcLink == pnnode->pcLink ) { pnnode->pcLink = pcLink->pNext; DPF(DL_TRACE|FA_NOTE,("removed head pControlLink") ); } else { for( pcTmp=pnnode->pcLink;pcTmp!=NULL;pcTmp=pcTmp->pNext) { if( pcTmp->pNext == pcLink ) { pcTmp->pNext = pcLink->pNext; DPF(DL_TRACE|FA_NOTE,("Removed Middle pControlLink") ); break; } } } // DPFASSERT(IsValidNoteNode(pnnode)); return pcLink; } ///////////////////////////////////////////////////////////////////////////// // // kmxlQueryControlChange // // Query's to see if control change notifications are supported on this // control. // NTSTATUS kmxlQueryControlChange( IN PFILE_OBJECT pfo, // Handle of the topology driver instance IN ULONG NodeId //PMXLCONTROL pControl ) { NTSTATUS Status; KSE_NODE KsNodeEvent; ULONG BytesReturned; PAGED_CODE(); // initialize event for basic support query RtlZeroMemory(&KsNodeEvent,sizeof(KSE_NODE)); KsNodeEvent.Event.Set = KSEVENTSETID_AudioControlChange; KsNodeEvent.Event.Id = KSEVENT_CONTROL_CHANGE; KsNodeEvent.Event.Flags = KSEVENT_TYPE_BASICSUPPORT | KSPROPERTY_TYPE_TOPOLOGY; KsNodeEvent.NodeId = NodeId; DPF(DL_TRACE|FA_SYSAUDIO,("IOCTL_KS_ENABLE_EVENT") ); Status = KsSynchronousIoControlDevice( pfo, // The FILE_OBJECT for SysAudio KernelMode, // Call originates in Kernel mode IOCTL_KS_ENABLE_EVENT, // KS PROPERTY IOCTL &KsNodeEvent, // Pointer to the KSNODEPROPERTY struct sizeof(KSE_NODE), // Number or bytes input NULL, // Pointer to the buffer to store output 0, // Size of the output buffer &BytesReturned // Number of bytes returned from the call ); if (!NT_SUCCESS(Status)) { DPF( DL_MAX|FA_HARDWAREEVENT, ("NODE #%d: KSEVENT_CONTROL_CHANGE Not Supported Error %08X",NodeId,Status) ); RETURN( Status ); } DPF(DL_TRACE|FA_HARDWAREEVENT ,("NodeId #%d: KSEVENT_CONTROL_CHANGE Supported",NodeId) ); return Status; } ///////////////////////////////////////////////////////////////////////////// // // kmxlEnableControlChange // // Turns on Control chnage notifications on this control. // NTSTATUS kmxlEnableControlChange( IN PFILE_OBJECT pfo, // Handle of the topology driver instance IN ULONG NodeId, //PMXLCONTROL pControl, IN OUT PKSEVENTDATA pksed ) { NTSTATUS Status; KSE_NODE KsNodeEvent; ULONG BytesReturned; PAGED_CODE(); // try to add an event RtlZeroMemory(&KsNodeEvent,sizeof(KSE_NODE)); KsNodeEvent.Event.Set = KSEVENTSETID_AudioControlChange; KsNodeEvent.Event.Id = KSEVENT_CONTROL_CHANGE; KsNodeEvent.Event.Flags = KSEVENT_TYPE_ENABLE | KSPROPERTY_TYPE_TOPOLOGY; KsNodeEvent.NodeId = NodeId; DPF(DL_TRACE|FA_SYSAUDIO,("IOCTL_KS_ENABLE_EVENT") ); Status = KsSynchronousIoControlDevice( pfo, // The FILE_OBJECT for SysAudio KernelMode, // Call originates in Kernel mode IOCTL_KS_ENABLE_EVENT, // KS PROPERTY IOCTL &KsNodeEvent, // Pointer to the KSNODEPROPERTY struct sizeof(KSE_NODE), // Number or bytes input pksed, // Pointer to the buffer to store output sizeof(KSEVENTDATA), // Size of the output buffer &BytesReturned // Number of bytes returned from the call ); if (!NT_SUCCESS(Status)) { DPF(DL_WARNING|FA_HARDWAREEVENT ,("KSEVENT_CONTROL_CHANGE Enable FAILED Error %08X",Status) ); RETURN( Status ); } DPF(DL_TRACE|FA_HARDWAREEVENT , ("KSEVENT_CONTROL_CHANGE Enabled on NodeId #%d",NodeId) ); return Status; } ///////////////////////////////////////////////////////////////////////////// // // kmxlDisableControlChange // // Turns off Control chnage notifications on this control. // NTSTATUS kmxlDisableControlChange( IN PFILE_OBJECT pfo, // Handle of the topology driver instance IN ULONG NodeId, //PMXLCONTROL pControl, IN OUT PKSEVENTDATA pksed ) { NTSTATUS Status; KSE_NODE KsNodeEvent; ULONG BytesReturned; PAGED_CODE(); // initialize event for basic support query RtlZeroMemory(&KsNodeEvent,sizeof(KSE_NODE)); KsNodeEvent.Event.Set = KSEVENTSETID_AudioControlChange; KsNodeEvent.Event.Id = KSEVENT_CONTROL_CHANGE; KsNodeEvent.Event.Flags = KSEVENT_TYPE_BASICSUPPORT | KSPROPERTY_TYPE_TOPOLOGY; KsNodeEvent.NodeId = NodeId; DPF(DL_TRACE|FA_SYSAUDIO,("IOCTL_KS_DISABLE_EVENT") ); Status = KsSynchronousIoControlDevice( pfo, // The FILE_OBJECT for SysAudio KernelMode, // Call originates in Kernel mode IOCTL_KS_DISABLE_EVENT, // KS PROPERTY IOCTL pksed, // Pointer to the buffer to store output sizeof(KSEVENTDATA), // Size of the output buffer NULL, // Pointer to the KSNODEPROPERTY struct 0, // Number or bytes input &BytesReturned // Number of bytes returned from the call ); if (!NT_SUCCESS(Status)) { DPF(DL_WARNING|FA_HARDWAREEVENT,("KSEVENT_CONTROL_CHANGE Disable FAILED") ); RETURN( Status ); } DPF(DL_TRACE|FA_HARDWAREEVENT, ("KSEVENT_CONTROL_CHANGE disabled on NodeId #%d",NodeId) ); return Status; } VOID kmxlGrabNoteMutex( ) { // // Turn off the APCDisable flag in the thread structure before going for our // mutex. This will prevent us from getting suspeneded while holding this // mutex. // KeEnterCriticalRegion(); KeWaitForMutexObject(&mtxNote, Executive, KernelMode, FALSE, NULL); } VOID kmxlReleaseNoteMutex( ) { KeReleaseMutex(&mtxNote, FALSE); KeLeaveCriticalRegion(); } ///////////////////////////////////////////////////////////////////////////// // // kmxlEnableControlChangeNotifications // // This routine gets called every time a new control is created. At that point // we're going to query the node to see if it supports change notifications // and enable them in this context. // NTSTATUS kmxlEnableControlChangeNotifications( IN PMIXEROBJECT pmxobj, IN PMXLLINE pLine, // Line that owns control IN PMXLCONTROL pControl // Control to Enable ) { PNOTENODE pnnode; NTSTATUS Status; LONG i; PMIXERDEVICE pmxd; PWDMACONTEXT pWdmaContext; kmxlGrabNoteMutex(); DPFASSERT(IsValidMixerObject(pmxobj)); pmxd=pmxobj->pMixerDevice; DPFASSERT(IsValidMixerDevice(pmxd)); pWdmaContext=pmxd->pWdmaContext; PAGED_CODE(); // // Never allow anything in the list if it's not valid! // DPFASSERT(IsValidControl(pControl)); // // The first thing we do is look to see if a control of this ID is enabled in // this context. If so, we'll get a PNOTENODE structure returned to us that // contains that ID. All we should have to do is add our new pControl. // if( pnnode=kmxlFindIdInContextInNoteList(pWdmaContext,pmxd,pControl->Id) ) // Not pControl->Control.dwControlID { // // yes there is. Add this pControl to this pnnode and we're done. // Status=kmxlAddControlToNoteList(pnnode,pControl); } else { // // There is no Control with this ID in this Context. Let's try to // Add one. // // // This node is not in our list, we need to add it if and only if it // supports change notifications // Status=kmxlQueryControlChange(pmxd->pfo,pControl->Id); //pLocfo if( NT_SUCCESS(Status) ) { // // This control supports notifications, thus add info to our // global list - if it's not already there... // if( (pnnode=kmxlNewNoteNode()) != NULL ) { if( (pnnode->pcLink=kmxlNewControlLink(pControl)) != NULL ) { pnnode->NodeId=pControl->Id; // // We have a new notification node to fill out. // pnnode->ControlID=pControl->Control.dwControlID; pnnode->ControlType=pControl->Control.dwControlType; pnnode->LineID=pLine->Line.dwLineID; pnnode->pContext=pWdmaContext; //NOTENODE pnnode->pmxd=pmxd; DPF(DL_TRACE|FA_NOTE , ("Init pnnode, NodeId=#%d: CtrlID=%X CtrlType=%X Context=%08X", pnnode->NodeId, //pControl->Id pnnode->ControlID, //pControl->Control.dwControlID, pnnode->ControlType, pnnode->pContext) ); //pControl->Control.dwControlType) ); // // Now setup the DPC handling // KeInitializeDpc(&pnnode->NodeEventDPC,NodeEventDPCHandler,pnnode); pnnode->NodeEventData.NotificationType=KSEVENTF_DPC; pnnode->NodeEventData.Dpc.Dpc=&pnnode->NodeEventDPC; // // At this point, we've got a little window. We call and enable // events on this control. From that time until the time we actually // add it to the list, if an event fires, we will not find this node // in the list - thus we will not process it. // Status=kmxlEnableControlChange(pnnode->pmxd->pfo, pnnode->NodeId, //pControl, &pnnode->NodeEventData); //&NodeEvent[NumEventDPCs].NodeEventData); if( NT_SUCCESS(Status) ) { DPF(DL_TRACE|FA_HARDWAREEVENT,("Enabled Control #%d in context(%08X)!",pControl->Id,pWdmaContext) ); // // Now let's add it to our global list // // kmxlAddNoteNodeToList(pnnode); DPFASSERT(IsValidNoteNode(pnnode)); } else { DPF(DL_WARNING|FA_HARDWAREEVENT,("Failed to Enable Control #%d!",pControl->Id) ); DPFBTRAP(); // // For some reason, this node failed to enable. // kmxlFreeControlLink(pnnode->pcLink); kmxlFreeNoteNode(pnnode); } } else { DPF(DL_ERROR|FA_NOTE,("kmxlNewControlLink failed") ); kmxlFreeNoteNode(pnnode); } } else { DPF(DL_WARNING|FA_NOTE,("kmxlNewNoteNode failed") ); } } else { DPF(DL_MAX|FA_HARDWAREEVENT,("This control #%d doesn't support change notifications",pControl->Id) ); } } kmxlReleaseNoteMutex(); return Status; } ///////////////////////////////////////////////////////////////////////////// // // kmxlDisableControlChangeNotifications // // This routine gets called every time a control is freed. We walk the list // of enable controls and see if it's there. If so, we disable and clean up. // if it's not in the list, it didn't support change notifications. // NTSTATUS kmxlDisableControlChangeNotifications( IN PMXLCONTROL pControl ) { NTSTATUS Status=STATUS_SUCCESS; PNOTENODE pnnode; PAGED_CODE(); kmxlGrabNoteMutex(); DPFASSERT(IsValidControl(pControl)); // // Find this control in our list. // if(pnnode=kmxlFindControlInNoteList(pControl)) //pWdmaContext, { PCONTROLLINK pcLink; #ifdef DEBUG if( pControl->Id != pnnode->NodeId ) { DPF(DL_ERROR|FA_NOTE,("Control NodeId Changed! CtrlId=%08X,pnnodeID=%08X", pControl->Id,pnnode->NodeId) ); } #endif // // This call removes pControl from this node. note that after this // control is removed, pnnode->pcLink will be NULL if there are no // more controls hanging on this Notification node. Thus, we need // to disable it. // pcLink=kmxlRemoveControlFromNoteList(pnnode,pControl); if( pnnode->pcLink == NULL ) { // // During cleanup, the mixer device structure will have already been // cleaned up. This can be seen because the pmxd->Device entry will // be UNUSED_DEVICE. Thus, we can't do anything on that mixerdevice. // if( ( pnnode->pmxd->Device != UNUSED_DEVICE ) && ( pnnode->pmxd->pfo != NULL ) ) { // // There are no references to this node, we can free it. But, if // this disable call fails, then the node was already distroyed. // Status=kmxlDisableControlChange(pnnode->pmxd->pfo,pnnode->NodeId,&pnnode->NodeEventData); if( !NT_SUCCESS(Status) ) { DPF(DL_WARNING|FA_NOTE,("Not able to disable! pnnode %08X",pnnode) ); } } else { DPF(DL_WARNING|FA_NOTE,("pmxd is invalid %08X",pnnode->pmxd) ); } kmxlRemoveNoteNodeFromList(pnnode); kmxlFreeNoteNode(pnnode); } DPF(DL_TRACE|FA_NOTE,("Removed PCONTROLLINK(%08X) for pControl(%08X)",pcLink,pcLink->pControl) ); kmxlFreeControlLink(pcLink); } else { // // We get called on every control free. Thus, many will not be in our list. // DPF(DL_MAX|FA_NOTE,("Control=%d not in List!",pControl->Id) ); } kmxlReleaseNoteMutex(); return Status; } ///////////////////////////////////////////////////////////////////////////// // // kmxlRemoveContextFromNoteList // // This routine gets called when this context is going away. Thus, we need to // remove it from our list. // VOID kmxlRemoveContextFromNoteList( IN PWDMACONTEXT pContext ) { NTSTATUS Status; PNOTENODE pnnode; PCONTROLLINK pcLink; PAGED_CODE(); kmxlGrabNoteMutex(); // // If we find this context is still alive in our list, someone leaked a control. // Things should have been cleaned up when the controls went away! But, for // some reasons they didn't. // // There could be multiple pContext nodes in list. // while( (pnnode=kmxlFindContextInNoteList(pContext)) != NULL ) { DPFASSERT(IsValidNoteNode(pnnode)); kmxlRemoveNoteNodeFromList(pnnode); // // There can be multiple Controls on this Notification node. // while( (pnnode->pcLink != NULL) && ( (pcLink=kmxlRemoveControlFromNoteList(pnnode,pnnode->pcLink->pControl)) != NULL) ) { // // To get here, pcLink is Valid. If it was the last pControl, then // we want to turn off change notifications on it. // if( pnnode->pcLink == NULL ) { // // There are no references to this node, we can free it. // Status=kmxlDisableControlChange(pnnode->pmxd->pfo,pnnode->NodeId,&pnnode->NodeEventData); DPFASSERT( Status == STATUS_SUCCESS ); DPFBTRAP(); } kmxlFreeControlLink(pcLink); DPFBTRAP(); } kmxlFreeNoteNode(pnnode); } DPF(DL_TRACE|FA_NOTE,("pWdmaContext %08X going away",pContext) ); kmxlReleaseNoteMutex(); } ///////////////////////////////////////////////////////////////////////////// // // kmxlCleanupNotelist // // Driver is unloading, turn everything off and free the memory! // VOID kmxlCleanupNoteList( ) { NTSTATUS Status; PNOTENODE pnnode,pnnodeFree; PCONTROLLINK pcLink; PAGED_CODE(); kmxlGrabNoteMutex(); // // If we find this context is still alive in our list, someone leaked a control. // Things should have been cleaned up when the controls went away! But, for // some reasons they didn't. // // There could be multiple pContext nodes in list. // pnnode=firstnotenode; while( pnnode ) { DPFASSERT(IsValidNoteNode(pnnode)); DPF(DL_ERROR|FA_NOTE,("pnnode(%08X) found in Notification List!",pnnode) ); kmxlRemoveNoteNodeFromList(pnnode); // // There can be multiple Controls on this Notification node. // while( (pnnode->pcLink != NULL) && ( (pcLink=kmxlRemoveControlFromNoteList(pnnode,pnnode->pcLink->pControl)) != NULL) ) { // // To get here, pcLink is Valid. If it was the last pControl, then // we want to turn off change notifications on it. // if( pnnode->pcLink == NULL ) { // // There are no references to this node, we can free it. // Status=kmxlDisableControlChange(pnnode->pmxd->pfo,pnnode->NodeId,&pnnode->NodeEventData); DPFASSERT( Status == STATUS_SUCCESS ); DPFBTRAP(); } kmxlFreeControlLink(pcLink); DPFBTRAP(); } pnnodeFree=pnnode; pnnode=pnnode->pNext; kmxlFreeNoteNode(pnnodeFree); DPFBTRAP(); } DPF(DL_TRACE|FA_NOTE,("Done with cleanup") ); kmxlReleaseNoteMutex(); } ///////////////////////////////////////////////////////////////////////////// // // kmxlPersistHWControlWorker // // When kmxlPersistHWControlWorker gets called, the pData value is a pointer // to a globally allocated NOTENODE structure. Thus, we have all the context // we need with regard to persisting the control. We just need to make sure // that our node didn't go away between the time the evern was scheduled and // when we got called. // VOID kmxlPersistHWControlWorker( PVOID pReference ) { NTSTATUS Status; PMXLCONTROL pControl; PVOID paDetails = NULL; // kmxlPersistSingleControl() allocates paDetails // via kmxlGetCurrentControlValue() PNOTENODE pnnode; PHWLINK phwlink; PLIST_ENTRY ple; PAGED_CODE(); // // We set HardwareCallbackSchedule to 0 here so that we can start adding new // events for handling hardware notifications. Note: we do that here at the // start of the routine so that there will not be a window where we have // something in the list that we never get a event scheduled for. In other // words, this routine handles empty lists. // HardwareCallbackScheduled=0; // // while we have events in our queue, get one and service it. // while((ple = ExInterlockedRemoveHeadList(&HardwareCallbackListHead, &HardwareCallbackSpinLock)) != NULL) { phwlink = CONTAINING_RECORD(ple, HWLINK, Next); DPFASSERT(phwlink->dwSig == HWLINK_SIGNATURE); // // Get our data for this event and then free the link that was allocated in // the DPC handler. // pnnode=phwlink->pnnode; AudioFreeMemory(sizeof(HWLINK),&phwlink); // // We are going to be working in this context for a while. Thus, we're going // to enter our mtxNote mutex to make sure that our node doesn't go away // while we're persisting the values! // kmxlGrabNoteMutex(); // // Now our list can't chnage! Is this node still valid? If we don't find // it in the list, it was removed before this event fired. Thus, there is // nothing that we can do. --- Free mutex and leave. // Status=kmxlFindNodeInNoteList(pnnode); if( NT_SUCCESS(Status) ) { DPF( DL_TRACE|FA_HARDWAREEVENT , ("Entering NodeId %d LineID %X ControlID %d ControlType = %X", pnnode->NodeId, pnnode->LineID, pnnode->ControlID, pnnode->ControlType) ); // // Yes. It's still valid. Persist the control. // pControl=kmxlFindControlTypeInList(pnnode,pnnode->ControlType); if( pControl ) { Status = kmxlPersistSingleControl( pnnode->pmxd->pfo, pnnode->pmxd, pControl, // pControl here... paDetails ); } if( !NT_SUCCESS(Status) ) { // // On shutdown, we might get an event that fires after things have // been cleaned up. // if( Status != STATUS_TOO_LATE ) { DPF(DL_WARNING|FA_NOTE, ("Failure from kmxlPersistSingleControl Status=%X",Status) ); } } else { DPF(DL_TRACE|FA_HARDWAREEVENT ,("Done - success") ); } } else { DPF(DL_WARNING|FA_NOTE,("pnnode=%08X has been removed!",pnnode) ); } // // Persist this control! // kmxlReleaseNoteMutex(); } DPF(DL_TRACE|FA_HARDWAREEVENT ,("exit") ); } ///////////////////////////////////////////////////////////////////////////// // // kmxlGetLineForControl // // For every line on this mixer device, look at every control to see if we // can find this control. If found, return this line pointer. // NTSTATUS kmxlEnableAllControls( IN PMIXEROBJECT pmxobj ) { NTSTATUS Status=STATUS_SUCCESS; PMIXERDEVICE pmxd; PMXLLINE pLine; PMXLCONTROL pControl; PAGED_CODE(); // // The first time through we will most likily not have a pfo in the MIXERDEVICE // structure, thus fill it in! // DPFASSERT(pmxobj->dwSig == MIXEROBJECT_SIGNATURE ); DPFASSERT(pmxobj->pMixerDevice != NULL ); DPFASSERT(pmxobj->pMixerDevice->dwSig == MIXERDEVICE_SIGNATURE ); pmxd=pmxobj->pMixerDevice; if( pmxd->pfo == NULL ) { DPF(DL_WARNING|FA_NOTE,("fo is NULL, it should have been set!") ); // // We need to assign a SysAudio FILE_OBJECT to this mixer device so that // we can talk to it. // if( NULL==(pmxd->pfo=kmxlOpenSysAudio())) { DPF(DL_WARNING|FA_NOTE,("OpenSysAudio failed") ); return STATUS_UNSUCCESSFUL; } Status = SetSysAudioProperty( pmxd->pfo, KSPROPERTY_SYSAUDIO_DEVICE_INSTANCE, sizeof( pmxd->Device ), &pmxd->Device ); if( !NT_SUCCESS( Status ) ) { kmxlCloseSysAudio( pmxd->pfo ); pmxd->pfo=NULL; DPF(DL_WARNING|FA_NOTE,("SetSysAudioProperty failed %X",Status) ); return Status; } } DPFASSERT(IsValidMixerObject(pmxobj)); for(pLine = kmxlFirstInList( pmxd->listLines ); pLine != NULL; pLine = kmxlNextLine( pLine ) ) { DPFASSERT(IsValidLine(pLine)); for(pControl = kmxlFirstInList( pLine->Controls ); pControl != NULL; pControl = kmxlNextControl( pControl ) ) { DPFASSERT(IsValidControl(pControl)); // // Enable Notifications if it supports it here. // DPF(DL_TRACE|FA_NOTE,("pControl->Id=%d, pControl->Control.dwControlID=%d", pControl->Id,pControl->Control.dwControlID) ); Status = kmxlEnableControlChangeNotifications(pmxobj,pLine,pControl); } } return Status; } VOID kmxlCloseMixerDevice( IN OUT PMIXERDEVICE pmxd ) { if(pmxd->pfo) { kmxlCloseSysAudio( pmxd->pfo ); pmxd->pfo = NULL; } } ///////////////////////////////////////////////////////////////////////////// // // GetHardwareEventData // // Called by user mode driver to get the notification information. // VOID GetHardwareEventData(LPDEVICEINFO pDeviceInfo) { PAGED_CODE(); if (emptyindex!=loadindex) { (pDeviceInfo->dwID)[0]=callbacks[emptyindex%CALLBACKARRAYSIZE].dwControlID; pDeviceInfo->dwLineID=callbacks[emptyindex%CALLBACKARRAYSIZE].dwLineID; pDeviceInfo->dwCallbackType=callbacks[emptyindex%CALLBACKARRAYSIZE].dwCallbackType; pDeviceInfo->ControlCallbackCount=1; emptyindex++; } pDeviceInfo->mmr=MMSYSERR_NOERROR; } /////////////////////////////////////////////////////////////////////// // // kmxdInit // // Checks to see if the mixer lines have been built for the given // index. If not, it calls kmxlBuildLines() to build up the lines. // // The topology information is kept around so that it can be dumped // via a debugger command. // // NTSTATUS kmxlInit( IN PFILE_OBJECT pfo, // Handle of the topology driver instance IN PMIXERDEVICE pMixer ) { NTSTATUS Status = STATUS_SUCCESS; HANDLE hKey; ULONG ResultLength; PAGED_CODE(); // // Check to see if the lines have already been built for this device. // If so, return success. // if( pMixer->listLines ) { RETURN( STATUS_SUCCESS ); } // // Build the lines for this device. // Status = kmxlBuildLines( pMixer, pfo, &pMixer->listLines, &pMixer->cDestinations, &pMixer->Topology ); if( NT_SUCCESS( Status ) ) { Status = kmxlOpenInterfaceKey( pfo, pMixer->Device, &hKey ); if( !NT_SUCCESS( Status ) ) { pMixer->Mapping = MIXER_MAPPING_LOGRITHMIC; Status = STATUS_SUCCESS; goto exit; } Status = kmxlRegQueryValue( hKey, L"CurveType", &pMixer->Mapping, sizeof( pMixer->Mapping ), &ResultLength ); if( !NT_SUCCESS( Status ) ) { kmxlRegCloseKey( hKey ); pMixer->Mapping = MIXER_MAPPING_LOGRITHMIC; Status = STATUS_SUCCESS; goto exit; } kmxlRegCloseKey( hKey ); } exit: // // Free up the topology allocated when the lines are built (RETAIL only). // #ifndef DEBUG if(pMixer->Topology.Categories) { ExFreePool( ( (( PKSMULTIPLE_ITEM ) pMixer->Topology.Categories )) - 1 ); pMixer->Topology.Categories = NULL; } if(pMixer->Topology.TopologyNodes) { ExFreePool( ( (( PKSMULTIPLE_ITEM ) pMixer->Topology.TopologyNodes )) - 1 ); pMixer->Topology.TopologyNodes = NULL; } if(pMixer->Topology.TopologyConnections) { ExFreePool( ( (( PKSMULTIPLE_ITEM ) pMixer->Topology.TopologyConnections )) - 1 ); pMixer->Topology.TopologyConnections = NULL; } #endif // !DEBUG RETURN( Status ); } //////////////////////////////////////////////////////////////////////// // // kmxdDeInit // // Loops through each of the lines freeing the control structures and // then the line structures. // // NTSTATUS kmxlDeInit( PMIXERDEVICE pMixer ) { PMXLLINE pLine = NULL; PMXLCONTROL pControl = NULL; PAGED_CODE(); if( pMixer->Device != UNUSED_DEVICE ) { while( pMixer->listLines ) { pLine = kmxlRemoveFirstLine( pMixer->listLines ); while( pLine && pLine->Controls ) { pControl = kmxlRemoveFirstControl( pLine->Controls ); kmxlFreeControl( pControl ); } AudioFreeMemory( sizeof(MXLLINE),&pLine ); } // // Here we need to close sysaudio as used in this mixer device. // kmxlCloseMixerDevice(pMixer); ASSERT( pMixer->listLines == NULL ); // // Free up the topology (DEBUG only) // #ifdef DEBUG if(pMixer->Topology.Categories) { ExFreePool(( (( PKSMULTIPLE_ITEM ) pMixer->Topology.Categories )) - 1 ); pMixer->Topology.Categories = NULL; } if(pMixer->Topology.TopologyNodes) { ExFreePool(( (( PKSMULTIPLE_ITEM ) pMixer->Topology.TopologyNodes )) - 1 ); pMixer->Topology.TopologyNodes = NULL; } if(pMixer->Topology.TopologyConnections) { ExFreePool(( (( PKSMULTIPLE_ITEM ) pMixer->Topology.TopologyConnections )) - 1 ); pMixer->Topology.TopologyConnections = NULL; } #endif // !DEBUG } // if RETURN( STATUS_SUCCESS ); } /////////////////////////////////////////////////////////////////////// // // kmxlBuildLines // // Builds up the line structures and count of destinations for the // given instance. // // NTSTATUS kmxlBuildLines( IN PMIXERDEVICE pMixer, // The mixer IN PFILE_OBJECT pfoInstance, // The FILE_OBJECT of a filter instance IN OUT LINELIST* plistLines, // Pointer to the list of all lines IN OUT PULONG pcDestinations, // Pointer to the number of dests IN OUT PKSTOPOLOGY pTopology // Pointer to a topology structure ) { NTSTATUS Status = STATUS_SUCCESS; MIXEROBJECT mxobj; LINELIST listSourceLines = NULL; NODELIST listSources = NULL; NODELIST listDests = NULL; PMXLNODE pTemp = NULL; ULONG i; PAGED_CODE(); ASSERT( pfoInstance ); ASSERT( plistLines ); ASSERT( pcDestinations ); ASSERT( pTopology ); RtlZeroMemory( &mxobj, sizeof( MIXEROBJECT ) ); // Set up the MIXEROBJECT. Note that this structure is used only within // the scope of this function, so it is okay to simply copy the // DeviceInterface pointer from the MIXERDEV structure. mxobj.pfo = pfoInstance; mxobj.pTopology = pTopology; mxobj.pMixerDevice = pMixer; mxobj.DeviceInterface = pMixer->DeviceInterface; #ifdef DEBUG mxobj.dwSig = MIXEROBJECT_SIGNATURE; #endif // // Read the topology from the device // DPF(DL_TRACE|FA_MIXER,("Querying Topology") ); Status = kmxlQueryTopology( mxobj.pfo, mxobj.pTopology ); if( !NT_SUCCESS( Status ) ) { goto exit; } // // Build up the node table. The node table is the mixer line's internal // representation of the topology for easier processing. // DPF(DL_TRACE|FA_MIXER,("Building Node Table") ); mxobj.pNodeTable = kmxlBuildNodeTable( mxobj.pTopology ); if( !mxobj.pNodeTable ) { Status = STATUS_INSUFFICIENT_RESOURCES; DPF(DL_WARNING|FA_MIXER,("kmxlBuildNodeTable failed") ); goto exit; } // // Parse the topology and build the necessary data structures // to walk the topology. // DPF(DL_TRACE|FA_MIXER,("Parsing Topology") ); Status = kmxlParseTopology( &mxobj, &listSources, &listDests ); if( !NT_SUCCESS( Status ) ) { DPF(DL_WARNING|FA_MIXER,("kmxlParseTopoloty failed Status=%X",Status) ); goto exit; } // // Build up a list of destination lines. // DPF(DL_TRACE|FA_MIXER,("Building Destination lines") ); *plistLines = kmxlBuildDestinationLines( &mxobj, listDests ); if( !(*plistLines) ) { Status = STATUS_INSUFFICIENT_RESOURCES; DPF(DL_WARNING|FA_MIXER,("kmxlBuildDestinationLines failed") ); goto exit; } // // Assign the line Ids and the Control Ids for the destinations. Also, // fill in the number of destinations. // kmxlAssignLineAndControlIds( &mxobj, (*plistLines), DESTINATION_LIST ); *pcDestinations = kmxlListLength( (*plistLines) ); // // Build up a list of source lines // DPF(DL_TRACE|FA_MIXER,("Building Source lines") ); listSourceLines = kmxlBuildSourceLines( &mxobj, listSources, listDests ); if( !listSourceLines ) { Status = STATUS_INSUFFICIENT_RESOURCES; DPF(DL_WARNING|FA_MIXER,("kmxlBuildSourceLines failed") ); goto exit; } // // Polish off the lines. First sort them by destination so that // the source Ids will be assigned correctly. // DPF(DL_TRACE|FA_MIXER,("Sort By Destination") ); kmxlSortByDestination( &listSourceLines ); DPF(DL_TRACE|FA_MIXER,("Assign Line and Control Ids") ); kmxlAssignLineAndControlIds( &mxobj, listSourceLines, SOURCE_LIST ); // // Now assign destinations to sources and construct the line Ids for // the source lines. // DPF(DL_TRACE|FA_MIXER,("Assign Destinations to Sources") ); kmxlAssignDestinationsToSources( listSourceLines, (*plistLines) ); // // Update the number of sources mapping to each of the destinations. // DPF(DL_TRACE|FA_MIXER,("Update Destination Connection Count") ); kmxlUpdateDestintationConnectionCount( listSourceLines, (*plistLines) ); // // Assign the dwComponentIds for the source and destination lines. // DPF(DL_TRACE|FA_MIXER,("Assign Conponent IDs") ); kmxlAssignComponentIds( &mxobj, listSourceLines, (*plistLines) ); // // Construct a single list of lines. Destinations will be first in // increasing numerical order by line id, folowed by sources in // increasing numerical order. // kmxlAppendListToEndOfList( (PSLIST*) plistLines, (PSLIST) listSourceLines ); // // Eliminate any lines that are invalid. // DPF(DL_TRACE|FA_MIXER,("Eliminate Invalid Lines") ); kmxlEliminateInvalidLines( plistLines ); // // Update the mux line IDs to match real line IDs // DPF(DL_TRACE|FA_MIXER,("Assign Mux IDs") ); kmxlAssignMuxIds( &mxobj, *plistLines ); // // Here is where we want to Enable Change Notifications on all controls // that support notifications. // DPF(DL_TRACE|FA_MIXER,("Enable All Controls") ); kmxlEnableAllControls(&mxobj); exit: // // If we got here because of an error, clean up all the mixer lines // if( !NT_SUCCESS( Status ) ) { PMXLLINE pLine; PMXLCONTROL pControl; while( (*plistLines) ) { pLine = kmxlRemoveFirstLine( (*plistLines) ); while( pLine && pLine->Controls ) { pControl = kmxlRemoveFirstControl( pLine->Controls ); kmxlFreeControl( pControl ); } AudioFreeMemory( sizeof(MXLLINE),&pLine ); } } // // Free up the mux control list. Note that we don't want to free // the controls using kmxlFreeControl() because we need the special // mux instance data to persist. // { PMXLCONTROL pControl; while( mxobj.listMuxControls ) { pControl = kmxlRemoveFirstControl( mxobj.listMuxControls ); ASSERT( pControl->pChannelStepping == NULL); AudioFreeMemory( sizeof(MXLCONTROL),&pControl ); } } // // Free up the source and destination lists. Both types of these lists // are allocated list nodes and allocated nodes. Both need to be freed. // The Children and Parent lists, though, are only allocated list nodes. // The actual nodes are contained in the node table and will be deallocated // in one chunk in the next block of code. // while( listSources ) { pTemp = kmxlRemoveFirstNode( listSources ); kmxlFreePeerList( pTemp->Children ); AudioFreeMemory( sizeof(MXLNODE),&pTemp ); } while( listDests ) { pTemp = kmxlRemoveFirstNode( listDests ); kmxlFreePeerList( pTemp->Parents ); AudioFreeMemory( sizeof(MXLNODE),&pTemp ); } // // Free up the peer lists for the children and parents inside the // nodes of the node table. Finally, deallocate the array of nodes. // if( mxobj.pNodeTable ) { for( i = 0; i < mxobj.pTopology->TopologyNodesCount; i++ ) { kmxlFreePeerList( mxobj.pNodeTable[ i ].Children ); kmxlFreePeerList( mxobj.pNodeTable[ i ].Parents ); } AudioFreeMemory_Unknown( &mxobj.pNodeTable ); } RETURN( Status ); } /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// // // // M I X E R L I N E F U N C T I O N S // // // /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// // // kmxlBuildDestinationLines // // Loops through each of the destination nodes, allocates a line // structure for it, and calls kmxlBuildDestinationControls to // build the controls for that line. // // LINELIST kmxlBuildDestinationLines( IN PMIXEROBJECT pmxobj, IN NODELIST listDests // The list of destination nodes ) { LINELIST listDestLines = NULL; PMXLNODE pDest = NULL; PMXLLINE pLine = NULL; PMXLCONTROL pControl = NULL; ULONG MaxChannelsForLine; ASSERT( pmxobj ); ASSERT( listDests ); PAGED_CODE(); // // Loop over all the destination node allocating a line structure // for each. // pDest = kmxlFirstInList( listDests ); while( pDest ) { // // Allocate a new line structure and add it to the list of // destination lines. // pLine = kmxlAllocateLine( TAG_AudL_LINE ); if( !pLine ) { goto exit; } kmxlAddToList( listDestLines, pLine ); // // Fill in the details of the line structure. Some fields will // be filled in later. // pLine->DestId = pDest->Id; pLine->Type = pDest->NodeType; pLine->Communication = pDest->Communication; pLine->Line.cbStruct = sizeof( MIXERLINE ); pLine->Line.dwSource = (DWORD) -1; pLine->Line.dwDestination = (DWORD) -1; kmxlGetPinName( pmxobj->pfo, pDest->Id, pLine ); // // HACK! The ACTIVE flag should only be set when the line is active // but then no lines show up in SNDVOL32. It works if the flag is // always set to ACTIVE for destinations. Also, the number of channels // should be queried not hard coded. WDM Audio does not provide a // way to easily query this. // pLine->Line.fdwLine = MIXERLINE_LINEF_ACTIVE; pLine->Line.cChannels = 1; // should this default to 1 or 2? // // Build up a list of the controls on this destination // if( !NT_SUCCESS( kmxlBuildDestinationControls( pmxobj, pDest, pLine ) ) ) { DPF(DL_WARNING|FA_MIXER,("kmxlBuildDestinationControls failed") ); goto exit; } pDest = kmxlNextNode( pDest ); } pLine = kmxlFirstInList( listDestLines ); while( pLine ) { MaxChannelsForLine = 1; pControl = kmxlFirstInList( pLine->Controls ); while( pControl ) { ASSERT( IsValidControl( pControl ) ); if ( pControl->NumChannels > MaxChannelsForLine) { MaxChannelsForLine = pControl->NumChannels; } pControl = kmxlNextControl( pControl ); } if( pLine->Controls == NULL ) { pLine->Line.cChannels = 1; // should this default to 1 or 2? } else { pLine->Line.cChannels = MaxChannelsForLine; } pLine = kmxlNextLine( pLine ); } return( listDestLines ); exit: // // A memory allocation failed. Clean up the destination lines and // return failure. // while( listDestLines ) { pLine = kmxlRemoveFirstLine( listDestLines ); while( pLine && pLine->Controls ) { pControl = kmxlRemoveFirstControl( pLine->Controls ); kmxlFreeControl( pControl ); } AudioFreeMemory_Unknown( &pLine ); } return( NULL ); } /////////////////////////////////////////////////////////////////////// // // BuildDestinationControls // // Starts at the destination node and translates all the parent nodes // to mixer line controls. This process stops when the first SUM node // is encountered, indicating the end of a destination line. // // NTSTATUS kmxlBuildDestinationControls( IN PMIXEROBJECT pmxobj, IN PMXLNODE pDest, // The destination to built controls for IN PMXLLINE pLine // The line to add the controls to ) { PPEERNODE pTemp = NULL; PMXLCONTROL pControl; PAGED_CODE(); ASSERT( pmxobj ); ASSERT( pLine ); // // Start at the immediate parent of the node passed. // pTemp = kmxlFirstParentNode( pDest ); while( pTemp ) { if( IsEqualGUID( &pTemp->pNode->NodeType, &KSNODETYPE_SUM ) || ( pTemp->pNode->Type == SOURCE ) || ( pTemp->pNode->Type == DESTINATION ) ) { // // We've found a SUM node. Discontinue the loop... we've // found all the controls. // break; } if( IsEqualGUID( &pTemp->pNode->NodeType, &KSNODETYPE_MUX ) ) { if (kmxlTranslateNodeToControl( pmxobj, pTemp->pNode, &pControl )) { kmxlAppendListToList( (PSLIST*) &pLine->Controls, (PSLIST) pControl ); } break; } if( ( kmxlParentListLength( pTemp->pNode ) > 1 ) ) { // // Found a node with multiple parents that is not a SUM node. // Can't handle that here so add any controls for this node // and discontinue the loop. // if( kmxlTranslateNodeToControl( pmxobj, pTemp->pNode, &pControl ) ) { kmxlAppendListToList( (PSLIST*) &pLine->Controls, (PSLIST) pControl ); } break; } // // By going up through the parents and inserting nodes at // the front of the list, the list will contain the controls // in the right order. // if( kmxlTranslateNodeToControl( pmxobj, pTemp->pNode, &pControl ) ) { kmxlAppendListToList( (PSLIST*) &pLine->Controls, (PSLIST) pControl ); } pTemp = kmxlFirstParentNode( pTemp->pNode ); } DPF(DL_TRACE|FA_MIXER,( "Found %d controls on destination %d:", kmxlListLength( pLine->Controls ), pDest->Id ) ); RETURN( STATUS_SUCCESS ); } /////////////////////////////////////////////////////////////////////// // // kmxlBuildSourceLines // // Loops through each of the source nodes, allocating a new line // structure, and calling kmxlBuildPath() to build the controls // for the line (and possibly creating new lines if there are splits // in the topology). // // LINELIST kmxlBuildSourceLines( IN PMIXEROBJECT pmxobj, IN NODELIST listSources, // The list of source nodes IN NODELIST listDests // The list of dest. nodes ) { NTSTATUS Status; LINELIST listSourceLines = NULL; PMXLNODE pSource = NULL; PMXLLINE pTemp = NULL; PMXLCONTROL pControl; ULONG MaxChannelsForLine; ASSERT( pmxobj ); ASSERT( pmxobj->pfo ); ASSERT( pmxobj->pNodeTable ); ASSERT( listSources ); ASSERT( listDests ); PAGED_CODE(); pSource = kmxlFirstInList( listSources ); while( pSource ) { // // Allocate a new line structure and insert it into the list of // source lines. // pTemp = kmxlAllocateLine( TAG_AudL_LINE ); if( !pTemp ) { goto exit; } kmxlAddToEndOfList( listSourceLines, pTemp ); // // Fill in the fields of the line structure. Some fields will need // to be filled in later. // pTemp->SourceId = pSource->Id; pTemp->Type = pSource->NodeType; pTemp->Communication = pSource->Communication; pTemp->Line.cbStruct = sizeof( MIXERLINE ); pTemp->Line.dwSource = (DWORD) -1; pTemp->Line.dwDestination = (DWORD) -1; pTemp->Line.fdwLine = MIXERLINE_LINEF_SOURCE | MIXERLINE_LINEF_ACTIVE; kmxlGetPinName( pmxobj->pfo, pSource->Id, pTemp ); // DPF(DL_TRACE|FA_MIXER,( "Building path for %s (%d).", // PinCategoryToString( &pSource->NodeType ), // pSource->Id // ) ); // // Build the controls for this line and identify the destination(s) // it conntects to. // Status = kmxlBuildPath( pmxobj, pSource, // The source line to build controls for pSource, // The node to start with pTemp, // The line structure to add to &listSourceLines, // The list of all source lines listDests // THe list of all destinations ); if( !NT_SUCCESS( Status ) ) { DPF(DL_WARNING|FA_MIXER,("kmxlBuildPath failed Status=%X",Status) ); goto exit; } pSource = kmxlNextNode( pSource ); } // while( pSource ) pTemp = kmxlFirstInList( listSourceLines ); while( pTemp ) { MaxChannelsForLine = 1; pControl = kmxlFirstInList( pTemp->Controls ); while( pControl ) { ASSERT( IsValidControl( pControl ) ); if ( pControl->NumChannels > MaxChannelsForLine) { MaxChannelsForLine = pControl->NumChannels; } pControl = kmxlNextControl( pControl ); } if( pTemp->Controls == NULL ) { pTemp->Line.cChannels = 1; // should this default to 1 or 2? } else { pTemp->Line.cChannels = MaxChannelsForLine; } pTemp = kmxlNextLine( pTemp ); } return( listSourceLines ); exit: // // Something went wrong. Clean up all memory allocated and return NULL // to indicate the error. // while( listSourceLines ) { pTemp = kmxlRemoveFirstLine( listSourceLines ); while( pTemp && pTemp->Controls ) { pControl = kmxlRemoveFirstControl( pTemp->Controls ); kmxlFreeControl( pControl ); } AudioFreeMemory_Unknown( &pTemp ); } return( NULL ); } /////////////////////////////////////////////////////////////////////// // // kmxlBuildPath // // Builds the controls for a source line. A source line ends when a // SUM node, a destination node, a node contained in a destination line // is encountered. When splits are encountered in the topology, new // lines need to be created and the controls on those lines enumerated. // // kmxlBuildPath will recurse to find the controls on subnodes. // // NTSTATUS kmxlBuildPath( IN PMIXEROBJECT pmxobj, IN PMXLNODE pSource, // The source node for this path IN PMXLNODE pNode, // The current node in the path IN PMXLLINE pLine, // The current line IN OUT LINELIST* plistLines, // The list of lines build so far IN NODELIST listDests // The list of the destinations ) { NTSTATUS Status; PMXLCONTROL pControl = NULL; PMXLLINE pNewLine = NULL; ULONG nControls; PPEERNODE pChild = NULL; ASSERT( pmxobj ); ASSERT( pSource ); ASSERT( pNode ); ASSERT( pLine ); ASSERT( plistLines ); PAGED_CODE(); DPF(DL_TRACE|FA_MIXER,( "Building path for %d(0x%x) (%s) NODE=%08x", pNode->Id,pNode->Id, NodeTypeToString( &pNode->NodeType ), pNode ) ); // // Check to see if this is the end of this line. // if( ( IsEqualGUID( &pNode->NodeType, &KSNODETYPE_SUM ) ) || ( IsEqualGUID( &pNode->NodeType, &KSNODETYPE_MUX ) ) || ( pNode->Type == DESTINATION ) || ( kmxlIsDestinationNode( listDests, pNode ) ) ) { // // Find the destination node and update the line structure. // If this IS the destination node, then set the ID in the line // structure and return. There is no need to check the children, // since there won't be any. // if( pNode->Type == DESTINATION ) { pLine->DestId = pNode->Id; RETURN( STATUS_SUCCESS ); } // // Find the destination node for the source. It is possible to // have branches in the topology, so this may recurse. // pLine->DestId = kmxlFindDestinationForNode( pmxobj, pNode, pNode, pLine, plistLines ); RETURN( STATUS_SUCCESS ); } // // Retrieve and translate the node for the first child, appending any // controls created onto the list of controls for this line. // pChild = kmxlFirstChildNode( pNode ); if( pChild == NULL ) { RETURN( STATUS_SUCCESS ); } // // Save the number of controls here. If a split occurs beneath this // node, we don't want to include children followed on the first // child's path. // nControls = kmxlListLength( pLine->Controls ); if (kmxlTranslateNodeToControl(pmxobj, pChild->pNode, &pControl) ) { if( pControl && IsEqualGUID( pControl->NodeType, &KSNODETYPE_MUX ) ) { if( kmxlIsDestinationNode( listDests, pChild->pNode ) ) { pControl->Parameters.bPlaceholder = TRUE; } } kmxlAppendListToEndOfList( (PSLIST*) &pLine->Controls, (PSLIST) pControl ); } // // Recurse to build the controls for this child. // Status = kmxlBuildPath( pmxobj, pSource, pChild->pNode, pLine, plistLines, listDests ); if( !NT_SUCCESS( Status ) ) { RETURN( Status ); } // // For the rest of the children // // Create a new line based on pSource. // Duplicate the list of controls in pLine. // Recurse over the child node. // pChild = kmxlNextPeerNode( pChild ); while( pChild ) { pNewLine = kmxlAllocateLine( TAG_AudL_LINE ); if( pNewLine == NULL ) { RETURN( STATUS_INSUFFICIENT_RESOURCES ); } // // Insert this new node into the list of source lines // RtlCopyMemory( pNewLine, pLine, sizeof( MXLLINE ) ); pNewLine->List.Next = NULL; pNewLine->Controls = NULL; kmxlAddToEndOfList( *plistLines, pNewLine ); // // Since this is a new line, the control structures also need to be // duplicated. // Status = kmxlDuplicateLineControls( pNewLine, pLine, nControls ); if( !NT_SUCCESS( Status ) ) { RETURN( Status ); } // // Just as for the first child, translate the node, append the // controls to the list of controls for this list, and recurse // to build the controls for its children. // if (kmxlTranslateNodeToControl( pmxobj, pChild->pNode, &pControl ) ) { kmxlAppendListToEndOfList( (PSLIST*) &pNewLine->Controls, (PSLIST) pControl ); } Status = kmxlBuildPath( pmxobj, pSource, pChild->pNode, pNewLine, plistLines, listDests ); if( !NT_SUCCESS( Status ) ) { RETURN( Status ); } pChild = kmxlNextPeerNode( pChild ); } // while( pChild ) RETURN( STATUS_SUCCESS ); } /////////////////////////////////////////////////////////////////////// // // kmxlIsDestinationNode // // Searches all the list of controls on the given list of destinations // to see if the node appears in any of those lists. // // BOOL kmxlIsDestinationNode( IN NODELIST listDests, // The list of destinations IN PMXLNODE pNode // The node to check ) { PMXLNODE pTemp; PPEERNODE pParent; PAGED_CODE(); if( pNode->Type == SOURCE ) { return( FALSE ); } if( pNode->Type == DESTINATION ) { return( TRUE ); } ASSERT(pNode->Type == NODE); // // Loop over each of the destinations // pTemp = kmxlFirstInList( listDests ); while( pTemp ) { // // Loop over the parent. // pParent = kmxlFirstParentNode( pTemp ); while( pParent ) { if( ( pParent->pNode->Type == NODE ) && ( pParent->pNode->Id == pNode->Id) ) { return( TRUE ); } if( ( IsEqualGUID( &pParent->pNode->NodeType, &KSNODETYPE_SUM ) ) || ( IsEqualGUID( &pParent->pNode->NodeType, &KSNODETYPE_MUX ) ) || ( pParent->pNode->Type == SOURCE ) ) { break; } // // Check for the node Ids matching. // pParent = kmxlFirstParentNode( pParent->pNode ); } pTemp = kmxlNextNode( pTemp ); } return( FALSE ); } /////////////////////////////////////////////////////////////////////// // // kmxlDuplicateLine // // Duplicates a line and the associated controls. // // NTSTATUS kmxlDuplicateLine( IN PMXLLINE* ppTargetLine, // Pointer to the new line IN PMXLLINE pSourceLine // The line to duplicate ) { PAGED_CODE(); ASSERT( ppTargetLine ); ASSERT( pSourceLine ); DPF(DL_TRACE|FA_MIXER,( "Duplicated line with source=%d.", pSourceLine->SourceId ) ); // // Allocate a new line structure and copy the information from the // source line. // *ppTargetLine = kmxlAllocateLine( TAG_AudL_LINE ); if( *ppTargetLine == NULL ) { RETURN( STATUS_INSUFFICIENT_RESOURCES ); } ASSERT( *ppTargetLine ); // DPF(DL_TRACE|FA_MIXER,( "Duplicated %s (%d).", // PinCategoryToString( &pSourceLine->Type ), // pSourceLine->SourceId // ) ); RtlCopyMemory( *ppTargetLine, pSourceLine, sizeof( MXLLINE ) ); // // Null out the controls and next pointer. This line does not have // either of its own yet. // (*ppTargetLine)->List.Next = NULL; (*ppTargetLine)->Controls = NULL; // // Duplicate all the controls for the source line. // return( kmxlDuplicateLineControls( *ppTargetLine, pSourceLine, kmxlListLength( pSourceLine->Controls ) ) ); } /////////////////////////////////////////////////////////////////////// // // kmxlDuplicateLineControls // // Duplicates the controls for a line by allocating a new control // structure for each and copying the information to the new node. // // NTSTATUS kmxlDuplicateLineControls( IN PMXLLINE pTargetLine, // The line to put the controls into IN PMXLLINE pSourceLine, // The line with the controls to dup IN ULONG nCount // The number of controls to dup ) { PMXLCONTROL pControl, pNewControl; NTSTATUS Status; PAGED_CODE(); ASSERT( pTargetLine->Controls == NULL ); if( nCount == 0 ) { RETURN( STATUS_SUCCESS ); } // // Iterate over the list allocating and copying the controls // pControl = kmxlFirstInList( pSourceLine->Controls ); while( pControl ) { ASSERT( IsValidControl( pControl ) ); // // Allocate a new control structure. // pNewControl = kmxlAllocateControl( TAG_AudC_CONTROL ); if( pNewControl == NULL ) { goto exit; } // // Copy the entire MXLCONTROL structure and NULL out the // List.Next field. This control will be part of a different // list. // RtlCopyMemory( pNewControl, pControl, sizeof( MXLCONTROL ) ); pNewControl->List.Next = NULL; pNewControl->pChannelStepping = NULL; // // Copy the channel steppings from the original control // ASSERT(pControl->NumChannels > 0); Status = AudioAllocateMemory_Paged(pNewControl->NumChannels * sizeof( CHANNEL_STEPPING ), TAG_AuDD_CHANNEL, DEFAULT_MEMORY, &pNewControl->pChannelStepping ); if( !NT_SUCCESS( Status ) ) { pNewControl->NumChannels = 0; goto exit; } RtlCopyMemory( pNewControl->pChannelStepping, pControl->pChannelStepping, pNewControl->NumChannels * sizeof( CHANNEL_STEPPING ) ); // // We just made a copy of a MUX node. Mark the datastructures // is has as a copy so it doesn't get freed from underneath // somebody else. // if( IsEqualGUID( pNewControl->NodeType, &KSNODETYPE_MUX ) ) { pNewControl->Parameters.bHasCopy = TRUE; } kmxlAddToList( pTargetLine->Controls, pNewControl ); // // Decrement and check the number of controls copied. If we copied // the requested number, stop. // --nCount; if( nCount == 0 ) { break; } pControl = kmxlNextControl( pControl ); } RETURN( STATUS_SUCCESS ); exit: // // Failed to allocate the control structure. Free up all the // controls already allocated and return an error. // while( pTargetLine->Controls ) { pControl = kmxlRemoveFirstControl( pTargetLine->Controls ); kmxlFreeControl( pControl ); } RETURN( STATUS_INSUFFICIENT_RESOURCES ); } /////////////////////////////////////////////////////////////////////// // // kmxlFindDestinationForNode // // Finds a destination for the given node, duplicating lines if splits // are found in the topology. // // ULONG kmxlFindDestinationForNode( IN PMIXEROBJECT pmxobj, IN PMXLNODE pNode, // The node to find dest for IN PMXLNODE pParent, // The original parent IN PMXLLINE pLine, // The current line it's on IN OUT LINELIST* plistLines // The list of all lines ) { PPEERNODE pChild, pPeerChild; PMXLLINE pNewLine; PMXLNODE pShadow = pNode; PAGED_CODE(); DPF(DL_TRACE|FA_MIXER,( "Finding destination for node %d(0x%x) (%s), parent %d(0x%x) (%s).", pNode->Id,pNode->Id, NodeTypeToString( &pNode->NodeType ), pParent->Id,pParent->Id, NodeTypeToString( &pNode->NodeType ) ) ); ASSERT( pmxobj ) ; ASSERT( pNode ); ASSERT( pParent ); ASSERT( pLine ); ASSERT( plistLines ); if( pNode->Type == DESTINATION ) { return( pNode->Id ); } // // Loop over the first children. // pChild = kmxlFirstChildNode( pNode ); while( pChild ) { DPF(DL_TRACE|FA_MIXER,( "First child is %d(0x%x) (%s) NODE:%08x.", pChild->pNode->Id, pChild->pNode->Id, NodeTypeToString( &pChild->pNode->NodeType ), pChild->pNode ) ); if( pChild->pNode == pParent ) { DPF(DL_TRACE|FA_MIXER,( "Child node is same as original parent!" ) ); return( INVALID_ID ); } // // Loop over the rest of the children. // pPeerChild = kmxlNextPeerNode( pChild ); while( pPeerChild ) { if( pPeerChild->pNode == pParent ) { DPF(DL_TRACE|FA_MIXER,( "Child node is same as original parent!" ) ); return( INVALID_ID ); } DPF(DL_TRACE|FA_MIXER,( "Peer node of %d(0x%x) (%s) is %d(0x%x) (%s).", pChild->pNode->Id,pChild->pNode->Id, NodeTypeToString( &pChild->pNode->NodeType ), pPeerChild->pNode->Id,pPeerChild->pNode->Id, NodeTypeToString( &pPeerChild->pNode->NodeType ) ) ); // // This line has more than 1 child. Duplicate this line // and add it to the list of lines. // if( !NT_SUCCESS( kmxlDuplicateLine( &pNewLine, pLine ) ) ) { DPF(DL_WARNING|FA_MIXER,("kmxlDuplicateLine failed") ); continue; } kmxlAddToEndOfList( *plistLines, pNewLine ); if( IsEqualGUID( &pPeerChild->pNode->NodeType, &KSNODETYPE_MUX ) ) { // // We've found a MUX after a SUM or another MUX node. Mark // the current line as invalid and build a new, virtual // line that feeds into the MUX. // pNewLine->DestId = INVALID_ID; kmxlBuildVirtualMuxLine( pmxobj, pShadow, pPeerChild->pNode, plistLines ); } else { // // Now to find the destination for this new line. Recurse // on the node of this child. // pNewLine->DestId = kmxlFindDestinationForNode( pmxobj, pPeerChild->pNode, pParent, pNewLine, plistLines ); } DPF(DL_TRACE|FA_MIXER,( "Found %x as dest for %d(0x%x) (%s).", pNewLine->DestId, pPeerChild->pNode->Id,pPeerChild->pNode->Id, NodeTypeToString( &pPeerChild->pNode->NodeType ), pPeerChild->pNode ) ); pPeerChild = kmxlNextPeerNode( pPeerChild ); } if( IsEqualGUID( &pChild->pNode->NodeType, &KSNODETYPE_MUX ) ) { // // We've found a MUX after a SUM or another MUX node. Mark // the current line as invalid and build a new, virtual // line that feeds into the MUX. // kmxlBuildVirtualMuxLine( pmxobj, pShadow, pChild->pNode, plistLines ); return( INVALID_ID ); } // // Found the destination! // if( pChild->pNode->Type == DESTINATION ) { DPF(DL_TRACE|FA_MIXER,( "Found %x as dest for %d.", pChild->pNode->Id, pNode->Id ) ); return( pChild->pNode->Id ); } pShadow = pChild->pNode; pChild = kmxlFirstChildNode( pChild->pNode ); } DPF(DL_WARNING|FA_MIXER,("returning INVALID_ID") ); return( INVALID_ID ); } /////////////////////////////////////////////////////////////////////// // // kmxlBuildVirtualMuxLine // // NTSTATUS kmxlBuildVirtualMuxLine( IN PMIXEROBJECT pmxobj, IN PMXLNODE pParent, IN PMXLNODE pMux, IN OUT LINELIST* plistLines ) { PMXLLINE pLine, pTemp; PMXLNODE pNode; PMXLCONTROL pControl; MXLCONTROL Control; PAGED_CODE(); // // Allocate a new line to represent the virtual mux input line. // pLine = kmxlAllocateLine( TAG_AudL_LINE ); if( pLine == NULL ) { RETURN( STATUS_INSUFFICIENT_RESOURCES ); } DPF(DL_TRACE|FA_MIXER,("Virtual line %08x for Parent NODE:%08x",pLine,pParent) ); // // Translate the mux control so that it will appear in this line. // if (kmxlTranslateNodeToControl( pmxobj, pMux, &pControl ) ) { pControl->Parameters.bPlaceholder = TRUE; kmxlAppendListToList( (PSLIST*) &pLine->Controls, (PSLIST) pControl ); } // // Now start searching up from the parent. // pNode = pParent; while( pNode ) { // // Translate the control. // if (kmxlTranslateNodeToControl( pmxobj, pNode, &pControl ) ) { kmxlAppendListToList( (PSLIST*) &pLine->Controls, (PSLIST) pControl ); } // // If we found a node with multiple parents, then this will be the // "pin" for this line. // if( ( kmxlParentListLength( pNode ) > 1 ) || ( pNode->Type == SOURCE ) || ( IsEqualGUID( &pNode->NodeType, &KSNODETYPE_MUX ) ) || ( IsEqualGUID( &pNode->NodeType, &KSNODETYPE_SUM ) ) ) { // // Check to see if this node has already been used in a virtual // line. // pTemp = kmxlFirstInList( *plistLines ); while( pTemp ) { if( pTemp->SourceId == ( 0x8000 + pNode->Id ) ) { while( pLine->Controls ) { pControl = kmxlRemoveFirstControl( pLine->Controls ); kmxlFreeControl( pControl ); } AudioFreeMemory_Unknown( &pLine ); RETURN( STATUS_SUCCESS ); } pTemp = kmxlNextLine( pTemp ); } // // Set up the pin. The name will be the name of the node. // pLine->SourceId = 0x8000 + pNode->Id; Control.NodeType = &pNode->NodeType; kmxlGetNodeName( pmxobj->pfo, pNode->Id, &Control ); RtlCopyMemory( pLine->Line.szShortName, Control.Control.szShortName, min( sizeof( pLine->Line.szShortName ), sizeof( Control.Control.szShortName ) ) ); RtlCopyMemory( pLine->Line.szName, Control.Control.szName, min( sizeof( pLine->Line.szName ), sizeof( Control.Control.szName ) ) ); break; } pNode = (kmxlFirstParentNode( pNode ))->pNode; } // // By making this line type of "SUM" (which technically it is), it // will guarantee that this line gets a target type of UNDEFINED. // pLine->Type = KSNODETYPE_SUM; pLine->Communication = KSPIN_COMMUNICATION_NONE; pLine->Line.cbStruct = sizeof( MIXERLINE ); pLine->Line.dwSource = (DWORD) -1; pLine->Line.dwDestination = (DWORD) -1; pLine->Line.fdwLine = MIXERLINE_LINEF_SOURCE | MIXERLINE_LINEF_ACTIVE; kmxlAddToEndOfList( plistLines, pLine ); pLine->DestId = kmxlFindDestinationForNode( pmxobj, pMux, pMux, pLine, plistLines ); RETURN( STATUS_SUCCESS ); } /////////////////////////////////////////////////////////////////////// // // kmxlTranslateNodeToControl // // // Translates a NodeType GUID into a mixer line control. The memory // for the control(s) is allocated and as much information about the // control is filled in. // // NOTES // This function returns the number of controls added to the ppControl // array. // // Returns the number of controls actually created. // // ULONG kmxlTranslateNodeToControl( IN PMIXEROBJECT pmxobj, IN PMXLNODE pNode, // The node to translate into a control OUT PMXLCONTROL* ppControl // The control to fill in ) { PMXLCONTROL pControl; NTSTATUS Status = STATUS_SUCCESS; ASSERT( pmxobj ); ASSERT( pNode ); ASSERT( ppControl ); PAGED_CODE(); // // Bug fix. The caller might not clear this. This needs to be NULL do // the caller doesn't think controls were created when the function // fails. // *ppControl = NULL; // // If the node is NULL, there's nothing to do. // if( pNode == NULL ) { *ppControl = NULL; return( 0 ); } DPF(DL_TRACE|FA_MIXER,( "Translating %d(0x%x) ( %s ) NODE:%08x", pNode->Id,pNode->Id, NodeTypeToString( &pNode->NodeType ), pNode ) ); /////////////////////////////////////////////////////////////////// if( IsEqualGUID( &pNode->NodeType, &KSNODETYPE_AGC ) ) { /////////////////////////////////////////////////////////////////// // // AGC is represented by an ONOFF control. // // AGC is a UNIFORM (mono) control. // /////////////////////////////////////////////////////////////////// // // Check to see if the node properly supports AGC. // Status = kmxlSupportsControl( pmxobj->pfo, pNode->Id, KSPROPERTY_AUDIO_AGC ); if (!NT_SUCCESS(Status)) { DPF(DL_TRACE|FA_MIXER,( "AGC node fails property!" ) ); goto exit; } // // Allocate the new control structure. // *ppControl = kmxlAllocateControl( TAG_AudC_CONTROL ); if( *ppControl == NULL ) { goto exit; } // // Fill in as much information as possible. // (*ppControl)->NodeType = &KSNODETYPE_AGC; (*ppControl)->Id = pNode->Id; (*ppControl)->PropertyId = KSPROPERTY_AUDIO_AGC; (*ppControl)->bScaled = FALSE; (*ppControl)->Control.cbStruct = sizeof( MIXERCONTROL ); (*ppControl)->Control.dwControlType = MIXERCONTROL_CONTROLTYPE_ONOFF; (*ppControl)->Control.cMultipleItems = 0; (*ppControl)->Control.Bounds.dwMinimum = 0; (*ppControl)->Control.Bounds.dwMaximum = 1; (*ppControl)->Control.Metrics.cSteps = 0; Status = kmxlGetControlChannels( pmxobj->pfo, *ppControl ); if (!NT_SUCCESS(Status)) { kmxlFreeControl( *ppControl ); *ppControl = NULL; goto exit; } else { kmxlGetNodeName( pmxobj->pfo, pNode->Id, (*ppControl)); ASSERT( IsValidControl( *ppControl ) ); } /////////////////////////////////////////////////////////////////// } else if( IsEqualGUID( &pNode->NodeType, &KSNODETYPE_LOUDNESS ) ) { /////////////////////////////////////////////////////////////////// // // LOUNDNESS is represented by an ONOFF-type control. // // LOUDNESS is a UNIFORM (mono) control. // /////////////////////////////////////////////////////////////////// // // Check to see if the node properly supports LOUDNESS. // Status = kmxlSupportsControl( pmxobj->pfo, pNode->Id, KSPROPERTY_AUDIO_LOUDNESS ); if (!NT_SUCCESS(Status)) { DPF(DL_TRACE|FA_MIXER,( "Loudness node fails property!" ) ); goto exit; } // // Allocate the new control structure // *ppControl = kmxlAllocateControl( TAG_AudC_CONTROL ); if( *ppControl == NULL ) { goto exit; } // // Fill in as much information as possible. // (*ppControl)->NodeType = &KSNODETYPE_LOUDNESS; (*ppControl)->Id = pNode->Id; (*ppControl)->PropertyId = KSPROPERTY_AUDIO_LOUDNESS; (*ppControl)->bScaled = FALSE; (*ppControl)->Control.cbStruct = sizeof( MIXERCONTROL ); (*ppControl)->Control.dwControlType = MIXERCONTROL_CONTROLTYPE_LOUDNESS; (*ppControl)->Control.cMultipleItems = 0; (*ppControl)->Control.Bounds.dwMinimum = 0; (*ppControl)->Control.Bounds.dwMaximum = 1; (*ppControl)->Control.Metrics.cSteps = 0; Status = kmxlGetControlChannels( pmxobj->pfo, *ppControl ); if (!NT_SUCCESS(Status)) { kmxlFreeControl( *ppControl ); *ppControl = NULL; goto exit; } else { kmxlGetNodeName( pmxobj->pfo, pNode->Id, (*ppControl)); ASSERT( IsValidControl( *ppControl ) ); } /////////////////////////////////////////////////////////////////// } else if( IsEqualGUID( &pNode->NodeType, &KSNODETYPE_MUTE ) ) { /////////////////////////////////////////////////////////////////// // // MUTE is represented by an ONOFF-type control. // // MUTE is a UNIFORM (mono) control. // /////////////////////////////////////////////////////////////////// // // Check to see if the node properly supports MUTE. // Status = kmxlSupportsControl( pmxobj->pfo, pNode->Id, KSPROPERTY_AUDIO_MUTE ); if (!NT_SUCCESS(Status)) { DPF(DL_TRACE|FA_MIXER,( "Mute node fails property!" ) ); goto exit; } // // Allocate the new control structure // *ppControl = kmxlAllocateControl( TAG_AudC_CONTROL ); if( *ppControl == NULL ) { goto exit; } // // Fill in as much information as possible. // (*ppControl)->NodeType = &KSNODETYPE_MUTE; (*ppControl)->Id = pNode->Id; (*ppControl)->PropertyId = KSPROPERTY_AUDIO_MUTE; (*ppControl)->bScaled = FALSE; (*ppControl)->Control.cbStruct = sizeof( MIXERCONTROL ); (*ppControl)->Control.dwControlType = MIXERCONTROL_CONTROLTYPE_MUTE; (*ppControl)->Control.cMultipleItems = 0; (*ppControl)->Control.Bounds.dwMinimum = 0; (*ppControl)->Control.Bounds.dwMaximum = 1; (*ppControl)->Control.Metrics.cSteps = 0; Status = kmxlGetControlChannels( pmxobj->pfo, *ppControl ); if (!NT_SUCCESS(Status)) { kmxlFreeControl( *ppControl ); *ppControl = NULL; goto exit; } else { kmxlGetNodeName( pmxobj->pfo, pNode->Id, (*ppControl)); ASSERT( IsValidControl( *ppControl ) ); } /////////////////////////////////////////////////////////////////// } else if( IsEqualGUID( &pNode->NodeType, &KSNODETYPE_TONE ) ) { /////////////////////////////////////////////////////////////////// // // A TONE node can represent up to 3 controls: // Treble: A fader control // Bass: A fader control // Bass Boost: A OnOff control // // Both Treble and Bass are UNIFORM (mono) controls. // // To determine which control(s) the TONE node represents, a helper // function is called to query the particular property. If the // helper function succeeds, a control is created for that property. // /////////////////////////////////////////////////////////////////// Status = kmxlSupportsControl( pmxobj->pfo, pNode->Id, KSPROPERTY_AUDIO_BASS_BOOST ); if (NT_SUCCESS(Status)) { // // Bass boost control is supported. Allocate a new structure. // pControl = kmxlAllocateControl( TAG_AudC_CONTROL ); if( pControl == NULL ) { goto exit; } // // Fill in as much information as possible. // pControl->NodeType = &KSNODETYPE_TONE; pControl->Id = pNode->Id; pControl->PropertyId = KSPROPERTY_AUDIO_BASS_BOOST; pControl->bScaled = FALSE; pControl->Control.cbStruct = sizeof( MIXERCONTROL ); pControl->Control.dwControlType = MIXERCONTROL_CONTROLTYPE_ONOFF; pControl->Control.cMultipleItems = 0; pControl->Control.Bounds.dwMinimum = 0; pControl->Control.Bounds.dwMaximum = 1; pControl->Control.Metrics.cSteps = 0; Status = kmxlGetControlChannels( pmxobj->pfo, pControl ); if (!NT_SUCCESS(Status)) { kmxlFreeControl( pControl ); pControl = NULL; goto exit; } kmxlGetNodeName( pmxobj->pfo, pNode->Id, pControl); ASSERT( IsValidControl( pControl ) ); // // Add this new control to the list. // kmxlAddToList( *ppControl, pControl ); pControl = kmxlAllocateControl( TAG_AudC_CONTROL ); if( pControl ) { RtlCopyMemory( pControl, *ppControl, sizeof( MXLCONTROL ) ); // // Copy the channel steppings from the original control // // // Sense we copied the control above, we might have gotten // a pChannelStepping pointer in the copy. We'll NULL that out // for the memory allocation. // pControl->pChannelStepping = NULL; ASSERT(pControl->NumChannels > 0); Status = AudioAllocateMemory_Paged(pControl->NumChannels * sizeof( CHANNEL_STEPPING ), TAG_AuDC_CHANNEL, DEFAULT_MEMORY, &pControl->pChannelStepping ); if( !NT_SUCCESS( Status ) ) { pControl->NumChannels = 0; kmxlFreeControl( pControl ); pControl = NULL; goto exit; } RtlCopyMemory( pControl->pChannelStepping, (*ppControl)->pChannelStepping, pControl->NumChannels * sizeof( CHANNEL_STEPPING ) ); pControl->Control.dwControlType = MIXERCONTROL_CONTROLTYPE_BASS_BOOST; kmxlAddToList( *ppControl, pControl ); ASSERT( IsValidControl( pControl ) ); } } Status = kmxlSupportsBassControl( pmxobj->pfo, pNode->Id ); if (NT_SUCCESS(Status)) { // // Bass control is supported. Allocate a new structure. // pControl = kmxlAllocateControl( TAG_AudC_CONTROL ); if( pControl == NULL ) { goto exit; } // // Fill in as much information as possible. // pControl->NodeType = &KSNODETYPE_TONE; pControl->Id = pNode->Id; pControl->PropertyId = KSPROPERTY_AUDIO_BASS; pControl->bScaled = TRUE; pControl->Control.cbStruct = sizeof( MIXERCONTROL ); pControl->Control.dwControlType = MIXERCONTROL_CONTROLTYPE_BASS; pControl->Control.fdwControl = MIXERCONTROL_CONTROLF_UNIFORM; pControl->Control.cMultipleItems = 0; pControl->Control.Bounds.dwMinimum = DEFAULT_STATICBOUNDS_MIN; pControl->Control.Bounds.dwMaximum = DEFAULT_STATICBOUNDS_MAX; pControl->Control.Metrics.cSteps = DEFAULT_STATICMETRICS_CSTEPS; Status = kmxlGetControlRange( pmxobj->pfo, pControl ); if (!NT_SUCCESS(Status)) { kmxlFreeControl( pControl ); pControl = NULL; goto exit; } else { kmxlGetNodeName( pmxobj->pfo, pNode->Id, pControl); // // Add this new control to the list. // ASSERT( IsValidControl( pControl ) ); kmxlAddToList( *ppControl, pControl ); } } Status = kmxlSupportsTrebleControl( pmxobj->pfo, pNode->Id ); if (NT_SUCCESS(Status)) { // // Treble is supported. Allocate a new control structure. // pControl = kmxlAllocateControl( TAG_AudC_CONTROL ); if( pControl == NULL ) { goto exit; } // // Fill in as much information as possible. // pControl->NodeType = &KSNODETYPE_TONE; pControl->Id = pNode->Id; pControl->PropertyId = KSPROPERTY_AUDIO_TREBLE; pControl->bScaled = TRUE; pControl->Control.cbStruct = sizeof( MIXERCONTROL ); pControl->Control.dwControlType = MIXERCONTROL_CONTROLTYPE_TREBLE; pControl->Control.fdwControl = MIXERCONTROL_CONTROLF_UNIFORM; pControl->Control.cMultipleItems = 0; pControl->Control.Bounds.dwMinimum = DEFAULT_STATICBOUNDS_MIN; pControl->Control.Bounds.dwMaximum = DEFAULT_STATICBOUNDS_MAX; pControl->Control.Metrics.cSteps = DEFAULT_STATICMETRICS_CSTEPS; Status = kmxlGetControlRange( pmxobj->pfo, pControl ); if (!NT_SUCCESS(Status)) { kmxlFreeControl( pControl ); pControl = NULL; goto exit; } else { kmxlGetNodeName( pmxobj->pfo, pNode->Id, pControl); // // Add this new control to the list. // ASSERT( IsValidControl( pControl ) ); kmxlAddToList( *ppControl, pControl ); } } /////////////////////////////////////////////////////////////////// } else if( IsEqualGUID( &pNode->NodeType, &KSNODETYPE_VOLUME ) ) { /////////////////////////////////////////////////////////////////// // // A VOLUME is a fader-type control // // To determine if a node supports volume changes // /////////////////////////////////////////////////////////////////// // // Check to see if the node properly supports volume // Status = kmxlSupportsControl( pmxobj->pfo, pNode->Id, KSPROPERTY_AUDIO_VOLUMELEVEL ); if (!NT_SUCCESS(Status)) { DPF(DL_TRACE|FA_MIXER,( "Volume node fails property!" ) ); goto exit; } // // Allocate the new control structure // *ppControl = kmxlAllocateControl( TAG_AudC_CONTROL ); if( *ppControl == NULL ) { goto exit; } // // Fill in as much information as possible. // (*ppControl)->NodeType = &KSNODETYPE_VOLUME; (*ppControl)->Id = pNode->Id; (*ppControl)->PropertyId = KSPROPERTY_AUDIO_VOLUMELEVEL; (*ppControl)->bScaled = TRUE; (*ppControl)->Control.cbStruct = sizeof( MIXERCONTROL ); (*ppControl)->Control.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME; (*ppControl)->Control.Bounds.dwMinimum = DEFAULT_STATICBOUNDS_MIN; (*ppControl)->Control.Bounds.dwMaximum = DEFAULT_STATICBOUNDS_MAX; (*ppControl)->Control.Metrics.cSteps = DEFAULT_STATICMETRICS_CSTEPS; (*ppControl)->Control.cMultipleItems = 0; Status = kmxlGetControlRange( pmxobj->pfo, (*ppControl) ); if (!NT_SUCCESS(Status)) { kmxlFreeControl( *ppControl ); *ppControl = NULL; goto exit; } kmxlGetNodeName( pmxobj->pfo, pNode->Id, (*ppControl)); ASSERT( IsValidControl( *ppControl ) ); /////////////////////////////////////////////////////////////////// } else if( IsEqualGUID( &pNode->NodeType, &KSNODETYPE_PEAKMETER ) ) { /////////////////////////////////////////////////////////////////// // // To determine if a node supports peak meter properties // /////////////////////////////////////////////////////////////////// // // Check to see if the node properly supports peakmeter // Status = kmxlSupportsControl( pmxobj->pfo, pNode->Id, KSPROPERTY_AUDIO_PEAKMETER ); if (!NT_SUCCESS(Status)) { DPF(DL_TRACE|FA_MIXER,( "Peakmeter node fails property!" ) ); goto exit; } // // Allocate the new control structure // *ppControl = kmxlAllocateControl( TAG_AudC_CONTROL ); if( *ppControl == NULL ) { goto exit; } // // Fill in as much information as possible. // (*ppControl)->NodeType = &KSNODETYPE_PEAKMETER; (*ppControl)->Id = pNode->Id; (*ppControl)->PropertyId = KSPROPERTY_AUDIO_PEAKMETER; (*ppControl)->bScaled = FALSE; (*ppControl)->Control.cbStruct = sizeof( MIXERCONTROL ); (*ppControl)->Control.dwControlType = MIXERCONTROL_CONTROLTYPE_PEAKMETER; (*ppControl)->Control.Bounds.dwMinimum = DEFAULT_STATICBOUNDS_MIN; (*ppControl)->Control.Bounds.dwMaximum = DEFAULT_STATICBOUNDS_MAX; (*ppControl)->Control.Metrics.cSteps = DEFAULT_STATICMETRICS_CSTEPS; (*ppControl)->Control.cMultipleItems = 0; Status = kmxlGetControlRange( pmxobj->pfo, (*ppControl) ); if (!NT_SUCCESS(Status)) { kmxlFreeControl( *ppControl ); *ppControl = NULL; goto exit; } kmxlGetNodeName( pmxobj->pfo, pNode->Id, (*ppControl)); ASSERT( IsValidControl( *ppControl ) ); /////////////////////////////////////////////////////////////////// } else if( IsEqualGUID( &pNode->NodeType, &KSNODETYPE_MUX ) ) { /////////////////////////////////////////////////////////////////// // // A MUX is a single select type control. // /////////////////////////////////////////////////////////////////// { ULONG Line; // // Do a quick check and see if the mux responds properly. // If not, just get out of here quick. // if( !NT_SUCCESS( kmxlGetNodeProperty( pmxobj->pfo, &KSPROPSETID_Audio, KSPROPERTY_AUDIO_MUX_SOURCE, pNode->Id, 0, NULL, &Line, sizeof( Line ) ) ) ) { goto exit; } // // Look to see if a control has already been generated for this // node. If so, the control information can be used from it // instead of creating a new one. // pControl = kmxlFirstInList( pmxobj->listMuxControls ); while( pControl ) { ASSERT( IsValidControl( pControl ) ); if( pControl->Id == pNode->Id ) { break; } pControl = kmxlNextControl( pControl ); } // // Allocate the new control structure // *ppControl = kmxlAllocateControl( TAG_AudC_CONTROL ); if( *ppControl == NULL ) { goto exit; } if( pControl == NULL ) { // // This node has not been seen before. Fill in as much info as // possible. // (*ppControl)->NodeType = &KSNODETYPE_MUX; (*ppControl)->Id = pNode->Id; (*ppControl)->PropertyId = KSPROPERTY_AUDIO_MUX_SOURCE; (*ppControl)->bScaled = FALSE; (*ppControl)->Control.cbStruct = sizeof( MIXERCONTROL ); (*ppControl)->Control.dwControlType = MIXERCONTROL_CONTROLTYPE_MUX; (*ppControl)->Control.cMultipleItems = kmxlGetNumMuxLines( pmxobj->pTopology, pNode->Id ); (*ppControl)->Control.fdwControl = MIXERCONTROL_CONTROLF_MULTIPLE | MIXERCONTROL_CONTROLF_UNIFORM; (*ppControl)->Control.Bounds.dwMinimum = 0; (*ppControl)->Control.Bounds.dwMaximum = (*ppControl)->Control.cMultipleItems - 1; (*ppControl)->Control.Metrics.cSteps = (*ppControl)->Control.cMultipleItems; kmxlGetNodeName( pmxobj->pfo, pNode->Id, (*ppControl)); kmxlGetMuxLineNames( pmxobj, *ppControl ); pControl = kmxlAllocateControl( TAG_AudC_CONTROL ); if( pControl == NULL ) { kmxlFreeControl( *ppControl ); *ppControl = NULL; goto exit; } // // Make a copy of this control for the mux list // (*ppControl)->Control.dwControlID = pmxobj->dwControlId++; RtlCopyMemory( pControl, *ppControl, sizeof( MXLCONTROL ) ); ASSERT( IsValidControl( pControl ) ); pControl->Parameters.bHasCopy = TRUE; (*ppControl)->Parameters.bHasCopy = FALSE; kmxlAddToList( pmxobj->listMuxControls, pControl ); } else { RtlCopyMemory( *ppControl, pControl, sizeof( MXLCONTROL ) ); ASSERT( IsValidControl( *ppControl ) ); (*ppControl)->Parameters.bHasCopy = TRUE; (*ppControl)->List.Next = NULL; } } #ifdef STEREO_ENHANCE /////////////////////////////////////////////////////////////////// } else if( IsEqualGUID( &pNode->NodeType, &KSNODETYPE_STEREO_WIDE ) ) { /////////////////////////////////////////////////////////////////// // // Stereo Enhance is a boolean control. // /////////////////////////////////////////////////////////////////// // // Check to see if the node properly supports stereo wide // Status = kmxlSupportsControl( pfoInstance, pNode->Id, KSPROPERTY_AUDIO_WIDE_MODE ); if (!NT_SUCCESS(Status)) { DPF(DL_TRACE|FA_MIXER,( "Stereo Wide node fails property!" ) ); goto exit; } // // Allocate the new control structure // *ppControl = kmxlAllocateControl( TAG_AudC_CONTROL ); if( *ppControl == NULL ) { goto exit; } // // Fill in as much information as possible. // (*ppControl)->NodeType = &KSNODETYPE_STEREO_ENHANCE; (*ppControl)->Id = pNode->Id; (*ppControl)->PropertyId = KSPROPERTY_AUDIO_WIDE_MODE; (*ppControl)->bScaled = FALSE; (*ppControl)->Control.cbStruct = sizeof( MIXERCONTROL ); (*ppControl)->Control.dwControlType = MIXERCONTROL_CONTROLTYPE_STEREOENH; (*ppControl)->Control.cMultipleItems = 0; (*ppControl)->Control.Bounds.dwMinimum = 0; (*ppControl)->Control.Bounds.dwMaximum = 1; (*ppControl)->Control.Metrics.cSteps = 0; Status = kmxlGetControlChannels( pfoInstance, *ppControl ); if (!NT_SUCCESS(Status)) { kmxlFreeControl( *ppControl ); *ppControl = NULL; goto exit; } kmxlGetNodeName( pfoInstance, pNode->Id, (*ppControl)); #endif /////////////////////////////////////////////////////////////////// } else if( IsEqualGUID( &pNode->NodeType, &KSNODETYPE_STEREO_WIDE ) ) { /////////////////////////////////////////////////////////////////// // // Check to see if the node properly supports stereo wide // Status = kmxlSupportsControl( pmxobj->pfo, pNode->Id, KSPROPERTY_AUDIO_WIDENESS ); if (!NT_SUCCESS(Status)) { DPF(DL_TRACE|FA_MIXER,( "Stereo wide node fails property!" ) ); goto exit; } // // Allocate the new control structure // *ppControl = kmxlAllocateControl( TAG_AudC_CONTROL ); if( *ppControl == NULL ) { goto exit; } // // Fill in as much information as possible. // (*ppControl)->NodeType = &KSNODETYPE_STEREO_WIDE; (*ppControl)->Id = pNode->Id; (*ppControl)->PropertyId = KSPROPERTY_AUDIO_WIDENESS; (*ppControl)->bScaled = FALSE; (*ppControl)->Control.cbStruct = sizeof( MIXERCONTROL ); (*ppControl)->Control.dwControlType = MIXERCONTROL_CONTROLTYPE_FADER; (*ppControl)->Control.cMultipleItems = 0; (*ppControl)->Control.Bounds.dwMinimum = DEFAULT_STATICBOUNDS_MIN; (*ppControl)->Control.Bounds.dwMaximum = DEFAULT_STATICBOUNDS_MAX; (*ppControl)->Control.Metrics.cSteps = DEFAULT_STATICMETRICS_CSTEPS; Status = kmxlGetControlRange( pmxobj->pfo, (*ppControl) ); if (!NT_SUCCESS(Status)) { kmxlFreeControl( *ppControl ); *ppControl = NULL; goto exit; } kmxlGetNodeName( pmxobj->pfo, pNode->Id, (*ppControl)); ASSERT( IsValidControl( *ppControl ) ); /////////////////////////////////////////////////////////////////// } else if( IsEqualGUID( &pNode->NodeType, &KSNODETYPE_CHORUS ) ) { /////////////////////////////////////////////////////////////////// // // Check to see if the node properly supports chorus // Status = kmxlSupportsControl( pmxobj->pfo, pNode->Id, KSPROPERTY_AUDIO_CHORUS_LEVEL ); if (!NT_SUCCESS(Status)) { DPF(DL_TRACE|FA_MIXER,( "Chorus node fails property!" ) ); goto exit; } // // Allocate the new control structure // *ppControl = kmxlAllocateControl( TAG_AudC_CONTROL ); if( *ppControl == NULL ) { goto exit; } // // Fill in as much information as possible. // (*ppControl)->NodeType = &KSNODETYPE_CHORUS; (*ppControl)->Id = pNode->Id; (*ppControl)->PropertyId = KSPROPERTY_AUDIO_CHORUS_LEVEL; (*ppControl)->bScaled = FALSE; (*ppControl)->Control.cbStruct = sizeof( MIXERCONTROL ); (*ppControl)->Control.dwControlType = MIXERCONTROL_CONTROLTYPE_FADER; (*ppControl)->Control.cMultipleItems = 0; (*ppControl)->Control.Bounds.dwMinimum = DEFAULT_STATICBOUNDS_MIN; (*ppControl)->Control.Bounds.dwMaximum = DEFAULT_STATICBOUNDS_MAX; (*ppControl)->Control.Metrics.cSteps = DEFAULT_STATICMETRICS_CSTEPS; // (*ppControl)->Control.Metrics.cSteps = 0xFFFF; Status = kmxlGetControlChannels( pmxobj->pfo, *ppControl ); // Should we also get the range? if (!NT_SUCCESS(Status)) { kmxlFreeControl( *ppControl ); *ppControl = NULL; goto exit; } else { kmxlGetNodeName( pmxobj->pfo, pNode->Id, (*ppControl)); ASSERT( IsValidControl( *ppControl ) ); } /////////////////////////////////////////////////////////////////// } else if( IsEqualGUID( &pNode->NodeType, &KSNODETYPE_REVERB ) ) { /////////////////////////////////////////////////////////////////// // // Check to see if the node properly supports reverb // Status = kmxlSupportsControl( pmxobj->pfo, pNode->Id, KSPROPERTY_AUDIO_REVERB_LEVEL ); if (!NT_SUCCESS(Status)) { DPF(DL_TRACE|FA_MIXER,( "Reverb node fails property!" ) ); goto exit; } // // Allocate the new control structure // *ppControl = kmxlAllocateControl( TAG_AudC_CONTROL ); if( *ppControl == NULL ) { goto exit; } // // Fill in as much information as possible. // (*ppControl)->NodeType = &KSNODETYPE_REVERB; (*ppControl)->Id = pNode->Id; (*ppControl)->PropertyId = KSPROPERTY_AUDIO_REVERB_LEVEL; (*ppControl)->bScaled = FALSE; (*ppControl)->Control.cbStruct = sizeof( MIXERCONTROL ); (*ppControl)->Control.dwControlType = MIXERCONTROL_CONTROLTYPE_FADER; (*ppControl)->Control.cMultipleItems = 0; (*ppControl)->Control.Bounds.dwMinimum = DEFAULT_STATICBOUNDS_MIN; (*ppControl)->Control.Bounds.dwMaximum = DEFAULT_STATICBOUNDS_MAX; (*ppControl)->Control.Metrics.cSteps = DEFAULT_STATICMETRICS_CSTEPS; // (*ppControl)->Control.Metrics.cSteps = 0xFFFF; Status = kmxlGetControlChannels( pmxobj->pfo, *ppControl ); // Should we also get the range? if (!NT_SUCCESS(Status)) { kmxlFreeControl( *ppControl ); *ppControl = NULL; goto exit; } else { kmxlGetNodeName( pmxobj->pfo, pNode->Id, (*ppControl)); ASSERT( IsValidControl( *ppControl ) ); } /////////////////////////////////////////////////////////////////// } else if( IsEqualGUID( &pNode->NodeType, &KSNODETYPE_SUPERMIX ) ) { /////////////////////////////////////////////////////////////////// // // SuperMix nodes can be supported as MUTE controls if the MUTE // property is supported. // /////////////////////////////////////////////////////////////////// PKSAUDIO_MIXCAP_TABLE pMixCaps; PLONG pReferenceCount = NULL; ULONG i, Size; BOOL bMutable; BOOL bVolume = FALSE; PKSAUDIO_MIXLEVEL pMixLevels = NULL; #ifdef SUPERMIX_AS_VOL ULONG Channels; #endif if( !NT_SUCCESS( kmxlGetSuperMixCaps( pmxobj->pfo, pNode->Id, &pMixCaps ) ) ) { goto exit; } Status = AudioAllocateMemory_Paged(sizeof( LONG ), TAG_AudS_SUPERMIX, ZERO_FILL_MEMORY, &pReferenceCount ); if( !NT_SUCCESS( Status ) ) { AudioFreeMemory_Unknown( &pMixCaps ); *ppControl = NULL; goto exit; } *pReferenceCount=0; Size = pMixCaps->InputChannels * pMixCaps->OutputChannels; Status = AudioAllocateMemory_Paged(Size * sizeof( KSAUDIO_MIXLEVEL ), TAG_Audl_MIXLEVEL, ZERO_FILL_MEMORY, &pMixLevels ); if( !NT_SUCCESS( Status ) ) { AudioFreeMemory_Unknown( &pMixCaps ); AudioFreeMemory( sizeof(LONG),&pReferenceCount ); *ppControl = NULL; goto exit; } Status = kmxlGetNodeProperty( pmxobj->pfo, &KSPROPSETID_Audio, KSPROPERTY_AUDIO_MIX_LEVEL_TABLE, pNode->Id, 0, NULL, pMixLevels, Size * sizeof( KSAUDIO_MIXLEVEL ) ); if( !NT_SUCCESS( Status ) ) { AudioFreeMemory_Unknown( &pMixCaps ); AudioFreeMemory( sizeof(LONG),&pReferenceCount ); AudioFreeMemory_Unknown( &pMixLevels ); DPF(DL_WARNING|FA_MIXER,("kmxlGetNodeProperty failed Status=%X",Status) ); *ppControl = NULL; goto exit; } bMutable = TRUE; for( i = 0; i < Size; i++ ) { // // If the channel is mutable, then all is well for this entry. // if( pMixCaps->Capabilities[ i ].Mute ) { continue; } // // The the entry is not mutable but is fully attenuated, // this will work too. // if( ( pMixCaps->Capabilities[ i ].Minimum == LONG_MIN ) && ( pMixCaps->Capabilities[ i ].Maximum == LONG_MIN ) && ( pMixCaps->Capabilities[ i ].Reset == LONG_MIN ) ) { continue; } bMutable = FALSE; break; } #ifdef SUPERMIX_AS_VOL bVolume = TRUE; Channels = 0; for( i = 0; i < Size; i += pMixCaps->OutputChannels + 1 ) { if( ( pMixCaps->Capabilities[ i ].Maximum - pMixCaps->Capabilities[ i ].Minimum ) > 0 ) { ++Channels; continue; } bVolume = FALSE; break; } #endif // // This node cannot be used as a MUTE control. // if( !bMutable && !bVolume ) { AudioFreeMemory_Unknown( &pMixCaps ); AudioFreeMemory( sizeof(LONG),&pReferenceCount ); AudioFreeMemory_Unknown( &pMixLevels ); *ppControl = NULL; goto exit; } if( bMutable ) { // // The Supermix is verifiably usable as a MUTE. Fill in all the // details. // pControl = kmxlAllocateControl( TAG_AudC_CONTROL ); if( pControl != NULL ) { pControl->NodeType = &KSNODETYPE_SUPERMIX; pControl->Id = pNode->Id; pControl->PropertyId = KSPROPERTY_AUDIO_MIX_LEVEL_TABLE; pControl->bScaled = FALSE; pControl->Control.cbStruct = sizeof( MIXERCONTROL ); pControl->Control.dwControlType = MIXERCONTROL_CONTROLTYPE_MUTE; pControl->Control.fdwControl = MIXERCONTROL_CONTROLF_UNIFORM; pControl->Control.cMultipleItems = 0; pControl->Control.Bounds.dwMinimum = 0; pControl->Control.Bounds.dwMaximum = 1; pControl->Control.Metrics.cSteps = 0; InterlockedIncrement(pReferenceCount); pControl->Parameters.pReferenceCount = pReferenceCount; pControl->Parameters.Size = pMixCaps->InputChannels * pMixCaps->OutputChannels; pControl->Parameters.pMixCaps = pMixCaps; pControl->Parameters.pMixLevels = pMixLevels; Status = AudioAllocateMemory_Paged(sizeof( CHANNEL_STEPPING ), TAG_AuDE_CHANNEL, ZERO_FILL_MEMORY, &pControl->pChannelStepping ); if( !NT_SUCCESS( Status ) ) { AudioFreeMemory_Unknown( &pMixCaps ); AudioFreeMemory( sizeof(LONG),&pReferenceCount ); AudioFreeMemory_Unknown( &pMixLevels ); *ppControl = NULL; goto exit; } pControl->NumChannels = 1; pControl->pChannelStepping->MinValue = pMixCaps->Capabilities[ 0 ].Minimum; pControl->pChannelStepping->MaxValue = pMixCaps->Capabilities[ 0 ].Maximum; pControl->pChannelStepping->Steps = 32; kmxlGetNodeName( pmxobj->pfo, pNode->Id, pControl); kmxlAddToList( *ppControl, pControl ); ASSERT( IsValidControl( pControl ) ); } } #ifdef SUPERMIX_AS_VOL if( bVolume ) { pControl = kmxlAllocateControl( TAG_AudC_CONTROL ); if( pControl != NULL ) { pControl->NodeType = &KSNODETYPE_SUPERMIX; pControl->Id = pNode->Id; pControl->PropertyId = KSPROPERTY_AUDIO_MIX_LEVEL_TABLE; pControl->bScaled = TRUE; pControl->Control.cbStruct = sizeof( MIXERCONTROL ); pControl->Control.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME; pControl->Control.cMultipleItems = 0; pControl->Control.Bounds.dwMinimum = DEFAULT_STATICBOUNDS_MIN; pControl->Control.Bounds.dwMaximum = DEFAULT_STATICBOUNDS_MAX; pControl->Control.Metrics.cSteps = 32; InterlockedIncrement(pReferenceCount); pControl->Parameters.pReferenceCount = pReferenceCount; pControl->Parameters.Size = pMixCaps->InputChannels * pMixCaps->OutputChannels; pControl->Parameters.pMixCaps = pMixCaps; pControl->Parameters.pMixLevels = pMixLevels; if( Channels == 1 ) { pControl->Control.fdwControl = MIXERCONTROL_CONTROLF_UNIFORM; } else { pControl->Control.fdwControl = 0; } kmxlGetNodeName( pmxobj->pfo, pNode->Id, pControl ); kmxlAddToList( *ppControl, pControl ); ASSERT( IsValidControl( pControl ) ); } } #endif // SUPERMIX_AS_VOL if( *ppControl == NULL ) { AudioFreeMemory_Unknown( &pMixCaps ); AudioFreeMemory( sizeof(LONG),&pReferenceCount ); AudioFreeMemory_Unknown( &pMixLevels ); } } exit: if( *ppControl ) { DPF(DL_TRACE|FA_MIXER,( "Translated %d controls.", kmxlListLength( *ppControl ) ) ); return( kmxlListLength( *ppControl ) ); } else { DPF(DL_TRACE|FA_MIXER,( "Translated no controls." ) ); return( 0 ); } } #define KsAudioPropertyToString( Property ) \ Property == KSPROPERTY_AUDIO_VOLUMELEVEL ? "Volume" : \ Property == KSPROPERTY_AUDIO_MUTE ? "Mute" : \ Property == KSPROPERTY_AUDIO_BASS ? "Bass" : \ Property == KSPROPERTY_AUDIO_TREBLE ? "Treble" : \ Property == KSPROPERTY_AUDIO_AGC ? "AGC" : \ Property == KSPROPERTY_AUDIO_LOUDNESS ? "Loudness" : \ Property == KSPROPERTY_AUDIO_PEAKMETER ? "Peakmeter" : \ "Unknown" /////////////////////////////////////////////////////////////////////// // // kmxlSupportsControl // // Queries for property on control to see if it is actually supported // // NTSTATUS kmxlSupportsControl( IN PFILE_OBJECT pfoInstance, // The instance to check for IN ULONG Node, // The node id to query IN ULONG Property // The property to check for ) { NTSTATUS Status; LONG Level; ASSERT( pfoInstance ); PAGED_CODE(); // // Check to see if the property works on the first channel. // Status = kmxlGetAudioNodeProperty( pfoInstance, Property, Node, 0, // Channel 0 - first channel NULL, 0, &Level, sizeof( Level ) ); if( !NT_SUCCESS( Status ) ) { DPF(DL_WARNING|FA_MIXER,( "SupportsControl for (%d,%X) failed on first channel with %x.", Node, Property, Status ) ); } RETURN( Status ); } /////////////////////////////////////////////////////////////////////// // // kmxlSupportsMultiChannelControl // // Queries for property on the second channel of the control to see // independent levels can be set. It is assumed that the first channel // already succeeded in kmxlSupportsControl // // NTSTATUS kmxlSupportsMultiChannelControl( IN PFILE_OBJECT pfoInstance, // The instance to check for IN ULONG Node, // The node id to query IN ULONG Property // The property to check for ) { NTSTATUS Status; LONG Level; ASSERT( pfoInstance ); PAGED_CODE(); // // Just check the property on the second channel because we have already checked // the first channel already. // Status = kmxlGetAudioNodeProperty( pfoInstance, Property, Node, 1, // Second channel equals a channel value of 1 NULL, 0, &Level, sizeof( Level ) ); RETURN( Status ); } NTSTATUS kmxlAssignLineAndControlIdsWorker( IN PMIXEROBJECT pmxobj, IN LINELIST listLines, // The list to assign ids for IN ULONG ListType, // LIST_SOURCE or LIST_DESTINATION IN OUT ULONG *pLineID, IN GUID *pDestGuid ) { NTSTATUS Status = STATUS_SUCCESS; PMXLLINE pLine = NULL; PMXLCONTROL pControl = NULL; ULONG LineID = 0; ULONG Dest; PAGED_CODE(); ASSERT ( ListType==SOURCE_LIST || ListType==DESTINATION_LIST ); if (pLineID!=NULL) { LineID=*pLineID; } // // Loop through each of the line structures // pLine = kmxlFirstInList( listLines ); if( pLine == NULL ) { RETURN( Status ); } Dest = pLine->DestId; while( pLine ) { // // For destinations, set the dwDestination field and set // the dwSource field for sources. // if( ListType == DESTINATION_LIST ) { // Check if this line has already been assigned an ID. // If so, then go to next line in list. if (pLine->Line.dwDestination!=(DWORD)(-1)) { pLine = kmxlNextLine( pLine ); continue; } // Now if we can only number lines of a particular GUID, // then make sure this destination line type matches that guid. if (pDestGuid!=NULL && !IsEqualGUID( pDestGuid, &pLine->Type )) { pLine = kmxlNextLine( pLine ); continue; } // // Assign the destination Id. Create the line Id by // using -1 for the source in the highword and the // destination in the loword. // pLine->Line.dwDestination = LineID++; pLine->Line.dwLineID = MAKELONG( pLine->Line.dwDestination, -1 ); if (pLineID!=NULL) { *pLineID=LineID; } } else if( ListType == SOURCE_LIST ) { pLine->Line.dwSource = LineID++; } else { RETURN( STATUS_INVALID_PARAMETER ); } // // Set up the number of controls on this line. // pLine->Line.cControls = kmxlListLength( pLine->Controls ); // // Loop through the controls, assigning them a control ID // that is a pointer to the MXLCONTROL structure for that // control. // pControl = kmxlFirstInList( pLine->Controls ); while( pControl ) { if( pControl->Control.dwControlType == MIXERCONTROL_CONTROLTYPE_MUX ) { // // MUX controls are already numbered by this point. Just skip // it and go onto the next one. // pControl = kmxlNextControl( pControl ); continue; } pControl->Control.dwControlID = pmxobj->dwControlId++; pControl = kmxlNextControl( pControl ); } pLine = kmxlNextLine( pLine ); if( pLine == NULL ) { continue; } if( ( ListType == SOURCE_LIST ) && ( pLine->DestId != Dest ) ) { LineID = 0; Dest = pLine->DestId; } } RETURN( Status ); } #define GUIDCOUNT 13 /////////////////////////////////////////////////////////////////////// // // kmxlAssignLineAndControlIds // // Loops through the list of lines and assigns ids for those line. // For destinations, the Id starts a 0 and is incremented each time. // The line id is a long of -1 and the dest id. For sources, the // line Ids will need to be specified elsewhere so only dwSource // field is assigned. // // For controls, each control is given an Id of the address to the // MXLCONTROL structure. // // NTSTATUS kmxlAssignLineAndControlIds( IN PMIXEROBJECT pmxobj, IN LINELIST listLines, // The list to assign ids for IN ULONG ListType // LIST_SOURCE or LIST_DESTINATION ) { PAGED_CODE(); ASSERT ( ListType==SOURCE_LIST || ListType==DESTINATION_LIST ); if (SOURCE_LIST==ListType) { return( kmxlAssignLineAndControlIdsWorker(pmxobj, listLines, ListType, NULL, NULL) ); } else if (DESTINATION_LIST==ListType) { // In order to help sndvol32 do the right thing as far as which // lines displayed as the default playback and record lines, we // number lines based on what their destinations are. // We use guid the pLine->Type field to decide how to number lines. // Lines are prioritized in the following way: speakers, then // headphones, then telephones. Non prioritized guids are assigned // last in whatever order they appear in the list. ULONG LineID=0; ULONG i; GUID prioritizeddestinationguids[GUIDCOUNT]= { STATIC_KSNODETYPE_ROOM_SPEAKER, STATIC_KSNODETYPE_DESKTOP_SPEAKER, STATIC_KSNODETYPE_SPEAKER, STATIC_KSNODETYPE_COMMUNICATION_SPEAKER, STATIC_KSNODETYPE_HEAD_MOUNTED_DISPLAY_AUDIO, STATIC_KSNODETYPE_ANALOG_CONNECTOR, STATIC_KSNODETYPE_SPDIF_INTERFACE, STATIC_KSNODETYPE_HEADPHONES, STATIC_KSNODETYPE_TELEPHONE, STATIC_KSNODETYPE_PHONE_LINE, STATIC_KSNODETYPE_DOWN_LINE_PHONE, STATIC_PINNAME_CAPTURE, STATIC_KSCATEGORY_AUDIO, }; // Cycle through the list for each prioritized guid and number // those lines that match that particular guid. for (i=0; iDestId == pDest->DestId ) { // // Heh, whatchya know? // pSource->Line.dwDestination = pDest->Line.dwDestination; pSource->Line.dwLineID = MAKELONG( (WORD) pSource->Line.dwDestination, (WORD) pSource->Line.dwSource ); break; } pDest = kmxlNextLine( pDest ); } pSource = kmxlNextLine( pSource ); } RETURN( STATUS_SUCCESS ); } /////////////////////////////////////////////////////////////////////// // // kmxlUpdateDestinationConnectionCount // // For each of the destinations, loop through each of the sources // and find those that connect to this destination. That count is // then stored in the MIXERLINE.cConnections for the line. // // NTSTATUS kmxlUpdateDestintationConnectionCount( IN LINELIST listSourceLines, // The list of source lines IN LINELIST listDestLines // The list of destination lines ) { PMXLLINE pDest, pSource; ULONG Count; PAGED_CODE(); // // Loop through each destination finding all the sources that connect // to it. The total number of sources connecting to a destination // is sourced in the cConnections field of the MIXERLINE struct. // pDest = kmxlFirstInList( listDestLines ); while( pDest ) { // // Initialize the source ID. This will mark this as a valid // destination. // pDest->SourceId = (ULONG) -1; Count = 0; // // Loop through the sources looking for sources that connect to // the current destination. // pSource = kmxlFirstInList( listSourceLines ); while( pSource ) { // // Found a match. Increment the count. // if( pSource->DestId == pDest->DestId ) { ++Count; } pSource = kmxlNextLine( pSource ); } pDest->Line.cConnections = Count; pDest = kmxlNextLine( pDest ); } RETURN( STATUS_SUCCESS ); } VOID CleanupLine( PMXLLINE pLine ) { PMXLCONTROL pControl; while( pLine->Controls ) { pControl = kmxlRemoveFirstControl( pLine->Controls ); kmxlFreeControl( pControl ); } AudioFreeMemory( sizeof(MXLLINE),&pLine ); } /////////////////////////////////////////////////////////////////////// // // kmxlEliminateInvalidLines // // Loops through the lines removing lines that are invalid. Refer // to the function for IsValidLine() for details on what is an invalid // line. // // NTSTATUS kmxlEliminateInvalidLines( IN LINELIST* listLines // The list of lines ) { PMXLLINE pLine, pTemp, pShadow; PAGED_CODE(); // // Eliminate all invalid lines at the start of the list. // pLine = kmxlFirstInList( *listLines ); while( pLine ) { // // Found the first valid line. Break out of this loop. // if( Is_Valid_Line( pLine ) ) { break; } // // This is an invalid line. Remove it from the list, free up // all its control structures, and free the line structure. // pTemp = kmxlRemoveFirstLine( pLine ); CleanupLine(pTemp); } // // Assign listLines to point to the first valid line. // *listLines = pLine; if( pLine == NULL ) { RETURN( STATUS_SUCCESS ); } // // At this point, pLine is a valid line. Keeping a hold on the prev // line, loop through the lines eliminating the invalid ones. // pShadow = pLine; while( pShadow && kmxlNextLine( pShadow ) ) { pLine = kmxlNextLine( pShadow ); if( pLine && !Is_Valid_Line( pLine ) ) { // // Remove the invalid line from the list // pShadow->List.Next = pLine->List.Next; pLine->List.Next = NULL; CleanupLine(pLine); continue; } pShadow = kmxlNextLine( pShadow ); } // All the invalid lines have been eliminated. Now eliminate bad // duplicates. pShadow = kmxlFirstInList( *listLines ); while( pShadow ) { // // Walk all the lines looking for a match. // pLine = kmxlNextLine( pShadow ); pTemp = NULL; while( pLine ) { if( ( pShadow->SourceId == pLine->SourceId ) && ( pShadow->DestId == pLine->DestId ) ) { DPF(DL_TRACE|FA_MIXER,( "Line %x is equal to line %x!", pShadow->Line.dwLineID, pLine->Line.dwLineID ) ); // // Found a match. // if( pTemp == NULL ) { // // pShadow is our previous line. Remove this line from the // list. // pShadow->List.Next = pLine->List.Next; pLine->List.Next = NULL; CleanupLine(pLine); // // Now adjust pLine to the next line and loop // pLine = kmxlNextLine( pShadow ); continue; } else { // // pTemp is our previous line. Remove this line from the // list. // pTemp->List.Next = pLine->List.Next; pLine->List.Next = NULL; CleanupLine(pLine); // // Now adjust pLine to the next line and loop // pLine = kmxlNextLine( pTemp ); continue; } } pTemp = pLine; //temp is previous line pLine = kmxlNextLine( pLine ); } pShadow = kmxlNextLine( pShadow ); } RETURN( STATUS_SUCCESS ); } /////////////////////////////////////////////////////////////////////// // // kmxlAssignComponentIds // // Loops through all the destinations then the sources and determines // their component type and target types. // // VOID kmxlAssignComponentIds( IN PMIXEROBJECT pmxobj, IN LINELIST listSourceLines, IN LINELIST listDestLines ) { PMXLLINE pLine; PAGED_CODE(); // // Loop through the destinations... // pLine = kmxlFirstInList( listDestLines ); while( pLine ) { pLine->Line.dwComponentType = kmxlDetermineDestinationType( pmxobj, pLine ); pLine = kmxlNextLine( pLine ); } // // Loop through the sources... // pLine = kmxlFirstInList( listSourceLines ); while( pLine ) { pLine->Line.dwComponentType = kmxlDetermineSourceType( pmxobj, pLine ); pLine = kmxlNextLine( pLine ); } } /////////////////////////////////////////////////////////////////////// // // kmxlUpdateMuxLines // // Updates the name, line ID, and componenttype of a line that has // a mux control on it. The MixerControlDetails array is searched for // an entry that has a matching source id and replaced with the info // from this line. // // VOID kmxlUpdateMuxLines( IN PMXLLINE pLine, IN PMXLCONTROL pControl ) { ULONG i; PAGED_CODE(); for( i = 0; i < pControl->Parameters.Count; i++ ) { if( ( pLine->SourceId == pControl->Parameters.lpmcd_lt[ i ].dwParam1 ) && ( pControl->Parameters.lpmcd_lt[ i ].dwParam2 == (DWORD) -1 ) ) { wcscpy( pControl->Parameters.lpmcd_lt[ i ].szName, pLine->Line.szName ); pControl->Parameters.lpmcd_lt[ i ].dwParam1 = pLine->Line.dwLineID; pControl->Parameters.lpmcd_lt[ i ].dwParam2 = pLine->Line.dwComponentType; } } } /////////////////////////////////////////////////////////////////////// // // kmxlAssignMuxIds // // Updates the source IDs stored in the MixerControlDetails array of // the muxes and removes the muxes placed in lines as place holders. // // NTSTATUS kmxlAssignMuxIds( IN PMIXEROBJECT pmxobj, IN LINELIST listLines ) { PMXLLINE pLine; PMXLCONTROL pControl; CONTROLLIST listControls = NULL; PAGED_CODE(); pLine = kmxlFirstInList( listLines ); while( pLine ) { // // Loop through the controls by removing them from the line's // control list and building a new control list. This new // control list will have the extra mux controls removed. // pControl = kmxlRemoveFirstControl( pLine->Controls ); while( pControl ) { if( IsEqualGUID( pControl->NodeType, &KSNODETYPE_MUX ) ) { kmxlUpdateMuxLines( pLine, pControl ); if( pControl->Parameters.bPlaceholder ) { // // This mux was here only to mark this line. Free // up only the control memory and leave the parameters // memeory alone. // ASSERT( pControl->pChannelStepping == NULL); AudioFreeMemory_Unknown( &pControl ); --pLine->Line.cControls; } else { // // This is a real mux control. Add it back into the // list. // kmxlAddToEndOfList( listControls, pControl ); } } else { // // Wasn't a mux. Put it onto the end of the new control // list. kmxlAddToEndOfList( listControls, pControl ); } // // Remove the next one! // pControl = kmxlRemoveFirstControl( pLine->Controls ); } // // Reassign the new control list back into this line. // pLine->Controls = listControls; pLine = kmxlNextLine( pLine ); listControls = NULL; } RETURN( STATUS_SUCCESS ); } /////////////////////////////////////////////////////////////////////// // // TargetCommon // // Fills in the common fields of the target function. // // VOID TargetCommon( IN PMIXEROBJECT pmxobj, IN PMXLLINE pLine, IN DWORD DeviceType ) { PWDMACONTEXT pWdmaContext; PWAVEDEVICE paWaveOutDevs, paWaveInDevs; PMIDIDEVICE paMidiOutDevs, paMidiInDevs; ULONG i; PAGED_CODE(); pWdmaContext = pmxobj->pMixerDevice->pWdmaContext; paWaveOutDevs = pWdmaContext->WaveOutDevs; paWaveInDevs = pWdmaContext->WaveInDevs; paMidiOutDevs = pWdmaContext->MidiOutDevs; paMidiInDevs = pWdmaContext->MidiInDevs; for( i = 0; i < MAXNUMDEVS; i++ ) { if( DeviceType == WaveOutDevice ) { if( (paWaveOutDevs[i].Device != UNUSED_DEVICE) && !MyWcsicmp(pmxobj->DeviceInterface, paWaveOutDevs[ i ].DeviceInterface) ) { WAVEOUTCAPS wc; ((PWAVEOUTCAPSA)(PVOID)&wc)->wMid=UNICODE_TAG; wdmaudGetDevCaps( pWdmaContext, WaveOutDevice, i, (BYTE*) &wc, sizeof( WAVEOUTCAPS ) ); wcsncpy( pLine->Line.Target.szPname, wc.szPname, MAXPNAMELEN ); pLine->Line.Target.wMid = wc.wMid; pLine->Line.Target.wPid = wc.wPid; pLine->Line.Target.vDriverVersion = wc.vDriverVersion; return; } } if( DeviceType == WaveInDevice ) { if( (paWaveInDevs[i].Device != UNUSED_DEVICE) && !MyWcsicmp(pmxobj->DeviceInterface, paWaveInDevs[ i ].DeviceInterface) ) { WAVEINCAPS wc; ((PWAVEINCAPSA)(PVOID)&wc)->wMid=UNICODE_TAG; wdmaudGetDevCaps( pWdmaContext, WaveInDevice, i, (BYTE*) &wc, sizeof( WAVEINCAPS ) ); wcsncpy( pLine->Line.Target.szPname, wc.szPname, MAXPNAMELEN ); pLine->Line.Target.wMid = wc.wMid; pLine->Line.Target.wPid = wc.wPid; pLine->Line.Target.vDriverVersion = wc.vDriverVersion; return; } } if( DeviceType == MidiOutDevice ) { if( (paMidiOutDevs[i].Device != UNUSED_DEVICE) && !MyWcsicmp(pmxobj->DeviceInterface, paMidiOutDevs[ i ].DeviceInterface) ) { MIDIOUTCAPS mc; ((PMIDIOUTCAPSA)(PVOID)&mc)->wMid=UNICODE_TAG; wdmaudGetDevCaps( pWdmaContext, MidiOutDevice, i, (BYTE*) &mc, sizeof( MIDIOUTCAPS ) ); wcsncpy( pLine->Line.Target.szPname, mc.szPname, MAXPNAMELEN ); pLine->Line.Target.wMid = mc.wMid; pLine->Line.Target.wPid = mc.wPid; pLine->Line.Target.vDriverVersion = mc.vDriverVersion; return; } } if( DeviceType == MidiInDevice ) { if( (paMidiInDevs[i].Device != UNUSED_DEVICE) && !MyWcsicmp(pmxobj->DeviceInterface, paMidiInDevs[ i ].DeviceInterface) ) { MIDIINCAPS mc; ((PMIDIINCAPSA)(PVOID)&mc)->wMid=UNICODE_TAG; wdmaudGetDevCaps( pWdmaContext, MidiInDevice, i, (BYTE*) &mc, sizeof( MIDIINCAPS ) ); wcsncpy( pLine->Line.Target.szPname, mc.szPname, MAXPNAMELEN) ; pLine->Line.Target.wMid = mc.wMid; pLine->Line.Target.wPid = mc.wPid; pLine->Line.Target.vDriverVersion = mc.vDriverVersion; return; } } } } /////////////////////////////////////////////////////////////////////// // // TargetTypeWaveOut // // Fills in the fields of aLine's target structure to be a waveout // target. // // VOID TargetTypeWaveOut( IN PMIXEROBJECT pmxobj, IN PMXLLINE pLine ) { PAGED_CODE(); pLine->Line.Target.dwType = MIXERLINE_TARGETTYPE_WAVEOUT; TargetCommon( pmxobj, pLine, WaveOutDevice ); } /////////////////////////////////////////////////////////////////////// // // TargetTypeWaveIn // // Fills in the fields of aLine's target structure to be a wavein // target. // // #define TargetTypeWaveIn( pmxobj, pLine ) \ (pLine)->Line.Target.dwType = MIXERLINE_TARGETTYPE_WAVEIN; \ (pLine)->Line.Target.wPid = MM_MSFT_WDMAUDIO_WAVEIN; \ TargetCommon( pmxobj, pLine, WaveInDevice ) /////////////////////////////////////////////////////////////////////// // // TargetTypeMidiOut // // Fills in the fields of aLine's target structure to be a midi out // target. // // #define TargetTypeMidiOut( pmxobj, pLine ) \ (pLine)->Line.Target.dwType = MIXERLINE_TARGETTYPE_MIDIOUT; \ (pLine)->Line.Target.wPid = MM_MSFT_WDMAUDIO_MIDIOUT; \ TargetCommon( pmxobj, pLine, MidiOutDevice ) /////////////////////////////////////////////////////////////////////// // // TargetTypeMidiIn // // Fills in the fields of aLine's target structure to be a midi in // target. // // #define TargetTypeMidiIn( pmxobj, pLine ) \ (aLine)->Line.Target.dwType = MIXERLINE_TARGETTYPE_MIDIOUT; \ (aLine)->Line.Target.wPid = MM_MSFT_WDMAUDIO_MIDIIN; \ TargetCommon( pmxobj, pLine, MidiInDevice ) /////////////////////////////////////////////////////////////////////// // // TargetTypeAuxCD // // Fills in the fields of aLine's target structure to be a CD // target. // // #define TargetTypeAuxCD( pmxobj, pLine ) \ (pLine)->Line.Target.dwType = MIXERLINE_TARGETTYPE_AUX; \ TargetCommon( pmxobj, pLine, WaveOutDevice ); \ (pLine)->Line.Target.wPid = MM_MSFT_SB16_AUX_CD /////////////////////////////////////////////////////////////////////// // // TargetTypeAuxLine // // Fills in the fields of aLine's target structure to be a aux line // target. // // #define TargetTypeAuxLine( pmxobj, pLine ) \ (pLine)->Line.Target.dwType = MIXERLINE_TARGETTYPE_AUX; \ TargetCommon( pmxobj, pLine, WaveOutDevice );\ (pLine)->Line.Target.wPid = MM_MSFT_SB16_AUX_LINE /////////////////////////////////////////////////////////////////////// // // kmxlDetermineDestinationType // // Determines the destination and target types by using the Type // GUID stored in the line structure. // // ULONG kmxlDetermineDestinationType( IN PMIXEROBJECT pmxobj, // Instance data IN PMXLLINE pLine // The line to determine type of ) { PAGED_CODE(); // // Speaker type destinations // if( IsEqualGUID( &pLine->Type, &KSNODETYPE_SPEAKER ) || IsEqualGUID( &pLine->Type, &KSNODETYPE_DESKTOP_SPEAKER ) || IsEqualGUID( &pLine->Type, &KSNODETYPE_ROOM_SPEAKER ) || IsEqualGUID( &pLine->Type, &KSNODETYPE_COMMUNICATION_SPEAKER ) ) { TargetTypeWaveOut( pmxobj, pLine ); return( MIXERLINE_COMPONENTTYPE_DST_SPEAKERS ); } // // WaveIn type destinations // if( IsEqualGUID( &pLine->Type, &KSCATEGORY_AUDIO ) || IsEqualGUID( &pLine->Type, &PINNAME_CAPTURE ) ) { TargetTypeWaveIn( pmxobj, pLine ); return( MIXERLINE_COMPONENTTYPE_DST_WAVEIN ); } // // Headphone destination // if( IsEqualGUID( &pLine->Type, &KSNODETYPE_HEADPHONES ) || IsEqualGUID( &pLine->Type, &KSNODETYPE_HEAD_MOUNTED_DISPLAY_AUDIO ) ) { TargetTypeWaveOut( pmxobj, pLine ); return( MIXERLINE_COMPONENTTYPE_DST_HEADPHONES ); } // // Telephone destination // if( IsEqualGUID( &pLine->Type, &KSNODETYPE_TELEPHONE ) || IsEqualGUID( &pLine->Type, &KSNODETYPE_PHONE_LINE ) || IsEqualGUID( &pLine->Type, &KSNODETYPE_DOWN_LINE_PHONE ) ) { pLine->Line.Target.dwType = MIXERLINE_TARGETTYPE_UNDEFINED; return( MIXERLINE_COMPONENTTYPE_DST_TELEPHONE ); } // // Ambiguous destination type. Figure out the destination type by looking // at the Communication. // if( IsEqualGUID( &pLine->Type, &KSNODETYPE_ANALOG_CONNECTOR ) || IsEqualGUID( &pLine->Type, &KSNODETYPE_SPDIF_INTERFACE ) ) { if (pLine->Communication == KSPIN_COMMUNICATION_BRIDGE) { TargetTypeWaveOut( pmxobj, pLine ); return( MIXERLINE_COMPONENTTYPE_DST_SPEAKERS ); } else { TargetTypeWaveIn( pmxobj, pLine ); return( MIXERLINE_COMPONENTTYPE_DST_WAVEIN ); } } // // Does not match the others. Default to Undefined destination. // pLine->Line.Target.dwType = MIXERLINE_TARGETTYPE_UNDEFINED; return( MIXERLINE_COMPONENTTYPE_DST_UNDEFINED ); } /////////////////////////////////////////////////////////////////////// // // kmxlDetermineSourceType // // Determines the destination and target types by using the Type // GUID stored in the line structure. // // ULONG kmxlDetermineSourceType( IN PMIXEROBJECT pmxobj, // Instance data IN PMXLLINE pLine // The line to determine type of ) { PAGED_CODE(); // // All microphone type sources are a microphone source. // // // We are only checking two microphone GUIDs here. We may // want to consider the rest of the microphone types in // ksmedia.h // if( IsEqualGUID( &pLine->Type, &KSNODETYPE_MICROPHONE ) || IsEqualGUID( &pLine->Type, &KSNODETYPE_DESKTOP_MICROPHONE ) ) { TargetTypeWaveIn( pmxobj, pLine ); return( MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE ); } // // Legacy audio connector and the speaker type sources represent a // waveout source. // if( IsEqualGUID( &pLine->Type, &KSNODETYPE_LEGACY_AUDIO_CONNECTOR ) || IsEqualGUID( &pLine->Type, &KSNODETYPE_SPEAKER ) || IsEqualGUID( &pLine->Type, &KSCATEGORY_AUDIO ) ) { TargetTypeWaveOut( pmxobj, pLine ); return( MIXERLINE_COMPONENTTYPE_SRC_WAVEOUT ); } // // CD player is a compact disc source. // if( IsEqualGUID( &pLine->Type, &KSNODETYPE_CD_PLAYER ) ) { TargetTypeAuxCD( pmxobj, pLine ); pLine->Line.Target.dwType = MIXERLINE_TARGETTYPE_UNDEFINED; return( MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC ); } // // Synthesizer is a sythesizer source. // if( IsEqualGUID( &pLine->Type, &KSNODETYPE_SYNTHESIZER ) ) { TargetTypeMidiOut( pmxobj, pLine ); return( MIXERLINE_COMPONENTTYPE_SRC_SYNTHESIZER ); } if( IsEqualGUID( &pLine->Type, &KSNODETYPE_LINE_CONNECTOR ) ) { TargetTypeAuxLine( pmxobj, pLine ); pLine->Line.Target.dwType = MIXERLINE_TARGETTYPE_UNDEFINED; return( MIXERLINE_COMPONENTTYPE_SRC_LINE ); } if( IsEqualGUID( &pLine->Type, &KSNODETYPE_PHONE_LINE ) || IsEqualGUID( &pLine->Type, &KSNODETYPE_TELEPHONE ) || IsEqualGUID( &pLine->Type, &KSNODETYPE_DOWN_LINE_PHONE ) ) { pLine->Line.Target.dwType = MIXERLINE_TARGETTYPE_UNDEFINED; return( MIXERLINE_COMPONENTTYPE_SRC_TELEPHONE ); } if( IsEqualGUID( &pLine->Type, &KSNODETYPE_ANALOG_CONNECTOR ) ) { // // Ambiguous src type. Figure out the destination type by looking // at the Communication. // if (pLine->Communication == KSPIN_COMMUNICATION_BRIDGE) { TargetTypeWaveIn( pmxobj, pLine ); } else { TargetTypeWaveOut( pmxobj, pLine ); } return( MIXERLINE_COMPONENTTYPE_SRC_ANALOG ); } // // Digital in/out (SPDIF) source // if( IsEqualGUID( &pLine->Type, &KSNODETYPE_SPDIF_INTERFACE ) ) { // // Ambiguous src type. Figure out the destination type by looking // at the Communication. // if (pLine->Communication == KSPIN_COMMUNICATION_BRIDGE) { TargetTypeWaveIn( pmxobj, pLine ); } else { TargetTypeWaveOut( pmxobj, pLine ); } return( MIXERLINE_COMPONENTTYPE_SRC_DIGITAL ); } // // All others are lumped under Undefined source. // pLine->Line.Target.dwType = MIXERLINE_TARGETTYPE_UNDEFINED; return( MIXERLINE_COMPONENTTYPE_SRC_UNDEFINED ); } /////////////////////////////////////////////////////////////////////// // // PinCategoryToString // // Converts the Pin category GUIDs to a string. #ifdef DEBUG #pragma LOCKED_CODE #endif #define _EG_(x,y) if (IsEqualGUID( NodeType, &x)) { return y; } const char* PinCategoryToString ( IN CONST GUID* NodeType // The GUID to translate ) { _EG_(KSNODETYPE_MICROPHONE,"Microphone"); _EG_(KSNODETYPE_DESKTOP_MICROPHONE,"Desktop Microphone"); _EG_(KSNODETYPE_SPEAKER,"Speaker"); _EG_(KSNODETYPE_HEADPHONES,"Headphones"); _EG_(KSNODETYPE_LEGACY_AUDIO_CONNECTOR,"Wave"); _EG_(KSNODETYPE_CD_PLAYER,"CD Player"); _EG_(KSNODETYPE_SYNTHESIZER,"Synthesizer"); _EG_(KSCATEGORY_AUDIO,"Wave"); _EG_(PINNAME_CAPTURE,"Wave In"); _EG_(KSNODETYPE_LINE_CONNECTOR,"Aux Line"); _EG_(KSNODETYPE_TELEPHONE,"Telephone"); _EG_(KSNODETYPE_PHONE_LINE,"Phone Line"); _EG_(KSNODETYPE_DOWN_LINE_PHONE,"Downline Phone"); _EG_(KSNODETYPE_ANALOG_CONNECTOR,"Analog connector"); //New debug names... _EG_(KSAUDFNAME_MONO_OUT,"Mono Out"); _EG_(KSAUDFNAME_STEREO_MIX,"Stereo Mix"); _EG_(KSAUDFNAME_MONO_MIX,"Mono Mix"); _EG_(KSAUDFNAME_AUX,"Aux"); _EG_(KSAUDFNAME_VIDEO,"Video"); _EG_(KSAUDFNAME_LINE_IN,"Line In"); DPF(DL_WARNING|FA_MIXER,("Path Trap send me GUID - dt %08X _GUID",NodeType) ); return "Unknown Pin Category"; } /////////////////////////////////////////////////////////////////////// // // NodeTypeToString // // Converts a NodeType GUID to a string // // const char* NodeTypeToString ( IN CONST GUID* NodeType // The GUID to translate ) { _EG_(KSNODETYPE_DAC,"DAC"); _EG_(KSNODETYPE_ADC,"ADC"); _EG_(KSNODETYPE_SRC,"SRC"); _EG_(KSNODETYPE_SUPERMIX,"SuperMIX"); _EG_(KSNODETYPE_SUM,"Sum"); _EG_(KSNODETYPE_MUTE,"Mute"); _EG_(KSNODETYPE_VOLUME,"Volume"); _EG_(KSNODETYPE_TONE,"Tone"); _EG_(KSNODETYPE_AGC,"AGC"); _EG_(KSNODETYPE_DELAY,"Delay"); _EG_(KSNODETYPE_LOUDNESS,"LOUDNESS"); _EG_(KSNODETYPE_3D_EFFECTS,"3D Effects"); _EG_(KSNODETYPE_DEV_SPECIFIC,"Dev Specific"); _EG_(KSNODETYPE_STEREO_WIDE,"Stereo Wide"); _EG_(KSNODETYPE_REVERB,"Reverb"); _EG_(KSNODETYPE_CHORUS,"Chorus"); _EG_(KSNODETYPE_ACOUSTIC_ECHO_CANCEL,"AEC"); _EG_(KSNODETYPE_EQUALIZER,"Equalizer"); _EG_(KSNODETYPE_MUX,"Mux"); _EG_(KSNODETYPE_DEMUX,"Demux"); _EG_(KSNODETYPE_STEREO_ENHANCE,"Stereo Enhance"); _EG_(KSNODETYPE_SYNTHESIZER,"Synthesizer"); _EG_(KSNODETYPE_PEAKMETER,"Peakmeter"); _EG_(KSNODETYPE_LINE_CONNECTOR,"Line Connector"); _EG_(KSNODETYPE_SPEAKER,"Speaker"); _EG_(KSNODETYPE_DESKTOP_SPEAKER,""); _EG_(KSNODETYPE_ROOM_SPEAKER,"Room Speaker"); _EG_(KSNODETYPE_COMMUNICATION_SPEAKER,"Communication Speaker"); _EG_(KSNODETYPE_LOW_FREQUENCY_EFFECTS_SPEAKER,"? Whatever..."); _EG_(KSNODETYPE_HANDSET,"Handset"); _EG_(KSNODETYPE_HEADSET,"Headset"); _EG_(KSNODETYPE_SPEAKERPHONE_NO_ECHO_REDUCTION,"Speakerphone no echo reduction"); _EG_(KSNODETYPE_ECHO_SUPPRESSING_SPEAKERPHONE,"Echo Suppressing Speakerphone"); _EG_(KSNODETYPE_ECHO_CANCELING_SPEAKERPHONE,"Echo Canceling Speakerphone"); _EG_(KSNODETYPE_CD_PLAYER,"CD Player"); _EG_(KSNODETYPE_MICROPHONE,"Microphone"); _EG_(KSNODETYPE_DESKTOP_MICROPHONE,"Desktop Microphone"); _EG_(KSNODETYPE_PERSONAL_MICROPHONE,"Personal Microphone"); _EG_(KSNODETYPE_OMNI_DIRECTIONAL_MICROPHONE,"Omni Directional Microphone"); _EG_(KSNODETYPE_MICROPHONE_ARRAY,"Microphone Array"); _EG_(KSNODETYPE_PROCESSING_MICROPHONE_ARRAY,"Processing Microphone Array"); _EG_(KSNODETYPE_ANALOG_CONNECTOR,"Analog Connector"); _EG_(KSNODETYPE_PHONE_LINE,"Phone Line"); _EG_(KSNODETYPE_HEADPHONES,"Headphones"); _EG_(KSNODETYPE_HEAD_MOUNTED_DISPLAY_AUDIO,"Head Mounted Display Audio"); _EG_(KSNODETYPE_LEGACY_AUDIO_CONNECTOR,"Legacy Audio Connector"); // _EG_(KSNODETYPE_SURROUND_ENCODER,"Surround Encoder"); _EG_(KSNODETYPE_NOISE_SUPPRESS,"Noise Suppress"); _EG_(KSNODETYPE_DRM_DESCRAMBLE,"DRM Descramble"); _EG_(KSNODETYPE_SWMIDI,"SWMidi"); _EG_(KSNODETYPE_SWSYNTH,"SWSynth"); _EG_(KSNODETYPE_MULTITRACK_RECORDER,"Multitrack Recorder"); _EG_(KSNODETYPE_RADIO_TRANSMITTER,"Radio Transmitter"); _EG_(KSNODETYPE_TELEPHONE,"Telephone"); _EG_(KSAUDFNAME_MONO_OUT,"Mono Out"); _EG_(KSAUDFNAME_LINE_IN,"Line in"); _EG_(KSAUDFNAME_VIDEO,"Video"); _EG_(KSAUDFNAME_AUX,"Aux"); _EG_(KSAUDFNAME_MONO_MIX,"Mono Mix"); _EG_(KSAUDFNAME_STEREO_MIX,"Stereo Mix"); _EG_(KSCATEGORY_AUDIO,"Audio"); _EG_(PINNAME_VIDEO_CAPTURE,"Video Capture"); DPF(DL_WARNING|FA_MIXER,("Path Trap send me GUID - dt %08X _GUID",NodeType) ); return "Unknown NodeType"; }