1*9c5db199SXin Li# Copyright 2021 The Chromium OS Authors. All rights reserved. 2*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 3*9c5db199SXin Li# found in the LICENSE file. 4*9c5db199SXin Li 5*9c5db199SXin Liimport os 6*9c5db199SXin Liimport re 7*9c5db199SXin Liimport subprocess 8*9c5db199SXin Li 9*9c5db199SXin Lifrom autotest_lib.client.common_lib import error 10*9c5db199SXin Lifrom autotest_lib.client.cros.bluetooth.bluetooth_audio_test_data import ( 11*9c5db199SXin Li VISQOL_PATH, VISQOL_SIMILARITY_MODEL) 12*9c5db199SXin Li 13*9c5db199SXin Li 14*9c5db199SXin Lidef parse_visqol_output(stdout, stderr, log_dir): 15*9c5db199SXin Li """ 16*9c5db199SXin Li Parses stdout and stderr string from VISQOL output and parse into 17*9c5db199SXin Li a float score. 18*9c5db199SXin Li 19*9c5db199SXin Li On error, stderr will contain the error message, otherwise will be None. 20*9c5db199SXin Li On success, stdout will be a string, first line will be 21*9c5db199SXin Li VISQOL version, followed by indication of speech mode. Followed by 22*9c5db199SXin Li paths to reference and degraded file, and a float MOS-LQO score, which 23*9c5db199SXin Li is what we're interested in. Followed by more detailed charts about 24*9c5db199SXin Li specific scoring by segments of the files. Stdout is None on error. 25*9c5db199SXin Li 26*9c5db199SXin Li @param stdout: The stdout bytes from commandline output of VISQOL. 27*9c5db199SXin Li @param stderr: The stderr bytes from commandline output of VISQOL. 28*9c5db199SXin Li @param log_dir: Directory path for storing VISQOL log. 29*9c5db199SXin Li 30*9c5db199SXin Li @returns: A tuple of a float score and string representation of the 31*9c5db199SXin Li srderr or None if there was no error. 32*9c5db199SXin Li """ 33*9c5db199SXin Li stdout = '' if stdout is None else stdout.decode('utf-8') 34*9c5db199SXin Li stderr = '' if stderr is None else stderr.decode('utf-8') 35*9c5db199SXin Li 36*9c5db199SXin Li # Log verbose VISQOL output: 37*9c5db199SXin Li log_file = os.path.join(log_dir, 'VISQOL_LOG.txt') 38*9c5db199SXin Li with open(log_file, 'a+') as f: 39*9c5db199SXin Li f.write('String Error:\n{}\n'.format(stderr)) 40*9c5db199SXin Li f.write('String Out:\n{}\n'.format(stdout)) 41*9c5db199SXin Li 42*9c5db199SXin Li # pattern matches first float or int after 'MOS-LQO:' in stdout, 43*9c5db199SXin Li # e.g. it would match the line 'MOS-LQO 2.3' in the stdout 44*9c5db199SXin Li score_pattern = re.compile(r'.*MOS-LQO:\s*(\d+.?\d*)') 45*9c5db199SXin Li score_search = re.search(score_pattern, stdout) 46*9c5db199SXin Li 47*9c5db199SXin Li # re.search returns None if no pattern match found, otherwise the score 48*9c5db199SXin Li # would be in the match object's group 1 matches just the float score 49*9c5db199SXin Li score = float(score_search.group(1)) if score_search else -1.0 50*9c5db199SXin Li return stderr, score 51*9c5db199SXin Li 52*9c5db199SXin Li 53*9c5db199SXin Lidef get_visqol_score(ref_file, 54*9c5db199SXin Li deg_file, 55*9c5db199SXin Li log_dir, 56*9c5db199SXin Li speech_mode=True, 57*9c5db199SXin Li verbose=True): 58*9c5db199SXin Li """ 59*9c5db199SXin Li Runs VISQOL using the subprocess library on the provided reference file 60*9c5db199SXin Li and degraded file and returns the VISQOL score. 61*9c5db199SXin Li 62*9c5db199SXin Li Notes that the difference between the duration of reference and degraded 63*9c5db199SXin Li audio must be smaller than 1.0 second. 64*9c5db199SXin Li 65*9c5db199SXin Li @param ref_file: File path to the reference wav file. 66*9c5db199SXin Li @param deg_file: File path to the degraded wav file. 67*9c5db199SXin Li @param log_dir: Directory path for storing VISQOL log. 68*9c5db199SXin Li @param speech_mode: [Optional] Defaults to True, accepts 16k sample 69*9c5db199SXin Li rate files and ignores frequencies > 8kHz for scoring. 70*9c5db199SXin Li @param verbose: [Optional] Defaults to True, outputs more details. 71*9c5db199SXin Li 72*9c5db199SXin Li @returns: A float score for the tested file. 73*9c5db199SXin Li """ 74*9c5db199SXin Li visqol_cmd = [VISQOL_PATH] 75*9c5db199SXin Li visqol_cmd += ['--reference_file', ref_file] 76*9c5db199SXin Li visqol_cmd += ['--degraded_file', deg_file] 77*9c5db199SXin Li visqol_cmd += ['--similarity_to_quality_model', VISQOL_SIMILARITY_MODEL] 78*9c5db199SXin Li 79*9c5db199SXin Li if speech_mode: 80*9c5db199SXin Li visqol_cmd.append('--use_speech_mode') 81*9c5db199SXin Li if verbose: 82*9c5db199SXin Li visqol_cmd.append('--verbose') 83*9c5db199SXin Li 84*9c5db199SXin Li visqol_process = subprocess.Popen(visqol_cmd, 85*9c5db199SXin Li stdout=subprocess.PIPE, 86*9c5db199SXin Li stderr=subprocess.PIPE) 87*9c5db199SXin Li stdout, stderr = visqol_process.communicate() 88*9c5db199SXin Li 89*9c5db199SXin Li err, score = parse_visqol_output(stdout, stderr, log_dir) 90*9c5db199SXin Li 91*9c5db199SXin Li if err: 92*9c5db199SXin Li raise error.TestError(err) 93*9c5db199SXin Li elif score < 0.0: 94*9c5db199SXin Li raise error.TestError('Failed to parse score, got {}'.format(score)) 95*9c5db199SXin Li 96*9c5db199SXin Li return score 97