Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Den här artikeln förklarar ACX-strömning och buffring, vilket är viktigt för en felfri ljudupplevelse. Den beskriver hur drivrutinen kommunicerar strömtillstånd och hanterar strömbufferten. En lista över vanliga ACX-ljudvillkor och en introduktion till ACX finns i översikten över ACX-ljudklasstillägg.
ACX-strömningstyper
En AcxStream representerar en ljudström på en specifik kretsens maskinvara. En AcxStream kan aggregera ett eller flera AcxElements-liknande objekt.
ACX stöder två strömtyper. Med den första strömtypen , RT Packet Stream, kan du allokera RT-paket och använda dem för att överföra ljuddata till eller från enhetens maskinvara, tillsammans med strömtillståndsövergångar. Den andra strömtypen, den grundläggande strömmen, stöder endast strömtillståndsövergångar.
I en slutpunkt för en enskild krets är kretsen en strömningskrets som skapar en RT Packet Stream. Om två eller flera kretsar kopplas samman för att bilda en slutpunkt, är den första kretsen i slutpunkten strömningskretsen och skapar en RT Packet Stream. Anslutna kretsar skapar Grundläggande strömmar för att ta emot händelser relaterade till övergångar av strömtillstånd.
Mer information finns i ACX Stream i Sammanfattning av ACX-objekt. DDI för strömmar definieras i acxstreams.h-huvudet.
ACX-strömningskommunikationsstack
Det finns två typer av kommunikation för ACX-strömning. En kommunikationskanal styr strömningsbeteendet. Till exempel kommandon som Start, Skapa och Allokera som använder acx-standardkommunikation. ACX-ramverket använder I/O-köer och skickar WDF-begäranden med hjälp av köerna. Köbeteendet är dolt från den faktiska drivrutinskoden med hjälp av händelseåteranrop och ACX-funktioner. Drivrutinen får också möjlighet att förbearbeta alla WDF-begäranden.
Den andra och mer intressanta kommunikationsvägen hanterar ljudströmningssignalering. Signalering innebär att berätta för drivrutinen när ett paket är klart och tar emot data och när drivrutinen har bearbetat ett paket.
Huvudkrav för strömningssignalering:
- Stöd för glitch-fri uppspelning
- Låg svarstid
- Alla nödvändiga lås är begränsade till strömmen i fråga
- Användarvänlighet för drivrutinsutvecklare
För att kommunicera med drivrutinen för att signalera strömningstillstånd använder ACX händelser med en delad buffert och direkta IRP-anrop. Dessa tekniker beskrivs härnäst.
Delad buffert
En delad buffert och en händelse används för att kommunicera från drivrutinen till klienten. Händelsen och den delade bufferten ser till att klienten inte behöver vänta eller kontrollera. Klienten kan fastställa allt som behövs för att fortsätta direktuppspelningen samtidigt som behovet av direkta IRP-anrop minskar eller elimineras.
Enhetens drivrutin använder en delad buffert för att kommunicera med klienten vilket paket som renderas eller fångas. Den här delade bufferten innehåller paketantalet (enbaserad) för det senast slutförda paketet tillsammans med QPC-värdet (QueryPerformanceCounter) för slutförandetiden. För enhetsdrivrutinen måste den ange den här informationen genom att anropa AcxRtStreamNotifyPacketComplete. När enhetsdrivrutinen anropar AcxRtStreamNotifyPacketComplete uppdaterar ACX-ramverket den delade bufferten med det nya paketantalet och QPC och signalerar en händelse som delas med klienten för att indikera att klienten kan läsa det nya antalet paket.
Direkt-IRP-anrop
Direkta IRP-anrop kommunicerar från klienten till drivrutinen.
Klienten kan begära det aktuella antalet paket eller ange det aktuella antalet paket till enhetsdrivrutinen när som helst. Dessa begäranden anropar händelsehanterare för EvtAcxStreamGetCurrentPacket och EvtAcxStreamSetRenderPacket-enhetsdrivrutiner . Klienten kan också begära det aktuella insamlingspaketet, som anropar händelsehanteraren för EvtAcxStreamGetCapturePacket-enhetsdrivrutiner .
Likheter med PortCls
Kombinationen av direkta IRP-anrop och delad buffert som ACX använder liknar hur PortCls kommunicerar hanteringen av bufferkomplettering.
För att förhindra glitchar måste drivrutinerna se till att de inte gör något som kräver åtkomst till lås som också används i kontrollvägar för dataflöden.
Stort buffertstöd för uppspelning med låg ström
Minska energiförbrukningen under uppspelningen genom att minska den tid som APU:n tillbringar i ett högt energitillstånd. Eftersom normal ljuduppspelning använder buffertar på 10 ms förblir APU:n aktiv. ACX-drivrutiner kan annonsera stöd för större buffertar i intervallet 1–2 sekunder för att låta APU:n gå in i ett lägre energisparläge.
I befintliga strömningsmodeller stöder avlastningsuppspelning med låg effekt. En ljuddrivrutin indikerar stöd för avlastad uppspelning genom att visa en Audio Engine-nod på vågfiltret för en slutpunkt. Noden AudioEngine ger ett sätt att styra DSP-motorn som drivrutinen använder för att återge ljudet från de stora buffertarna med önskad bearbetning.
Noden AudioEngine innehåller följande funktioner:
- Ljudmotorbeskrivning anger för ljudstacken vilka stift på vågfiltret som ger stöd för avlastning och loopback (och stöd för värdåtergivning).
- Buffertstorleksintervall talar om för ljudstacken de minsta och högsta buffertstorlekar som kan stödjas för avlastning. uppspelning. Buffertstorleksintervallet kan ändras dynamiskt baserat på systemaktivitet.
- Formatstöd, inklusive format som stöds, det aktuella enhetsmixformatet och enhetsformatet.
- Volym, inklusive stöd för rampning, eftersom programvaruvolymen med större buffertar inte svarar.
- Loopback Protection, som instruerar drivrutinen att stänga av AudioEngine Loopback-pinnen om en eller flera av de offloadade strömmarna innehåller skyddat innehåll.
- Globalt FX-tillstånd för att aktivera eller inaktivera GFX på AudioEngine.
När du skapar ett flöde på avlastningsuttaget stöder flödet volym, lokal FX och loopback-skydd.
Uppspelning med låg effekt med ACX
ACX-ramverket använder samma modell för uppspelning med låg ström. Drivrutinen skapar tre separata ACXPIN-objekt för host-, offload- och loopback-strömning, tillsammans med ett ACXAUDIOENGINE-element som anger vilka av dessa stift som används för host, offload och loopback. Drivrutinen lägger till stiften och ACXAUDIOENGINE-elementet i ACXCIRCUIT under kretsens skapande.
Skapande av avlastad dataström
Drivrutinen lägger också till ett ACXAUDIOENGINE-element till strömmar som skapats för avlastning av data för att tillåta kontroll över volym, ljudavstängning och toppskala.
Strömningsdiagram
Det här diagrammet visar en ACX-drivrutin med flera staplar.
Varje ACX-drivrutin styr en separat del av ljudmaskinvaran, som kan komma från en annan leverantör. ACX tillhandahåller ett kompatibelt kernelströmningsgränssnitt så att program körs utan ändringar.
Strömstift
Varje ACXCIRCUIT har minst en pin-kod för mottagare och en pin-kod för källan. Dessa pins används av ACX-ramverket för att exponera kretsens anslutningar till ljudstacken. För en återgivningskrets används källstiftet för att styra återgivningsbeteendet för alla strömmar som skapats från kretsen. För en Capture-krets används "Sink Pin" för att styra upptagningsbeteendet för alla strömmar som skapats från kretsen.
ACXPIN är det objekt som används för att styra strömning i ljudvägen. Den streaming-komponenten ACXCIRCUIT ansvarar för att skapa lämpliga ACXPIN-objekt för slutpunktens ljudväg vid tiden för skapande av krets och att registrera ACXPIN:er med ACX. ACXCIRCUIT skapar endast återgivnings- eller avbildningsstiften för kretsen. ACX-ramverket skapar det andra stiftet som behövs för att kommunicera med och ansluta till kretsen.
Streamingkrets
När en slutpunkt består av en enda krets är den kretsen strömningskretsen.
När en slutpunkt består av mer än en krets som skapats av en eller flera enhetsdrivrutiner avgör ACXCOMPOSITETEMPLATE som beskriver den sammansatta slutpunkten den specifika ordning som ansluter kretsarna. Den första kretsen i slutpunkten är strömningskretsen för slutpunkten.
Strömningskretsen bör använda AcxRtStreamCreate för att skapa en RT-paketström som svar på EvtAcxCircuitCreateStream. ACXSTREAM som skapats med AcxRtStreamCreate gör att drivrutinen för strömningskretsen kan allokera bufferten som används för strömning och styra strömningsflödet som svar på klientens och maskinvarans behov.
Följande kretsar i slutpunkten bör använda AcxStreamCreate för att skapa en grundläggande ström som svar på EvtAcxCircuitCreateStream. ACXSTREAM-objekten som skapats med AcxStreamCreate av följande kretsar gör det möjligt för drivrutinerna att konfigurera maskinvara som svar på dataströmtillståndsändringar som Pausa eller Kör.
ACXCIRCUIT för streaming tar emot den första begäran om att skapa en stream. Begäran innehåller enheten, pin-koden och dataformatet (inklusive läge).
Varje ACXCIRCUIT i ljudsökvägen skapar ett ACXSTREAM-objekt som representerar kretsens ströminstans. ACX-ramverket länkar samman ACXSTREAM-objekten, ungefär som det länkar ACXCIRCUIT-objekt.
Uppströms- och nedströmskretsar
Stream-skapandet startar vid strömningskretsen och vidarebefordras till varje nedströmskrets i den ordning kretsarna är anslutna. Anslutningarna görs mellan bryggstift som skapats med kommunikation som är lika med AcxPinCommunicationNone. ACX-ramverket skapar en eller flera bryggstift för en krets om drivrutinen inte lägger till dem när kretsen skapas.
För varje krets som börjar med strömningskretsen ansluter AcxPinTypeSource-bryggstiftet till nästa nedströmskrets. Den sista kretsen har en slutpunktstift som beskriver slutpunktsmaskinvara för ljud (till exempel om slutpunkten är en mikrofon eller en högtalare och om jacket är anslutet).
För varje krets som följer strömningskretsen ansluter AcxPinTypeSink-bryggstiftet till nästa överordnade krets.
Stream-formatförhandling
Drivrutinen annonserar de format som stöds för att skapa dataströmmar genom att lägga till de format som stöds per läge i den ACXPIN som används för att skapa dataströmmar med AcxPinAssignModeDataFormatList och AcxPinGetRawDataFormatList. För slutpunkter för flera kretsar kan en ACXSTREAMBRIDGE användas för att samordna läges- och formatstöd mellan ACX-kretsar. De strömmande ACXPIN:er som skapats av strömningskretsen avgör vilka strömformat som stöds för slutpunkten. De format som används av följande kretsar bestäms av bryggstiftet för den tidigare kretsen i slutpunkten.
Som standard skapar ACX-ramverket en ACXSTREAMBRIDGE mellan varje krets i en slutpunkt för flera kretsar. Standard-ACXSTREAMBRIDGE använder RAW-lägets standardformat för bryggpinnen i den uppströmskretsen när den vidarebefordrar strömskapelsebegäran till nedströmskretsen. Om den överordnade kretsens bryggstift inte har några format används det ursprungliga strömformatet. Om det anslutna stift i den nedströmskretsen inte stöder det format som används, misslyckas strömflödeskapandet.
Om en enhetskrets utför en ändring av strömningsformatet bör enhetsdrivrutinen lägga till nedströmsformatet i nedströmsbryggstiftet.
Skapande av ström
Det första steget i Stream Creation är att skapa ACXSTREAM-instansen för varje ACXCIRCUIT i ljudvägen för slutpunkten. ACX anropar varje krets EvtAcxCircuitCreateStream. ACX börjar med huvudkretsen och anropar varje krets EvtAcxCircuitCreateStream i ordning, och slutar med sista kretsen. Sekvensen kan omvändas genom att ange flaggan AcxStreamBridgeInvertChangeStateSequence (definieras i ACX_STREAM_BRIDGE_CONFIG_FLAGS) för stream-bryggan. När alla kretsar har skapat ett strömobjekt hanterar strömobjekten strömmande logik.
Stream Creation Request skickas till lämplig PIN-kod som genereras som en del av huvudkretsens topologigenerering genom att anropa EvtAcxCircuitCreateStream som angavs när huvudkretsen skapades.
Strömningskretsen är den överordnade kretsen som ursprungligen hanterar begäran om att skapa dataströmmen.
- Den uppdaterar ACXSTREAM_INIT struktur och tilldelar AcxStreamCallbacks och AcxRtStreamCallbacks
- Det skapar ACXSTREAM-objektet med Hjälp av AcxRtStreamCreate
- Den skapar alla strömspecifika element (till exempel ACXVOLUME eller ACXAUDIOENGINE)
- Elementen läggs till i ACXSTREAM-objektet
- Det returnerar ACXSTREAM-objektet som skapades till ACX-ramverket
ACX vidarebefordrar sedan skapandet av dataströmmen till nästa underordnade krets.
- Den uppdaterar ACXSTREAM_INIT struktur och tilldelar AcxStreamCallbacks
- Det skapar ACXSTREAM-objektet med Hjälp av AcxStreamCreate
- Den skapar alla dataströmsspecifika element
- Elementen läggs till i ACXSTREAM-objektet
- Det returnerar ACXSTREAM-objektet som skapades till ACX-ramverket
Kommunikationskanalen mellan kretsar i en ljudsignalväg använder ACXTARGETSTREAM-objekt. Varje krets har åtkomst till en I/O-kö för kretsen framför den och kretsen bakom den i ljudvägen vid slutpunkten. Slutpunktens ljudbana är linjär och dubbelriktad. ACX-ramverket hanterar den faktiska I/O-köbearbetningen.
När du skapar ACXSTREAM-objektet kan varje krets lägga till kontextinformation i ACXSTREAM-objektet för att lagra och spåra privata data för strömmen.
Exempel på återgivningsström
Skapa en återgivningsström på en slutpunktens ljudväg som består av tre kretsar: DSP, CODEC och AMP. DSP-kretsen fungerar som strömningskrets och har tillhandahållit en EvtAcxPinCreateStream-hanterare. DSP-kretsen fungerar också som en filterkrets: beroende på strömläge och konfiguration kan den tillämpa signalbearbetning på ljuddata. CODEC-kretsen representerar DAC och tillhandahåller ljudmottagarens funktioner. AMP-kretsen representerar den analoga maskinvaran mellan DAC och högtalaren. AMP-kretsen kan hantera uttagsidentifiering eller annan maskinvaruinformation för slutpunkten.
- AudioKSE anropar NtCreateFile för att skapa en ström.
- Detta filtrerar via ACX och slutar med att anropa DSP-kretsens EvtAcxPinCreateStream med stiftet, dataformatet (inklusive läge) och enhetsinformation.
- DSP-kretsen verifierar dataformatinformationen för att säkerställa att den kan hantera den skapade strömmen.
- DSP-kretsen skapar ACXSTREAM-objektet för att representera strömmen.
- DSP-kretsen allokerar en privat kontextstruktur och associerar den med ACXSTREAM.
- DSP-kretsen returnerar körningsflödet till ACX-ramverket, som sedan anropar till nästa krets i Endpoint Audio Path, CODEC-kretsen.
- CODEC-kretsen verifierar dataformatsinformationen för att bekräfta att den kan hantera återgivning av data.
- CODEC-kretsen allokerar en privat kontextstruktur och associerar den med ACXSTREAM.
- CODEC-kretsen lägger till sig själv som en strömmottagare till ACXSTREAM.
- CODEC-kretsen returnerar körningsflödet till ACX-ramverket, som sedan anropar till nästa krets i Endpoint Audio Path, AMP-kretsen.
- AMP-kretsen allokerar en privat kontextstruktur och associerar den med ACXSTREAM.
- AMP-kretsen returnerar körningsflödet till ACX-ramverket. Nu är det klart att skapa dataströmmen.
Stora buffertströmmar
Stora buffertströmmar skapas på DEN ACXPIN som är avsedd för avlastning av ACXCIRCUIT:s ACXAUDIOENGINE-element.
För att stödja avlastningsströmmar bör enhetsdrivrutinen utföra följande åtgärder när strömningskretsen skapas:
- Skapa ACXPIN-objekten Host, Offload och Loopback och lägg till dem i ACXCIRCUIT.
- Skapa elementen ACXVOLUME, ACXMUTE och ACXPEAKMETER. Dessa läggs inte till direkt i ACXCIRCUIT.
- Initiera en ACX_AUDIOENGINE_CONFIG struktur och tilldela objekten HostPin, OffloadPin, LoopbackPin, VolumeElement, MuteElement och PeakMeterElement.
- Skapa elementet ACXAUDIOENGINE.
Drivrutiner behöver utföra liknande steg för att lägga till ett ACXSTREAMAUDIOENGINE-element när de skapar en signal på offload-stiftet.
Strömma resursallokering
Strömningsmodellen för ACX är paketbaserad, med stöd för ett eller två paket för en ström. Återgivnings- eller avbildnings-ACXPIN för strömningskretsen får en begäran om att allokera minnespaketen som används i strömmen. För att stöda ombalansering måste det allokerade minnet vara systemminne i stället för enhetsminne som mappas till systemet. Drivrutinen kan använda befintliga WDF-funktioner för att utföra allokeringen och returnera en matris med pekare till buffertallokeringarna. Om drivrutinen kräver ett enda sammanhängande block kan den allokera båda paketen som en enda buffert. Det andra paketet har WdfMemoryDescriptorTypeInvalid och förskjutningen för det andra paketet finns i bufferten som beskrivs av det första paketet.
Om ett enda paket allokeras måste drivrutinen allokera en sidorjusterad buffert med en längd som är sidindelbar. Förskjutningen för det enskilda paketet måste också vara 0. ACX-ramverket mappar det här paketet till användarläge två gånger i följd.
| paket 0 | paket 0 |
Detta gör att GetBuffer kan returnera en pekare till en enda sammanhängande minnesbuffert som kan sträcka sig från slutet av bufferten till början utan att programmet behöver hantera omslutning av minnesåtkomsten.
Om två paket allokeras mappas de till användarläge:
| paket 0 | paket 1 |
Med den första ACX-paketströmningen finns det bara två paket som allokerats i början. När allokeringen och mappningen har utförts är den virtuella klientminnesmappningen giltig, utan att den ändras under strömmens livslängd. Det finns en händelse kopplad till strömmen för att indikera slutförandet av båda paketen. Det finns också en delad buffert som ACX-ramverket använder för att kommunicera vilket paket som har slutförts med händelsen.
För PacketCount=1, om programmet ber om 10 ms data, skickar ljudstacken en begäran om en enda buffert på 10 ms till drivrutinen (den fördubblar inte buffertstorleken som skickas till drivrutinen).
Drivrutinen allokerar en sidjusterad buffert som har en varaktighet på minst 10 ms. För en 48k 2ch 2 byte per exempelström är den minsta timerdrivna bufferten som kan allokeras 1 024 exempel (en sida minne), vilket är 21,333 ms. För en 48 kHz 8ch 2 byte per samplingsström är den minsta timerdrivna bufferten som kan allokeras 512 samplingar (en sida med minne) eller 10.667 ms. För en 48k 6ch 2 byte per samplingsström är den minsta timerdrivna bufferten fortfarande 1 024 samplingar (tre sidor minne, för att se till att slutet av en sampling överensstämmer med slutet av bufferten), vilket är 21,333 ms.
ACX-ramverket mappar denna sidorade buffert till användarlägesprocessen två gånger i följd. Processen för användarläge kan sedan skriva upp till en bufferts värde av data till mappningen i användarläge som börjar var som helst i bufferten utan att behöva göra några omslutningar.
Drivrutinen anropar NotifyPacketComplete när hela paketet har lästs från systemminnet, så att systemet vet att det kan skriva nästa paket med ljuddata till paketets buffert.
Det finns en fördröjning mellan NotifyPacketComplete och när det sista exemplet på paketet återges. Den här fördröjningen uttrycks som ett resultat av EvtAcxStreamGetHwLatency.
Pingisbuffertar
Ping-pongbuffertar kan användas, där en buffert läses (ping), medan den andra fylls (pong). Detta gör att en buffert kan bearbetas medan den andra samlar in nästa uppsättning data. I ACX tar drivrutinen internt hand om växlingen när en buffert fylls. När pingbufferten har fyllts meddelas den med ett registrerat återanrop. I återanropet hämtas den bearbetade buffertens adress och bufferten skickas på nytt. Under tiden samlar pong-bufferten in data i bakgrunden. Den här mekanismen säkerställer kontinuerlig databehandling utan avbrott.
För en ping-pong-buffert är den begärda paketstorleken för en enda buffert (antingen ping eller pong) och antalet paket är två.
När du delar en enda buffert mellan två paket, konfigurerar du det andra paketet som beskrivs i callback-funktionen EVT_ACX_STREAM_ALLOCATE_RTPACKETS. Den del av bufferten som beskrivs av det första paketet (minne, förskjutning och längd) är pingbufferten, medan den del som beskrivs av det andra paketet (inget minne som anger att bufferten delas med det första paketet plus förskjutning som pekar på bufferten strax efter det första paketet) är pongbufferten.
Lägga till ytterligare information i paketrubriken
Det går bara att lägga till ytterligare information i pakethuvudinformationen, till exempel för loggning eller bokföring, i början av paketet för ping/pong-händelsedrivna strömmar (där antal paket = 2). För timerstyrda strömmar med endast ett paket måste paketet vara helt sidjusterat (börja och sluta på en sidgräns) eftersom paketet mappas in i användarläge två gånger.
I det här fallet kan appen skriva förbi slutet av den första mappningen till den andra mappningen, som skriver i slutet av systembufferten och sedan i början av samma systembuffert.
Den enda allokerade bufferten måste vara sidjusterad eftersom mappningen av virtuellt minne till användarläge sker per sida.
Timerdrivna buffertar
Timerdrivna buffertar i ACX kan användas för att säkerställa en felfri ljudupplevelse genom att upprätthålla exakt tid och synkronisering. För timerdrivna buffertar i ACX:
- Klienten använder värdet från EvtAcxStreamGetPresentationPosition för att avgöra hur många bildrutor som kan skrivas.
- Presentationspositionen måste uppdateras mer än en gång per pass genom bufferten. Klienten skriver till bufferten med början vid den position som den senast skrev till, fram till den position som drivrutinen rapporterar (vilket bör vara de data som maskinvaran förbrukade sedan den senaste gången positionen efterfrågades).
- Ju mer detaljerad positionen är, desto mindre sannolikt är det att du får problem.
- I tidsinställda buffertar kan DSP inte bara förbruka hela bufferten innan positionen uppdateras.
- I ett tidtagningsdrivet system kan drivrutinen potentiellt dela en tidsstyrd buffert i flera DSP-buffertar och uppdatera positionen medan DSP:n arbetar genom varje buffert (till exempel skulle en 20 ms tidsstyrd buffert uppdelad i tio 2 ms buffertar fungera ganska bra i ett tidtagningsdrivet läge).
Paketstorlekar för stora buffertströmmar
När du exponerar stöd för stora buffertar ger drivrutinen också ett återanrop som används för att fastställa minsta och högsta paketstorlekar för uppspelning av stor buffert.
Paketstorleken för dataströmbuffertallokering bestäms baserat på lägsta och högsta.
Eftersom minsta och högsta buffertstorlekar kan vara flyktiga kan drivrutinen misslyckas med paketallokeringsanropet om det finns ändringar i minsta och högsta buffertstorlekar.
Ange begränsningar för ACX-buffert
Om du vill ange begränsningar för ACX-buffert kan ACX-drivrutiner använda inställningen KS/PortCls-egenskaper – KSAUDIO_PACKETSIZE_CONSTRAINTS2 och KSAUDIO_PACKETSIZE_PROCESSINGMODE_CONSTRAINT struktur.
Följande kodexempel visar hur du anger begränsningar för buffertstorlek för WaveRT-buffertar för olika signalbearbetningslägen.
//
// Describe buffer size constraints for WaveRT buffers
// Note: 10msec for each of the Modes is the default system behavior.
//
static struct
{
KSAUDIO_PACKETSIZE_CONSTRAINTS2 TransportPacketConstraints; // 1
KSAUDIO_PACKETSIZE_PROCESSINGMODE_CONSTRAINT AdditionalProcessingConstraints[4]; // + 4 = 5
} DspR_RtPacketSizeConstraints =
{
{
10 * HNSTIME_PER_MILLISECOND, // 10 ms minimum processing interval
FILE_BYTE_ALIGNMENT, // 1 byte packet size alignment
0, // no maximum packet size constraint
5, // 5 processing constraints follow
{
STATIC_AUDIO_SIGNALPROCESSINGMODE_RAW, // constraint for raw processing mode
0, // NA samples per processing frame
10 * HNSTIME_PER_MILLISECOND, // 100000 hns (10ms) per processing frame
},
},
{
{
STATIC_AUDIO_SIGNALPROCESSINGMODE_DEFAULT, // constraint for default processing mode
0, // NA samples per processing frame
10 * HNSTIME_PER_MILLISECOND, // 100000 hns (10ms) per processing frame
},
{
STATIC_AUDIO_SIGNALPROCESSINGMODE_COMMUNICATIONS, // constraint for movie communications mode
0, // NA samples per processing frame
10 * HNSTIME_PER_MILLISECOND, // 100000 hns (10ms) per processing frame
},
{
STATIC_AUDIO_SIGNALPROCESSINGMODE_MEDIA, // constraint for default media mode
0, // NA samples per processing frame
10 * HNSTIME_PER_MILLISECOND, // 100000 hns (10ms) per processing frame
},
{
STATIC_AUDIO_SIGNALPROCESSINGMODE_MOVIE, // constraint for movie movie mode
0, // NA samples per processing frame
10 * HNSTIME_PER_MILLISECOND, // 100000 hns (10ms) per processing frame
},
}
};
En DSP_DEVPROPERTY struktur används för att lagra begränsningarna.
typedef struct _DSP_DEVPROPERTY {
const DEVPROPKEY *PropertyKey;
DEVPROPTYPE Type;
ULONG BufferSize;
__field_bcount_opt(BufferSize) PVOID Buffer;
} DSP_DEVPROPERTY, PDSP_DEVPROPERTY;
Och en matris med dessa strukturer skapas.
const DSP_DEVPROPERTY DspR_InterfaceProperties[] =
{
{
&DEVPKEY_KsAudio_PacketSize_Constraints2, // Key
DEVPROP_TYPE_BINARY, // Type
sizeof(DspR_RtPacketSizeConstraints), // BufferSize
&DspR_RtPacketSizeConstraints, // Buffer
},
};
Senare i funktionen EvtCircuitCompositeCircuitInitialize används hjälpfunktionen AddPropertyToCircuitInterface för att lägga till matrisen med gränssnittsegenskaper i kretsen.
// Set RT buffer constraints.
//
status = AddPropertyToCircuitInterface(Circuit, ARRAYSIZE(DspC_InterfaceProperties), DspC_InterfaceProperties);
Hjälpfunktionen AddPropertyToCircuitInterface tar AcxCircuitGetSymbolicLinkName för kretsen och anropar sedan IoGetDeviceInterfaceAlias för att hitta det ljudgränssnitt som används av kretsen.
Sedan anropar funktionen SetDeviceInterfacePropertyDataMultiple funktionen IoSetDeviceInterfacePropertyData för att ändra det aktuella värdet för egenskapen för enhetsgränssnittet – KS-ljudegenskapsvärdena i ljudgränssnittet för ACXCIRCUIT.
PAGED_CODE_SEG
NTSTATUS AddPropertyToCircuitInterface(
_In_ ACXCIRCUIT Circuit,
_In_ ULONG PropertyCount,
_In_reads_opt_(PropertyCount) const DSP_DEVPROPERTY * Properties
)
{
PAGED_CODE();
NTSTATUS status = STATUS_UNSUCCESSFUL;
UNICODE_STRING acxLink = {0};
UNICODE_STRING audioLink = {0};
WDFSTRING wdfLink = AcxCircuitGetSymbolicLinkName(Circuit);
bool freeStr = false;
// Get the underline unicode string.
WdfStringGetUnicodeString(wdfLink, &acxLink);
// Make sure there is a string.
if (!acxLink.Length || !acxLink.Buffer)
{
status = STATUS_INVALID_DEVICE_STATE;
DrvLogError(g_BthLeVDspLog, FLAG_INIT,
L"AcxCircuitGetSymbolicLinkName failed, Circuit: %p, %!STATUS!",
Circuit, status);
goto exit;
}
// Get the audio interface.
status = IoGetDeviceInterfaceAlias(&acxLink, &KSCATEGORY_AUDIO, &audioLink);
if (!NT_SUCCESS(status))
{
DrvLogError(g_BthLeVDspLog, FLAG_INIT,
L"IoGetDeviceInterfaceAlias failed, Circuit: %p, symbolic link name: %wZ, %!STATUS!",
Circuit, &acxLink, status);
goto exit;
}
freeStr = true;
// Set specified properties on the audio interface for the ACXCIRCUIT.
status = SetDeviceInterfacePropertyDataMultiple(&audioLink, PropertyCount, Properties);
if (!NT_SUCCESS(status))
{
DrvLogError(g_BthLeVDspLog, FLAG_INIT,
L"SetDeviceInterfacePropertyDataMultiple failed, Circuit: %p, symbolic link name: %wZ, %!STATUS!",
Circuit, &audioLink, status);
goto exit;
}
status = STATUS_SUCCESS;
exit:
if (freeStr)
{
RtlFreeUnicodeString(&audioLink);
freeStr = false;
}
return status;
}
Dataströmtillståndsändringar
När en ändring av strömtillståndet sker tar varje strömobjekt i slutpunktens ljudsökväg för strömmen emot en meddelandehändelse från ACX-ramverket. I vilken ordning detta sker beror på tillståndsändringen och flödet i dataströmmen.
För återgivningsströmmar som går från ett mindre aktivt tillstånd till ett mer aktivt tillstånd tar strömningskretsen (som registrerade SINK) emot händelsen först. När kretsen hanterar händelsen tar nästa krets i slutpunktens ljudsökväg emot händelsen.
För återgivningsströmmar som går från ett mer aktivt tillstånd till ett mindre aktivt tillstånd tar strömningskretsen emot händelsen senast.
För Avbildningsströmmar som går från ett mindre aktivt tillstånd till ett mer aktivt tillstånd tar strömningskretsen emot händelsen senast.
För Avbildningsströmmar som går från ett mer aktivt tillstånd till ett mindre aktivt tillstånd tar strömningskretsen emot händelsen först.
Beställningen är standardinställningen som tillhandahålls av ACX-ramverket. En drivrutin kan begära motsatt beteende genom att ange AcxStreamBridgeInvertChangeStateSequence (definierat i ACX_STREAM_BRIDGE_CONFIG_FLAGS) när du skapar ACXSTREAMBRIDGE som drivrutinen lägger till i strömningskretsen.
Strömma ljuddata
När du har skapat strömmen och allokerat lämpliga buffertar är strömmen i paustillstånd och väntar på att strömmen ska starta. När klienten försätter strömmen i uppspelningstillstånd anropar ACX-ramverket alla ACXSTREAM-objekt som är associerade med strömmen för att indikera att strömtillståndet är i Play. ACXPIN placeras sedan i uppspelningstillståndet och data börjar flöda.
Återge ljuddata
När du har skapat strömmen och allokerat resurserna anropar programmet Starta på strömmen för att starta uppspelningen. Programmet bör anropa GetBuffer/ReleaseBuffer innan strömmen startas för att se till att det första paketet som börjar spelas upp har giltiga ljuddata.
Klienten börjar med att förregistrera en buffert. När klienten anropar ReleaseBuffer översätts detta till ett anrop i AudioKSE som anropar till ACX-lagret, som anropar EvtAcxStreamSetRenderPacket på den aktiva ACXSTREAM. Denna egenskap innehåller paketindexet (nollbaserat) och, om lämpligt, ett EOS-flagg med byteförskjutningen för strömmens slut i det aktuella paketet.
När strömningskretsen har slutfört bearbetningen av ett paket utlöses buffertslutförandemeddelandet som frigör klienter som väntar på att fylla nästa paket med renderad ljuddata.
Det timerdrivna strömningsläget stöds och anges med ett PacketCount-värde på 1 när du anropar drivrutinens EvtAcxStreamAllocateRtPackets-återanrop .
Samla in ljuddata
När strömmen körs fyller källkretsen avbildningspaketet med ljuddata. När det första paketet är fyllt släpper källkretsen paketet till ACX-ramverket. Nu signalerar ACX-ramverket stream-meddelandehändelsen.
När streammeddelandet har signalerats kan klienten skicka KSPROPERTY_RTAUDIO_GETREADPACKET för att hämta indexet (nollbaserat) för det paket som har hämtats. När klienten skickar GETCAPTUREPACKET kan drivrutinen anta att alla tidigare paket bearbetas och är tillgängliga för fyllning.
För Burst-avbildning kan källkretsen släppa ett nytt paket till ACX-ramverket så snart GETREADPACKET har anropats.
Klienten kan också använda KSPROPERTY_RTAUDIO_PACKETVREGISTER för att få en pekare till RTAUDIO_PACKETVREGISTER-struktur för strömmen. ACX-ramverket uppdaterar den här strukturen innan signalpaketet slutförs.
Äldre KS-kerneluppspelningsbeteende
Ibland, till exempel när en drivrutin implementerar burst capture (till exempel en nyckelordsspäckare), måste du använda det äldre beteendet för hantering av kernel-strömningspaket i stället för PacketVRegister. Om du vill använda det tidigare paketbaserade beteendet returnerar drivrutinen STATUS_NOT_SUPPORTED för KSPROPERTY_RTAUDIO_PACKETVREGISTER.
Följande exempel visar hur du gör detta i AcxStreamInitAssignAcxRequestPreprocessCallback för en ACXSTREAM. Mer information finns i AcxStreamDispatchAcxRequest.
Circuit_EvtStreamRequestPreprocess(
_In_ ACXOBJECT Object,
_In_ ACXCONTEXT DriverContext,
_In_ WDFREQUEST Request)
{
ACX_REQUEST_PARAMETERS params;
PCIRCUIT_STREAM_CONTEXT streamCtx;
streamCtx = GetCircuitStreamContext(Object);
// The driver would define the pin type to track which pin is the keyword pin.
// The driver would add this to the driver-defined context when the stream is created.
// The driver would use AcxStreamInitAssignAcxRequestPreprocessCallback to set
// the Circuit_EvtStreamRequestPreprocess callback for the stream.
if (streamCtx && streamCtx->PinType == CapturePinTypeKeyword)
{
if (IsEqualGUID(params.Parameters.Property.Set, KSPROPSETID_RtAudio) &&
params.Parameters.Property.Id == KSPROPERTY_RTAUDIO_PACKETVREGISTER)
{
status = STATUS_NOT_SUPPORTED;
outDataCb = 0;
WdfRequestCompleteWithInformation(Request, status, outDataCb);
return;
}
}
(VOID)AcxStreamDispatchAcxRequest((ACXSTREAM)Object, Request);
}
Strömposition
ACX-ramverket anropar EvtAcxStreamGetPresentationPosition-återanropet för att hämta den aktuella strömpositionen. Den aktuella strömpositionen innehåller PlayOffset och WriteOffset.
WaveRT-strömningsmodellen gör att ljuddrivrutinen kan exponera ett HW-positionsregister för klienten. ACX-strömningsmodellen stöder inte att exponera några HW-register eftersom dessa skulle förhindra att en ombalansering sker.
Varje gång strömningskretsen slutför ett paket anropas AcxRtStreamNotifyPacketComplete med det nollbaserade paketindexet och det QPC-värde som tas så nära paketens slutförande som möjligt (t.ex. avbrottstjänstrutinen kan beräkna QPC-värdet). Klienter kan hämta den här informationen via KSPROPERTY_RTAUDIO_PACKETVREGISTER, som returnerar en pekare till en struktur som innehåller CompletedPacketCount, CompletedPacketQPC och ett värde som kombinerar de två (så att klienten kan kontrollera att CompletedPacketCount och CompletedPacketQPC kommer från samma paket).
Strömtillståndsövergångar
När en ström har skapats kommer ACX att växla strömmen till olika tillstånd med hjälp av följande återkopplingar:
- EvtAcxStreamPrepareHardware övergår strömmen från tillståndet AcxStreamStateStop till Tillståndet AcxStreamStatePause. Drivrutinen bör reservera nödvändig maskinvara, till exempel DMA-motorer, när den tar emot EvtAcxStreamPrepareHardware.
- EvtAcxStreamRun övergår strömmen från tillståndet AcxStreamStatePause till tillståndet AcxStreamStateRun.
- EvtAcxStreamPause övergår strömmen från tillståndet AcxStreamStateRun till tillståndet AcxStreamStatePause.
- EvtAcxStreamReleaseHardware övergår strömmen från tillståndet AcxStreamStatePause till tillståndet AcxStreamStateStop. Drivrutinen bör släppa nödvändig maskinvara, till exempel DMA-motorer när den tar emot EvtAcxStreamReleaseHardware.
Strömmen kan ta emot EvtAcxStreamPrepareHardware-återanropet när den har tagit emot EvtAcxStreamReleaseHardware-återanropet. Då överförs strömmen tillbaka till tillståndet AcxStreamStatePause.
Paketallokering med EvtAcxStreamAllocateRtPackets sker normalt före det första anropet till EvtAcxStreamPrepareHardware. De allokerade paketen frigörs normalt med EvtAcxStreamFreeRtPackets efter det senaste anropet till EvtAcxStreamReleaseHardware. Den här beställningen är inte garanterad.
Tillståndet AcxStreamStateAcquire används inte. ACX tar bort behovet av att drivrutinen har förvärvat tillstånd eftersom det här tillståndet är implicit med återanropen prepare hardware (EvtAcxStreamPrepareHardware) och frigöra maskinvaran (EvtAcxStreamReleaseHardware).
Stöd för stora buffertströmmar och avlastningsmotor
ACX använder ACXAUDIOENGINE-elementet för att ange en ACXPIN som hanterar skapande av avlastningsström och de olika element som krävs för avlastning av strömvolym, ljudavstängning och högsta mätartillstånd. Detta liknar den befintliga ljudmotornoden i WaveRT-drivrutiner.
Processen för att stänga dataströmmen
När klienten stänger strömmen tar drivrutinen emot EvtAcxStreamPause och EvtAcxStreamReleaseHardware innan ACXSTREAM-objektet tas bort av ACX-ramverket. Drivrutinen kan tillhandahålla standardposten WDF EvtCleanupCallback i WDF_OBJECT_ATTRIBUTES-strukturen när den anropar AcxStreamCreate för att utföra den slutliga rensning för ACXSTREAM. WDF anropar EvtCleanupCallback när ramverket försöker ta bort objektet. Använd inte EvtDestroyCallback, som anropas först när alla referenser till objektet har släppts, vilket är obestämt.
Drivrutinen bör rensa systemminnesresurser som är associerade med ACXSTREAM-objektet i EvtCleanupCallback om resurserna inte redan rensas i EvtAcxStreamReleaseHardware.
Drivrutinen bör inte rensa resurser som stöder dataströmmen förrän klienten begär det.
Tillståndet AcxStreamStateAcquire används inte. ACX tar bort behovet av att drivrutinen inhämtar tillståndet eftersom det här tillståndet är implicit med återanropen prepare hardware (EvtAcxStreamPrepareHardware) och frigöra maskinvaran (EvtAcxStreamReleaseHardware).
Överraskningsborttagning och ogiltigförklaring av dataström
Om drivrutinen fastställer att strömmen är ogiltig (t.ex. att uttagen är urkopplad) stänger kretsen av alla strömmar.
Rensning av strömminne
Borttagningen av dataströmmens resurser kan göras i drivrutins rensning av strömkontexten (förstör inte). Placera inte bortskaffandet av något som delas i ett objekts kontext för att förstöra återanrop. Den här vägledningen gäller för alla ACX-objekt.
Återanropet destroy anropas när den senaste referensen är borta, vilket är obestämt.
I allmänhet anropas strömmens återanrop för rensning när handtaget stängs. Ett undantag är när drivrutinen skapar strömmen i återanropet. Om ACX inte kan lägga till den här strömmen i sin strömbrygga precis innan den returneras från stream-skapandeåtgärden avbryts strömmen asynkront och den aktuella tråden returnerar ett fel till create stream-klienten. Strömmen bör inte ha några minnesallokeringar just nu. Mer information finns i EVT_ACX_STREAM_RELEASE_HARDWARE återanrop.
Rensningssekvens för strömminne
Strömbufferten är en systemresurs och du bör bara släppa den när klienten i användarläge stänger strömmens handtag. Bufferten (som skiljer sig från enhetens maskinvaruresurser) har samma livslängd som strömmens handtag. När klienten stänger handtaget, anropar ACX strömobjektets cleanup callback, och sedan strömobjektets delete callback när objektets referensantal når noll.
Det är möjligt för ACX att skjuta upp en stream-objektborttagning till en arbetsuppgift när drivrutinen skapade ett stream-objekt och sedan misslyckades med create-stream-återanropet. För att förhindra ett dödläge med en WDF-tråd som stängs av skjuter ACX upp borttagningen till en annan tråd. För att undvika eventuella biverkningar av det här beteendet (uppskjuten frigöring av resurser) kan drivrutinen frigöra de allokerade strömresurserna innan den returnerar ett fel från stream-create.
Drivrutinen måste frigöra ljudbuffertarna när ACX anropar EVT_ACX_STREAM_FREE_RTPACKETS-callback. Det här återanropet inträffar när användaren stänger strömhandtagen.
Eftersom RT-buffertar mappas i användarläge är buffertens livslängd densamma som handtagets livslängd. Drivrutinen ska inte släppa eller frigöra ljudbuffertarna innan ACX anropar callback-funktionen.
EVT_ACX_STREAM_FREE_RTPACKETS återanrop ska anropas efter EVT_ACX_STREAM_RELEASE_HARDWARE återanrop och avslutas innan EvtDeviceReleaseHardware.
Det här återanropet kan inträffa efter att drivrutinen har bearbetat återanropet för frisläppande av hårdvara i WDF eftersom klienten i användarläge kan behålla sina handtag under lång tid. Föraren bör inte vänta tills handtagen försvinner. Den här åtgärden skapar en 0x9f DRIVER_POWER_STATE_FAILURE buggkontroll. Mer information finns i EVT_WDF_DEVICE_RELEASE_HARDWARE återanropsfunktion .
Den här EvtDeviceReleaseHardware-koden från ACX-exempeldrivrutinen visar ett exempel på hur du anropar AcxDeviceRemoveCircuit och sedan släpper det strömmande maskinvaruminnet.
RETURN_NTSTATUS_IF_FAILED(AcxDeviceRemoveCircuit(Device, devCtx->Render));
RETURN_NTSTATUS_IF_FAILED(AcxDeviceRemoveCircuit(Device, devCtx->Capture));
// NOTE: Release streaming h/w resources here.
CSaveData::DestroyWorkItems();
CWaveReader::DestroyWorkItems();
Sammanfattningsvis:
- Frigör maskinvaruresurser för WDF-enhet.
- AcxStreamFreeRtPackets: släpp eller frigör ljudbufferten som är associerad med handtaget.
Mer information om hur du hanterar WDF- och kretsobjekt finns i ACX WDF-drivrutinslivshantering.
DDI:er för direktuppspelning
Strömningsstrukturer
ACX_RTPACKET-struktur
Den här strukturen representerar ett enda allokerat paket. PacketBuffer kan vara en WDFMEMORY-handle, en MDL eller en buffert. Den har en associerad initieringsfunktion , ACX_RTPACKET_INIT.
ACX_STREAM_CALLBACKS
Den här strukturen identifierar drivrutinsåteranrop för direktuppspelning till ACX-ramverket. Den här strukturen är en del av ACX_PIN_CONFIG struktur.
Återanrop för streaming
EvtAcxStreamAllocateRtPackets
Händelsen EvtAcxStreamAllocateRtPackets instruerar drivrutinen att allokera RtPackets för strömning. En AcxRtStream tar emot PacketCount = 2 för händelsedriven strömning eller PacketCount = 1 för timerbaserad strömning. Om drivrutinen använder en enda buffert för båda paketen bör den andra RtPacketBuffer ha en WDF_MEMORY_DESCRIPTOR med Type = WdfMemoryDescriptorTypeInvalid med en RtPacketOffset som överensstämmer med slutet av det första paketet (paket[2]. RtPacketOffset = packet[1]. RtPacketOffset+packet[1]. RtPacketSize).
EvtAcxStreamFreeRtPackets
Händelsen EvtAcxStreamFreeRtPackets uppmanar drivrutinen att frigöra de RtPackets som allokerades i ett tidigare anrop till EvtAcxStreamAllocateRtPackets. Samma paket från det specifika anropet ingår.
EvtAcxStreamGetHwLatency
Händelsen EvtAcxStreamGetHwLatency instruerar drivrutinen att tillhandahålla strömfördröjning för den specifika kretsen i den här strömmen (den övergripande svarstiden är en summa av svarstiden för de olika kretsarna). FifoSize är i bytes och fördröjningen är i enheter av 100 nanosekunder.
EvtAcxStreamSetRenderPacket
Händelsen EvtAcxStreamSetRenderPacket talar om för drivrutinen vilket paket som just släpptes av klienten. Om det inte finns några problem bör det här paketet vara (CurrentRenderPacket + 1), där CurrentRenderPacket är det paket som drivrutinen för närvarande strömmar från.
Flaggor kan vara 0 eller KSSTREAM_HEADER_OPTIONSF_ENDOFSTREAM = 0x200, vilket indikerar att paketet är det sista paketet i strömmen, och EosPacketLength är en giltig längd i byte för paketet. Mer information finns i AlternativFlags i KSSTREAM_HEADER struktur (ks.h).
Drivrutinen fortsätter att öka CurrentRenderPacket medan paket bearbetas i stället för att ändra dess CurrentRenderPacket så att det matchar det här värdet.
EvtAcxStreamGetCurrentPacket
EvtAcxStreamGetCurrentPacket instruerar drivrutinen att ange vilket paket (nollbaserat) som återges till maskinvaran eller som för närvarande fylls i av insamlingsmaskinvaran.
EvtAcxStreamGetCapturePacket
EvtAcxStreamGetCapturePacket instruerar drivrutinen att ange vilket paket (nollbaserat) som fylldes senast, inklusive QPC-värdet när drivrutinen började fylla paketet.
EvtAcxStreamGetPresentationPosition
EvtAcxStreamGetPresentationPosition instruerar drivrutinen att ange den aktuella positionen tillsammans med QPC-värdet när den aktuella positionen beräknades.
STREAM-TILLSTÅNDSHÄNDELSER
Följande API:er hanterar strömningstillståndet för en ACXSTREAM.
- EVT_ACX_STREAM_PREPARE_HARDWARE
- EVT_ACX_STREAM_RELEASE_HARDWARE
- EVT_ACX_STREAM_RUN
- EVT_ACX_STREAM_PAUSE
ACX-API:er för direktuppspelning
AcxStreamCreate
AcxStreamCreate skapar en ACX Stream som kan användas för att styra strömningsbeteendet.
AcxRtStreamCreate
AcxRtStreamCreate skapar en ACX Stream som kan användas för att styra strömningsbeteendet och hantera paketallokering och kommunicera strömningstillstånd.
AcxRtStreamNotifyPacketComplete
Drivrutinen anropar detta ACX-API när ett paket har slutförts. Paketens slutförandetid och det nollbaserade paketindexet ingår för att förbättra klientprestandan. ACX-ramverket anger alla meddelandehändelser som är associerade med strömmen.