I want to detect the cutoff frequency of the AAC audio encoder used to compress an M4A audio file. This cutoff frequency (or maximum frequency) is an indicator of audio quality. High-quality audio has a cutoff around 20KHz (fullband), medium-quality audio has a cutoff around 14KHz (superwideband), low-quality audio has a cutoff around 7KHz (wideband), super-low-quality audio has a cutoff around 3KHz (narrowband). See also: voice frequency Example spectrum of a 2 hours movie, generated with sox, with a maximum frequency around 19.6KHz: The program should ignore noise below a certain loudness, for example -80dB. Here is a Python script generated by deepseek.com but it returns 0.2KHz instead of 19.6KHz. #!/usr/bin/env python3 # get the maximum frequency # of an audio spectrum # as an indicator # of the actual audio quality # generated by deepseek.com # prompt """ create a python script to detect the maximum frequency in an m4a audio file. that maximum frequency is produced by the lowpass filter of the aac audio encoder. high-quality audio has a maximum frequency around 20 KHz (fullband), low-quality audio has a maximum frequency around 3 KHz (narrowband). use ffmpeg to decode the audio to pcm in chunks of 10 seconds. for each chunk: detect the local maximum, print the local maximum and the chunk time with the format f"t={t}sec f={f}KHz", update the global maximum. to detect the local maximum, remove the noise floor around -110dB, then find the maximum frequency in the spectrum. accept some command line options: --ss n: pass as "-ss n" to ffmpeg. --to n: pass as "-to n" to ffmpeg. both -ss and -to args must come before the -i arg for ffmpeg input seeking. print all frequencies in KHz. add a shebang line before the script, spaced by an empty line. do not recode the audio with ffmpeg. use ffprobe to get the input samplerate, usually 48KHz or 44.1KHz. create a python class, so we dont have to pass all parameters to functions. add a command line option to select the audio track id, by default zero. """ #!/usr/bin/env python3 import argparse import numpy as np import subprocess import sys from tempfile import NamedTemporaryFile class AudioAnalyzer: def __init__(self, input_file, audio_track=0, start_time=None, end_time=None): self.input_file = input_file self.audio_track = audio_track self.start_time = start_time self.end_time = end_time self.sample_rate = self._get_sample_rate() self.global_max_freq = 0 self.global_max_time = 0 def _get_sample_rate(self): cmd = [ 'ffprobe', '-v', 'error', '-select_streams', f'a:{self.audio_track}', '-show_entries', 'stream=sample_rate', '-of', 'default=noprint_wrappers=1:nokey=1', self.input_file ] result = subprocess.run(cmd, capture_output=True, text=True) return float(result.stdout.strip()) def _get_ffmpeg_command(self): cmd = [ 'ffmpeg', '-hide_banner', '-loglevel', 'error', ] if self.start_time is not None: cmd.extend(['-ss', str(self.start_time)]) if self.end_time is not None: cmd.extend(['-to', str(self.end_time)]) cmd.extend([ '-i', self.input_file, '-map', f'0:a:{self.audio_track}', '-ac', '1', # convert to mono '-f', 'f32le', # 32-bit float PCM '-' ]) return cmd def analyze(self, chunk_size=10): ffmpeg_cmd = self._get_ffmpeg_command() with subprocess.Popen(ffmpeg_cmd, stdout=subprocess.PIPE) as process: chunk_samples = int(chunk_size * self.sample_rate) bytes_per_sample = 4 # 32-bit float chunk_bytes = chunk_samples * bytes_per_sample current_time = self.start_time if self.start_time is not None else 0 while True: raw_data = process.stdout.read(chunk_bytes) if not raw_data: break samples = np.frombuffer(raw_data, dtype=np.float32) if len(samples) == 0: continue local_max_freq = self._analyze_chunk(samples) print(f"t={current_time:.1f}sec f={local_max_freq:.1f}KHz") if local_max_freq > self.global_max_freq: self.global_max_freq = local_max_freq self.global_max_time = current_time current_time += chunk_size def _analyze_chunk(self, samples): # Apply Hanning window window = np.hanning(len(samples)) windowed_samples = samples * window # Compute FFT fft = np.fft.rfft(windowed_samples) magnitudes = np.abs(fft) # Convert to dB eps = 1e-10 # avoid log(0) magnitudes_db = 20 * np.log10(magnitudes + eps) # Frequency bins freqs = np.fft.rfftfreq(len(samples), 1.0 / self.sample_rate) / 1000 # in KHz # Remove noise floor (-110dB) threshold = -110 valid_indices = magnitudes_db > threshold valid_freqs = freqs[valid_indices] valid_magnitudes = magnitudes_db[valid_indices] if len(valid_freqs) == 0: return 0 # Find frequency with maximum magnitude max_idx = np.argmax(valid_magnitudes) max_freq = valid_freqs[max_idx] return max_freq def main(): parser = argparse.ArgumentParser(description='Detect maximum frequency in audio file') parser.add_argument('input_file', help='Input audio file (m4a)') parser.add_argument('--ss', type=float, help='Start time in seconds') parser.add_argument('--to', type=float, help='End time in seconds') parser.add_argument('--track', type=int, default=0, help='Audio track ID (default: 0)') args = parser.parse_args() analyzer = AudioAnalyzer( input_file=args.input_file, audio_track=args.track, start_time=args.ss, end_time=args.to ) print(f"Analyzing audio file: {args.input_file}") print(f"Sample rate: {analyzer.sample_rate/1000:.1f} KHz") print(f"Audio track: {args.track}") if args.ss is not None: print(f"Start time: {args.ss} sec") if args.to is not None: print(f"End time: {args.to} sec") print("---") analyzer.analyze() print("---") print(f"Global maximum: t={analyzer.global_max_time:.1f}sec f={analyzer.global_max_freq:.1f}KHz") if analyzer.global_max_freq > 15: print("Quality: Fullband (high quality)") elif analyzer.global_max_freq > 5: print("Quality: Wideband (medium quality)") else: print("Quality: Narrowband (low quality)") if __name__ == '__main__': main() Similar question: How to find the max frequency at a certain db in a fft signal Continue reading...