Skip to content

Commit a0335fd

Browse files
committed
refactor(processor): migrate AnalyzeAudio to use runFilterGraph
- Replace inline frame loop with runFilterGraph abstraction - Move pre-filter work (level calculation, RMS/peak accumulation, interval sampling, progress updates) into OnInputFrame callback - Move spectral extraction into OnFrame callback returning FrameDiscard - Configure strict error handlers for read/push/pull paths - Remove manual filteredFrame allocation (managed by runFilterGraph) - Preserve filterFreed guard pattern for deferred cleanup Reduces function complexity by centralising frame loop mechanics whilst maintaining closure capture pattern for interval/spectral accumulators and progress state. Signed-off-by: Martin Wimpress <code@wimpress.io>
1 parent 7b5c7e1 commit a0335fd

1 file changed

Lines changed: 43 additions & 82 deletions

File tree

internal/processor/analyzer.go

Lines changed: 43 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -2229,10 +2229,6 @@ func AnalyzeAudio(filename string, config *FilterChainConfig, progressCallback f
22292229
}
22302230
}()
22312231

2232-
// Process all frames through the filter
2233-
filteredFrame := ffmpeg.AVFrameAlloc()
2234-
defer ffmpeg.AVFrameFree(&filteredFrame)
2235-
22362232
// Track frames for periodic progress updates
22372233
frameCount := 0
22382234
updateInterval := 100 // Send progress update every N frames
@@ -2253,59 +2249,49 @@ func AnalyzeAudio(filename string, config *FilterChainConfig, progressCallback f
22532249
var inputSamplesProcessed int64
22542250
inputSampleRate := float64(reader.GetDecoderContext().SampleRate())
22552251

2256-
for {
2257-
frame, err := reader.ReadFrame()
2258-
if err != nil {
2259-
return nil, fmt.Errorf("failed to read frame: %w", err)
2260-
}
2261-
if frame == nil {
2262-
break // EOF
2263-
}
2264-
2265-
// Calculate audio level from frame
2266-
currentLevel = calculateFrameLevel(frame)
2267-
2268-
// Calculate input frame time based on samples processed (before filter graph upsampling)
2269-
inputFrameTime := time.Duration(float64(inputSamplesProcessed) / inputSampleRate * float64(time.Second))
2270-
inputSamplesProcessed += int64(frame.NbSamples())
2271-
lastFrameTime = inputFrameTime
2272-
2273-
// Accumulate RMS and peak from INPUT frame (before filter graph which upsamples to 192kHz)
2274-
// This gives accurate RMS and peak values matching the original audio levels
2275-
intervalAcc.addFrameRMSAndPeak(frame)
2276-
2277-
// Check if interval complete (250ms elapsed) based on input time
2278-
if inputFrameTime-intervalStartTime >= intervalDuration {
2279-
// Finalize and store completed interval
2280-
intervals = append(intervals, intervalAcc.finalize(intervalStartTime))
2281-
intervalStartTime = inputFrameTime
2282-
intervalAcc.reset()
2283-
}
2284-
2285-
// Send periodic progress updates based on frame count
2286-
if frameCount%updateInterval == 0 && progressCallback != nil && estimatedTotalFrames > 0 {
2287-
progress := float64(frameCount) / estimatedTotalFrames
2288-
if progress > 1.0 {
2289-
progress = 1.0
2252+
// Process all frames through the filter graph
2253+
if err := runFilterGraph(reader, bufferSrcCtx, bufferSinkCtx, FrameLoopConfig{
2254+
OnReadError: func(err error) error {
2255+
return fmt.Errorf("failed to read frame: %w", err)
2256+
},
2257+
OnPushError: func(err error) error {
2258+
return fmt.Errorf("failed to add frame to filter: %w", err)
2259+
},
2260+
OnPullError: func(err error) error {
2261+
return fmt.Errorf("failed to get filtered frame: %w", err)
2262+
},
2263+
OnInputFrame: func(inputFrame *ffmpeg.AVFrame) {
2264+
// Calculate audio level from frame
2265+
currentLevel = calculateFrameLevel(inputFrame)
2266+
2267+
// Calculate input frame time based on samples processed (before filter graph upsampling)
2268+
inputFrameTime := time.Duration(float64(inputSamplesProcessed) / inputSampleRate * float64(time.Second))
2269+
inputSamplesProcessed += int64(inputFrame.NbSamples())
2270+
lastFrameTime = inputFrameTime
2271+
2272+
// Accumulate RMS and peak from INPUT frame (before filter graph which upsamples to 192kHz)
2273+
// This gives accurate RMS and peak values matching the original audio levels
2274+
intervalAcc.addFrameRMSAndPeak(inputFrame)
2275+
2276+
// Check if interval complete (250ms elapsed) based on input time
2277+
if inputFrameTime-intervalStartTime >= intervalDuration {
2278+
// Finalize and store completed interval
2279+
intervals = append(intervals, intervalAcc.finalize(intervalStartTime))
2280+
intervalStartTime = inputFrameTime
2281+
intervalAcc.reset()
22902282
}
2291-
progressCallback(PassAnalysis, "Analyzing", progress, currentLevel, nil)
2292-
}
2293-
frameCount++
2294-
2295-
// Push frame into filter graph
2296-
if _, err := ffmpeg.AVBuffersrcAddFrameFlags(bufferSrcCtx, frame, 0); err != nil {
2297-
return nil, fmt.Errorf("failed to add frame to filter: %w", err)
2298-
}
22992283

2300-
// Pull filtered frames and extract spectral metadata
2301-
for {
2302-
if _, err := ffmpeg.AVBuffersinkGetFrame(bufferSinkCtx, filteredFrame); err != nil {
2303-
if errors.Is(err, ffmpeg.EAgain) || errors.Is(err, ffmpeg.AVErrorEOF) {
2304-
break
2284+
// Send periodic progress updates based on frame count
2285+
if frameCount%updateInterval == 0 && progressCallback != nil && estimatedTotalFrames > 0 {
2286+
progress := float64(frameCount) / estimatedTotalFrames
2287+
if progress > 1.0 {
2288+
progress = 1.0
23052289
}
2306-
return nil, fmt.Errorf("failed to get filtered frame: %w", err)
2290+
progressCallback(PassAnalysis, "Analyzing", progress, currentLevel, nil)
23072291
}
2308-
2292+
frameCount++
2293+
},
2294+
OnFrame: func(_, filteredFrame *ffmpeg.AVFrame) (FrameAction, error) {
23092295
// Extract spectral metrics once, reuse for both whole-file and interval accumulators
23102296
metadata := filteredFrame.Metadata()
23112297
spectral := extractSpectralMetrics(metadata)
@@ -2317,35 +2303,10 @@ func AnalyzeAudio(filename string, config *FilterChainConfig, progressCallback f
23172303
// Filtered frames roughly correspond to input timing (just at higher sample rate)
23182304
intervalAcc.add(extractIntervalFrameMetrics(metadata, spectral))
23192305

2320-
ffmpeg.AVFrameUnref(filteredFrame)
2321-
}
2322-
}
2323-
2324-
// Flush the filter graph
2325-
if _, err := ffmpeg.AVBuffersrcAddFrameFlags(bufferSrcCtx, nil, 0); err != nil {
2326-
return nil, fmt.Errorf("failed to flush filter: %w", err)
2327-
}
2328-
2329-
// Pull remaining frames
2330-
for {
2331-
if _, err := ffmpeg.AVBuffersinkGetFrame(bufferSinkCtx, filteredFrame); err != nil {
2332-
if errors.Is(err, ffmpeg.EAgain) || errors.Is(err, ffmpeg.AVErrorEOF) {
2333-
break
2334-
}
2335-
return nil, fmt.Errorf("failed to get filtered frame: %w", err)
2336-
}
2337-
2338-
// Extract spectral metrics once, reuse for both whole-file and interval accumulators
2339-
metadata := filteredFrame.Metadata()
2340-
spectral := extractSpectralMetrics(metadata)
2341-
2342-
// Extract measurements from remaining frames
2343-
extractFrameMetadata(metadata, acc, spectral)
2344-
2345-
// Also accumulate into current interval for per-interval spectral data
2346-
intervalAcc.add(extractIntervalFrameMetrics(metadata, spectral))
2347-
2348-
ffmpeg.AVFrameUnref(filteredFrame)
2306+
return FrameDiscard, nil
2307+
},
2308+
}); err != nil {
2309+
return nil, err
23492310
}
23502311

23512312
// Finalize any remaining partial interval (if it has data)

0 commit comments

Comments
 (0)