blob: 2a376fcc5e1afbb289d39108344e57a8c4f2be5b [file] [log] [blame]
Mark De Ruyterc5ffb422019-01-22 15:46:39 -08001#!/usr/bin/env python3
global edgec044d5b2018-10-12 17:17:30 +05302#
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-bidwell07a759c2018-11-30 16:52:55 -080017import functools
global edgec044d5b2018-10-12 17:17:30 +053018import logging
19import os
Aidan Holloway-bidwell07a759c2018-11-30 16:52:55 -080020from soundfile import SoundFile
global edgec044d5b2018-10-12 17:17:30 +053021
22from acts.controllers.utils_lib.ssh import connection
23from acts.controllers.utils_lib.ssh import settings
Aidan Holloway-bidwell70c902d2019-01-16 11:49:26 -080024from acts.test_utils.audio_analysis_lib import audio_analysis
global edgec044d5b2018-10-12 17:17:30 +053025from acts.test_utils.audio_analysis_lib.check_quality import quality_analysis
26from acts.test_utils.coex.audio_capture import AudioCapture
Aidan Holloway-bidwell07a759c2018-11-30 16:52:55 -080027from acts.test_utils.coex.audio_capture import RECORD_FILE_TEMPLATE
global edgec044d5b2018-10-12 17:17:30 +053028
Aidan Holloway-bidwell70c902d2019-01-16 11:49:26 -080029ANOMALY_DETECTION_BLOCK_SIZE = audio_analysis.ANOMALY_DETECTION_BLOCK_SIZE
30ANOMALY_GROUPING_TOLERANCE = audio_analysis.ANOMALY_GROUPING_TOLERANCE
31PATTERN_MATCHING_THRESHOLD = audio_analysis.PATTERN_MATCHING_THRESHOLD
Aidan Holloway-bidwell07a759c2018-11-30 16:52:55 -080032ANALYSIS_FILE_TEMPLATE = "audio_analysis_%s.txt"
global edgec044d5b2018-10-12 17:17:30 +053033bits_per_sample = 32
34
35
gobaledge71a8e5b2019-02-28 18:54:44 +053036class FileNotFound(Exception):
37 """Raises Exception if file is not present"""
38
39
global edgec044d5b2018-10-12 17:17:30 +053040class SshAudioCapture(AudioCapture):
41
42 def __init__(self, test_params, path):
Aidan Holloway-bidwell07a759c2018-11-30 16:52:55 -080043 super(SshAudioCapture, self).__init__(test_params, path)
global edgec044d5b2018-10-12 17:17:30 +053044 self.remote_path = path
gobaledge71a8e5b2019-02-28 18:54:44 +053045 self.ssh_session = None
global edgec044d5b2018-10-12 17:17:30 +053046
Aidan Holloway-bidwell07a759c2018-11-30 16:52:55 -080047 def capture_audio(self, trim_beginning=0, trim_end=0):
Globaledge731dd672019-03-14 17:02:06 +053048 """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 edgec044d5b2018-10-12 17:17:30 +053061 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)
gobaledgeb56c4c82019-02-14 21:29:19 +053065 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 edgec044d5b2018-10-12 17:17:30 +053068 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))
gobaledge71a8e5b2019-02-28 18:54:44 +053075 self.ssh_session.pull_file(
76 self.remote_path, os.path.join(
77 self.audio_params["dest_path"], "*.wav"),
78 ignore_status=True)
gobaledge215dc642019-02-27 17:34:41 +053079 return bool(not job_result.exit_status)
global edgec044d5b2018-10-12 17:17:30 +053080 else:
Aidan Holloway-bidwell07a759c2018-11-30 16:52:55 -080081 return self.capture_and_store_audio(trim_beginning, trim_end)
global edgec044d5b2018-10-12 17:17:30 +053082
83 def terminate_and_store_audio_results(self):
84 """Terminates audio and stores audio files."""
85 if self.audio_params["ssh_config"]:
gobaledge71a8e5b2019-02-28 18:54:44 +053086 self.ssh_session.run('rm *.wav', ignore_status=True)
global edgec044d5b2018-10-12 17:17:30 +053087 else:
88 self.terminate_audio()
89
Aidan Holloway-bidwell5d55c132019-01-18 17:53:23 -080090 def THDN(self, win_size=None, step_size=None, q=1, freq=None):
Aidan Holloway-bidwell07a759c2018-11-30 16:52:55 -080091 """Calculate THD+N value for most recently recorded file.
Aidan Holloway-bidwell5d55c132019-01-18 17:53:23 -080092
Aidan Holloway-bidwell07a759c2018-11-30 16:52:55 -080093 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-bidwell5d55c132019-01-18 17:53:23 -0800101 freq: the fundamental frequency to remove from the signal. If none,
102 the fundamental frequency will be determined using FFT.
Aidan Holloway-bidwell07a759c2018-11-30 16:52:55 -0800103 Returns:
Aidan Holloway-bidwell5d55c132019-01-18 17:53:23 -0800104 channel_results (list): THD+N value for each channel's signal.
105 List index corresponds to channel index.
Aidan Holloway-bidwell07a759c2018-11-30 16:52:55 -0800106 """
Aidan Holloway-bidwell70c902d2019-01-16 11:49:26 -0800107 latest_file_path = self.record_file_template % self.last_fileno()
Aidan Holloway-bidwell5d55c132019-01-18 17:53:23 -0800108 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-bidwell07a759c2018-11-30 16:52:55 -0800112 else:
Aidan Holloway-bidwell5d55c132019-01-18 17:53:23 -0800113 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-bidwell07a759c2018-11-30 16:52:55 -0800118
Aidan Holloway-bidwell5d55c132019-01-18 17:53:23 -0800119 def detect_anomalies(self, freq=None,
120 block_size=ANOMALY_DETECTION_BLOCK_SIZE,
Aidan Holloway-bidwell70c902d2019-01-16 11:49:26 -0800121 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-bidwell5d55c132019-01-18 17:53:23 -0800130 freq (int|float): fundamental frequency of the signal. All other
131 frequencies are noise. If None, will be calculated with FFT.
Aidan Holloway-bidwell70c902d2019-01-16 11:49:26 -0800132 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-bidwell5d55c132019-01-18 17:53:23 -0800139 channel_results (list): anomaly durations for each channel's signal.
140 List index corresponds to channel index.
Aidan Holloway-bidwell70c902d2019-01-16 11:49:26 -0800141 """
142 latest_file_path = self.record_file_template % self.last_fileno()
Aidan Holloway-bidwell5d55c132019-01-18 17:53:23 -0800143 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-bidwell70c902d2019-01-16 11:49:26 -0800149
global edgec044d5b2018-10-12 17:17:30 +0530150 def audio_quality_analysis(self, path):
gobaledge71a8e5b2019-02-28 18:54:44 +0530151 """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 edgec044d5b2018-10-12 17:17:30 +0530159 dest_file_path = os.path.join(path,
Aidan Holloway-bidwell07a759c2018-11-30 16:52:55 -0800160 RECORD_FILE_TEMPLATE % self.last_fileno())
global edgec044d5b2018-10-12 17:17:30 +0530161 analysis_path = os.path.join(path,
Aidan Holloway-bidwell07a759c2018-11-30 16:52:55 -0800162 ANALYSIS_FILE_TEMPLATE % self.last_fileno())
gobaledge71a8e5b2019-02-28 18:54:44 +0530163 if not os.path.exists(dest_file_path):
164 raise FileNotFound("Recorded file not found")
global edgec044d5b2018-10-12 17:17:30 +0530165 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
gobaledge71a8e5b2019-02-28 18:54:44 +0530176