1#!/usr/bin/env python3
2#
3# Copyright (C) 2017 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"""
17Test script to automate the Bluetooth audio testing and analysis.
18
19Quick way to generate necessary audio files:
20sudo apt-get install sox
21sox -b 16 -r 48000 -c 2 -n audio_file_2k1k_10_sec.wav synth 10 sine 2000 sine 3000
22sox -b 16 -r 48000 -c 2 -n audio_file_2k1k_300_sec.wav synth 300 sine 2000 sine 3000
23
24"""
25import os
26import subprocess
27import time
28
29from acts.test_decorators import test_tracker_info
30from acts.test_utils.audio_analysis_lib.check_quality import quality_analysis
31from acts.test_utils.bt.BtFunhausBaseTest import BtFunhausBaseTest
32from acts.test_utils.bt.bt_constants import audio_bits_per_sample_32
33from acts.test_utils.bt.bt_constants import audio_channel_mode_8
34from acts.test_utils.bt.bt_constants import audio_sample_rate_48000
35from acts.test_utils.bt.bt_constants import delay_after_binding_seconds
36from acts.test_utils.bt.bt_constants import delay_before_record_seconds
37from acts.test_utils.bt.bt_constants import fpga_linein_bus_endpoint
38from acts.test_utils.bt.bt_constants import headphone_bus_endpoint
39from acts.test_utils.bt.bt_constants import silence_wait_seconds
40
41
42class BtChameleonTest(BtFunhausBaseTest):
43
44    audio_file_2k1k_10_sec = "audio_file_2k1k_10_sec.wav"
45    audio_file_2k1k_300_sec = "audio_file_2k1k_300_sec.wav"
46    android_sdcard_music_path = "/sdcard/Music"
47
48    def setup_class(self):
49        super().setup_class()
50        self.chameleon = self.chameleon_devices[0]
51        self.dut = self.android_devices[0]
52        self.raw_audio_dest = "{}/{}".format(self.android_devices[0].log_path,
53                                             "Chameleon_audio")
54        os.makedirs(self.raw_audio_dest, exist_ok=True)
55        self.chameleon.audio_board_connect(1, headphone_bus_endpoint)
56        self.chameleon.audio_board_connect(1, fpga_linein_bus_endpoint)
57        time.sleep(delay_after_binding_seconds)
58
59    def _orchestrate_audio_quality_test(self, output_file_prefix_name,
60                                        bits_per_sample, rate, record_seconds,
61                                        channel, audio_to_play):
62        audio_analysis_filename = "{}_audio_analysis.txt".format(
63            output_file_prefix_name)
64        bluetooth_bind_time_seconds = 5
65        port_id = 6
66        has_file = True
67        # Additional sleep to allow full connection of Bluetooth device
68        # from test setup.
69        time.sleep(bluetooth_bind_time_seconds)
70        self.chameleon.start_capturing_audio(port_id, has_file)
71        time.sleep(delay_before_record_seconds)
72        self.dut.droid.mediaPlayOpen("file://{}".format(audio_to_play))
73        time.sleep(record_seconds + silence_wait_seconds)
74        raw_audio_info = self.chameleon_devices[0].stop_capturing_audio(
75            port_id)
76        self.ad.droid.mediaPlayStopAll()
77        raw_audio_path = raw_audio_info[0]
78        dest_file_path = "{}/{}_recording.raw".format(self.raw_audio_dest,
79                                                      output_file_prefix_name)
80        self.chameleon.scp(raw_audio_path, dest_file_path)
81        self._collect_bluetooth_manager_dumpsys_logs(self.android_devices)
82        self.collect_bluetooth_manager_metrics_logs(self.android_devices)
83        analysis_path = "{}/{}".format(self.raw_audio_dest,
84                                       audio_analysis_filename)
85        try:
86            quality_analysis(
87                filename=dest_file_path,
88                output_file=analysis_path,
89                bit_width=bits_per_sample,
90                rate=rate,
91                channel=channel,
92                spectral_only=False)
93        except Exception as err:
94            self.log.exception("Failed to analyze raw audio: {}".format(err))
95            return False
96        # TODO: Log results to proto
97        return True
98
99    @test_tracker_info(uuid='b808fed6-5cb0-4e40-9522-c0f410cd77e8')
100    def test_run_bt_audio_quality_2k1k_10_sec_sine_wave(self):
101        """Measure audio quality over Bluetooth by playing a 1k2k sine wave.
102
103        Play a sine wave and measure the analysis of 1kHz and 2kHz on two
104            different channels for 10 seconds:
105        1. Delays during playback.
106        2. Noise before playback.
107        3. Noise after playback.
108        4. Bursts during playback.
109        5. Volume changes.
110
111        Steps:
112        1. Connect Chameleon headphone audio bus endpoint.
113        2. Connect FPGA line-in bus endpoint.
114        3. Clear audio routes on the Chameleon device.
115        4. Start capturing audio on the Chameleon device.
116        5. Start playing the sine wave on the Android device.
117        6. Record for record_seconds + silence_wait_seconds.
118        7. Stop recording audio on the Chameleon device.
119        8. Stop playing audio on the Android Device.
120        9. Pull raw recorded audio from the Chameleon device.
121        10. Analyze raw audio and log results.
122
123
124        Expected Result:
125        Audio is recorded and processed successfully.
126
127        Returns:
128          True if Pass
129          False if Fail
130
131        TAGS: Classic, A2DP, Chameleon
132        Priority: 2
133        """
134        sox_call = "{}{}".format("sox -b 16 -r 48000 -c 2 -n {}".format(
135            self.audio_file_2k1k_10_sec), " synth 10 sine 2000 sine 3000")
136        subprocess.call(sox_call, shell=True)
137        sox_audio_path = "{}/{}".format(
138            os.path.dirname(os.path.realpath(self.audio_file_2k1k_10_sec)),
139            self.audio_file_2k1k_10_sec)
140        sox_audio_path = os.path.join(
141            os.path.dirname(os.path.realpath(self.audio_file_2k1k_10_sec)),
142            self.audio_file_2k1k_10_sec)
143        self.dut.adb.push("{} {}".format(sox_audio_path,
144                                         self.android_sdcard_music_path))
145        output_file_prefix_name = "{}_{}".format("test_2k1k_10_sec",
146                                                 time.time())
147        bits_per_sample = audio_bits_per_sample_32
148        rate = audio_sample_rate_48000
149        record_seconds = 10  # The length in seconds for how long to record
150        channel = audio_channel_mode_8
151        audio_to_play = "{}/{}".format(self.android_sdcard_music_path,
152                                       self.audio_file_2k1k_10_sec)
153        audio_to_play = os.path.join(self.android_sdcard_music_path,
154                                     self.audio_file_2k1k_10_sec)
155        return self._orchestrate_audio_quality_test(
156            output_file_prefix_name=output_file_prefix_name,
157            bits_per_sample=bits_per_sample,
158            rate=rate,
159            record_seconds=record_seconds,
160            channel=channel,
161            audio_to_play=audio_to_play)
162
163    @test_tracker_info(uuid='7e971cef-6637-4198-929a-7ecc712121d7')
164    def test_run_bt_audio_quality_2k1k_300_sec_sine_wave(self):
165        """Measure audio quality over Bluetooth by playing a 1k2k sine wave.
166
167        Play a sine wave and measure the analysis of 1kHz and 2kHz on two
168            different channels for 300 seconds:
169        1. Delays during playback.
170        2. Noise before playback.
171        3. Noise after playback.
172        4. Bursts during playback.
173        5. Volume changes.
174
175        Steps:
176        1. Connect Chameleon headphone audio bus endpoint.
177        2. Connect FPGA line-in bus endpoint.
178        3. Clear audio routes on the Chameleon device.
179        4. Start capturing audio on the Chameleon device.
180        5. Start playing the sine wave on the Android device.
181        6. Record for record_seconds + silence_wait_seconds.
182        7. Stop recording audio on the Chameleon device.
183        8. Stop playing audio on the Android Device.
184        9. Pull raw recorded audio from the Chameleon device.
185        10. Analyze raw audio and log results.
186
187
188        Expected Result:
189        Audio is recorded and processed successfully.
190
191        Returns:
192          True if Pass
193          False if Fail
194
195        TAGS: Classic, A2DP, Chameleon
196        Priority: 2
197        """
198        sox_call = "{}{}".format("sox -b 16 -r 48000 -c 2 -n {}".format(
199            self.audio_file_2k1k_300_sec), " synth 300 sine 2000 sine 3000")
200        subprocess.call(sox_call, shell=True)
201        sox_audio_path = os.path.join(
202            os.path.dirname(os.path.realpath(self.audio_file_2k1k_300_sec)),
203            self.audio_file_2k1k_300_sec)
204        self.dut.adb.push("{} {}".format(sox_audio_path,
205                                         self.android_sdcard_music_path))
206        output_file_prefix_name = "{}_{}".format("test_2k1k_300_sec.wav",
207                                                 time.time())
208        bits_per_sample = audio_bits_per_sample_32
209        rate = audio_sample_rate_48000
210        record_seconds = 300  # The length in seconds for how long to record
211        channel = audio_channel_mode_8
212        audio_to_play = os.path.join(self.android_sdcard_music_path,
213                                     self.audio_file_2k1k_300_sec)
214
215        return self._orchestrate_audio_quality_test(
216            output_file_prefix_name=output_file_prefix_name,
217            bits_per_sample=bits_per_sample,
218            rate=rate,
219            record_seconds=record_seconds,
220            channel=channel,
221            audio_to_play=audio_to_play)
222