@@ -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