Back to "AudioSummarizer: Rajoitettu hämärä oikeudellinen audiokuvaus"

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: Rajoitettu hämärä oikeudellinen audiokuvaus

Saturday, 10 January 2026

Tilanne: AudioSummarizerM SK1Kore on parhaillaan kehityksessä osana lucidRAG, monimuotoisen RAG:n tuleva mostlylucid-tuote -modal RAG. täytäntöönpano on saatu päätökseen ja se on käynnissä

Lähde: github.com/scottgalM SK2lucidrag (branch v2)

Missä tämä sopii: AudioSummarizer on osa lucidRAG family of Reduced RAG implementations. Jokainen käsittelee erilaista liikennemuotoa:

Kaikki seuraavat samaa RAG-muodon vähentäminen: ekstrahoidaan signaalit kerranM SK1 tallennetaan todisteita, synteesoidaan rajattuun LLM-tulokseen


Monet demot, ensimmäiset audioanalyyseja koskevat putket, tekevät saman virheen, ne kohtelevat LLM:itä kuin akustisia insinöörejä.

He syövät aaltomuotoja malleihin., – he pyytävät, että " – havaitsee puheenlaadun, " – tai " – tunnistaa puhujat ja toivoo, että malli voi luulla rakenteellisista ominaisuuksista, jotka perustuvat perustavanlaatuiseen malliin.

AudioSummarizer yhdistää kaksi täydentävää mallia:

  1. Vähennetty RAG saataville - ekstrahoidaan signaalit kerran , tallennetaan todisteitaM SK2 kysytään tosiasioita vastaan
  2. Rajoitettu epämääräisyys harjoittamista varten -valotM SK1pohjainen putkilinja, määräysperusta rajoittaa todennäköisiä malleja

Sen sijaan, että välittäisiin raaka-audioa LLM:lle kysymyksen aikana, se vähentää jokaiseen audio-asiakirjaan merkkikirja (deterministiset signaalit, kuten RMS:n voimakkuus otetut todisteet (transcriptsM SK1 puhujaesimerkkikuvien clips , diarization turnsSSK3 Tämä ledger on säilynyt kerran, indeksoitu jäljitettäviksi

Sillä kyselyaika, LLM:n avulla synteesoidaan vastauksia pyytämällä ja perustella esiintyviä signaaleja, — ei toistamalla - analysoimalla audioa,. raskaskäyttöön liittyvää (signaalikäsittelyä,

Muodon kokoonpano: Reduced RAG handles mitä varastoida ja palauttaa. Rajoitetut hämäräisyydet miten to extract signals reliably. Yhdessä ne mahdollistavat oikeudellisen audiokuvauksen rajattuun LLM-kustannuksiin

Lisätietoja Reduced RAG -mallista: Reduced RAG: Signal-Driven Document Understanding

Keskeinen terminologia

  • Viestit: Kirjeltyjä tosiasioita luottamuksellisten tulosten mukaan (eM SK2g., audio.content_type = "speech",luottamus
  • Todisteet: Kuunnettavissa olevat artifactit
  • Signaalikirja: Kaikista signaaleista muodostuva pysyvä ryhmittymä
  • Puhemiehen ID: Paikallinen tunnistaja yhdessä ainoassa asiakirjassa SPEAKER_00)
  • VoiceprintId: CrossM SK1file stable hash derived from voice embedding vprint:a3f9c2e1)
  • Henkilö: ei selvästikään ole johdettuM SK1 emme koskaan väitä " tämä on John Smith

Identiteettimalli: SPEAKER_00 on paikallista yhteen audio-asiakirjaan. VoiceprintId on vakaa kaikissa asiakirjoissa, mutta nimettömiä. Emme koskaan kartoittaisi ihmisten nimiä

Rikosoikeudelliset vaatimukset: Jokainen signaali sisältää alkuperä (mikä aalto on tuottanut sen luottamus (varmuudenaste versionointi (malliM SK1kynnysversiot ). Tämä mahdollistaa jäljentämisen: samat mahdollisuudet inputin kanssa SSK4 sama konfigurointi → sama signaali ledger

Tulos on:

  • Nopeutettu, yksityisyys
  • Puhemiehen arvostaminen ilman Python-riippuvuuksia (pure .NET)
  • Anonymin puhujan samankaltaisuuden havaitseminen (ei PIIM SK1 ei nimiä)
  • Voice embeddings for "palka vastaavia puhujiaM SK1 kysymykset
  • Kaikki ilman audion lähettämistä pilvipalveluihin syöttämisen aikana

Tämä perustuu suoraan Rajoitettu epäselvyysmalli ja aalto---pohjainen arkkitehtuuri ImageSummarizer.

Keskeinen käsitys: LLM:n olisi perusteltava akustisia tosiseikkoja, ei laskettava niitäM SK1

Tämä artikla kattaa:

  • Miten AudioSummarizer panee täytäntöön vähennetyn RAG-muodon
  • Päätöslauselmatietoiset akustiset tosiasiat (ei LLM:n yhteenvedot
  • Todisteiden säilyttäminen: puhujaesityksetM SK1 käännökset
  • Puhdas .NET-puheenvuorojen vääristäminen (ei PythoniaM SK2ei pyannoittia)
  • Kysymys-aikasynteesiM SK1filtrointisignaalit → todisteiden keruu → LLM synteesizes

Yhteenkuuluvat artiklat:

  • Vähennetty RAG - Keskeinen malli, jota se soveltaa
  • Rajoitettu epäselvyysmalli - Perusmalli
  • RAG:n vähennetyt täytäntöönpanot
    • DocSummarizer - Dokumentti RAG entiteetin poistamiseksi ja tietografiikkojen avulla
    • DataSummarizer - Tietoprofiilien luominen ja schema-selvitykset RAG
    • ImageSummarizer - Kuva RAG:n ja 22-valonäkötiedonjohto
    • AudioSummarizer (artikkeliM SK1 - Audion oikeudellinen tunnustus puhujan vääristymisen kanssa

Ongelma: Audio-analyysi on vaikeaa ( ja kulttuurisesti laadittu)

Audioanalyysi epäonnistuu ennustettavissa tapauksissa:

  • Kulttuuriset väitteet: "Tämä on jazzM SK2 ♫ ♫ ( ♫ sanoo, kuka? ♫ minkä koulutustietojen perusteella
  • Puhemiehen nimitysArvoisa puhemies, puhuja on John Smith.
  • Musiikan tunnistaminen:
  • AksenttipoikkeusYhdistyneen kuningaskunnan kielellä puhuja on brittikielinen.
  • Pythonin riippuvuusMost diarization tools require pyannote (Python ecosystem lock

Perinteinen lähestymistapa: M SK1Run Whisper -äänestys käännökselle , pyanot diarrointia varten, lähetetään LLM:lle tiivistelmän saamiseksi

Ongelma: Tämä joko vuotaa PII:tä (puhdistajien nimet), maksaa liian paljon (cloud APIsM SK4 tai vaatii Pythonin runtimea |(pyannote | , | DIART ‐).

Ratkaisu: Rakennetaan puhdasta .NET-aaltoa M SK2pohjaista kaasuputkea, joka luonnehtii audioa rakenteellisesti ja akustillisesti


RAG-audiopuitteet

AudioSummarizer toteuttaa RAG-muodon vähentäminen audio-asiakirjoihin, sen kolmen perusperiaatteen mukaisesti :

1.Signaalien extrahointi (KaaluvaiheM SK2

Yksinkertaiset deterministit signaalit—ei LLMM SK1käsiteltyjä yhteenvedonottoja:

  • Väliaikainen: kesto
  • Akustiikka: RMS:n voimakkuusM SK1 spektraalinen centroidi, dynaaminen ulottuvuus
  • Identiteetti: SHA
  • Laatu: käännösluottamus
  • Puhemies: äänijäljen tunnisteet ( nimettömät hashesit
  • KategorisointiLuettelo puhujista

Esimerkki podcastin signaalikokonaisuudesta:

{
  "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"
}

Nämä signaalit ovat deterministinen (sama audio → samat arvotM SK2 indeksoitava (valmiuttaa filtroinninM SK1sortoidaan tietokannassa ), ja laskettavissa ilman LLM:itä (pure signal processing

2.Todisteiden säilyttäminen (Yritettyjen yksiköiden säilyttäminen

Sen sijaan, että varastoidaan vain "kuormituksia ," AudioSummarizer kauppiaitaM SK2

  • Viestit: Rakennetut kentät (JSONM SK2 deterministisen filtroinnin osalta
  • Sisällytykset: Äänestön sisällyttäminen (512-dim ECAPAM SK2TDNN) puhujan samankaltaisuutta varten
    • Luettelo yksityisyydestä: Olot eivät ole tässä järjestelmässä käyttökelpoisia käännettäviksi, mutta ne on pidettävä arkaluonteisina tietoina
  • Todisteiden artifactit:
    • Täyden käännöksen teksti (tutkimuskelpoinen )
    • Puhe sample clips (Base64 WAVM SK2 2-second clips for verification
    • Diarization turns (JSON puheenvuoron kanssa_idM SK2 aloitus / lopulliset aikarajat
  • Pointerit: File hash + todisteiden tunnistaminen tilintarkastuskelpoiseksi alkuperäksi

Todisteet ovat tilintarkastuskelpoisia: Käyttäjät voivat laatia äänitorvikuvauksia, lukea käännöksetM SK2 tarkastaa riisumien virtoja — ei vain luottaa LLM:n yhteenvetoon

3. KysymysM SK1Time Synthesis

Kysymyksen aikana, LLM koskaan näkee raaka-audion. Sen sijaan:

  1. Filtroiminen deterministisesti (tietokanta Missä lauseke

    WHERE audio.content_type = 'speech'
      AND speaker.count >= 2
      AND audio.rms_db > -25.0
      AND transcription.confidence > 0.8
    
  2. Hybridinen haku (BM25 + vektori

    • BM25 käännöstekstin osalta M SK1sanojen vastaavuus)
    • Äänestön sisällyttämisen vektoritehokkuus (puhdistajien samankaltaisuusM SK1
    • Palaa ylös 5 audio files
  3. Synteesio signaaleista (LLM katsoo rakenteellisen todisteiden paketin

    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 saa 5 rakenteelliset todisteiden paketit sen sijaan, että 500 raaka-ääneisiä metatietoja. Kontext- ikkunan vähentäminenM SK1 ~50× pienempi.

Kustannusvaikutukset (palkkaisia LLM-API:ita käyttäen ):Protsessointi \100audiofilet siirtyvät \ ~$5 \ ( \ toistaan - \ analysoimalla audiota jokaisessa kysymyksessä
Huomautus: lucidRAG on paikallistaM SK1ensimmäinen (Default-OllamaMSC3 nollakustannuksista ). Maksuttomat API:t | ( | Claude |, | GPT ‐-4) | ovat valinnaisiaMST8 | Kustannusten arvioinnissa otetaan huomioon maksullinen API:n käyttö |

Tämä on keskeinen vähennetty RAG-tietämys päätetään etukäteen, mikä on tärkeää (signaalit varastoida se määrätietoisesti (todisteet liittää LLM vain synteesiin (kysymys-aikaM SK2


Rajoitettu epämääräinen audioputki

Järjestelmä toimii aaltoina ensisijaisessa järjestyksessä (ylemmän luokan = toimii ensimmäisenäM SK2

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

Huomio:TuntemusM SK1Emotion detection intentionally excluded —culturally loaded and not part of forensic characterization

Rakennus

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

Tämä säilyttää tuttun Voima → signaali → valinnainen LLM loop, mutta ankkuroi sen deterministisiin tosiseikkoihin

  • Akustillinen profilointi on deterministista (samaa inputtia = samaa outputtiaM SK2
  • LLM toimii todisteiden perusteella, , ei raaka-audion perusteella.

Real-World Example: Podcast-esityksen käsittely

Let's trace a M SK1minute podcast episode through the pipeline

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)

Euroopan unionin Signaalikirja on se, mikä säilyy tietokantaan—täydellinen valikoima signaaleja, todisteita osoittavat indikaattoritMSC2 ja sisällytyksetM SK3 Siihen 'miltä tämä podcast näyttää

Luettelo merkinnöistä (luonnos

{
  "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..."
}

Mitä tämä mahdollistaa:

  • "Tech Insights käsittelee pilviinfrastruktuuriaM SK1 → löytää tämän jakson käännöksi
  • Kysymys "Katso äänimäärää voiceprintin avulla vprintin välityksellä
  • Kysykää "Mitä'mikä ovat podcast-jaksojemme dynaamiset mahdollisuudetM SK2 →yhdennettävät akustiset signaalit
  • Filtrointi "Hätä alhainenM SK1laatuiset tallenteet" → RMS CSK4 -25dB tai katkaiseminen > |1%
  • Tarkistaa "Did-puheenjohtajan tunnistamistyöM SK1 → käynti speaker.sample.speaker_00 lippu (yhdessäM SK1tietokanta )

Miksi signaalit ovat tärkeitä? (Even ilman LLM:ää

Tunnuskirjassa vastataan viivytteleviin kysymyksiin.

  • Onko tämä audiopuhe,musiikki, vai hiljaisuusM SK2
  • Kuinka monta puhujaa on havaittu?
  • Mikä on signaali, - to -, melusuhde
  • Onko teurasteita tai vääristymiä?
  • Mitkä ovat ?'?, ?Spektrallinen centroidi ?
  • Mitkä ovat ?'?, dynaaminen ulottuvuus?

Nämä ovat kysymykset, joita tavallisesti löydetään 30 minuuteissa Audacityn arkeologiasta

Luettelo antaa ne teille sekunnissa.


Wave Pipeline: Identiteetistä tiedusteluun

Valve 1:Identiteetti (Deterministic SubstrateM SK2

Peruslinja. Kriptografinen identiteetti ja ឯកសារ 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;
    }
}

Esitetyt keskeiset signaalit:

  • audio.hash.sha256 - Cryptografinen identiteetti
  • audio.duration_seconds - Pituus deterministinenM SK2
  • audio.sample_rate -
  • audio.channels -
  • audio.format -

Miksi deterministinen?

  • Sama asiakirja → sama hash → sama identiteetti
  • No sampling randomness, no temperature parameter
  • Luottamus = 1.0 ≥(ei ole todennäköistäM SK3

Valve 2: Akustillinen profiili (Signal ProcessingM SK2

Laaditaan rakenteellisia akustisia ominaisuuksia NAudion ja FftSharpin avulla

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)
        };
    }
}

Esimerkkitulos:

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)

Miksi tämä on tärkeää:

  • RMS:n voimakkuus merkitsee mastering-laatua
  • Clipping-suhde paljastaa tallennettujen artifaktien
  • Spektralinen centroidi erottaa puheen (2-4kHz) musiikkia (vaihtelevaM SK3
  • Kaikki deterministiset—sama audio tuottaa samat arvot

Voima 3:Lisäsisällön luokittelu (Speech vs MusicM SK2

Heuristinen luokittelu käyttämällä nollaa-siirron nopeutta ja taajuusvirtaa reittitarkoituksiinM SK1

Huomio: Nämä ääntelymenetelmät ovat luonteeltaan genre-läheisiä /perustan mukaan riippuvaisia — eivät ole luotettavasti täsmällisiä kaikissa sisältötyypeissä M SK3 Sovelletaan vain aaltosuuntauspäätöksiin

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;
    }
}

Esimerkki rautatieliikenteestä:

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)

Puhdas .NET-puheenvuorojen arvostaminen

lucidRAG':n käyttöönottorajoituksia varten: puhujan diarointi ilman pyannoittia , DIARTM SK2 tai muut Pythonin riippuvuussuhteet.

Pythonin-perusteinen arvioinnista johtuva ongelma

Perinteinen lähestymistapa:

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

Ongelmat:

  • pyannote 3.1+ poistettiin ONNX-tuki ( siirrettiin puhtaalle PyTorchille
  • Pythonin käyttöaika välttämätön (laajentumisen painajainen
  • HTTP wrapper lisää latentiaa ja monimutkaisuutta
  • Offline-tuki puuttuu

ratkaisu: Direktiivin täytäntöönpano puhtaassa .NET-järjestelmässä olemassa olevien äänen sisällyttämistä koskevien mallien avulla

Puhdas .NET-algoritmi

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

täytäntöönpano

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.