OLD | NEW |
| (Empty) |
1 #!/usr/bin/env python | |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
3 # Use of this source code is governed by a BSD-style license that can be | |
4 # found in the LICENSE file. | |
5 | |
6 """Audio tools for recording and analyzing audio. | |
7 | |
8 The audio tools provided here are mainly to: | |
9 - record playing audio. | |
10 - remove silence from beginning and end of audio file. | |
11 - compare audio files using PESQ tool. | |
12 | |
13 The tools are supported on Windows and Linux. | |
14 """ | |
15 | |
16 import commands | |
17 import ctypes | |
18 import logging | |
19 import os | |
20 import re | |
21 import subprocess | |
22 import sys | |
23 import threading | |
24 import time | |
25 | |
26 import pyauto_media | |
27 import pyauto | |
28 | |
29 | |
30 _TOOLS_PATH = os.path.abspath(os.path.join(pyauto.PyUITest.DataDir(), | |
31 'pyauto_private', 'media', 'tools')) | |
32 | |
33 WINDOWS = 'win32' in sys.platform | |
34 if WINDOWS: | |
35 _PESQ_PATH = os.path.join(_TOOLS_PATH, 'pesq.exe') | |
36 _SOX_PATH = os.path.join(_TOOLS_PATH, 'sox.exe') | |
37 _AUDIO_RECORDER = r'SoundRecorder.exe' | |
38 _FORCE_MIC_VOLUME_MAX_UTIL = os.path.join(_TOOLS_PATH, | |
39 r'force_mic_volume_max.exe') | |
40 else: | |
41 _PESQ_PATH = os.path.join(_TOOLS_PATH, 'pesq') | |
42 _SOX_PATH = commands.getoutput('which sox') | |
43 _AUDIO_RECORDER = commands.getoutput('which arecord') | |
44 _PACMD_PATH = commands.getoutput('which pacmd') | |
45 | |
46 | |
47 class AudioRecorderThread(threading.Thread): | |
48 """A thread that records audio out of the default audio output.""" | |
49 | |
50 def __init__(self, duration, output_file, record_mono=False): | |
51 threading.Thread.__init__(self) | |
52 self.error = '' | |
53 self._duration = duration | |
54 self._output_file = output_file | |
55 self._record_mono = record_mono | |
56 | |
57 def run(self): | |
58 """Starts audio recording.""" | |
59 if WINDOWS: | |
60 if self._record_mono: | |
61 logging.error("Mono recording not supported on Windows yet!") | |
62 | |
63 duration = time.strftime('%H:%M:%S', time.gmtime(self._duration)) | |
64 cmd = [_AUDIO_RECORDER, '/FILE', self._output_file, '/DURATION', | |
65 duration] | |
66 # This is needed to run SoundRecorder.exe on Win-64 using Python-32 bit. | |
67 ctypes.windll.kernel32.Wow64DisableWow64FsRedirection( | |
68 ctypes.byref(ctypes.c_long())) | |
69 else: | |
70 num_channels = 1 if self._record_mono else 2 | |
71 cmd = [_AUDIO_RECORDER, '-d', self._duration, '-f', 'dat', '-c', | |
72 str(num_channels), self._output_file] | |
73 | |
74 cmd = [str(s) for s in cmd] | |
75 logging.debug('Running command: %s', ' '.join(cmd)) | |
76 returncode = subprocess.call(cmd, stdout=subprocess.PIPE, | |
77 stderr=subprocess.PIPE) | |
78 if returncode != 0: | |
79 self.error = 'Failed to record audio.' | |
80 else: | |
81 logging.debug('Finished recording audio into %s.', self._output_file) | |
82 | |
83 | |
84 def RunPESQ(audio_file_ref, audio_file_test, sample_rate=16000): | |
85 """Runs PESQ to compare audio test file to a reference audio file. | |
86 | |
87 Args: | |
88 audio_file_ref: The reference audio file used by PESQ. | |
89 audio_file_test: The audio test file to compare. | |
90 sample_rate: Sample rate used by PESQ algorithm, possible values are only | |
91 8000 or 16000. | |
92 | |
93 Returns: | |
94 A tuple of float values representing PESQ scores of the audio_file_ref and | |
95 audio_file_test consecutively. | |
96 """ | |
97 # Work around a bug in PESQ when the ref file path is > 128 chars. PESQ will | |
98 # compute an incorrect score then (!), and the relative path to the ref file | |
99 # should be a lot shorter than the absolute one. | |
100 audio_file_ref = os.path.relpath(audio_file_ref) | |
101 cmd = [_PESQ_PATH, '+%d' % sample_rate, audio_file_ref, audio_file_test] | |
102 logging.debug('Running command: %s', ' '.join(cmd)) | |
103 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) | |
104 output, error = p.communicate() | |
105 if p.returncode != 0: | |
106 logging.error('Error running pesq: %s\n%s', output, error) | |
107 # Last line of PESQ output shows the results. Example: | |
108 # P.862 Prediction (Raw MOS, MOS-LQO): = 4.180 4.319 | |
109 result = re.search('Prediction.*= (\d{1}\.\d{3})\t(\d{1}\.\d{3})', | |
110 output) | |
111 if not result or len(result.groups()) != 2: | |
112 return None | |
113 return (float(result.group(1)), float(result.group(2))) | |
114 | |
115 | |
116 def RemoveSilence(input_audio_file, output_audio_file): | |
117 """Removes silence from beginning and end of the input_audio_file. | |
118 | |
119 Args: | |
120 input_audio_file: The audio file to remove silence from. | |
121 output_audio_file: The audio file to save the output audio. | |
122 """ | |
123 # SOX documentation for silence command: http://sox.sourceforge.net/sox.html | |
124 # To remove the silence from both beginning and end of the audio file, we call | |
125 # sox silence command twice: once on normal file and again on its reverse, | |
126 # then we reverse the final output. | |
127 # Silence parameters are (in sequence): | |
128 # ABOVE_PERIODS: The period for which silence occurs. Value 1 is used for | |
129 # silence at beginning of audio. | |
130 # DURATION: the amount of time in seconds that non-silence must be detected | |
131 # before sox stops trimming audio. | |
132 # THRESHOLD: value used to indicate what sample value is treates as silence. | |
133 ABOVE_PERIODS = '1' | |
134 DURATION = '2' | |
135 THRESHOLD = '5%' | |
136 | |
137 cmd = [_SOX_PATH, input_audio_file, output_audio_file, 'silence', | |
138 ABOVE_PERIODS, DURATION, THRESHOLD, 'reverse', 'silence', | |
139 ABOVE_PERIODS, DURATION, THRESHOLD, 'reverse'] | |
140 logging.debug('Running command: %s', ' '.join(cmd)) | |
141 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) | |
142 output, error = p.communicate() | |
143 if p.returncode != 0: | |
144 logging.error('Error removing silence from audio: %s\n%s', output, error) | |
145 | |
146 | |
147 def ForceMicrophoneVolumeTo100Percent(): | |
148 if WINDOWS: | |
149 # The volume max util is implemented in WebRTC in | |
150 # webrtc/tools/force_mic_volume_max/force_mic_volume_max.cc. | |
151 if not os.path.exists(_FORCE_MIC_VOLUME_MAX_UTIL): | |
152 raise Exception('Missing required binary %s.' % | |
153 _FORCE_MIC_VOLUME_MAX_UTIL) | |
154 cmd = [_FORCE_MIC_VOLUME_MAX_UTIL] | |
155 else: | |
156 # The recording device id is machine-specific. We assume here it is called | |
157 # Monitor of render (which corresponds to the id render.monitor). You can | |
158 # list the available recording devices with pacmd list-sources. | |
159 RECORDING_DEVICE_ID = 'render.monitor' | |
160 HUNDRED_PERCENT_VOLUME = '65536' | |
161 cmd = [_PACMD_PATH, 'set-source-volume', RECORDING_DEVICE_ID, | |
162 HUNDRED_PERCENT_VOLUME] | |
163 | |
164 logging.debug('Running command: %s', ' '.join(cmd)) | |
165 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) | |
166 output, error = p.communicate() | |
167 if p.returncode != 0: | |
168 logging.error('Error forcing mic volume to 100%%: %s\n%s', output, error) | |
OLD | NEW |