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