Mark De Ruyter | c5ffb42 | 2019-01-22 15:46:39 -0800 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
global edge | c044d5b | 2018-10-12 17:17:30 +0530 | [diff] [blame] | 2 | # |
| 3 | # Copyright (C) 2018 The Android Open Source Project |
| 4 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| 6 | # use this file except in compliance with the License. You may obtain a copy of |
| 7 | # the License at |
| 8 | # |
| 9 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | # |
| 11 | # Unless required by applicable law or agreed to in writing, software |
| 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| 14 | # License for the specific language governing permissions and limitations under |
| 15 | # the License. |
| 16 | |
Aidan Holloway-bidwell | 07a759c | 2018-11-30 16:52:55 -0800 | [diff] [blame] | 17 | import functools |
global edge | c044d5b | 2018-10-12 17:17:30 +0530 | [diff] [blame] | 18 | import logging |
| 19 | import os |
Aidan Holloway-bidwell | 07a759c | 2018-11-30 16:52:55 -0800 | [diff] [blame] | 20 | from soundfile import SoundFile |
global edge | c044d5b | 2018-10-12 17:17:30 +0530 | [diff] [blame] | 21 | |
| 22 | from acts.controllers.utils_lib.ssh import connection |
| 23 | from acts.controllers.utils_lib.ssh import settings |
Aidan Holloway-bidwell | 70c902d | 2019-01-16 11:49:26 -0800 | [diff] [blame] | 24 | from acts.test_utils.audio_analysis_lib import audio_analysis |
global edge | c044d5b | 2018-10-12 17:17:30 +0530 | [diff] [blame] | 25 | from acts.test_utils.audio_analysis_lib.check_quality import quality_analysis |
| 26 | from acts.test_utils.coex.audio_capture import AudioCapture |
Aidan Holloway-bidwell | 07a759c | 2018-11-30 16:52:55 -0800 | [diff] [blame] | 27 | from acts.test_utils.coex.audio_capture import RECORD_FILE_TEMPLATE |
global edge | c044d5b | 2018-10-12 17:17:30 +0530 | [diff] [blame] | 28 | |
Aidan Holloway-bidwell | 70c902d | 2019-01-16 11:49:26 -0800 | [diff] [blame] | 29 | ANOMALY_DETECTION_BLOCK_SIZE = audio_analysis.ANOMALY_DETECTION_BLOCK_SIZE |
| 30 | ANOMALY_GROUPING_TOLERANCE = audio_analysis.ANOMALY_GROUPING_TOLERANCE |
| 31 | PATTERN_MATCHING_THRESHOLD = audio_analysis.PATTERN_MATCHING_THRESHOLD |
Aidan Holloway-bidwell | 07a759c | 2018-11-30 16:52:55 -0800 | [diff] [blame] | 32 | ANALYSIS_FILE_TEMPLATE = "audio_analysis_%s.txt" |
global edge | c044d5b | 2018-10-12 17:17:30 +0530 | [diff] [blame] | 33 | bits_per_sample = 32 |
| 34 | |
| 35 | |
gobaledge | 71a8e5b | 2019-02-28 18:54:44 +0530 | [diff] [blame] | 36 | class FileNotFound(Exception): |
| 37 | """Raises Exception if file is not present""" |
| 38 | |
| 39 | |
global edge | c044d5b | 2018-10-12 17:17:30 +0530 | [diff] [blame] | 40 | class SshAudioCapture(AudioCapture): |
| 41 | |
| 42 | def __init__(self, test_params, path): |
Aidan Holloway-bidwell | 07a759c | 2018-11-30 16:52:55 -0800 | [diff] [blame] | 43 | super(SshAudioCapture, self).__init__(test_params, path) |
global edge | c044d5b | 2018-10-12 17:17:30 +0530 | [diff] [blame] | 44 | self.remote_path = path |
gobaledge | 71a8e5b | 2019-02-28 18:54:44 +0530 | [diff] [blame] | 45 | self.ssh_session = None |
global edge | c044d5b | 2018-10-12 17:17:30 +0530 | [diff] [blame] | 46 | |
Aidan Holloway-bidwell | 07a759c | 2018-11-30 16:52:55 -0800 | [diff] [blame] | 47 | def capture_audio(self, trim_beginning=0, trim_end=0): |
Globaledge | 731dd67 | 2019-03-14 17:02:06 +0530 | [diff] [blame^] | 48 | """Captures audio and store results. |
| 49 | |
| 50 | Args: |
| 51 | trim_beginning: To trim audio at the start in seconds. |
| 52 | trim_end: To trim audio at the end in seconds. |
| 53 | |
| 54 | Returns: |
| 55 | Returns exit status of ssh connection. |
| 56 | """ |
| 57 | if not trim_beginning: |
| 58 | trim_beginning = self.audio_params.get('trim_beginning', 0) |
| 59 | if not trim_end: |
| 60 | trim_end = self.audio_params.get('trim_end', 0) |
global edge | c044d5b | 2018-10-12 17:17:30 +0530 | [diff] [blame] | 61 | if self.audio_params["ssh_config"]: |
| 62 | ssh_settings = settings.from_config( |
| 63 | self.audio_params["ssh_config"]) |
| 64 | self.ssh_session = connection.SshConnection(ssh_settings) |
gobaledge | b56c4c8 | 2019-02-14 21:29:19 +0530 | [diff] [blame] | 65 | cur_path = os.path.dirname(os.path.realpath(__file__)) |
| 66 | local_path = os.path.join(cur_path, "audio_capture.py") |
| 67 | self.ssh_session.send_file(local_path, |
global edge | c044d5b | 2018-10-12 17:17:30 +0530 | [diff] [blame] | 68 | self.audio_params["dest_path"]) |
| 69 | path = self.audio_params["dest_path"] |
| 70 | test_params = str(self.audio_params).replace("\'", "\"") |
| 71 | self.cmd = "python3 audio_capture.py -p '{}' -t '{}'".format( |
| 72 | path, test_params) |
| 73 | job_result = self.ssh_session.run(self.cmd) |
| 74 | logging.debug("Job Result {}".format(job_result.stdout)) |
gobaledge | 71a8e5b | 2019-02-28 18:54:44 +0530 | [diff] [blame] | 75 | self.ssh_session.pull_file( |
| 76 | self.remote_path, os.path.join( |
| 77 | self.audio_params["dest_path"], "*.wav"), |
| 78 | ignore_status=True) |
gobaledge | 215dc64 | 2019-02-27 17:34:41 +0530 | [diff] [blame] | 79 | return bool(not job_result.exit_status) |
global edge | c044d5b | 2018-10-12 17:17:30 +0530 | [diff] [blame] | 80 | else: |
Aidan Holloway-bidwell | 07a759c | 2018-11-30 16:52:55 -0800 | [diff] [blame] | 81 | return self.capture_and_store_audio(trim_beginning, trim_end) |
global edge | c044d5b | 2018-10-12 17:17:30 +0530 | [diff] [blame] | 82 | |
| 83 | def terminate_and_store_audio_results(self): |
| 84 | """Terminates audio and stores audio files.""" |
| 85 | if self.audio_params["ssh_config"]: |
gobaledge | 71a8e5b | 2019-02-28 18:54:44 +0530 | [diff] [blame] | 86 | self.ssh_session.run('rm *.wav', ignore_status=True) |
global edge | c044d5b | 2018-10-12 17:17:30 +0530 | [diff] [blame] | 87 | else: |
| 88 | self.terminate_audio() |
| 89 | |
Aidan Holloway-bidwell | 5d55c13 | 2019-01-18 17:53:23 -0800 | [diff] [blame] | 90 | def THDN(self, win_size=None, step_size=None, q=1, freq=None): |
Aidan Holloway-bidwell | 07a759c | 2018-11-30 16:52:55 -0800 | [diff] [blame] | 91 | """Calculate THD+N value for most recently recorded file. |
Aidan Holloway-bidwell | 5d55c13 | 2019-01-18 17:53:23 -0800 | [diff] [blame] | 92 | |
Aidan Holloway-bidwell | 07a759c | 2018-11-30 16:52:55 -0800 | [diff] [blame] | 93 | Args: |
| 94 | win_size: analysis window size (must be less than length of |
| 95 | signal). Used with step size to analyze signal piece by |
| 96 | piece. If not specified, entire signal will be analyzed. |
| 97 | step_size: number of samples to move window per-analysis. If not |
| 98 | specified, entire signal will be analyzed. |
| 99 | q: quality factor for the notch filter used to remove fundamental |
| 100 | frequency from signal to isolate noise. |
Aidan Holloway-bidwell | 5d55c13 | 2019-01-18 17:53:23 -0800 | [diff] [blame] | 101 | freq: the fundamental frequency to remove from the signal. If none, |
| 102 | the fundamental frequency will be determined using FFT. |
Aidan Holloway-bidwell | 07a759c | 2018-11-30 16:52:55 -0800 | [diff] [blame] | 103 | Returns: |
Aidan Holloway-bidwell | 5d55c13 | 2019-01-18 17:53:23 -0800 | [diff] [blame] | 104 | channel_results (list): THD+N value for each channel's signal. |
| 105 | List index corresponds to channel index. |
Aidan Holloway-bidwell | 07a759c | 2018-11-30 16:52:55 -0800 | [diff] [blame] | 106 | """ |
Aidan Holloway-bidwell | 70c902d | 2019-01-16 11:49:26 -0800 | [diff] [blame] | 107 | latest_file_path = self.record_file_template % self.last_fileno() |
Aidan Holloway-bidwell | 5d55c13 | 2019-01-18 17:53:23 -0800 | [diff] [blame] | 108 | if not (win_size and step_size): |
| 109 | return audio_analysis.get_file_THDN(filename=latest_file_path, |
| 110 | q=q, |
| 111 | freq=freq) |
Aidan Holloway-bidwell | 07a759c | 2018-11-30 16:52:55 -0800 | [diff] [blame] | 112 | else: |
Aidan Holloway-bidwell | 5d55c13 | 2019-01-18 17:53:23 -0800 | [diff] [blame] | 113 | return audio_analysis.get_file_max_THDN(filename=latest_file_path, |
| 114 | step_size=step_size, |
| 115 | window_size=win_size, |
| 116 | q=q, |
| 117 | freq=freq) |
Aidan Holloway-bidwell | 07a759c | 2018-11-30 16:52:55 -0800 | [diff] [blame] | 118 | |
Aidan Holloway-bidwell | 5d55c13 | 2019-01-18 17:53:23 -0800 | [diff] [blame] | 119 | def detect_anomalies(self, freq=None, |
| 120 | block_size=ANOMALY_DETECTION_BLOCK_SIZE, |
Aidan Holloway-bidwell | 70c902d | 2019-01-16 11:49:26 -0800 | [diff] [blame] | 121 | threshold=PATTERN_MATCHING_THRESHOLD, |
| 122 | tolerance=ANOMALY_GROUPING_TOLERANCE): |
| 123 | """Detect anomalies in most recently recorded file. |
| 124 | |
| 125 | An anomaly is defined as a sample in a recorded sine wave that differs |
| 126 | by at least the value defined by the threshold parameter from a golden |
| 127 | generated sine wave of the same amplitude, sample rate, and frequency. |
| 128 | |
| 129 | Args: |
Aidan Holloway-bidwell | 5d55c13 | 2019-01-18 17:53:23 -0800 | [diff] [blame] | 130 | freq (int|float): fundamental frequency of the signal. All other |
| 131 | frequencies are noise. If None, will be calculated with FFT. |
Aidan Holloway-bidwell | 70c902d | 2019-01-16 11:49:26 -0800 | [diff] [blame] | 132 | block_size (int): the number of samples to analyze at a time in the |
| 133 | anomaly detection algorithm. |
| 134 | threshold (float): the threshold of the correlation index to |
| 135 | determine if two sample values match. |
| 136 | tolerance (float): the sample tolerance for anomaly time values |
| 137 | to be grouped as the same anomaly |
| 138 | Returns: |
Aidan Holloway-bidwell | 5d55c13 | 2019-01-18 17:53:23 -0800 | [diff] [blame] | 139 | channel_results (list): anomaly durations for each channel's signal. |
| 140 | List index corresponds to channel index. |
Aidan Holloway-bidwell | 70c902d | 2019-01-16 11:49:26 -0800 | [diff] [blame] | 141 | """ |
| 142 | latest_file_path = self.record_file_template % self.last_fileno() |
Aidan Holloway-bidwell | 5d55c13 | 2019-01-18 17:53:23 -0800 | [diff] [blame] | 143 | return audio_analysis.get_file_anomaly_durations( |
| 144 | filename=latest_file_path, |
| 145 | freq=freq, |
| 146 | block_size=block_size, |
| 147 | threshold=threshold, |
| 148 | tolerance=tolerance) |
Aidan Holloway-bidwell | 70c902d | 2019-01-16 11:49:26 -0800 | [diff] [blame] | 149 | |
global edge | c044d5b | 2018-10-12 17:17:30 +0530 | [diff] [blame] | 150 | def audio_quality_analysis(self, path): |
gobaledge | 71a8e5b | 2019-02-28 18:54:44 +0530 | [diff] [blame] | 151 | """Measures audio quality based on the audio file given as input. |
| 152 | |
| 153 | Args: |
| 154 | path: Log path |
| 155 | |
| 156 | Returns: |
| 157 | analysis_path on success. |
| 158 | """ |
global edge | c044d5b | 2018-10-12 17:17:30 +0530 | [diff] [blame] | 159 | dest_file_path = os.path.join(path, |
Aidan Holloway-bidwell | 07a759c | 2018-11-30 16:52:55 -0800 | [diff] [blame] | 160 | RECORD_FILE_TEMPLATE % self.last_fileno()) |
global edge | c044d5b | 2018-10-12 17:17:30 +0530 | [diff] [blame] | 161 | analysis_path = os.path.join(path, |
Aidan Holloway-bidwell | 07a759c | 2018-11-30 16:52:55 -0800 | [diff] [blame] | 162 | ANALYSIS_FILE_TEMPLATE % self.last_fileno()) |
gobaledge | 71a8e5b | 2019-02-28 18:54:44 +0530 | [diff] [blame] | 163 | if not os.path.exists(dest_file_path): |
| 164 | raise FileNotFound("Recorded file not found") |
global edge | c044d5b | 2018-10-12 17:17:30 +0530 | [diff] [blame] | 165 | try: |
| 166 | quality_analysis( |
| 167 | filename=dest_file_path, |
| 168 | output_file=analysis_path, |
| 169 | bit_width=bits_per_sample, |
| 170 | rate=self.audio_params["sample_rate"], |
| 171 | channel=self.audio_params["channel"], |
| 172 | spectral_only=False) |
| 173 | except Exception as err: |
| 174 | logging.exception("Failed to analyze raw audio: %s" % err) |
| 175 | return analysis_path |
gobaledge | 71a8e5b | 2019-02-28 18:54:44 +0530 | [diff] [blame] | 176 | |