Skip to content

Commit f0d4fa8

Browse files
committed
refactor(processor): remove obsolete WAV extraction from silence detection
DNS-1500 now uses inline noise learning via asendcmd with timestamps, making the separate WAV file extraction unnecessary. - Remove NoiseProfile.FilePath field (set but never read) - Remove createWAVEncoder function from encoder.go - Simplify extractNoiseProfile to measure-only (no file output) - Remove unused path/filepath import from analyzer.go
1 parent 0c868b2 commit f0d4fa8

2 files changed

Lines changed: 5 additions & 171 deletions

File tree

internal/processor/analyzer.go

Lines changed: 5 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"errors"
66
"fmt"
77
"math"
8-
"path/filepath"
98
"sort"
109
"strconv"
1110
"time"
@@ -24,7 +23,6 @@ type SilenceRegion struct {
2423

2524
// NoiseProfile contains information about an extracted noise sample
2625
type NoiseProfile struct {
27-
FilePath string `json:"file_path"` // Path to extracted noise sample WAV file
2826
Start time.Duration `json:"start"` // Start time of silence region used
2927
Duration time.Duration `json:"duration"` // Duration of extracted sample
3028
MeasuredNoiseFloor float64 `json:"measured_noise_floor"` // dBFS, RMS level of silence (average noise)
@@ -1891,8 +1889,7 @@ func AnalyzeAudio(filename string, config *FilterChainConfig, progressCallback f
18911889
measurements.SilenceCandidates = silenceResult.Candidates
18921890

18931891
if silenceResult.BestRegion != nil {
1894-
tempDir := filepath.Dir(filename) // Use same directory as input file
1895-
if profile, err := extractNoiseProfile(filename, silenceResult.BestRegion, tempDir); err == nil && profile != nil {
1892+
if profile, err := extractNoiseProfile(filename, silenceResult.BestRegion); err == nil && profile != nil {
18961893
measurements.NoiseProfile = profile
18971894
// If we got a noise profile measurement, use it as the primary noise floor
18981895
// This is more accurate than the overall RMS_trough because it's from pure silence
@@ -2500,21 +2497,15 @@ func measureSilenceCandidateSpectral(filename string, region SilenceRegion) *Sil
25002497
return metrics
25012498
}
25022499

2503-
// extractNoiseProfile extracts a noise sample from the silence region and measures its characteristics.
2504-
// The extracted sample is written as a WAV file for use with afftdn's noise profiling.
2500+
// extractNoiseProfile measures noise characteristics from a silence region.
25052501
// Uses atrim + astats filter chain to measure RMS level, peak level, and entropy.
2502+
// DNS-1500 uses inline noise learning via asendcmd with timestamps, so no WAV extraction needed.
25062503
// Returns nil, nil if no suitable silence region exists or extraction fails non-fatally.
2507-
func extractNoiseProfile(filename string, region *SilenceRegion, tempDir string) (*NoiseProfile, error) {
2504+
func extractNoiseProfile(filename string, region *SilenceRegion) (*NoiseProfile, error) {
25082505
if region == nil {
25092506
return nil, nil
25102507
}
25112508

2512-
// Generate output filename for the noise profile WAV
2513-
baseName := filepath.Base(filename)
2514-
ext := filepath.Ext(baseName)
2515-
nameWithoutExt := baseName[:len(baseName)-len(ext)]
2516-
outputPath := filepath.Join(tempDir, nameWithoutExt+"_noise_profile.wav")
2517-
25182509
// Open the audio file
25192510
reader, _, err := audio.OpenAudioFile(filename)
25202511
if err != nil {
@@ -2543,14 +2534,7 @@ func extractNoiseProfile(filename string, region *SilenceRegion, tempDir string)
25432534
}
25442535
defer ffmpeg.AVFilterGraphFree(&filterGraph)
25452536

2546-
// Create WAV encoder for writing the noise profile
2547-
wavEncoder, err := createWAVEncoder(outputPath, bufferSinkCtx)
2548-
if err != nil {
2549-
return nil, nil // Non-fatal - extraction skipped
2550-
}
2551-
defer wavEncoder.Close()
2552-
2553-
// Process frames through filter to measure noise and write to WAV
2537+
// Process frames through filter to measure noise
25542538
filteredFrame := ffmpeg.AVFrameAlloc()
25552539
defer ffmpeg.AVFrameFree(&filteredFrame)
25562540

@@ -2560,7 +2544,6 @@ func extractNoiseProfile(filename string, region *SilenceRegion, tempDir string)
25602544
var entropy float64
25612545
var noiseFloorFound bool
25622546
var framesProcessed int64
2563-
var wavWriteError error
25642547

25652548
for {
25662549
frame, err := reader.ReadFrame()
@@ -2585,13 +2568,6 @@ func extractNoiseProfile(filename string, region *SilenceRegion, tempDir string)
25852568
continue
25862569
}
25872570

2588-
// Write frame to WAV file (if encoder available)
2589-
if wavEncoder != nil && wavWriteError == nil {
2590-
if err := wavEncoder.WriteFrame(filteredFrame); err != nil {
2591-
wavWriteError = err // Stop trying to write after first error
2592-
}
2593-
}
2594-
25952571
// Extract noise measurements from metadata
25962572
if metadata := filteredFrame.Metadata(); metadata != nil {
25972573
// RMS_level: average noise floor
@@ -2621,13 +2597,6 @@ func extractNoiseProfile(filename string, region *SilenceRegion, tempDir string)
26212597
break
26222598
}
26232599

2624-
// Write remaining frames to WAV
2625-
if wavEncoder != nil && wavWriteError == nil {
2626-
if err := wavEncoder.WriteFrame(filteredFrame); err != nil {
2627-
wavWriteError = err
2628-
}
2629-
}
2630-
26312600
if metadata := filteredFrame.Metadata(); metadata != nil {
26322601
if value, ok := getFloatMetadata(metadata, metaKeyRMSLevel); ok {
26332602
measuredNoiseFloor = value
@@ -2646,13 +2615,6 @@ func extractNoiseProfile(filename string, region *SilenceRegion, tempDir string)
26462615
}
26472616
}
26482617

2649-
// Flush encoder
2650-
if wavEncoder != nil && wavWriteError == nil {
2651-
if err := wavEncoder.Flush(); err != nil {
2652-
wavWriteError = err
2653-
}
2654-
}
2655-
26562618
if framesProcessed == 0 {
26572619
return nil, nil // No frames in silence region
26582620
}
@@ -2666,7 +2628,6 @@ func extractNoiseProfile(filename string, region *SilenceRegion, tempDir string)
26662628
}
26672629

26682630
// Build noise profile result
2669-
// FilePath is set only if WAV was successfully written
26702631
profile := &NoiseProfile{
26712632
Start: region.Start,
26722633
Duration: region.Duration,
@@ -2675,11 +2636,6 @@ func extractNoiseProfile(filename string, region *SilenceRegion, tempDir string)
26752636
Entropy: entropy, // 1.0 = broadband noise, lower = tonal noise
26762637
}
26772638

2678-
// Set FilePath only if WAV was successfully written
2679-
if wavEncoder != nil && wavWriteError == nil {
2680-
profile.FilePath = outputPath
2681-
}
2682-
26832639
// Record warning if using silence region outside ideal range (8-18s)
26842640
if region.Duration < idealDurationMin {
26852641
profile.ExtractionWarning = fmt.Sprintf("using short silence region (%.1fs) - ideally need >=%ds", region.Duration.Seconds(), int(idealDurationMin.Seconds()))

internal/processor/encoder.go

Lines changed: 0 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -315,125 +315,3 @@ func calculateFrameLevel(frame *ffmpeg.AVFrame) float64 {
315315

316316
return levelDB
317317
}
318-
319-
// createWAVEncoder creates an encoder for WAV (PCM S16LE) output.
320-
// Used for extracting noise profile samples for afftdn noise profiling.
321-
// The encoder expects S16 mono 44100Hz frames from the filter sink.
322-
func createWAVEncoder(outputPath string, bufferSinkCtx *ffmpeg.AVFilterContext) (*Encoder, error) {
323-
// Allocate output format context for WAV
324-
outputPathC := ffmpeg.ToCStr(outputPath)
325-
defer outputPathC.Free()
326-
327-
var fmtCtx *ffmpeg.AVFormatContext
328-
if _, err := ffmpeg.AVFormatAllocOutputContext2(&fmtCtx, nil, nil, outputPathC); err != nil {
329-
return nil, fmt.Errorf("failed to allocate output context: %w", err)
330-
}
331-
332-
// Find PCM S16LE encoder (for WAV)
333-
codec := ffmpeg.AVCodecFindEncoder(ffmpeg.AVCodecIdPcmS16Le)
334-
if codec == nil {
335-
ffmpeg.AVFormatFreeContext(fmtCtx)
336-
return nil, fmt.Errorf("PCM S16LE encoder not found for output: %s", outputPath)
337-
}
338-
339-
// Create stream
340-
stream := ffmpeg.AVFormatNewStream(fmtCtx, nil)
341-
if stream == nil {
342-
ffmpeg.AVFormatFreeContext(fmtCtx)
343-
return nil, fmt.Errorf("failed to create stream for output: %s", outputPath)
344-
}
345-
346-
// Allocate encoder context
347-
encCtx := ffmpeg.AVCodecAllocContext3(codec)
348-
if encCtx == nil {
349-
ffmpeg.AVFormatFreeContext(fmtCtx)
350-
return nil, fmt.Errorf("failed to allocate encoder context for output: %s", outputPath)
351-
}
352-
353-
// Get audio parameters from filter output
354-
sampleRate, err := ffmpeg.AVBuffersinkGetSampleRate(bufferSinkCtx)
355-
if err != nil {
356-
ffmpeg.AVCodecFreeContext(&encCtx)
357-
ffmpeg.AVFormatFreeContext(fmtCtx)
358-
return nil, fmt.Errorf("failed to get sample rate: %w", err)
359-
}
360-
361-
timeBase := ffmpeg.AVBuffersinkGetTimeBase(bufferSinkCtx)
362-
363-
// Configure encoder - PCM S16LE for WAV
364-
encCtx.SetSampleFmt(ffmpeg.AVSampleFmtS16)
365-
encCtx.SetSampleRate(sampleRate)
366-
367-
// Get channel count from filter output
368-
channels, err := ffmpeg.AVBuffersinkGetChannels(bufferSinkCtx)
369-
if err != nil {
370-
ffmpeg.AVCodecFreeContext(&encCtx)
371-
ffmpeg.AVFormatFreeContext(fmtCtx)
372-
return nil, fmt.Errorf("failed to get channels: %w", err)
373-
}
374-
375-
// Set default channel layout for the encoder
376-
ffmpeg.AVChannelLayoutDefault(encCtx.ChLayout(), channels)
377-
378-
// Set global header flag if needed by the format
379-
if fmtCtx.Oformat().Flags()&ffmpeg.AVFmtGlobalheader != 0 {
380-
encCtx.SetFlags(encCtx.Flags() | ffmpeg.AVCodecFlagGlobalHeader)
381-
}
382-
383-
encCtx.SetTimeBase(timeBase)
384-
385-
// Open encoder
386-
if _, err := ffmpeg.AVCodecOpen2(encCtx, codec, nil); err != nil {
387-
ffmpeg.AVCodecFreeContext(&encCtx)
388-
ffmpeg.AVFormatFreeContext(fmtCtx)
389-
return nil, fmt.Errorf("failed to open encoder: %w", err)
390-
}
391-
392-
// Copy encoder parameters to stream
393-
if _, err := ffmpeg.AVCodecParametersFromContext(stream.Codecpar(), encCtx); err != nil {
394-
ffmpeg.AVCodecFreeContext(&encCtx)
395-
ffmpeg.AVFormatFreeContext(fmtCtx)
396-
return nil, fmt.Errorf("failed to copy encoder parameters: %w", err)
397-
}
398-
399-
stream.SetTimeBase(encCtx.TimeBase())
400-
401-
// Open output file
402-
if fmtCtx.Oformat().Flags()&ffmpeg.AVFmtNofile == 0 {
403-
var pb *ffmpeg.AVIOContext
404-
if _, err := ffmpeg.AVIOOpen(&pb, outputPathC, ffmpeg.AVIOFlagWrite); err != nil {
405-
ffmpeg.AVCodecFreeContext(&encCtx)
406-
ffmpeg.AVFormatFreeContext(fmtCtx)
407-
return nil, fmt.Errorf("failed to open output file: %w", err)
408-
}
409-
fmtCtx.SetPb(pb)
410-
}
411-
412-
// Write header
413-
if _, err := ffmpeg.AVFormatWriteHeader(fmtCtx, nil); err != nil {
414-
if fmtCtx.Pb() != nil {
415-
ffmpeg.AVIOClose(fmtCtx.Pb())
416-
}
417-
ffmpeg.AVCodecFreeContext(&encCtx)
418-
ffmpeg.AVFormatFreeContext(fmtCtx)
419-
return nil, fmt.Errorf("failed to write header: %w", err)
420-
}
421-
422-
packet := ffmpeg.AVPacketAlloc()
423-
if packet == nil {
424-
if fmtCtx.Pb() != nil {
425-
ffmpeg.AVIOClose(fmtCtx.Pb())
426-
}
427-
ffmpeg.AVCodecFreeContext(&encCtx)
428-
ffmpeg.AVFormatFreeContext(fmtCtx)
429-
return nil, fmt.Errorf("failed to allocate packet for output: %s", outputPath)
430-
}
431-
432-
return &Encoder{
433-
fmtCtx: fmtCtx,
434-
encCtx: encCtx,
435-
stream: stream,
436-
packet: packet,
437-
streamIdx: 0,
438-
}, nil
439-
}

0 commit comments

Comments
 (0)