def run_once(self): """Entry point of this test.""" self.chrome.browser.platform.SetHTTPServerDirectories(self.bindir) video_url = self.chrome.browser.platform.http_server.UrlOf( os.path.join(self.bindir, 'youtube.html')) logging.info('Playing back youtube media file %s.', video_url) noise_file = os.path.join(self.resultsdir, "noise.wav") recorded_file = os.path.join(self.resultsdir, "recorded.wav") loopback_file = os.path.join(self.resultsdir, "loopback.wav") # Record a sample of "silence" to use as a noise profile. cras_utils.capture(noise_file, duration=3) # Play a video and record the audio output self.play_video(self.chrome.browser.tabs[0], video_url) p1 = cmd_utils.popen( cras_utils.capture_cmd(recorded_file, duration=TEST_DURATION)) p2 = cmd_utils.popen( cras_utils.loopback_cmd(loopback_file, duration=TEST_DURATION)) cmd_utils.wait_and_check_returncode(p1, p2) # See if we recorded something loopback_stats = [ audio_helper.get_channel_sox_stat(loopback_file, i) for i in (1, 2) ] logging.info('loopback stats: %s', [str(s) for s in loopback_stats]) rms_value = audio_helper.reduce_noise_and_get_rms( recorded_file, noise_file)[0] self.write_perf_keyval({'rms_value': rms_value})
def reduce_noise_and_get_rms(input_audio, noise_file, channels=1, bits=16, rate=48000): """Reduces noise in the input audio by the given noise file and then gets the RMS values of all channels of the input audio. @param input_audio: The input audio file to be analyzed. @param noise_file: The noise file used to reduce noise in the input audio. @param channels: The number of channels in the input audio. @param bits: The number of bits of each audio sample. @param rate: The sampling rate. """ with tempfile.NamedTemporaryFile() as reduced_file: p1 = cmd_utils.popen(sox_utils.noise_profile_cmd(noise_file, '-', channels=channels, bits=bits, rate=rate), stdout=subprocess.PIPE) p2 = cmd_utils.popen(sox_utils.noise_reduce_cmd(input_audio, reduced_file.name, '-', channels=channels, bits=bits, rate=rate), stdin=p1.stdout) cmd_utils.wait_and_check_returncode(p1, p2) return get_rms(reduced_file.name, channels, bits, rate)
def rms_test(self, tab, media_file, noiseprof_file, test_duration): logging.info('rms test on media file %s.', media_file) recorded_file = os.path.join(self.resultsdir, 'recorded.wav') loopback_file = os.path.join(self.resultsdir, 'loopback.wav') # Plays the media_file in the browser. self.play_media(tab, media_file) # Record the audio output and also the CRAS loopback output. p1 = cmd_utils.popen( cras_utils.capture_cmd(recorded_file, duration=test_duration)) p2 = cmd_utils.popen( cras_utils.loopback_cmd(loopback_file, duration=test_duration)) cmd_utils.wait_and_check_returncode(p1, p2) # See if we recorded something. # We captured two channels of audio in the CRAS loopback. # The RMS values are for debugging only. loopback_stats = [ audio_helper.get_channel_sox_stat(loopback_file, i) for i in (1, 2) ] logging.info('loopback stats: %s', [str(s) for s in loopback_stats]) reduced_file = tempfile.NamedTemporaryFile() sox_utils.noise_reduce(recorded_file, reduced_file.name, noiseprof_file) rms = audio_helper.get_rms(reduced_file.name)[0] self._rms_values['%s_rms_value' % media_file.replace('.', '_')] = rms # Make sure the audio can be played to the end. self.wait_player_end(tab)
def get_channel_sox_stat( input_audio, channel_index, channels=2, bits=16, rate=48000): """Gets the sox stat info of the selected channel in the input audio file. @param input_audio: The input audio file to be analyzed. @param channel_index: The index of the channel to be analyzed. (1 for the first channel). @param channels: The number of channels in the input audio. @param bits: The number of bits of each audio sample. @param rate: The sampling rate. """ if channel_index <= 0 or channel_index > channels: raise ValueError('incorrect channel_indexi: %d' % channel_index) if channels == 1: return sox_utils.get_stat( input_audio, channels=channels, bits=bits, rate=rate) p1 = cmd_utils.popen( sox_utils.extract_channel_cmd( input_audio, '-', channel_index, channels=channels, bits=bits, rate=rate), stdout=subprocess.PIPE) p2 = cmd_utils.popen( sox_utils.stat_cmd('-', channels=1, bits=bits, rate=rate), stdin=p1.stdout, stderr=subprocess.PIPE) stat_output = p2.stderr.read() cmd_utils.wait_and_check_returncode(p1, p2) return sox_utils.parse_stat_output(stat_output)
def play_sine_tone(self, frequence, rate): """Plays a sine tone by cras and returns the processes. Args: frequence: the frequence of the sine wave. rate: the sampling rate. """ p1 = cmd_utils.popen(sox_utils.generate_sine_tone_cmd( filename='-', rate=rate, frequence=frequence, gain=-6), stdout=cmd_utils.PIPE) p2 = cmd_utils.popen(cras_utils.playback_cmd(playback_file='-', rate=rate), stdin=p1.stdout) return [p1, p2]
def get_default_record_device(): '''Gets the first record device. Returns the first record device or None if it fails to find one. ''' card_id = get_first_soundcard_with_control(cname='Mic Jack', scname='Mic') if card_id is None: return None # Get first device id of this card. cmd = ARECORD_PATH + ' -l' p = cmd_utils.popen(shlex.split(cmd), stdout=cmd_utils.PIPE) output, _ = p.communicate() if p.wait() != 0: raise RuntimeError('arecord -l command failed') dev_id = 0 for line in output.splitlines(): if 'card %d:' % card_id in line: match = DEV_NUM_RE.search(line) if match: dev_id = int(match.group(1)) break return 'plughw:%d,%d' % (card_id, dev_id)
def _get_soundcard_scontrols(card_id): '''Gets the simple mixer controls for a soundcard. @param card_id: Soundcard ID. @raise RuntimeError: If failed to get soundcard simple mixer controls. Simple mixer controls for a soundcard is retrieved by 'amixer scontrols' command. amixer output format: Simple mixer control 'Master',0 Simple mixer control 'Headphone',0 Simple mixer control 'Speaker',0 Simple mixer control 'PCM',0 Simple controls are parsed from the output and returned in a set. ''' cmd = AMIXER_PATH + ' -c %d scontrols' % card_id p = cmd_utils.popen(shlex.split(cmd), stdout=cmd_utils.PIPE) output, _ = p.communicate() if p.wait() != 0: raise RuntimeError('amixer command failed') scontrols = set() for line in output.splitlines(): match = SCONTROL_NAME_RE.findall(line) if match: scontrols.add(match[0]) return scontrols
def run_once(self): """Entry point of this test.""" # Sine wav file lasts 5 seconds wav_path = os.path.join(self.bindir, '5SEC.wav') data_format = dict(file_type='wav', sample_format='S16_LE', channel=2, rate=48000) wav_file = audio_test_data.GenerateAudioTestData( path=wav_path, data_format=data_format, duration_secs=5, frequencies=[440, 440], volume_scale=0.9) recorded_file = os.path.join(self.resultsdir, 'hw_recorded.wav') # Get selected input and output devices. cras_input = cras_utils.get_selected_input_device_name() cras_output = cras_utils.get_selected_output_device_name() logging.debug("Selected input=%s, output=%s", cras_input, cras_output) if cras_input is None: raise error.TestFail("Fail to get selected input device.") if cras_output is None: raise error.TestFail("Fail to get selected output device.") alsa_input = alsa_utils.convert_device_name(cras_input) alsa_output = alsa_utils.convert_device_name(cras_output) (output_type, input_type) = cras_utils.get_selected_node_types() if not any(t in input_type for t in ['MIC', 'USB']): raise error.TestFail("Wrong input type=%s", input_type) if not any(t in output_type for t in ['HEADPHONE', 'USB']): raise error.TestFail("Wrong output type=%s", output_type) # Stop CRAS to make sure the audio device won't be occupied. utils.stop_service('cras', ignore_status=True) p = cmd_utils.popen( alsa_utils.playback_cmd(wav_file.path, device=alsa_output)) try: # Wait one second to make sure the playback has been started. time.sleep(1) alsa_utils.record(recorded_file, duration=TEST_DURATION, device=alsa_input) # Make sure the audio is still playing. if p.poll() != None: raise error.TestError('playback stopped') finally: cmd_utils.kill_or_log_returncode(p) wav_file.delete() # Restart CRAS. utils.start_service('cras', ignore_status=True) rms_value = audio_helper.get_rms(recorded_file)[0] self.write_perf_keyval({'rms_value': rms_value})
def run_once(self): """Entry point of this test.""" # Multitone wav file lasts 10 seconds wav_path = os.path.join(self.bindir, '10SEC.wav') noise_file = os.path.join(self.resultsdir, 'cras_noise.wav') recorded_file = os.path.join(self.resultsdir, 'cras_recorded.wav') # Record a sample of "silence" to use as a noise profile. cras_utils.capture(noise_file, duration=1) self.wait_for_active_stream_count(0) p = cmd_utils.popen(cras_utils.playback_cmd(wav_path)) try: self.wait_for_active_stream_count(1) cras_utils.capture(recorded_file, duration=TEST_DURATION) # Make sure the audio is still playing. if p.poll() != None: raise error.TestError('playback stopped') finally: cmd_utils.kill_or_log_returncode(p) rms_value = audio_helper.reduce_noise_and_get_rms( recorded_file, noise_file)[0] self.write_perf_keyval({'rms_value': rms_value})
def _get_soundcard_controls(card_id): '''Gets the controls for a soundcard. @param card_id: Soundcard ID. @raise RuntimeError: If failed to get soundcard controls. Controls for a soundcard is retrieved by 'amixer controls' command. amixer output format: numid=32,iface=CARD,name='Front Headphone Jack' numid=28,iface=CARD,name='Front Mic Jack' numid=1,iface=CARD,name='HDMI/DP,pcm=3 Jack' numid=8,iface=CARD,name='HDMI/DP,pcm=7 Jack' Controls with iface=CARD are parsed from the output and returned in a set. ''' cmd = AMIXER_PATH + ' -c %d controls' % card_id p = cmd_utils.popen(shlex.split(cmd), stdout=cmd_utils.PIPE) output, _ = p.communicate() if p.wait() != 0: raise RuntimeError('amixer command failed') controls = set() for line in output.splitlines(): if not 'iface=CARD' in line: continue match = CONTROL_NAME_RE.search(line) if match: controls.add(match.group(1)) return controls
def run_once(self): """Entry point of this test.""" # Generate sine raw file that lasts 5 seconds. raw_path = os.path.join(self.bindir, '5SEC.raw') data_format = dict(file_type='raw', sample_format='S16_LE', channel=2, rate=48000) raw_file = audio_test_data.GenerateAudioTestData( path=raw_path, data_format=data_format, duration_secs=5, frequencies=[440, 440], volume_scale=0.9) recorded_file = os.path.join(self.resultsdir, 'cras_recorded.raw') self.wait_for_active_stream_count(0) p = cmd_utils.popen(cras_utils.playback_cmd(raw_file.path)) try: self.wait_for_active_stream_count(1) cras_utils.capture(recorded_file, duration=TEST_DURATION) # Make sure the audio is still playing. if p.poll() != None: raise error.TestError('playback stopped') finally: cmd_utils.kill_or_log_returncode(p) raw_file.delete() rms_value = audio_helper.get_rms(recorded_file)[0] self.write_perf_keyval({'rms_value': rms_value})
def _test_audio_disabled(self, policy_value): """ Verify the AudioOutputAllowed policy behaves as expected. Generate and play a sample audio file. When enabled, the difference between the muted and unmuted RMS should be greater than 0.75. When disabled, the RMS difference should be less than 0.05. @param policy_value: policy value for this case. @raises error.TestFail: In the case where the audio behavior does not match the policy value. """ audio_allowed = policy_value or policy_value is None RAW_FILE = os.path.join(self.enterprise_dir, 'test_audio.raw') noise_file = os.path.join(self.resultsdir, 'noise.wav') recorded_file = os.path.join(self.resultsdir, 'recorded-cras.raw') recorded_rms = [] # Record a sample of silence to use as a noise profile. cras_utils.capture(noise_file, duration=2) logging.info('NOISE: %s', audio_helper.get_rms(noise_file)) # Get two RMS samples: one when muted and one when not for muted in [False, True]: cras_utils.set_system_mute(muted) # Play the audio file and capture the output self.wait_for_active_stream_count(0) p = cmd_utils.popen(cras_utils.playback_cmd(RAW_FILE)) try: self.wait_for_active_stream_count(1) cras_utils.capture(recorded_file, duration=self.SAMPLE_DURATION) if p.poll() is not None: raise error.TestError('Audio playback stopped prematurely') finally: cmd_utils.kill_or_log_returncode(p) rms_value = audio_helper.reduce_noise_and_get_rms( recorded_file, noise_file)[0] logging.info('muted (%s): %s' % (muted, rms_value)) recorded_rms.append(rms_value) rms_diff = recorded_rms[0] - recorded_rms[1] self.write_perf_keyval({'rms_diff': rms_diff}) if audio_allowed: if rms_diff < 0.4: raise error.TestFail('RMS difference not large enough between ' 'mute and ummute: %s' % rms_diff) else: if abs(rms_diff) > 0.05: raise error.TestFail('RMS difference too wide while audio ' 'disabled: %s' % rms_diff)
def run_in_parallel(self, *commands): env = os.environ.copy() # To clear the temparory files created by vea_unittest. env['TMPDIR'] = self.tmpdir if not utils.is_freon(): env['DISPLAY'] = ':0' env['XAUTHORITY'] = '/home/chronos/.Xauthority' return map(lambda c: cmd_utils.popen(c, env=env), commands)
def _get_sysdefault(cmd): p = cmd_utils.popen(shlex.split(cmd), stdout=cmd_utils.PIPE) output, _ = p.communicate() if p.wait() != 0: raise RuntimeError('%s failed' % cmd) for line in output.splitlines(): if 'sysdefault' in line: return line return None
def get_stat(*args, **kargs): """A helper function to execute the stat_cmd. It returns the statistical information (in text) read from the standard error. """ p = cmd_utils.popen(stat_cmd(*args, **kargs), stderr=cmd_utils.PIPE) #The output is read from the stderr instead of stdout stat_output = p.stderr.read() cmd_utils.wait_and_check_returncode(p) return parse_stat_output(stat_output)
def playback(blocking=True, *args, **kargs): """A helper function to execute the playback_cmd. @param blocking: Blocks this call until playback finishes. @param args: args passed to playback_cmd. @param kargs: kargs passed to playback_cmd. @returns: The process running the playback command. Note that if the blocking parameter is true, this will return a finished process. """ process = cmd_utils.popen(playback_cmd(*args, **kargs)) if blocking: cmd_utils.wait_and_check_returncode(process) return process
def get_record_card_name(card_idx): '''Gets the recording sound card name for given card idx. Returns the card name inside the square brackets of arecord output lines. ''' card_name_re = re.compile(r'card %d: .*?\[(.*?)\]' % card_idx) cmd = [ARECORD_PATH, '-l'] p = cmd_utils.popen(cmd, stdout=subprocess.PIPE) output, _ = p.communicate() if p.wait() != 0: raise RuntimeError('arecord -l command failed') for line in output.splitlines(): match = card_name_re.search(line) if match: return match.group(1) return None
def mixer_cmd(card_id, cmd): '''Executes amixer command. @param card_id: Soundcard ID. @param cmd: Amixer command to execute. @raise RuntimeError: If failed to execute command. Amixer command like "set PCM 2dB+" with card_id 1 will be executed as: amixer -c 1 set PCM 2dB+ Command output will be returned if any. ''' cmd = AMIXER_PATH + ' -c %d ' % card_id + cmd p = cmd_utils.popen(shlex.split(cmd), stdout=cmd_utils.PIPE) output, _ = p.communicate() if p.wait() != 0: raise RuntimeError('amixer command failed') return output
def mixer_cmd(card_id, cmd): '''Executes amixer command. @param card_id: Soundcard ID. @param cmd: Amixer command to execute. @raise RuntimeError: If failed to execute command. Amixer command like ['set', 'PCM', '2dB+'] with card_id 1 will be executed as: amixer -c 1 set PCM 2dB+ Command output will be returned if any. ''' cmd = [AMIXER_PATH, '-c', str(card_id)] + cmd p = cmd_utils.popen(cmd, stdout=subprocess.PIPE) output, _ = p.communicate() if p.wait() != 0: raise RuntimeError('amixer command failed') return output
def start(self, data_format): """Starts recording. Starts recording subprocess. It can be stopped by calling stop(). @param data_format: A dict containing: file_type: 'raw'. sample_format: 'S16_LE' for 16-bit signed integer in little-endian. channel: channel number. rate: sampling rate. @raises: RecorderError: If recording subprocess is terminated unexpectedly. """ self._capture_subprocess = cmd_utils.popen( cras_utils.capture_cmd(capture_file=self.file_path, duration=None, channels=data_format['channel'], rate=data_format['rate']))
def run_in_parallel(self, *commands): env = os.environ.copy() # To clear the temparory files created by vea_unittest. env['TMPDIR'] = self.tmpdir return map(lambda c: cmd_utils.popen(c, env=env), commands)