Back to "AudioSummarizer: Begränsad Fuzzy rättsmedicinsk ljuddefiniering"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

AI Architecture Audio LLM ONNX Patterns Speaker Diarization

AudioSummarizer: Begränsad Fuzzy rättsmedicinsk ljuddefiniering

Saturday, 10 January 2026

Status: AudioSummarizer lucidRAG, ett framväxande merpartenlucid produkt för multi-modal RAG . Implementeringen är färdig och igångM SK3 artikeln dokumenterar arkitekturen och designen . CLI-integrationen kommer finnas tillgänglig i en framväxande lucidRAG-ut release

källa: github.com/scottgalM SK2lucidrag (branchM SK1 v2)

Där det passar: AudioSummarizer är en lucidRAG familj av Reduced RAG implementeringar. Var och en hanterar en annan modalitet:

Alla följer samma princip Reduzerad RAG-mönster: extraherar signaler en gång , sparar bevis , syntetiseras med begränsad LLM-input


Många demos, -, gör samma misstag med de första ljudanalysrören. De behandlar LLM som om de var akustiska ingenjörer.

De för in vågformer i modeller.,, ber dem att """, "" Detektor av talkvalitén, " "", eller ", identifiera högtalare och hoppas att modellen kan dra slutsatser om strukturella egenskaper från vad som egentligen är en mönster.

AudioSummarizer kombinerar två komplementära mönster:

  1. Reduzerad RAG för att få fram - extrahera signaler en gång , lagra bevis M SK2 ställa frågor mot fakta
  2. Begränsad otydighet för orkestrering - våg, - baserad pipeline, M SK2 deterministisk substrat begränsar sannolikhetsmodeller

Istället för att mata råa ljud till en LLM vid frågatid, så minskar varenda audiofile till en ledger (deterministiska signaler som RMS-ljuddhet , spektrala egenskaper extraherat bevis ( transkripter , högtalare provspel, clipper, , diariseringsströmmar, ). ledaren är kvar en gång,

På: frågatid, en LLM syntetiserar reaktioner genom att plocka upp och resonera över dominerande signaler — inte genom att göra om det

Patternskomposition: Reduzerade RAG-handlar vad för att lagra och få tillbaka. Begränsade Fuzziness-handlar hur att extrahera signaler på ett noggrant sätt. De gör det möjligt för rättsmedicinsk avbildning med begränsad LLM-kostnaden

Lär dig mer om Reduced RAG-mönster: Reduced RAG: Signal-Driven Document Understanding

Nyckeltermik

  • Signaler: Tiktade fakta med självförtroende poäng (eM SK2g., audio.content_type = "speech", självförtroende
  • Evidens: Lyssliga artefakter ( transkriptioner , högtalare provspelar
  • Ledger: Den fortsatta bunten av alla signaler , bevisspetsar, , och inbäddar som extraheras från en audiofile
  • SpeakerID: Lokalidentifisator inuti en enda file /diskriminering Run SPEAKER_00)
  • RöstbildID: Cross vprint:a3f9c2e1)
  • Person: Uppenbarligen inte givits ut

Identitetsmodell: SPEAKER_00 är lokal till en audiofile. VoiceprintId är stabil över filer men anonymt. Vi kartlägger aldrig människors namn

rättsmedicinsk krav: Varje signal inkluderar ursprung ( vilken våg den producerade ), självförtroende (säkerhetsgrad versionering (modellM SK1 tröskelversioner ). Det här gör att reproducerbarheten är möjliga: samma inmatning | + samma konfiguration |→ | samma ledger för signaler

Resultatet är:

  • Fast, integritet-bevarande av rättsmedicinsk ljuddefiniering
  • Lyssningsdiskriminering utan Pythons beroenden (pure .NET)
  • Anonyma talare likvärdighetsdetektion ( inga PII , inga namn
  • Rödlingsintegreringar för "finn liknande högtalareM SK1 sökningar
  • Allt utan att skicka ljud till moln-APIs under ingestion

Detta bygger direkt på Begränsad otydighetsmönster och architecturen baserat på vågen - från ImageSummarizer.

Kerninsikten: LLM ska resonera över akustiska fakta, inte beräkna dem.

Detta artikel omfattar:

  • Hur AudioSummarizer implementerar Reduced RAG-mönster
  • Signalextraktion: deterministiska akustiska fakta M SK1 inte LLM sammanfattningar)
  • Evidenslagning: högtalare prover, transkriptionerM SK2 diariseringsvirr
  • Rent . NET högtalare diarisering ( ingen Python
  • Query-tidssynthese: filtersignaler M SK2 hämta bevis → LLM syntetiserar

Related articles:

  • Reduzerad RAG - Den centrala mönstret som den implementerar
  • Begränsad otydighetsmönster - Grundmuten
  • Reduzerade RAG implementeringar:
    • DocSummarizer - Dokument RAG med enhetens extraktion och kunskapsgrafer
    • DataSummarizer - Dataprofilering och schemasuppfattning RAG
    • ImageSummarizer - Image RAG med 22- vågvisuell intelligens
    • AudioSummarizer (den här artikeln ) - Audiomedicinsk karakterisering med högtalare diarisering

Problemet : Audioanalys är svår ( och kulturellt laddad

Audioanalys misslyckas på förutsägbara sätt:

  • Kulturella påståenden: " Det här är jazz ♫ ♫ " | ( ♫ säger vem ♫
  • Berättande av talare: "Sprekern är John Smith" (avgrepp på privatliv
  • Musikidentifisering"" Sandstorm by Darude "" (Sandstorm av Darude) ""
  • Accent gissning: "Speaker has British accent
  • Pythons beroende: De flesta diariseringsverktyg kräver pyannot.

Ett traditionellt tillvägagångssätt : "Run Whisper för transkribering, pyannot för diariseringM SK3 skicka till LLM för sammanfattningen

Problemet: Det här antingen läcker PII ( högtalare namn | ), | kostar för mycket ♫ ( | moln-APIs | МSK4 | eller kräver Python-runtime | ( | pyannot | , | DIART | ).

Lösningen: Att bygga en ren pipeline som baserar sig på ., NET-vågor, -, som karakteriserar ljud strukturellt och akustiskt,—, utan att göra kulturella antaganden.


Reduced RAG Audio Framework

AudioSummarizer implementerar Reduzerad RAG-mönster för ljuddater , som följer dess tre grundprinciper:

1. Signalextraktion Ingestionsfasen

Deterministiska signaler extraherade en gång

  • Tydliga: livslängd , tidstämpningar M SK2 segmentgränser
  • Akustik: RMS högljuddhet , spektrala centroider M SK2 dynamiska intervall
  • Identitet: SHA
  • Jakt: transkriptionsförtroende
  • Speker: röstavtryckte IDs ( anonyma grejer
  • Kategoriserad: innehållstyp

Ett exempel på signaluppsättning för en podcast:

{
  "audio.hash.sha256": "3f2a9c8b1e4d...",
  "audio.duration_seconds": 912.0,
  "audio.rms_db": -18.2,
  "audio.spectral_centroid_hz": 2418.3,
  "audio.content_type": "speech",
  "speaker.count": 2,
  "speaker.classification": "two_speakers",
  "transcription.confidence": 0.87,
  "voice.voiceprint_id.speaker_00": "vprint:a3f9c2e1d4b8"
}

Dessa signaler är deterministisk (samma ljud → samma värdenM SK2 indexbar ( kan filtrera / sortera i databasen M SK2 och beräknat utan LLMs ( ren signalbehandling + ONNX-modeller

2. Evidens lagring Störda enheter

Istället för att bara lagra "kokare," AudioSummarizer affärer :

  • Signaler: Strukturerade fält (JSONM SK2 för deterministisk filtering
  • Embeddings: Sprachintegreringar (512-dim ECAPAM SK2TDNN) för högtalare likhet
    • Privacy note: Embeder är inte praktiskt omvändbara i det här systemet, men hanterar dem som känsliga data.
  • Evidensartefakter:
    • Den fulla transkriptionens text (sökningbar)
    • Sampler från högtalare (Base64 WAVM SK2 2-sekundliga klipp för verifiering
    • Diariserings svänger (JSON med högtalare_idM SK2 start / end timestamps
  • Speglar: File hash + bevisidentityder för kontrollbart ursprung

Evidens är kontrollbar: Användare kan spela upp högtalare prover , läsa transkriptioner , inspektera diariseringsströmmar M SK3 inte bara lita på en LLM sammanfattning

3. Fråga- Tidssynthes Limiterad LLM Input

Vid frågatid, LLM aldrig ser rå ljud. istället:

  1. Filtrera deterministiskt (databas Var clausen

    WHERE audio.content_type = 'speech'
      AND speaker.count >= 2
      AND audio.rms_db > -25.0
      AND transcription.confidence > 0.8
    
  2. Hybridsökning Vektorn: (BMM SK1

    • BM25 på transkriptionstext (ord som matchar ordet
    • Vektors likhet på ljudintegreringar (speakerliknande)
    • Kom tillbaka upp till 5 ljuddater
  3. Synthesera från signaler (LLM ser strukturerad bevispaket

    Audio 1: podcast_ep42.mp3
    - Duration: 15m 12s
    - Speakers: 2 (SPEAKER_00: 52%, SPEAKER_01: 48%)
    - Quality: RMS -18.2dB, no clipping
    - Transcript: "Welcome to Tech Insights. Today we're discussing..."
    - Entities: ["quantum computing", "Google", "IBM"]
    

LLM får 5 strukturerade bevispaket istället för 500 bitar av råa ljudmetadata. Reduktion av kontextutrymmet

Kustinverkan ( om man använder betalade LLM APIs): Bearbetar ♫ 100 ♫ ljudfiler går från ♫~$5 ♫ ♫ Anteckning: lucidRAG är lokalt-first M SK2Ollama standardmässigt , noll kostnadMSC4 Belönade APIs (ClaudeMska6 GPTMske7 är optionellaMskosten antar att betalade API-användbarhet är antaglig

Detta är grunden till Reduced RAG insight: bestämma vad som är viktigt (signaler förvara det deterministiskt ( bevis omfatta LLM bara för syntektion (fråga-tidM SK2


Den begränsade fuzziness-ljudsröret

Systemet sätter igång vågor i prioritära ordning (högare värden = sätter upp först

Wave Priority Order:
  100: IdentityWave          → SHA-256, file metadata, duration
   90: FingerprintWave       → Chromaprint perceptual hash (optional)
   80: AcousticProfileWave   → RMS, spectral features, SNR
   70: ContentClassifierWave → Speech vs music heuristics (routing)
   65: TranscriptionWave     → Whisper.NET (optional)
   60: SpeakerDiarizationWave→ Pure .NET speaker separation
   30: VoiceEmbeddingWave    → ECAPA-TDNN speaker similarity

Beteckning: känslor / känslodetektor avsiktligt utesluten M SK2 kulturellt laddad och inte en del av rättsmedicinsk karakterisering

Arkitektur

flowchart LR
    A[Audio file] --> B[IdentityWave]
    B --> C[AcousticProfileWave]
    C --> D[ContentClassifierWave]
    D --> E[TranscriptionWave]
    E --> F[SpeakerDiarizationWave]
    F --> G[VoiceEmbeddingWave]
    G --> H[Signal Ledger]
    H --> I[Optional LLM Synthesis]

    style B stroke:#333,stroke-width:4px
    style D stroke:#333,stroke-width:4px
    style F stroke:#333,stroke-width:4px
    style H stroke:#333,stroke-width:4px

Detta bevarar den välkända Wella → Signal → Optionell LLM loop, men ancorar den i deterministiska fakta

  • Akustikprofilering är deterministiskt ( samma ingång = samma utgång
  • LLM kör på bevis, ,, inte råa ljud

Verklig-World Example: Att bearbeta en Podcast-episode

Låt ' spåra ett 15-minuters podcast-episode genom kanalen

Input: podcast_ep42.mp3
  - File size: 14.2 MB
  - Format: MP3, 44.1kHz stereo, 192 kbps
  - Duration: 15m 12s (912 seconds)
  - Content: 2-person interview

Wave Execution (priority order 100 → 30):

1. IdentityWave (Priority 100, 87ms):
   ✓ SHA-256: 3f2a9c8b1e4d...
   ✓ Duration: 912.0s
   ✓ Channels: 2 (stereo)
   ✓ Sample rate: 44100 Hz
   ✓ File size: 14,897,234 bytes

2. AcousticProfileWave (Priority 80, 142ms):
   ✓ RMS loudness: -18.2 dB (good mastering)
   ✓ Peak amplitude: 0.94 (no clipping)
   ✓ Dynamic range: 22.1 dB
   ✓ Spectral centroid: 2418 Hz (speech-like)
   ✓ Spectral rolloff: 7892 Hz

3. ContentClassifierWave (Priority 70, 89ms):
   ✓ Zero-crossing rate: 0.17 (high → speech)
   ✓ Spectral flux: 0.28 (low → not music)
   → Classification: "speech" (confidence: 0.85)

4. TranscriptionWave (Priority 65, 12.3s):
   ✓ Whisper.NET base model
   ✓ Segments: 142
   ✓ Total words: 2,341
   ✓ Confidence: 0.87 (high)
   ✓ Text: "Welcome to Tech Insights. Today we're discussing..."

5. SpeakerDiarizationWave (Priority 60, 3.8s):
   ✓ VAD detected: 47 speech segments
   ✓ Embeddings extracted: 47 × 512-dim vectors
   ✓ Clustering (threshold 0.75): 2 speakers
   ✓ Turns before merge: 47
   ✓ Turns after merge: 23
   ✓ SPEAKER_00 participation: 52% (474s)
   ✓ SPEAKER_01 participation: 48% (438s)
   ✓ Sample clips extracted: 2 (Base64 WAV, ~30KB each)

6. VoiceEmbeddingWave (Priority 30, 178ms):
   ✓ ECAPA-TDNN inference
   ✓ Embedding dimension: 512
   ✓ Voiceprint ID (SPEAKER_00): "vprint:a3f9c2e1d4b8"
   ✓ Voiceprint ID (SPEAKER_01): "vprint:7e2d8f1a9c3b"

Total processing time: 16.6s
Signals emitted: 47
LLM calls: 0 (fully offline)
Cost: $0 (local processing only)

Den Ledger är vad som förblir kvar i databasen.

Ledger (avl.

{
  "identity.filename": "podcast_ep42.mp3",
  "audio.hash.sha256": "3f2a9c8b1e4d...",
  "audio.duration_seconds": 912.0,
  "audio.format": "mp3",
  "audio.sample_rate": 44100,
  "audio.channels": 2,
  "audio.channel_layout": "stereo",
  "audio.rms_db": -18.2,
  "audio.dynamic_range_db": 22.1,
  "audio.spectral_centroid_hz": 2418.3,
  "audio.spectral_rolloff_hz": 7892.1,
  "audio.content_type": "speech",
  "content.confidence": 0.85,
  "speaker.count": 2,
  "speaker.classification": "two_speakers",
  "speaker.turn_count": 23,
  "speaker.avg_turn_duration": 39.7,
  "speaker.diarization_method": "agglomerative_clustering",
  "speaker.participation": {
    "SPEAKER_00": 52.0,
    "SPEAKER_01": 48.0
  },
  "transcription.full_text": "Welcome to Tech Insights...",
  "transcription.word_count": 2341,
  "transcription.confidence": 0.87,
  "voice.embedding.speaker_00": [0.023, -0.511, 0.882, ...],
  "voice.voiceprint_id.speaker_00": "vprint:a3f9c2e1d4b8",
  "speaker.sample.speaker_00": "UklGRiQAAABXQVZF...",
  "speaker.sample.speaker_01": "UklGRiQBBBXQVZF..."
}

Vad detta gör möjligt:

  • sök efter "Tech Insights diskuterar molninfrastrukturen " → hittar den här episoden via transkription
  • Fråga "Finda ljud med röstavtryck vprint:aM SK2fMSC3cMska4eM Ska5 | Findar alla episoder med samma högtalare | Mska7 | Query
  • Fråga " Vad' är den dynamiska sträckan av våra podcast-episoderM SK2 → sammanlagda akustiska signaler
  • Filter "Finda lågt-kvaliténga inspelningarM SK2 → RMS
  • Verifiera "Did högtalare identifisering verk?" → spela speaker.sample.speaker_00 klipp (intilldatet-lêer )

Varför signaler är viktiga ( Även utan en LLM

Ett ledger svarar på tråkiga frågor omedelbart

  • Är det ljudspråket? Musik? Tystnad?
  • Hur många högtalare upptäcks?
  • Vad är signalen?
  • Finns det klippande artefakter eller förvrängningar?
  • Vad är spektrala centroidern?
  • Vad är den dynamiska intervallen?

Det här är de frågor du vanligtvis hittar 30 minuter i Audacity-arkeologin

Ledgern ger dem i sekunder.


The Wave Pipeline: Från identitet till intelligens

Wella 1: Identitet (Deterministisk substans

Basislinjen. Kriptografisk identitet och file-metadata .

public class IdentityWave : IAudioWave
{
    public string Name => "IdentityWave";
    public int Priority => 100;  // Runs first

    public async Task<IEnumerable<Signal>> AnalyzeAsync(
        string audioPath,
        AnalysisContext context,
        CancellationToken ct)
    {
        var signals = new List<Signal>();

        // Cryptographic hash (deterministic identity)
        var fileHash = await ComputeSha256Async(audioPath, ct);
        signals.Add(new Signal
        {
            Name = "audio.hash.sha256",
            Value = fileHash,
            Type = SignalType.Identity,
            Confidence = 1.0,
            Source = Name
        });

        // File-level metadata
        var fileInfo = new FileInfo(audioPath);
        signals.Add(new Signal
        {
            Name = "audio.file_size_bytes",
            Value = fileInfo.Length,
            Type = SignalType.Metadata,
            Source = Name
        });

        // Audio format metadata
        using var reader = new AudioFileReader(audioPath);

        signals.Add(new Signal
        {
            Name = "audio.duration_seconds",
            Value = reader.TotalTime.TotalSeconds,
            Type = SignalType.Metadata,
            Source = Name
        });

        signals.Add(new Signal
        {
            Name = "audio.sample_rate",
            Value = reader.WaveFormat.SampleRate,
            Type = SignalType.Acoustic,
            Source = Name
        });

        signals.Add(new Signal
        {
            Name = "audio.channels",
            Value = reader.WaveFormat.Channels,
            Type = SignalType.Acoustic,
            Source = Name
        });

        signals.Add(new Signal
        {
            Name = "audio.format",
            Value = Path.GetExtension(audioPath).TrimStart('.'),
            Type = SignalType.Metadata,
            Source = Name
        });

        return signals;
    }
}

Emiterade nyckelsignaler:

  • audio.hash.sha256 - Kriptografisk identitet
  • audio.duration_seconds -Längd (deterministisk
  • audio.sample_rate - 44100, ♫ ♫ 48000, ♫ och så vidare ♫
  • audio.channels -
  • audio.format - mp 3, wav, flacM SK3 etcMSC4

Varför deterministiskt?

  • Samma file → samma hash → samma identitet
  • Inga slumpmässiga prover , inga temperaturparameter
  • Förtroende =

Wave 2: Akustisk profil (Signalprocessing)

Extrahera strukturella akustiska egenskaper med hjälp av NAudio och FftSharp.

public class AcousticProfileWave : IAudioWave
{
    private readonly ILogger<AcousticProfileWave> _logger;

    public string Name => "AcousticProfileWave";
    public int Priority => 80;

    public async Task<IEnumerable<Signal>> AnalyzeAsync(
        string audioPath,
        AnalysisContext context,
        CancellationToken ct)
    {
        var signals = new List<Signal>();

        using var reader = new AudioFileReader(audioPath);

        // Convert to mono for analysis
        ISampleProvider sampleProvider = reader.WaveFormat.Channels == 1
            ? reader
            : new StereoToMonoSampleProvider(reader) { LeftVolume = 0.5f, RightVolume = 0.5f };

        // Read all samples
        var samples = ReadAllSamples(sampleProvider);

        // Time-domain analysis
        var rms = CalculateRms(samples);
        var peakAmplitude = samples.Max(Math.Abs);
        var dynamicRange = CalculateDynamicRange(samples);
        var clippingRatio = CalculateClippingRatio(samples, threshold: 0.99);

        signals.Add(new Signal
        {
            Name = "audio.rms_db",
            Value = 20 * Math.Log10(rms),  // Convert to decibels
            Type = SignalType.Acoustic,
            Source = Name
        });

        signals.Add(new Signal
        {
            Name = "audio.peak_amplitude",
            Value = peakAmplitude,
            Type = SignalType.Acoustic,
            Source = Name
        });

        signals.Add(new Signal
        {
            Name = "audio.dynamic_range_db",
            Value = dynamicRange,
            Type = SignalType.Acoustic,
            Source = Name
        });

        signals.Add(new Signal
        {
            Name = "audio.clipping_ratio",
            Value = clippingRatio,
            Type = SignalType.Acoustic,
            Confidence = clippingRatio > 0.01 ? 0.9 : 1.0,  // Low confidence if clipping detected
            Source = Name
        });

        // Frequency-domain analysis (FFT)
        var spectralFeatures = CalculateSpectralFeatures(samples, reader.WaveFormat.SampleRate);

        signals.Add(new Signal
        {
            Name = "audio.spectral_centroid_hz",
            Value = spectralFeatures.Centroid,
            Type = SignalType.Acoustic,
            Source = Name
        });

        signals.Add(new Signal
        {
            Name = "audio.spectral_rolloff_hz",
            Value = spectralFeatures.Rolloff,
            Type = SignalType.Acoustic,
            Source = Name
        });

        signals.Add(new Signal
        {
            Name = "audio.spectral_bandwidth_hz",
            Value = spectralFeatures.Bandwidth,
            Type = SignalType.Acoustic,
            Source = Name
        });

        return signals;
    }

    private SpectralFeatures CalculateSpectralFeatures(float[] samples, int sampleRate)
    {
        // Use FftSharp for frequency analysis
        int fftSize = 2048;
        var fftInput = new double[fftSize];

        // Take middle section of audio
        int offset = Math.Max(0, (samples.Length - fftSize) / 2);
        for (int i = 0; i < fftSize; i++)
        {
            fftInput[i] = samples[offset + i];
        }

        // Apply Hamming window
        var window = FftSharp.Window.Hamming(fftSize);
        for (int i = 0; i < fftSize; i++)
        {
            fftInput[i] *= window[i];
        }

        // Compute FFT
        var fft = FftSharp.Transform.FFT(fftInput);
        var magnitudes = fft.Select(c => Math.Sqrt(c.Real * c.Real + c.Imaginary * c.Imaginary)).ToArray();

        // Calculate spectral centroid (brightness)
        double sumWeightedFreq = 0;
        double sumMagnitude = 0;
        for (int i = 0; i < magnitudes.Length / 2; i++)
        {
            double freq = i * sampleRate / (double)fftSize;
            sumWeightedFreq += freq * magnitudes[i];
            sumMagnitude += magnitudes[i];
        }
        double centroid = sumMagnitude > 0 ? sumWeightedFreq / sumMagnitude : 0;

        // Calculate spectral rolloff (85% energy threshold)
        double totalEnergy = magnitudes.Take(magnitudes.Length / 2).Sum(m => m * m);
        double cumulativeEnergy = 0;
        double rolloff = 0;
        for (int i = 0; i < magnitudes.Length / 2; i++)
        {
            cumulativeEnergy += magnitudes[i] * magnitudes[i];
            if (cumulativeEnergy >= 0.85 * totalEnergy)
            {
                rolloff = i * sampleRate / (double)fftSize;
                break;
            }
        }

        return new SpectralFeatures
        {
            Centroid = centroid,
            Rolloff = rolloff,
            Bandwidth = CalculateBandwidth(magnitudes, centroid, sampleRate, fftSize)
        };
    }
}

exempel utgång:

Input: podcast.mp3 (15 minutes, 44.1kHz stereo)

Signals emitted:
  audio.rms_db = -18.2 dB (good loudness)
  audio.peak_amplitude = 0.94 (no clipping)
  audio.dynamic_range_db = 22 dB (moderate dynamics)
  audio.clipping_ratio = 0.003 (0.3% clipping, minimal)
  audio.spectral_centroid_hz = 2400 Hz (mid-brightness, speech-like)
  audio.spectral_rolloff_hz = 8000 Hz (most energy below 8kHz)
  audio.spectral_bandwidth_hz = 4200 Hz (moderate spread)

Varför är detta viktigt:

  • RMS högljud indikerar mästerskapskvalitet
  • Clipping-förhållandet detekterar inspelning av artefakter
  • Spectralla centroider skiljer tal från musik (2-4kHz
  • Alla deterministiska— samma ljud producerar samma värden

Wella 3: Inhoudsklassificering (Speech vs Music)

grov heuristisk klassificering med hjälp av noll-crossing rate and spectral flux for routing purposes

Beteckning: Dessa heuristiker är genrer / kontextberoende — inte pålitligt korrekta över alla innehållskategorier M SK3 Används endast för vågriktningsbeslut MSC4 e Mska5 g M Skaka dinarisering för musiken Mske7 inte som grunden till sanningen

public class ContentClassifierWave : IAudioWave
{
    public string Name => "ContentClassifierWave";
    public int Priority => 70;

    public async Task<IEnumerable<Signal>> AnalyzeAsync(
        string audioPath,
        AnalysisContext context,
        CancellationToken ct)
    {
        var signals = new List<Signal>();

        using var reader = new AudioFileReader(audioPath);
        var samples = ReadAllSamples(reader);

        // Zero-crossing rate (heuristic: speech tends higher ZCR - not universal)
        var zcr = CalculateZeroCrossingRate(samples);

        // Spectral flux (heuristic: music tends more consistent - varies by genre)
        var spectralFlux = CalculateSpectralFlux(samples, reader.WaveFormat.SampleRate);

        // Simple heuristic classifier (for routing, not identity)
        // Thresholds calibrated for typical podcast/interview content
        // Production: calibrate on your corpus for best routing accuracy
        string contentType;
        double confidence;

        if (zcr > 0.15 && spectralFlux < 0.3)  // Speech heuristic
        {
            contentType = "speech";
            confidence = 0.85;
        }
        else if (zcr < 0.10 && spectralFlux > 0.5)
        {
            contentType = "music";
            confidence = 0.80;
        }
        else
        {
            contentType = "mixed";
            confidence = 0.70;
        }

        // Check for silence
        var rmsDb = context.GetValue<double>("audio.rms_db");
        if (rmsDb < -50)
        {
            contentType = "silence";
            confidence = 0.95;
        }

        signals.Add(new Signal
        {
            Name = "audio.content_type",
            Value = contentType,
            Type = SignalType.Classification,
            Confidence = confidence,
            Source = Name,
            Metadata = new Dictionary<string, object>
            {
                ["zero_crossing_rate"] = zcr,
                ["spectral_flux"] = spectralFlux,
                ["rms_db"] = rmsDb
            }
        });

        return signals;
    }
}

Ett exempel på routing:

Speech (ZCR=0.18, flux=0.25):
  → audio.content_type = "speech"
  → Enables: TranscriptionWave, SpeakerDiarizationWave

Music (ZCR=0.08, flux=0.65):
  → audio.content_type = "music"
  → Disables: SpeakerDiarizationWave (no speakers to detect)

Silence (RMS=-52dB):
  → audio.content_type = "silence"
  → Disables: All downstream waves (early exit)

Reina .NET högtalare diarisering | ( | Ingen Python | МSK2

För användargränserna för lucidRAG': diarisering av högtalare utan pyannot , DIART

Problemet med Python-Based Diarization

Traditionell tillvägagångssätt

pyannote.audio (Python) → ONNX export (experimental) → C# wrapper (fragile)

Problemet:

  • pyannote 3.1+ tog bort support för ONNX ( flyttade till ren PyTorch
  • Python körtid som krävs (implementerings mardröm
  • HTTP-wrapper lägger till latens och komplexitet
  • Inget avgränsat stöd

Lösningen: Implementera dinarisering i rent .NET med hjälp av existerande röstbedämningsmodeller

Den ren .NET-algoritmen

1. Voice Activity Detection (VAD) → Detect speech segments (energy-based RMS)
2. Segment Embedding → Extract ECAPA-TDNN embeddings for each segment
3. Agglomerative Clustering → Group segments by speaker (cosine similarity)
4. Speaker Turns → Merge consecutive turns from same speaker

Implementering

csharp public class SpeakerDiarizationService { private readonly ILogger _logger; private readonly VoiceEmbeddingService _embeddingService; private readonly AudioConfig _config;

public virtual async Task<DiarizationResult> DiarizeAsync(
    string audioPath,
    CancellationToken ct = default)
{
    _logger.LogInformation("Starting speaker diarization for {AudioPath}", audioPath);

    // Step 1: Detect speech segments using VAD
    var segments = DetectSpeechSegments(audioPath);
    _logger.LogDebug("Detected {Count} speech segments", segments.Count);

    if (segments.Count == 0)
    {
        return new DiarizationResult
        {
            Turns = new List<SpeakerTurn>(),
            SpeakerCount = 0
        };
    }

    // Step 2: Extract embeddings for each segment
    var embeddings = new List<(SpeechSegment Segment, float[] Embedding)>();
    foreach (var segment in segments)
    {
        try
        {
            var embedding = await ExtractSegmentEmbeddingAsync(audioPath, segment, ct);
            embeddings.Add((segment, embedding));
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Failed to extract embedding for segment {Start}-{End}",
                segment.StartSeconds, segment.EndSeconds);
        }
    }

    // Step 3: Cluster embeddings to identify speakers
    var speakerClusters = ClusterSpeakers(embeddings);
    _logger.LogInformation("Identified {Count} speakers", speakerClusters.Keys.Count);

    // Step 4: Create speaker turns
    var turns = new List<SpeakerTurn>();
    foreach (var (segment, embedding) in embeddings)
    {
        var speakerId = FindSpeakerForEmbedding(embedding, speakerClusters);
        turns.Add(new SpeakerTurn
        {
            SpeakerId = speakerId,
            StartSeconds = segment.StartSeconds,
            EndSeconds = segment.EndSeconds,
            Confidence = 1.0  // TODO: Calculate based on cluster distance
        });
    }

    // Step 5: Merge consecutive turns from same speaker
    var mergedTurns = MergeConsecutiveTurns(turns);

    return new DiarizationResult
    {
        Turns = mergedTurns,
        SpeakerCount = speakerClusters.Keys.Count
    };
}

// Simple VAD using energy-based speech detection
private List<SpeechSegment> DetectSpeechSegments(string audioPath)
{
    using var reader = new AudioFileReader(audioPath);
    ISampleProvider sampleProvider = reader.WaveFormat.Channels == 1
        ? reader
        : new StereoToMonoSampleProvider(reader) { LeftVolume = 0.5f, RightVolume = 0.5f };

    var sampleRate = sampleProvider.WaveFormat.SampleRate;
    var windowSize = sampleRate / 10; // 100ms windows
    var buffer = new float[windowSize];

    var segments = new List<SpeechSegment>();
    SpeechSegment? currentSegment = null;

    double timeSeconds = 0;
    int samplesRead;

    while ((samplesRead = sampleProvider.Read(buffer, 0, buffer.Length)) > 0)
    {
        // Calculate RMS energy for this window
        double rms = Math.Sqrt(buffer.Take(samplesRead).Sum(s => s * s) / samplesRead);

        // Speech detection threshold (simple baseline - fragile across gain levels)
        // Production: use relative threshold (noise floor / percentile) or per-file calibration
        bool isSpeech = rms > 0.02;  // Fixed threshold for demonstration

        if (isSpeech)
        {
            if (currentSegment == null)
            {
                // Start new segment
                currentSegment = new SpeechSegment
                {
                    Star
logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.