607 lines
28 KiB
C#
607 lines
28 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Net;
|
|
using System.Net.Sockets;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using System.Threading;
|
|
|
|
namespace LoopbackCapture
|
|
{
|
|
class Program
|
|
{
|
|
const int SIO_UDP_CONNRESET = -1744830452;
|
|
|
|
static int selectedTargetPid = 0; // 0 = Tutto il sistema
|
|
|
|
static void Main(string[] args)
|
|
{
|
|
int port = 4242;
|
|
int cmdPort = 4243;
|
|
if (args.Length > 0)
|
|
{
|
|
int.TryParse(args[0], out port);
|
|
cmdPort = port + 1;
|
|
}
|
|
|
|
UdpClient udpSend = null;
|
|
try
|
|
{
|
|
udpSend = new UdpClient();
|
|
byte[] inValue = new byte[] { 0, 0, 0, 0 };
|
|
byte[] outValue = new byte[] { 0, 0, 0, 0 };
|
|
udpSend.Client.IOControl(SIO_UDP_CONNRESET, inValue, outValue);
|
|
}
|
|
catch { }
|
|
|
|
IPEndPoint remoteEp = new IPEndPoint(IPAddress.Loopback, port);
|
|
|
|
// Listener comandi di selezione processo inviati da Godot
|
|
Thread cmdListenerThread = new Thread(() =>
|
|
{
|
|
UdpClient udpCmd = null;
|
|
try
|
|
{
|
|
udpCmd = new UdpClient(cmdPort);
|
|
IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);
|
|
while (true)
|
|
{
|
|
byte[] bytes = udpCmd.Receive(ref sender);
|
|
string cmd = Encoding.UTF8.GetString(bytes).Trim();
|
|
if (cmd.StartsWith("PID:"))
|
|
{
|
|
int pid;
|
|
if (int.TryParse(cmd.Substring(4), out pid))
|
|
{
|
|
selectedTargetPid = pid;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch { }
|
|
});
|
|
cmdListenerThread.IsBackground = true;
|
|
cmdListenerThread.Start();
|
|
|
|
IMMDeviceEnumerator enumerator = (IMMDeviceEnumerator)new MMDeviceEnumeratorComObject();
|
|
IMMDevice device = null;
|
|
int hr = enumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia, out device);
|
|
if (hr != 0 || device == null)
|
|
{
|
|
hr = enumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eConsole, out device);
|
|
if (hr != 0 || device == null) return;
|
|
}
|
|
|
|
Guid iidMgr = typeof(IAudioSessionManager2).GUID;
|
|
object oMgr;
|
|
device.Activate(ref iidMgr, 1, IntPtr.Zero, out oMgr);
|
|
IAudioSessionManager2 sessionManager = (IAudioSessionManager2)oMgr;
|
|
|
|
Guid iidAudioClient = typeof(IAudioClient).GUID;
|
|
object oAudioClient;
|
|
hr = device.Activate(ref iidAudioClient, 1, IntPtr.Zero, out oAudioClient);
|
|
if (hr != 0 || oAudioClient == null) return;
|
|
|
|
IAudioClient audioClient = (IAudioClient)oAudioClient;
|
|
IntPtr pMixFormat;
|
|
hr = audioClient.GetMixFormat(out pMixFormat);
|
|
if (hr != 0 || pMixFormat == IntPtr.Zero) return;
|
|
|
|
WAVEFORMATEX wf = (WAVEFORMATEX)Marshal.PtrToStructure(pMixFormat, typeof(WAVEFORMATEX));
|
|
int channels = wf.nChannels;
|
|
int bitsPerSample = wf.wBitsPerSample;
|
|
int sampleRate = wf.nSamplesPerSec > 0 ? wf.nSamplesPerSec : 48000;
|
|
bool isFloat = (wf.wFormatTag == 3);
|
|
|
|
if (wf.wFormatTag == -2)
|
|
{
|
|
WAVEFORMATEXTENSIBLE wfe = (WAVEFORMATEXTENSIBLE)Marshal.PtrToStructure(pMixFormat, typeof(WAVEFORMATEXTENSIBLE));
|
|
if (wfe.SubFormat == new Guid("00000003-0000-0010-8000-00aa00389b71")) isFloat = true;
|
|
}
|
|
|
|
if (bitsPerSample == 32 && !isFloat)
|
|
{
|
|
byte[] raw = new byte[40];
|
|
Marshal.Copy(pMixFormat, raw, 0, 40);
|
|
if (raw[24] == 3) isFloat = true;
|
|
}
|
|
|
|
const int AUDCLNT_STREAMFLAGS_LOOPBACK = 0x00020000;
|
|
const int AUDCLNT_SHAREMODE_SHARED = 0;
|
|
Guid emptySession = Guid.Empty;
|
|
|
|
hr = audioClient.Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_LOOPBACK, 10000000, 0, pMixFormat, ref emptySession);
|
|
if (hr != 0) return;
|
|
|
|
Guid iidCaptureClient = typeof(IAudioCaptureClient).GUID;
|
|
object oCaptureClient;
|
|
hr = audioClient.GetService(ref iidCaptureClient, out oCaptureClient);
|
|
if (hr != 0 || oCaptureClient == null) return;
|
|
|
|
IAudioCaptureClient captureClient = (IAudioCaptureClient)oCaptureClient;
|
|
audioClient.Start();
|
|
|
|
// Filtro Passa-Basso sui Sub-Bassi (cassa e basse frequenze: 120 Hz)
|
|
float cutoffFreq = 120.0f;
|
|
float alpha = (float)(2.0 * Math.PI * cutoffFreq / sampleRate);
|
|
alpha = Math.Max(0.003f, Math.Min(0.08f, alpha));
|
|
float lp1 = 0f;
|
|
float lp2 = 0f;
|
|
|
|
float currentTotalRms = 0f;
|
|
|
|
// Buffer circolare onset flux temporizzato ai campioni audio reali (100 Hz = ogni 10ms esatti)
|
|
int hopSize = sampleRate / 100;
|
|
if (hopSize < 1) hopSize = 480;
|
|
const int BUFFER_HOPS = 400; // 4.0 secondi di finestra temporale
|
|
float[] onsetBuffer = new float[BUFFER_HOPS];
|
|
int onsetHead = 0;
|
|
int totalHopsRecorded = 0;
|
|
|
|
int hopFrameAccumulator = 0;
|
|
double hopKickSumSq = 0;
|
|
double hopTotalSumSq = 0;
|
|
float prevHopKickRms = 0;
|
|
float prevHopTotalRms = 0;
|
|
|
|
float detectedBpm = 0.0f;
|
|
int candidateBpm = 0;
|
|
int candidateStreak = 0;
|
|
int bpmEvalCounter = 0;
|
|
|
|
long lastAppListBroadcast = 0;
|
|
int myPid = Process.GetCurrentProcess().Id;
|
|
|
|
while (true)
|
|
{
|
|
// 1. Lettura campioni PCM WASAPI dal loopback di sistema
|
|
uint packetLength = 0;
|
|
hr = captureClient.GetNextPacketSize(out packetLength);
|
|
|
|
while (packetLength > 0 && hr == 0)
|
|
{
|
|
IntPtr pData;
|
|
uint numFramesRead;
|
|
int flags;
|
|
ulong devPos, qpcPos;
|
|
|
|
hr = captureClient.GetBuffer(out pData, out numFramesRead, out flags, out devPos, out qpcPos);
|
|
if (hr != 0) break;
|
|
|
|
if ((flags & 1) == 0 && numFramesRead > 0)
|
|
{
|
|
double totalSumSq = 0;
|
|
double kickSumSq = 0;
|
|
int frames = (int)numFramesRead;
|
|
|
|
unsafe
|
|
{
|
|
byte* ptr = (byte*)pData.ToPointer();
|
|
for (int f = 0; f < frames; f++)
|
|
{
|
|
float monoSample = 0f;
|
|
if (isFloat && bitsPerSample == 32)
|
|
{
|
|
float* fptr = (float*)ptr + (f * channels);
|
|
for (int c = 0; c < channels; c++) monoSample += fptr[c];
|
|
monoSample /= channels;
|
|
}
|
|
else if (!isFloat && bitsPerSample == 32)
|
|
{
|
|
int* iptr = (int*)ptr + (f * channels);
|
|
for (int c = 0; c < channels; c++) monoSample += (iptr[c] / 2147483648.0f);
|
|
monoSample /= channels;
|
|
}
|
|
else if (bitsPerSample == 16)
|
|
{
|
|
short* sptr = (short*)ptr + (f * channels);
|
|
for (int c = 0; c < channels; c++) monoSample += (sptr[c] / 32768.0f);
|
|
monoSample /= channels;
|
|
}
|
|
|
|
totalSumSq += (monoSample * monoSample);
|
|
|
|
// Filtro IIR passa-basso a doppio polo per estrarre la cassa
|
|
lp1 += alpha * (monoSample - lp1);
|
|
lp2 += alpha * (lp1 - lp2);
|
|
kickSumSq += (lp2 * lp2);
|
|
|
|
hopTotalSumSq += (monoSample * monoSample);
|
|
hopKickSumSq += (lp2 * lp2);
|
|
hopFrameAccumulator++;
|
|
|
|
// Ogni 10ms esatti di campioni audio registrati dal clock DAC
|
|
if (hopFrameAccumulator >= hopSize)
|
|
{
|
|
float hopKickRms = (float)Math.Sqrt(hopKickSumSq / hopSize);
|
|
float hopTotalRms = (float)Math.Sqrt(hopTotalSumSq / hopSize);
|
|
|
|
float kickFlux = Math.Max(0f, hopKickRms - prevHopKickRms);
|
|
float totalFlux = Math.Max(0f, hopTotalRms - prevHopTotalRms);
|
|
prevHopKickRms = hopKickRms;
|
|
prevHopTotalRms = hopTotalRms;
|
|
|
|
// L'onset combina la spinta del sub-basso (3.5x) e l'impatto a banda intera
|
|
float onset = (kickFlux * 3.5f) + totalFlux;
|
|
onsetBuffer[onsetHead] = onset;
|
|
onsetHead = (onsetHead + 1) % BUFFER_HOPS;
|
|
if (totalHopsRecorded < BUFFER_HOPS) totalHopsRecorded++;
|
|
|
|
hopFrameAccumulator = 0;
|
|
hopKickSumSq = 0;
|
|
hopTotalSumSq = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (frames > 0)
|
|
{
|
|
float packetTotalRms = (float)Math.Sqrt(totalSumSq / frames);
|
|
currentTotalRms = (currentTotalRms * 0.25f) + (packetTotalRms * 0.75f);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
currentTotalRms *= 0.7f;
|
|
}
|
|
|
|
captureClient.ReleaseBuffer(numFramesRead);
|
|
hr = captureClient.GetNextPacketSize(out packetLength);
|
|
}
|
|
|
|
// 2. Volume mirato se selezionata una specifica applicazione (es. Spotify/Chrome)
|
|
float displayRms = currentTotalRms;
|
|
if (selectedTargetPid > 0 && sessionManager != null)
|
|
{
|
|
try
|
|
{
|
|
IAudioSessionEnumerator sessionEnum;
|
|
sessionManager.GetSessionEnumerator(out sessionEnum);
|
|
int count;
|
|
sessionEnum.GetCount(out count);
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
object oSession;
|
|
sessionEnum.GetSession(i, out oSession);
|
|
var session2 = (IAudioSessionControl2)oSession;
|
|
uint pid;
|
|
session2.GetProcessId(out pid);
|
|
if (pid == selectedTargetPid)
|
|
{
|
|
var meter = (IAudioMeterInformation)oSession;
|
|
float peak = 0;
|
|
meter.GetPeakValue(out peak);
|
|
displayRms = peak;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
// 3. Autocorrelazione & Comb-Filter su spettro ritmico per rilevamento esatto del BPM
|
|
bpmEvalCounter++;
|
|
if (bpmEvalCounter >= 10) // Ogni ~80-100ms
|
|
{
|
|
bpmEvalCounter = 0;
|
|
if (totalHopsRecorded >= 120) // Almeno 1.2 secondi di audio
|
|
{
|
|
// Srotola il buffer circolare e trova il valore massimo per normalizzare
|
|
float[] env = new float[totalHopsRecorded];
|
|
float maxOnset = 0.0000001f;
|
|
for (int i = 0; i < totalHopsRecorded; i++)
|
|
{
|
|
int idx = (onsetHead - totalHopsRecorded + i + BUFFER_HOPS) % BUFFER_HOPS;
|
|
float val = onsetBuffer[idx];
|
|
env[i] = val;
|
|
if (val > maxOnset) maxOnset = val;
|
|
}
|
|
|
|
// Se c'è un segnale ritmico (anche con volume Windows molto basso)
|
|
if (maxOnset > 0.00002f)
|
|
{
|
|
// Normalizzazione: rende l'autocorrelazione totalmente indipendente dal volume di Windows!
|
|
for (int i = 0; i < totalHopsRecorded; i++)
|
|
{
|
|
env[i] /= maxOnset;
|
|
}
|
|
|
|
double[] scores = new double[225];
|
|
double[] weights = new double[] { 1.0, 0.5, 0.25, 0.125 };
|
|
|
|
// Valuta tutti i possibili BPM da 70 a 215
|
|
for (int bpm = 70; bpm <= 215; bpm++)
|
|
{
|
|
double lag = (60.0 / bpm) * 100.0; // 100.0 Hz
|
|
double score = 0;
|
|
for (int h = 1; h <= 4; h++)
|
|
{
|
|
int l = (int)Math.Round(h * lag);
|
|
if (l >= totalHopsRecorded) break;
|
|
double weight = weights[h - 1];
|
|
double dot = 0;
|
|
int count = 0;
|
|
for (int i = l; i < totalHopsRecorded; i++)
|
|
{
|
|
dot += env[i] * env[i - l];
|
|
count++;
|
|
}
|
|
if (count > 0) score += weight * (dot / count);
|
|
}
|
|
scores[bpm] = score;
|
|
}
|
|
|
|
// Bias di stabilità: leggero bonus al tempo attivo per assorbire micro-fill di 0.2s
|
|
if (detectedBpm > 50.0f)
|
|
{
|
|
for (int bpm = 70; bpm <= 215; bpm++)
|
|
{
|
|
float diff = Math.Abs(bpm - detectedBpm);
|
|
if (diff <= 6.0f)
|
|
{
|
|
scores[bpm] *= (1.0 + 0.20 * (1.0 - diff / 6.0));
|
|
}
|
|
}
|
|
}
|
|
|
|
int bestCandidateBpm = 70;
|
|
double bestCandidateScore = 0;
|
|
for (int bpm = 70; bpm <= 215; bpm++)
|
|
{
|
|
if (scores[bpm] > bestCandidateScore)
|
|
{
|
|
bestCandidateScore = scores[bpm];
|
|
bestCandidateBpm = bpm;
|
|
}
|
|
}
|
|
|
|
// Risoluzione ottave: distingue brani lenti (90 BPM) da brani veloci (180-190 BPM).
|
|
// Raddoppia solo se C'È EFFETTIVA energia percussiva al tempo veloce (rFast/rSlow >= 0.55),
|
|
// evitando che i leggeri charleston a 1/8 di un pezzo a 90 BPM forzino il raddoppio a 180!
|
|
if (bestCandidateBpm <= 105 && bestCandidateBpm * 2 <= 215)
|
|
{
|
|
int doubleBpm = bestCandidateBpm * 2;
|
|
if (scores[doubleBpm] >= bestCandidateScore * 0.80)
|
|
{
|
|
int lagSlow = (int)Math.Round((60.0 / bestCandidateBpm) * 100.0);
|
|
int lagFast = (int)Math.Round((60.0 / doubleBpm) * 100.0);
|
|
double rSlow = 0, rFast = 0;
|
|
int cSlow = 0, cFast = 0;
|
|
for (int i = lagSlow; i < totalHopsRecorded; i++) { rSlow += env[i] * env[i - lagSlow]; cSlow++; }
|
|
for (int i = lagFast; i < totalHopsRecorded; i++) { rFast += env[i] * env[i - lagFast]; cFast++; }
|
|
if (cSlow > 0) rSlow /= cSlow;
|
|
if (cFast > 0) rFast /= cFast;
|
|
|
|
if (rFast >= rSlow * 0.55)
|
|
{
|
|
bestCandidateBpm = doubleBpm;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bestCandidateScore > 0.0005)
|
|
{
|
|
if (detectedBpm <= 10.0f)
|
|
{
|
|
// Primo aggancio immediato
|
|
detectedBpm = bestCandidateBpm;
|
|
candidateBpm = bestCandidateBpm;
|
|
candidateStreak = 0;
|
|
}
|
|
else
|
|
{
|
|
float diff = Math.Abs(bestCandidateBpm - detectedBpm);
|
|
if (diff <= 4.0f)
|
|
{
|
|
// Micro-fluttuazione naturale del brano: adatta dolcemente
|
|
detectedBpm = (detectedBpm * 0.94f) + (bestCandidateBpm * 0.06f);
|
|
candidateStreak = 0;
|
|
}
|
|
else
|
|
{
|
|
// Il candidato si discosta significativamente dal tempo attuale.
|
|
// Per cambiare tempo (cambio canzone effettivo) deve rimanere stabile e confermato per almeno 10 cicli (~1 secondo)!
|
|
if (Math.Abs(bestCandidateBpm - candidateBpm) <= 3)
|
|
{
|
|
candidateStreak++;
|
|
if (candidateStreak >= 10)
|
|
{
|
|
detectedBpm = (detectedBpm * 0.4f) + (bestCandidateBpm * 0.6f);
|
|
if (Math.Abs(detectedBpm - bestCandidateBpm) < 2.0f)
|
|
{
|
|
detectedBpm = bestCandidateBpm;
|
|
candidateStreak = 0;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
candidateBpm = bestCandidateBpm;
|
|
candidateStreak = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 4. Invio lista applicazioni attive (FILTRANDO se stesso e Godot!)
|
|
long nowTicks = DateTime.UtcNow.Ticks;
|
|
if ((nowTicks - lastAppListBroadcast) >= (TimeSpan.TicksPerMillisecond * 1500) && sessionManager != null)
|
|
{
|
|
lastAppListBroadcast = nowTicks;
|
|
try
|
|
{
|
|
IAudioSessionEnumerator sessionEnum;
|
|
sessionManager.GetSessionEnumerator(out sessionEnum);
|
|
int count;
|
|
sessionEnum.GetCount(out count);
|
|
StringBuilder sb = new StringBuilder("APPS:0:Tutto il sistema");
|
|
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
object oSession;
|
|
sessionEnum.GetSession(i, out oSession);
|
|
var session2 = (IAudioSessionControl2)oSession;
|
|
uint pid;
|
|
session2.GetProcessId(out pid);
|
|
|
|
if (pid > 0 && pid != myPid)
|
|
{
|
|
string name = "";
|
|
try
|
|
{
|
|
Process p = Process.GetProcessById((int)pid);
|
|
name = p.ProcessName;
|
|
string lowerName = name.ToLower();
|
|
// FILTRA: nasconde LoopbackCapture e Godot dal menu!
|
|
if (lowerName.Contains("loopbackcapture") || lowerName.Contains("godot")) continue;
|
|
}
|
|
catch { }
|
|
|
|
if (!string.IsNullOrEmpty(name))
|
|
{
|
|
sb.AppendFormat(";{0}:{1}", pid, name);
|
|
}
|
|
}
|
|
}
|
|
|
|
byte[] appBytes = Encoding.UTF8.GetBytes(sb.ToString());
|
|
udpSend.Send(appBytes, appBytes.Length, remoteEp);
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
// 5. Invio dati audio a Godot (8 byte: RMS + BPM)
|
|
if (udpSend != null)
|
|
{
|
|
byte[] payload = new byte[8];
|
|
Array.Copy(BitConverter.GetBytes(displayRms), 0, payload, 0, 4);
|
|
Array.Copy(BitConverter.GetBytes(detectedBpm), 0, payload, 4, 4);
|
|
try
|
|
{
|
|
udpSend.Send(payload, payload.Length, remoteEp);
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
Thread.Sleep(8);
|
|
}
|
|
}
|
|
}
|
|
|
|
[ComImport]
|
|
[Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")]
|
|
class MMDeviceEnumeratorComObject { }
|
|
|
|
public enum EDataFlow { eRender, eCapture, eAll }
|
|
public enum ERole { eConsole, eMultimedia, eCommunications }
|
|
|
|
[Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
public interface IMMDeviceEnumerator
|
|
{
|
|
int EnumAudioEndpoints(EDataFlow dataFlow, int stateMask, out IntPtr devices);
|
|
int GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role, out IMMDevice endpoint);
|
|
int GetDevice(string id, out IMMDevice device);
|
|
int RegisterEndpointNotificationCallback(IntPtr client);
|
|
int UnregisterEndpointNotificationCallback(IntPtr client);
|
|
}
|
|
|
|
[Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
public interface IMMDevice
|
|
{
|
|
int Activate(ref Guid id, int clsCtx, IntPtr activationParams, [MarshalAs(UnmanagedType.IUnknown)] out object interfacePointer);
|
|
int OpenPropertyStore(int stgmAccess, out IntPtr properties);
|
|
int GetId([MarshalAs(UnmanagedType.LPWStr)] out string id);
|
|
int GetState(out int state);
|
|
}
|
|
|
|
[Guid("77AA99A0-1BD6-484F-8BC7-2C654C9A9B6F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
public interface IAudioSessionManager2
|
|
{
|
|
int GetAudioSessionControl(IntPtr sessionId, int priority, out IntPtr control);
|
|
int GetSimpleAudioVolume(IntPtr sessionId, int priority, out IntPtr volume);
|
|
int GetSessionEnumerator(out IAudioSessionEnumerator sessionEnum);
|
|
}
|
|
|
|
[Guid("E2F5BB11-0570-40CA-ACDD-3AA01277DEE8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
public interface IAudioSessionEnumerator
|
|
{
|
|
int GetCount(out int sessionCount);
|
|
int GetSession(int sessionIndex, [MarshalAs(UnmanagedType.IUnknown)] out object session);
|
|
}
|
|
|
|
[Guid("bfb7ff88-7239-4fc9-8fa2-07c950be9c6d"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
public interface IAudioSessionControl2
|
|
{
|
|
int GetState(out int state);
|
|
int GetDisplayName(out IntPtr name);
|
|
int SetDisplayName(string value, IntPtr eventContext);
|
|
int GetIconPath(out IntPtr path);
|
|
int SetIconPath(string value, IntPtr eventContext);
|
|
int GetGroupingParam(out Guid grouping);
|
|
int SetGroupingParam(ref Guid grouping, IntPtr eventContext);
|
|
int RegisterAudioSessionNotification(IntPtr client);
|
|
int UnregisterAudioSessionNotification(IntPtr client);
|
|
int GetSessionIdentifier(out IntPtr id);
|
|
int GetSessionInstanceIdentifier(out IntPtr id);
|
|
int GetProcessId(out uint processId);
|
|
int IsSystemSoundsSession();
|
|
int SetDuckingPreference(bool optOut);
|
|
}
|
|
|
|
[Guid("C02216F6-8C67-4B5B-9D00-D008E73E0064"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
public interface IAudioMeterInformation
|
|
{
|
|
int GetPeakValue(out float peak);
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
public struct WAVEFORMATEX
|
|
{
|
|
public short wFormatTag;
|
|
public short nChannels;
|
|
public int nSamplesPerSec;
|
|
public int nAvgBytesPerSec;
|
|
public short nBlockAlign;
|
|
public short wBitsPerSample;
|
|
public short cbSize;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
public struct WAVEFORMATEXTENSIBLE
|
|
{
|
|
public WAVEFORMATEX Format;
|
|
public short wValidBitsPerSample;
|
|
public int dwChannelMask;
|
|
public Guid SubFormat;
|
|
}
|
|
|
|
[Guid("1CB9AD4C-DBFA-4c32-B178-C2F568A703B2"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
public interface IAudioClient
|
|
{
|
|
int Initialize(int shareMode, int streamFlags, long hnsBufferDuration, long hnsPeriodicity, IntPtr pFormat, ref Guid audioSessionGuid);
|
|
int GetBufferSize(out uint bufferFrameCount);
|
|
int GetStreamLatency(out long hnsLatency);
|
|
int GetCurrentPadding(out uint numPaddingFrames);
|
|
int IsFormatSupported(int shareMode, IntPtr pFormat, out IntPtr ppClosestMatch);
|
|
int GetMixFormat(out IntPtr ppDeviceFormat);
|
|
int GetDevicePeriod(out long hnsDefaultDevicePeriod, out long hnsMinimumDevicePeriod);
|
|
int Start();
|
|
int Stop();
|
|
int Reset();
|
|
int SetEventHandle(IntPtr eventHandle);
|
|
int GetService(ref Guid interfaceId, [MarshalAs(UnmanagedType.IUnknown)] out object interfacePointer);
|
|
}
|
|
|
|
[Guid("C8ADBD64-E71E-48a0-A4DE-185C395CD317"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
public interface IAudioCaptureClient
|
|
{
|
|
int GetBuffer(out IntPtr dataPointer, out uint numFramesToRead, out int bufferFlags, out ulong devicePosition, out ulong qpcPosition);
|
|
int ReleaseBuffer(uint numFramesRead);
|
|
int GetNextPacketSize(out uint numFramesInNextPacket);
|
|
}
|
|
}
|