1#!/usr/bin/env python 2# 3# Copyright 2016 The Chromium OS Authors. All rights reserved. 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6"""Script functions as a web app and wrapper for the cras_router program.""" 7 8import re 9import subprocess 10import logging 11import cherrypy 12 13# Node Format: [Stable_Id, ID, Vol, Plugged, L/R_swapped, Time, Type, Name] 14ID_INDEX = 1 15PLUGGED_INDEX = 3 16TYPE_INDEX = 6 17NAME_INDEX = 7 18 19 20def get_plugged_nodes(plugged_nodes, lines, is_input): 21 start_str = 'Input Nodes:' if is_input else 'Output Nodes:' 22 end_str = 'Attached clients:' if is_input else 'Input Devices:' 23 for i in range(lines.index(start_str) + 2, 24 lines.index(end_str)): 25 node = filter(None, re.split(r'\s+|\*+', lines[i])) 26 # check for nodes that are plugged nodes and loopback 27 if node[PLUGGED_INDEX] == 'yes' and node[TYPE_INDEX][:4] != 'POST': 28 key = node[TYPE_INDEX] + ' ' + node[NAME_INDEX] 29 plugged_nodes[key] = node[ID_INDEX] 30 31 32class CrasRouterTest(object): 33 """Cherrypy class that builds and runs the HTML for audio testing tool.""" 34 35 @cherrypy.expose 36 def index(self): 37 """Builds up and displays the html for the audio testing tool. 38 39 Returns: 40 html that was built up based on plugged audio devices. 41 """ 42 43 # Stop program if currently being run. 44 if 'process' in cherrypy.session: 45 print 'Existing process' 46 # If return code is None process is still running 47 if cherrypy.session['process'].poll() is None: 48 print 'Killing existing process' 49 cherrypy.session['process'].kill() 50 else: 51 print 'Process already finished' 52 53 html = """<html> 54 <head> 55 <title>Audio Test</title> 56 </head> 57 <body style="background-color:lightgrey;"> 58 <font color="red"> 59 <h1>Audio Closed Loop Test</h1> 60 <font style="color:rgb(100, 149, 237)"> 61 <h3> 62 <form name="routerOptions" method="get" 63 onsubmit="return validateForm()" action="start_test"> 64 <h2>Input Type</h2> 65 """ 66 dump = subprocess.check_output(['cras_test_client', '--dump_s']) 67 if not dump: 68 return 'Could not connect to server' 69 dump_lines = dump.split('\n') 70 input_plugged_nodes = {} 71 get_plugged_nodes(input_plugged_nodes, dump_lines, True) 72 for name, node_id in input_plugged_nodes.items(): 73 line = '<input type ="radio" name="input_type" value="' 74 line += node_id + '">' +name + '<br>\n' 75 html += line 76 77 html += """<input type ="radio" id="i0" name="input_type" 78 value="file">File<br> 79 <div id="input_file" style="display:none;"> 80 Filename <input type="text" name="input_file"><br><br> 81 </div> 82 <h2>Output Type</h2>""" 83 output_plugged_nodes = {} 84 get_plugged_nodes(output_plugged_nodes, dump_lines, False) 85 for name, node_id in output_plugged_nodes.items(): 86 line = '<input type ="radio" name="output_type" value="' 87 line = line + node_id + '">' +name + '<br>\n' 88 html += line 89 90 html += """<input type ="radio" name="output_type" 91 value="file">File<br> 92 <div id="output_file" style="display:none;"> 93 Filename <input type="text" name="output_file"> 94 </div><br> 95 <h2>Sample Rate</h2> 96 <input type="radio" name="rate" id="sample_rate1" value=48000 97 checked>48,000 Hz<br> 98 <input type="radio" name="rate" id="sample_rate0" value=44100> 99 44,100 Hz<br><br> 100 <button type="submit" onclick="onOff(this)">Test!</button> 101 </h3> 102 </form> 103 </font> 104 </body> 105 </html> 106 """ 107 javascript = """ 108 <script> 109 /* Does basic error checking to make sure user doesn't 110 * give bad options to the router. 111 */ 112 function validateForm(){ 113 var input_type = 114 document.forms['routerOptions']['input_type'].value; 115 var output_type = 116 document.forms['routerOptions']['output_type'].value; 117 if (input_type == '' || output_type == '') { 118 alert('Please select an input and output type.'); 119 return false; 120 } 121 if (input_type == 'file' && output_type == 'file') { 122 alert('Input and Output Types cannot both be files!'); 123 return false; 124 } 125 //check if filename is valid 126 if (input_type == 'file') { 127 var input_file = 128 document.forms['routerOptions']['input_file'].value; 129 if (input_file == '') { 130 alert('Please enter a file name'); 131 return false; 132 } 133 } 134 if (output_type == 'file') { 135 var output_file = 136 document.forms['routerOptions']['output_file'].value; 137 if (output_file == '') { 138 alert('Please enter a file name'); 139 return false; 140 } 141 } 142 } 143 144 function show_filename(radio, file_elem) { 145 for(var i =0; i < radio.length; i++){ 146 radio[i].onclick = function(){ 147 if (this.value == 'file') { 148 file_elem.style.display = 'block'; 149 } else { 150 file_elem.style.display = 'none'; 151 } 152 } 153 } 154 } 155 /* Loops determine if filename field should be shown */ 156 var input_type_rad = 157 document.forms['routerOptions']['input_type']; 158 var input_file_elem = 159 document.getElementById('input_file'); 160 var output_type_rad = 161 document.forms['routerOptions']['output_type']; 162 var output_file_elem = 163 document.getElementById('output_file'); 164 show_filename(input_type_rad, input_file_elem); 165 show_filename(output_type_rad, output_file_elem); 166 </script>""" 167 html += javascript 168 return html 169 170 @cherrypy.expose 171 def start_test(self, input_type, output_type, input_file='', 172 output_file='', rate=48000): 173 """Capture audio from the input_type and plays it back to the output_type. 174 175 Args: 176 input_type: Node id for the selected input or 'file' for files 177 output_type: Node id for the selected output or 'file' for files 178 input_file: Path of the input if 'file' is input type 179 output_file: Path of the output if 'file' is output type 180 rate: Sample rate for the test. 181 182 Returns: 183 html for the tesing in progress page. 184 """ 185 print 'Beginning test' 186 if input_type == 'file' or output_type == 'file': 187 command = ['cras_test_client'] 188 else: 189 command = ['cras_router'] 190 if input_type == 'file': 191 command.append('--playback_file') 192 command.append(str(input_file)) 193 else: 194 set_input = ['cras_test_client', '--select_input', str(input_type)] 195 if subprocess.check_call(set_input): 196 print 'Error setting input' 197 if output_type == 'file': 198 command.append('--capture_file') 199 command.append(str(output_file)) 200 else: 201 set_output = ['cras_test_client', '--select_output', str(output_type)] 202 if subprocess.check_call(set_output): 203 print 'Error setting output' 204 command.append('--rate') 205 command.append(str(rate)) 206 print 'Running commmand: ' + str(command) 207 p = subprocess.Popen(command) 208 cherrypy.session['process'] = p 209 return """ 210 <html> 211 <head> 212 <style type="text/css"> 213 body { 214 background-color: #DC143C; 215 } 216 #test { 217 color: white; 218 text-align: center; 219 } 220 </style> 221 <title>Running test</title> 222 </head> 223 <body> 224 <div id="test"> 225 <h1>Test in progress</h1> 226 <form action="index"><!--Go back to orginal page--> 227 <button type="submit" id="stop">Click to stop</button> 228 </form> 229 <h2>Time Elapsed<br> 230 <time id="elapsed_time">00:00</time> 231 </h2> 232 </div> 233 </body> 234 </html> 235 <script type="text/javascript"> 236 var seconds = 0; 237 var minutes = 0; 238 var elapsed_time; 239 var start_time = new Date().getTime(); 240 function secondPassed(){ 241 var time = new Date().getTime() - start_time; 242 elapsed_time = Math.floor(time / 100) / 10; 243 minutes = Math.floor(elapsed_time / 60); 244 seconds = Math.floor(elapsed_time % 60); 245 var seconds_str = (seconds < 10 ? '0' + seconds: '' + seconds); 246 var minutes_str = (minutes < 10 ? '0' + minutes: '' + minutes); 247 var time_passed = minutes_str + ':' + seconds_str; 248 document.getElementById("elapsed_time").textContent = time_passed; 249 } 250 //have time tic every second 251 var timer = setInterval(secondPassed, 1000); 252 var stop = document.getElementById("stop"); 253 stop.onclick = function(){ 254 seconds = 0; 255 minutes = 0; 256 clearInterval(timer); 257 } 258 </script>""" 259 260if __name__ == '__main__': 261 conf = { 262 '/': { 263 'tools.sessions.on': True 264 } 265 } 266 cherrypy.quickstart(CrasRouterTest(), '/', conf) 267