1*9c5db199SXin Li#!/usr/bin/env python3 2*9c5db199SXin Li# Lint as: python2, python3 3*9c5db199SXin Li# Copyright 2016 The Chromium OS Authors. All rights reserved. 4*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 5*9c5db199SXin Li# found in the LICENSE file. 6*9c5db199SXin Li 7*9c5db199SXin Li"""Command line tool to analyze wave file and detect artifacts.""" 8*9c5db199SXin Li 9*9c5db199SXin Lifrom __future__ import absolute_import 10*9c5db199SXin Lifrom __future__ import division 11*9c5db199SXin Lifrom __future__ import print_function 12*9c5db199SXin Liimport argparse 13*9c5db199SXin Liimport collections 14*9c5db199SXin Liimport json 15*9c5db199SXin Liimport logging 16*9c5db199SXin Liimport numpy 17*9c5db199SXin Liimport pprint 18*9c5db199SXin Liimport subprocess 19*9c5db199SXin Liimport tempfile 20*9c5db199SXin Liimport wave 21*9c5db199SXin Lifrom six.moves import range 22*9c5db199SXin Li 23*9c5db199SXin Li# Normal autotest environment. 24*9c5db199SXin Litry: 25*9c5db199SXin Li import common 26*9c5db199SXin Li from autotest_lib.client.cros.audio import audio_analysis 27*9c5db199SXin Li from autotest_lib.client.cros.audio import audio_data 28*9c5db199SXin Li from autotest_lib.client.cros.audio import audio_quality_measurement 29*9c5db199SXin Li# Standalone execution without autotest environment. 30*9c5db199SXin Liexcept ImportError: 31*9c5db199SXin Li import audio_analysis 32*9c5db199SXin Li import audio_data 33*9c5db199SXin Li import audio_quality_measurement 34*9c5db199SXin Li 35*9c5db199SXin Li 36*9c5db199SXin Li# Holder for quality parameters used in audio_quality_measurement module. 37*9c5db199SXin LiQualityParams = collections.namedtuple('QualityParams', 38*9c5db199SXin Li ['block_size_secs', 39*9c5db199SXin Li 'frequency_error_threshold', 40*9c5db199SXin Li 'delay_amplitude_threshold', 41*9c5db199SXin Li 'noise_amplitude_threshold', 42*9c5db199SXin Li 'burst_amplitude_threshold']) 43*9c5db199SXin Li 44*9c5db199SXin Li 45*9c5db199SXin Lidef add_args(parser): 46*9c5db199SXin Li """Adds command line arguments.""" 47*9c5db199SXin Li parser.add_argument('filename', metavar='FILE', type=str, 48*9c5db199SXin Li help='The wav or raw file to check.' 49*9c5db199SXin Li 'The file format is determined by file extension.' 50*9c5db199SXin Li 'For raw format, user must also pass -b, -r, -c' 51*9c5db199SXin Li 'for bit width, rate, and number of channels.') 52*9c5db199SXin Li parser.add_argument('--debug', action='store_true', default=False, 53*9c5db199SXin Li help='Show debug message.') 54*9c5db199SXin Li parser.add_argument('--spectral-only', action='store_true', default=False, 55*9c5db199SXin Li help='Only do spectral analysis on each channel.') 56*9c5db199SXin Li parser.add_argument('--freqs', metavar='FREQ', type=float, 57*9c5db199SXin Li nargs='*', 58*9c5db199SXin Li help='Expected frequencies in the channels. ' 59*9c5db199SXin Li 'Frequencies are separated by space. ' 60*9c5db199SXin Li 'E.g.: --freqs 1000 2000. ' 61*9c5db199SXin Li 'It means only the first two ' 62*9c5db199SXin Li 'channels (1000Hz, 2000Hz) are to be checked. ' 63*9c5db199SXin Li 'Unwanted channels can be specified by 0. ' 64*9c5db199SXin Li 'E.g.: --freqs 1000 0 2000 0 3000. ' 65*9c5db199SXin Li 'It means only channe 0,2,4 are to be examined.') 66*9c5db199SXin Li parser.add_argument('--freq-threshold', metavar='FREQ_THRESHOLD', type=float, 67*9c5db199SXin Li default=5, 68*9c5db199SXin Li help='Frequency difference threshold in Hz. ' 69*9c5db199SXin Li 'Default is 5Hz') 70*9c5db199SXin Li parser.add_argument('--ignore-high-freq', metavar='HIGH_FREQ_THRESHOLD', 71*9c5db199SXin Li type=float, default=5000, 72*9c5db199SXin Li help='Frequency threshold in Hz to be ignored for ' 73*9c5db199SXin Li 'high frequency. Default is 5KHz') 74*9c5db199SXin Li parser.add_argument('--output-file', metavar='OUTPUT_FILE', type=str, 75*9c5db199SXin Li help='Output file to dump analysis result in JSON format') 76*9c5db199SXin Li parser.add_argument('-b', '--bit-width', metavar='BIT_WIDTH', type=int, 77*9c5db199SXin Li default=32, 78*9c5db199SXin Li help='For raw file. Bit width of a sample. ' 79*9c5db199SXin Li 'Assume sample format is little-endian signed int. ' 80*9c5db199SXin Li 'Default is 32') 81*9c5db199SXin Li parser.add_argument('-r', '--rate', metavar='RATE', type=int, 82*9c5db199SXin Li default=48000, 83*9c5db199SXin Li help='For raw file. Sampling rate. Default is 48000') 84*9c5db199SXin Li parser.add_argument('-c', '--channel', metavar='CHANNEL', type=int, 85*9c5db199SXin Li default=8, 86*9c5db199SXin Li help='For raw file. Number of channels. ' 87*9c5db199SXin Li 'Default is 8.') 88*9c5db199SXin Li 89*9c5db199SXin Li # Arguments for quality measurement customization. 90*9c5db199SXin Li parser.add_argument( 91*9c5db199SXin Li '--quality-block-size-secs', 92*9c5db199SXin Li metavar='BLOCK_SIZE_SECS', type=float, 93*9c5db199SXin Li default=audio_quality_measurement.DEFAULT_BLOCK_SIZE_SECS, 94*9c5db199SXin Li help='Block size for quality measurement. ' 95*9c5db199SXin Li 'Refer to audio_quality_measurement module for detail.') 96*9c5db199SXin Li parser.add_argument( 97*9c5db199SXin Li '--quality-frequency-error-threshold', 98*9c5db199SXin Li metavar='FREQ_ERR_THRESHOLD', type=float, 99*9c5db199SXin Li default=audio_quality_measurement.DEFAULT_FREQUENCY_ERROR, 100*9c5db199SXin Li help='Frequency error threshold for identifying sine wave' 101*9c5db199SXin Li 'in quality measurement. ' 102*9c5db199SXin Li 'Refer to audio_quality_measurement module for detail.') 103*9c5db199SXin Li parser.add_argument( 104*9c5db199SXin Li '--quality-delay-amplitude-threshold', 105*9c5db199SXin Li metavar='DELAY_AMPLITUDE_THRESHOLD', type=float, 106*9c5db199SXin Li default=audio_quality_measurement.DEFAULT_DELAY_AMPLITUDE_THRESHOLD, 107*9c5db199SXin Li help='Amplitude ratio threshold for identifying delay in sine wave' 108*9c5db199SXin Li 'in quality measurement. ' 109*9c5db199SXin Li 'Refer to audio_quality_measurement module for detail.') 110*9c5db199SXin Li parser.add_argument( 111*9c5db199SXin Li '--quality-noise-amplitude-threshold', 112*9c5db199SXin Li metavar='NOISE_AMPLITUDE_THRESHOLD', type=float, 113*9c5db199SXin Li default=audio_quality_measurement.DEFAULT_NOISE_AMPLITUDE_THRESHOLD, 114*9c5db199SXin Li help='Amplitude ratio threshold for identifying noise in sine wave' 115*9c5db199SXin Li 'in quality measurement. ' 116*9c5db199SXin Li 'Refer to audio_quality_measurement module for detail.') 117*9c5db199SXin Li parser.add_argument( 118*9c5db199SXin Li '--quality-burst-amplitude-threshold', 119*9c5db199SXin Li metavar='BURST_AMPLITUDE_THRESHOLD', type=float, 120*9c5db199SXin Li default=audio_quality_measurement.DEFAULT_BURST_AMPLITUDE_THRESHOLD, 121*9c5db199SXin Li help='Amplitude ratio threshold for identifying burst in sine wave' 122*9c5db199SXin Li 'in quality measurement. ' 123*9c5db199SXin Li 'Refer to audio_quality_measurement module for detail.') 124*9c5db199SXin Li 125*9c5db199SXin Li 126*9c5db199SXin Lidef parse_args(parser): 127*9c5db199SXin Li """Parses args.""" 128*9c5db199SXin Li args = parser.parse_args() 129*9c5db199SXin Li return args 130*9c5db199SXin Li 131*9c5db199SXin Li 132*9c5db199SXin Liclass WaveFileException(Exception): 133*9c5db199SXin Li """Error in WaveFile.""" 134*9c5db199SXin Li pass 135*9c5db199SXin Li 136*9c5db199SXin Li 137*9c5db199SXin Liclass WaveFormatExtensibleException(Exception): 138*9c5db199SXin Li """Wave file is in WAVE_FORMAT_EXTENSIBLE format which is not supported.""" 139*9c5db199SXin Li pass 140*9c5db199SXin Li 141*9c5db199SXin Li 142*9c5db199SXin Liclass WaveFile(object): 143*9c5db199SXin Li """Class which handles wave file reading. 144*9c5db199SXin Li 145*9c5db199SXin Li Properties: 146*9c5db199SXin Li raw_data: audio_data.AudioRawData object for data in wave file. 147*9c5db199SXin Li rate: sampling rate. 148*9c5db199SXin Li 149*9c5db199SXin Li """ 150*9c5db199SXin Li def __init__(self, filename): 151*9c5db199SXin Li """Inits a wave file. 152*9c5db199SXin Li 153*9c5db199SXin Li @param filename: file name of the wave file. 154*9c5db199SXin Li 155*9c5db199SXin Li """ 156*9c5db199SXin Li self.raw_data = None 157*9c5db199SXin Li self.rate = None 158*9c5db199SXin Li 159*9c5db199SXin Li self._wave_reader = None 160*9c5db199SXin Li self._n_channels = None 161*9c5db199SXin Li self._sample_width_bits = None 162*9c5db199SXin Li self._n_frames = None 163*9c5db199SXin Li self._binary = None 164*9c5db199SXin Li 165*9c5db199SXin Li try: 166*9c5db199SXin Li self._read_wave_file(filename) 167*9c5db199SXin Li except WaveFormatExtensibleException: 168*9c5db199SXin Li logging.warning( 169*9c5db199SXin Li 'WAVE_FORMAT_EXTENSIBLE is not supproted. ' 170*9c5db199SXin Li 'Try command "sox in.wav -t wavpcm out.wav" to convert ' 171*9c5db199SXin Li 'the file to WAVE_FORMAT_PCM format.') 172*9c5db199SXin Li self._convert_and_read_wav_file(filename) 173*9c5db199SXin Li 174*9c5db199SXin Li 175*9c5db199SXin Li def _convert_and_read_wav_file(self, filename): 176*9c5db199SXin Li """Converts the wav file and read it. 177*9c5db199SXin Li 178*9c5db199SXin Li Converts the file into WAVE_FORMAT_PCM format using sox command and 179*9c5db199SXin Li reads its content. 180*9c5db199SXin Li 181*9c5db199SXin Li @param filename: The wave file to be read. 182*9c5db199SXin Li 183*9c5db199SXin Li @raises: RuntimeError: sox is not installed. 184*9c5db199SXin Li 185*9c5db199SXin Li """ 186*9c5db199SXin Li # Checks if sox is installed. 187*9c5db199SXin Li try: 188*9c5db199SXin Li subprocess.check_output(['sox', '--version']) 189*9c5db199SXin Li except: 190*9c5db199SXin Li raise RuntimeError('sox command is not installed. ' 191*9c5db199SXin Li 'Try sudo apt-get install sox') 192*9c5db199SXin Li 193*9c5db199SXin Li with tempfile.NamedTemporaryFile(suffix='.wav') as converted_file: 194*9c5db199SXin Li command = ['sox', filename, '-t', 'wavpcm', converted_file.name] 195*9c5db199SXin Li logging.debug('Convert the file using sox: %s', command) 196*9c5db199SXin Li subprocess.check_call(command) 197*9c5db199SXin Li self._read_wave_file(converted_file.name) 198*9c5db199SXin Li 199*9c5db199SXin Li 200*9c5db199SXin Li def _read_wave_file(self, filename): 201*9c5db199SXin Li """Reads wave file header and samples. 202*9c5db199SXin Li 203*9c5db199SXin Li @param filename: The wave file to be read. 204*9c5db199SXin Li 205*9c5db199SXin Li @raises WaveFormatExtensibleException: Wave file is in 206*9c5db199SXin Li WAVE_FORMAT_EXTENSIBLE format. 207*9c5db199SXin Li @raises WaveFileException: Wave file format is not supported. 208*9c5db199SXin Li 209*9c5db199SXin Li """ 210*9c5db199SXin Li try: 211*9c5db199SXin Li self._wave_reader = wave.open(filename, 'r') 212*9c5db199SXin Li self._read_wave_header() 213*9c5db199SXin Li self._read_wave_binary() 214*9c5db199SXin Li except wave.Error as e: 215*9c5db199SXin Li if 'unknown format: 65534' in str(e): 216*9c5db199SXin Li raise WaveFormatExtensibleException() 217*9c5db199SXin Li else: 218*9c5db199SXin Li logging.exception('Unsupported wave format') 219*9c5db199SXin Li raise WaveFileException() 220*9c5db199SXin Li finally: 221*9c5db199SXin Li if self._wave_reader: 222*9c5db199SXin Li self._wave_reader.close() 223*9c5db199SXin Li 224*9c5db199SXin Li 225*9c5db199SXin Li def _read_wave_header(self): 226*9c5db199SXin Li """Reads wave file header. 227*9c5db199SXin Li 228*9c5db199SXin Li @raises WaveFileException: wave file is compressed. 229*9c5db199SXin Li 230*9c5db199SXin Li """ 231*9c5db199SXin Li # Header is a tuple of 232*9c5db199SXin Li # (nchannels, sampwidth, framerate, nframes, comptype, compname). 233*9c5db199SXin Li header = self._wave_reader.getparams() 234*9c5db199SXin Li logging.debug('Wave header: %s', header) 235*9c5db199SXin Li 236*9c5db199SXin Li self._n_channels = header[0] 237*9c5db199SXin Li self._sample_width_bits = header[1] * 8 238*9c5db199SXin Li self.rate = header[2] 239*9c5db199SXin Li self._n_frames = header[3] 240*9c5db199SXin Li comptype = header[4] 241*9c5db199SXin Li compname = header[5] 242*9c5db199SXin Li 243*9c5db199SXin Li if comptype != 'NONE' or compname != 'not compressed': 244*9c5db199SXin Li raise WaveFileException('Can not support compressed wav file.') 245*9c5db199SXin Li 246*9c5db199SXin Li 247*9c5db199SXin Li def _read_wave_binary(self): 248*9c5db199SXin Li """Reads in samples in wave file.""" 249*9c5db199SXin Li self._binary = self._wave_reader.readframes(self._n_frames) 250*9c5db199SXin Li format_str = 'S%d_LE' % self._sample_width_bits 251*9c5db199SXin Li self.raw_data = audio_data.AudioRawData( 252*9c5db199SXin Li binary=self._binary, 253*9c5db199SXin Li channel=self._n_channels, 254*9c5db199SXin Li sample_format=format_str) 255*9c5db199SXin Li 256*9c5db199SXin Li 257*9c5db199SXin Li def get_number_frames(self): 258*9c5db199SXin Li """Get the number of frames in the wave file.""" 259*9c5db199SXin Li return self._n_frames 260*9c5db199SXin Li 261*9c5db199SXin Li 262*9c5db199SXin Liclass QualityCheckerError(Exception): 263*9c5db199SXin Li """Error in QualityChecker.""" 264*9c5db199SXin Li pass 265*9c5db199SXin Li 266*9c5db199SXin Li 267*9c5db199SXin Liclass CompareFailure(QualityCheckerError): 268*9c5db199SXin Li """Exception when frequency comparison fails.""" 269*9c5db199SXin Li pass 270*9c5db199SXin Li 271*9c5db199SXin Li 272*9c5db199SXin Liclass QualityFailure(QualityCheckerError): 273*9c5db199SXin Li """Exception when quality check fails.""" 274*9c5db199SXin Li pass 275*9c5db199SXin Li 276*9c5db199SXin Li 277*9c5db199SXin Liclass QualityChecker(object): 278*9c5db199SXin Li """Quality checker controls the flow of checking quality of raw data.""" 279*9c5db199SXin Li def __init__(self, raw_data, rate): 280*9c5db199SXin Li """Inits a quality checker. 281*9c5db199SXin Li 282*9c5db199SXin Li @param raw_data: An audio_data.AudioRawData object. 283*9c5db199SXin Li @param rate: Sampling rate. 284*9c5db199SXin Li 285*9c5db199SXin Li """ 286*9c5db199SXin Li self._raw_data = raw_data 287*9c5db199SXin Li self._rate = rate 288*9c5db199SXin Li self._spectrals = [] 289*9c5db199SXin Li self._quality_result = [] 290*9c5db199SXin Li 291*9c5db199SXin Li 292*9c5db199SXin Li def do_spectral_analysis(self, ignore_high_freq, check_quality, 293*9c5db199SXin Li quality_params): 294*9c5db199SXin Li """Gets the spectral_analysis result. 295*9c5db199SXin Li 296*9c5db199SXin Li @param ignore_high_freq: Ignore high frequencies above this threshold. 297*9c5db199SXin Li @param check_quality: Check quality of each channel. 298*9c5db199SXin Li @param quality_params: A QualityParams object for quality measurement. 299*9c5db199SXin Li 300*9c5db199SXin Li """ 301*9c5db199SXin Li self.has_data() 302*9c5db199SXin Li for channel_idx in range(self._raw_data.channel): 303*9c5db199SXin Li signal = self._raw_data.channel_data[channel_idx] 304*9c5db199SXin Li max_abs = max(numpy.abs(signal)) 305*9c5db199SXin Li logging.debug('Channel %d max abs signal: %f', channel_idx, max_abs) 306*9c5db199SXin Li if max_abs == 0: 307*9c5db199SXin Li logging.info('No data on channel %d, skip this channel', 308*9c5db199SXin Li channel_idx) 309*9c5db199SXin Li continue 310*9c5db199SXin Li 311*9c5db199SXin Li saturate_value = audio_data.get_maximum_value_from_sample_format( 312*9c5db199SXin Li self._raw_data.sample_format) 313*9c5db199SXin Li normalized_signal = audio_analysis.normalize_signal( 314*9c5db199SXin Li signal, saturate_value) 315*9c5db199SXin Li logging.debug('saturate_value: %f', saturate_value) 316*9c5db199SXin Li logging.debug('max signal after normalized: %f', max(normalized_signal)) 317*9c5db199SXin Li spectral = audio_analysis.spectral_analysis( 318*9c5db199SXin Li normalized_signal, self._rate) 319*9c5db199SXin Li 320*9c5db199SXin Li logging.debug('Channel %d spectral:\n%s', channel_idx, 321*9c5db199SXin Li pprint.pformat(spectral)) 322*9c5db199SXin Li 323*9c5db199SXin Li # Ignore high frequencies above the threshold. 324*9c5db199SXin Li spectral = [(f, c) for (f, c) in spectral if f < ignore_high_freq] 325*9c5db199SXin Li 326*9c5db199SXin Li logging.info('Channel %d spectral after ignoring high frequencies ' 327*9c5db199SXin Li 'above %f:\n%s', channel_idx, ignore_high_freq, 328*9c5db199SXin Li pprint.pformat(spectral)) 329*9c5db199SXin Li 330*9c5db199SXin Li if check_quality: 331*9c5db199SXin Li quality = audio_quality_measurement.quality_measurement( 332*9c5db199SXin Li signal=normalized_signal, 333*9c5db199SXin Li rate=self._rate, 334*9c5db199SXin Li dominant_frequency=spectral[0][0], 335*9c5db199SXin Li block_size_secs=quality_params.block_size_secs, 336*9c5db199SXin Li frequency_error_threshold=quality_params.frequency_error_threshold, 337*9c5db199SXin Li delay_amplitude_threshold=quality_params.delay_amplitude_threshold, 338*9c5db199SXin Li noise_amplitude_threshold=quality_params.noise_amplitude_threshold, 339*9c5db199SXin Li burst_amplitude_threshold=quality_params.burst_amplitude_threshold) 340*9c5db199SXin Li 341*9c5db199SXin Li logging.debug('Channel %d quality:\n%s', channel_idx, 342*9c5db199SXin Li pprint.pformat(quality)) 343*9c5db199SXin Li self._quality_result.append(quality) 344*9c5db199SXin Li 345*9c5db199SXin Li self._spectrals.append(spectral) 346*9c5db199SXin Li 347*9c5db199SXin Li 348*9c5db199SXin Li def has_data(self): 349*9c5db199SXin Li """Checks if data has been set. 350*9c5db199SXin Li 351*9c5db199SXin Li @raises QualityCheckerError: if data or rate is not set yet. 352*9c5db199SXin Li 353*9c5db199SXin Li """ 354*9c5db199SXin Li if not self._raw_data or not self._rate: 355*9c5db199SXin Li raise QualityCheckerError('Data and rate is not set yet') 356*9c5db199SXin Li 357*9c5db199SXin Li 358*9c5db199SXin Li def check_freqs(self, expected_freqs, freq_threshold): 359*9c5db199SXin Li """Checks the dominant frequencies in the channels. 360*9c5db199SXin Li 361*9c5db199SXin Li @param expected_freq: A list of frequencies. If frequency is 0, it 362*9c5db199SXin Li means this channel should be ignored. 363*9c5db199SXin Li @param freq_threshold: The difference threshold to compare two 364*9c5db199SXin Li frequencies. 365*9c5db199SXin Li 366*9c5db199SXin Li """ 367*9c5db199SXin Li logging.debug('expected_freqs: %s', expected_freqs) 368*9c5db199SXin Li for idx, expected_freq in enumerate(expected_freqs): 369*9c5db199SXin Li if expected_freq == 0: 370*9c5db199SXin Li continue 371*9c5db199SXin Li if not self._spectrals[idx]: 372*9c5db199SXin Li raise CompareFailure( 373*9c5db199SXin Li 'Failed at channel %d: no dominant frequency' % idx) 374*9c5db199SXin Li dominant_freq = self._spectrals[idx][0][0] 375*9c5db199SXin Li if abs(dominant_freq - expected_freq) > freq_threshold: 376*9c5db199SXin Li raise CompareFailure( 377*9c5db199SXin Li 'Failed at channel %d: %f is too far away from %f' % ( 378*9c5db199SXin Li idx, dominant_freq, expected_freq)) 379*9c5db199SXin Li 380*9c5db199SXin Li 381*9c5db199SXin Li def check_quality(self): 382*9c5db199SXin Li """Checks the quality measurement results on each channel. 383*9c5db199SXin Li 384*9c5db199SXin Li @raises: QualityFailure when there is artifact. 385*9c5db199SXin Li 386*9c5db199SXin Li """ 387*9c5db199SXin Li error_msgs = [] 388*9c5db199SXin Li 389*9c5db199SXin Li for idx, quality_res in enumerate(self._quality_result): 390*9c5db199SXin Li artifacts = quality_res['artifacts'] 391*9c5db199SXin Li if artifacts['noise_before_playback']: 392*9c5db199SXin Li error_msgs.append( 393*9c5db199SXin Li 'Found noise before playback: %s' % ( 394*9c5db199SXin Li artifacts['noise_before_playback'])) 395*9c5db199SXin Li if artifacts['noise_after_playback']: 396*9c5db199SXin Li error_msgs.append( 397*9c5db199SXin Li 'Found noise after playback: %s' % ( 398*9c5db199SXin Li artifacts['noise_after_playback'])) 399*9c5db199SXin Li if artifacts['delay_during_playback']: 400*9c5db199SXin Li error_msgs.append( 401*9c5db199SXin Li 'Found delay during playback: %s' % ( 402*9c5db199SXin Li artifacts['delay_during_playback'])) 403*9c5db199SXin Li if artifacts['burst_during_playback']: 404*9c5db199SXin Li error_msgs.append( 405*9c5db199SXin Li 'Found burst during playback: %s' % ( 406*9c5db199SXin Li artifacts['burst_during_playback'])) 407*9c5db199SXin Li if error_msgs: 408*9c5db199SXin Li raise QualityFailure('Found bad quality: %s', '\n'.join(error_msgs)) 409*9c5db199SXin Li 410*9c5db199SXin Li 411*9c5db199SXin Li def dump(self, output_file): 412*9c5db199SXin Li """Dumps the result into a file in json format. 413*9c5db199SXin Li 414*9c5db199SXin Li @param output_file: A file path to dump spectral and quality 415*9c5db199SXin Li measurement result of each channel. 416*9c5db199SXin Li 417*9c5db199SXin Li """ 418*9c5db199SXin Li dump_dict = { 419*9c5db199SXin Li 'spectrals': self._spectrals, 420*9c5db199SXin Li 'quality_result': self._quality_result 421*9c5db199SXin Li } 422*9c5db199SXin Li with open(output_file, 'w') as f: 423*9c5db199SXin Li json.dump(dump_dict, f) 424*9c5db199SXin Li 425*9c5db199SXin Li 426*9c5db199SXin Liclass CheckQualityError(Exception): 427*9c5db199SXin Li """Error in check_quality main function.""" 428*9c5db199SXin Li pass 429*9c5db199SXin Li 430*9c5db199SXin Li 431*9c5db199SXin Lidef read_audio_file(args): 432*9c5db199SXin Li """Reads audio file. 433*9c5db199SXin Li 434*9c5db199SXin Li @param args: The namespace parsed from command line arguments. 435*9c5db199SXin Li 436*9c5db199SXin Li @returns: A tuple (raw_data, rate) where raw_data is 437*9c5db199SXin Li audio_data.AudioRawData, rate is sampling rate. 438*9c5db199SXin Li 439*9c5db199SXin Li """ 440*9c5db199SXin Li if args.filename.endswith('.wav'): 441*9c5db199SXin Li wavefile = WaveFile(args.filename) 442*9c5db199SXin Li raw_data = wavefile.raw_data 443*9c5db199SXin Li rate = wavefile.rate 444*9c5db199SXin Li elif args.filename.endswith('.raw'): 445*9c5db199SXin Li binary = None 446*9c5db199SXin Li with open(args.filename, 'rb') as f: 447*9c5db199SXin Li binary = f.read() 448*9c5db199SXin Li 449*9c5db199SXin Li raw_data = audio_data.AudioRawData( 450*9c5db199SXin Li binary=binary, 451*9c5db199SXin Li channel=args.channel, 452*9c5db199SXin Li sample_format='S%d_LE' % args.bit_width) 453*9c5db199SXin Li rate = args.rate 454*9c5db199SXin Li else: 455*9c5db199SXin Li raise CheckQualityError( 456*9c5db199SXin Li 'File format for %s is not supported' % args.filename) 457*9c5db199SXin Li 458*9c5db199SXin Li return raw_data, rate 459*9c5db199SXin Li 460*9c5db199SXin Li 461*9c5db199SXin Lidef get_quality_params(args): 462*9c5db199SXin Li """Gets quality parameters in arguments. 463*9c5db199SXin Li 464*9c5db199SXin Li @param args: The namespace parsed from command line arguments. 465*9c5db199SXin Li 466*9c5db199SXin Li @returns: A QualityParams object. 467*9c5db199SXin Li 468*9c5db199SXin Li """ 469*9c5db199SXin Li quality_params = QualityParams( 470*9c5db199SXin Li block_size_secs=args.quality_block_size_secs, 471*9c5db199SXin Li frequency_error_threshold=args.quality_frequency_error_threshold, 472*9c5db199SXin Li delay_amplitude_threshold=args.quality_delay_amplitude_threshold, 473*9c5db199SXin Li noise_amplitude_threshold=args.quality_noise_amplitude_threshold, 474*9c5db199SXin Li burst_amplitude_threshold=args.quality_burst_amplitude_threshold) 475*9c5db199SXin Li 476*9c5db199SXin Li return quality_params 477*9c5db199SXin Li 478*9c5db199SXin Li 479*9c5db199SXin Liif __name__ == "__main__": 480*9c5db199SXin Li parser = argparse.ArgumentParser( 481*9c5db199SXin Li description='Check signal quality of a wave file. Each channel should' 482*9c5db199SXin Li ' either be all zeros, or sine wave of a fixed frequency.') 483*9c5db199SXin Li add_args(parser) 484*9c5db199SXin Li args = parse_args(parser) 485*9c5db199SXin Li 486*9c5db199SXin Li level = logging.DEBUG if args.debug else logging.INFO 487*9c5db199SXin Li format = '%(asctime)-15s:%(levelname)s:%(pathname)s:%(lineno)d: %(message)s' 488*9c5db199SXin Li logging.basicConfig(format=format, level=level) 489*9c5db199SXin Li 490*9c5db199SXin Li raw_data, rate = read_audio_file(args) 491*9c5db199SXin Li 492*9c5db199SXin Li checker = QualityChecker(raw_data, rate) 493*9c5db199SXin Li 494*9c5db199SXin Li quality_params = get_quality_params(args) 495*9c5db199SXin Li 496*9c5db199SXin Li checker.do_spectral_analysis(ignore_high_freq=args.ignore_high_freq, 497*9c5db199SXin Li check_quality=(not args.spectral_only), 498*9c5db199SXin Li quality_params=quality_params) 499*9c5db199SXin Li 500*9c5db199SXin Li if args.output_file: 501*9c5db199SXin Li checker.dump(args.output_file) 502*9c5db199SXin Li 503*9c5db199SXin Li if args.freqs: 504*9c5db199SXin Li checker.check_freqs(args.freqs, args.freq_threshold) 505*9c5db199SXin Li 506*9c5db199SXin Li if not args.spectral_only: 507*9c5db199SXin Li checker.check_quality() 508