audio_helper: Refactor to use callback functions instead of commands in loopback_test_channels.

It is more general, powerful, and straightforward.

BUG=None
TEST=Run related tests on parrot

Change-Id: If5a9519e141f7753af24cef32be9762e8e7ea55f
Reviewed-on: https://chromium-review.googlesource.com/177332
Reviewed-by: Owen Lin <owenlin@chromium.org>
Commit-Queue: Owen Lin <owenlin@chromium.org>
Tested-by: Owen Lin <owenlin@chromium.org>
diff --git a/client/cros/audio/audio_helper.py b/client/cros/audio/audio_helper.py
index 3904f5e..7d9d0ad 100644
--- a/client/cros/audio/audio_helper.py
+++ b/client/cros/audio/audio_helper.py
@@ -381,31 +381,6 @@
     '''
     utils.system('%s %s' % (record_command, tmpfile))
 
-
-class RecordSampleThread(threading.Thread):
-    '''Wraps the execution of arecord in a thread.'''
-    def __init__(self, recordfile, record_command=_DEFAULT_REC_COMMAND):
-        threading.Thread.__init__(self)
-        self._recordfile = recordfile
-        self._record_command = record_command
-
-    def run(self):
-        record_sample(self._recordfile, self._record_command)
-
-
-class RecordMixThread(threading.Thread):
-    '''
-    Wraps the execution of recording the mixed loopback stream in
-    cras_test_client in a thread.
-    '''
-    def __init__(self, recordfile, mix_command):
-        threading.Thread.__init__(self)
-        self._mix_command = mix_command
-        self._recordfile = recordfile
-
-    def run(self):
-        utils.system('%s %s' % (self._mix_command, self._recordfile))
-
 def create_wav_file(wav_dir, prefix=""):
     '''Creates a unique name for wav file.
 
@@ -417,60 +392,66 @@
     filename = "%s-%s.wav" % (prefix, time.time())
     return os.path.join(wav_dir, filename)
 
+def run_in_parallel(*funs):
+    threads = []
+    for f in funs:
+        t = threading.Thread(target=f)
+        t.start()
+        threads.append(t)
+
+    for t in threads:
+        t.join()
+
 def loopback_test_channels(noise_file_name, wav_dir,
-                           loopback_callback=None,
+                           playback_callback=None,
                            check_recorded_callback=check_audio_rms,
                            preserve_test_file=True,
                            num_channels = _DEFAULT_NUM_CHANNELS,
-                           record_command=_DEFAULT_REC_COMMAND,
-                           mix_command=None):
+                           record_callback=record_sample,
+                           mix_callback=None):
     '''Tests loopback on all channels.
 
     @param noise_file_name: Name of the file contains pre-recorded noise.
-    @param loopback_callback: The callback to do the loopback for
+    @param playback_callback: The callback to do the playback for
         one channel.
+    @param record_callback: The callback to do the recording.
     @param check_recorded_callback: The callback to check recorded file.
     @param preserve_test_file: Retain the recorded files for future debugging.
     '''
     for channel in xrange(num_channels):
-        reduced_file_name = create_wav_file(wav_dir,
-                                            "reduced-%d" % channel)
         record_file_name = create_wav_file(wav_dir,
                                            "record-%d" % channel)
-        record_thread = RecordSampleThread(record_file_name,
-                                           record_command)
-        record_thread.start()
+        functions = [lambda: record_callback(record_file_name)]
 
-        if mix_command:
-            mix_file_name = create_wav_file(wav_dir,
-                                            "mix-%d" % channel)
-            mix_thread = RecordMixThread(mix_file_name, mix_command)
-            mix_thread.start()
+        if playback_callback:
+            functions.append(lambda: playback_callback(channel))
 
-        if loopback_callback:
-            loopback_callback(channel)
+        if mix_callback:
+            mix_file_name = create_wav_file(wav_dir, "mix-%d" % channel)
+            functions.append(lambda: mix_callback(mix_file_name))
 
-        if mix_command:
-            mix_thread.join()
+        run_in_parallel(*functions)
+
+        if mix_callback:
             sox_output_mix = sox_stat_output(mix_file_name, channel)
             rms_val_mix = get_audio_rms(sox_output_mix)
             logging.info('Got mixed audio RMS value of %f.', rms_val_mix)
 
-        record_thread.join()
         sox_output_record = sox_stat_output(record_file_name, channel)
         rms_val_record = get_audio_rms(sox_output_record)
         logging.info('Got recorded audio RMS value of %f.', rms_val_record)
 
+        reduced_file_name = create_wav_file(wav_dir,
+                                            "reduced-%d" % channel)
         noise_reduce_file(record_file_name, noise_file_name,
                           reduced_file_name)
 
-        sox_output_reduced = sox_stat_output(reduced_file_name,
-                                             channel)
+        sox_output_reduced = sox_stat_output(reduced_file_name, channel)
 
         if not preserve_test_file:
             os.unlink(reduced_file_name)
             os.unlink(record_file_name)
-            if mix_command:
+            if mix_callback:
                 os.unlink(mix_file_name)
 
         check_recorded_callback(sox_output_reduced)
diff --git a/client/site_tests/audio_SuspendResumeStress/audio_SuspendResumeStress.py b/client/site_tests/audio_SuspendResumeStress/audio_SuspendResumeStress.py
index 10af9b6..b88a5a9 100644
--- a/client/site_tests/audio_SuspendResumeStress/audio_SuspendResumeStress.py
+++ b/client/site_tests/audio_SuspendResumeStress/audio_SuspendResumeStress.py
@@ -60,13 +60,17 @@
         for _ in xrange(_DEFAULT_ITERATIONS):
             logging.info('Start %s audio verification before suspend.' % _)
             self.play_media()
+
+            def record_callback(filename):
+                audio_helper.record_sample(filename, self._cmd_rec)
+
             audio_helper.loopback_test_channels(noise_file_name,
                     self.resultsdir,
                     None,
                     lambda x:self.check_recorded(x, _),
                     preserve_test_file=False,
                     num_channels=self._num_channels,
-                    record_command=self._cmd_rec)
+                    record_callback=record_callback)
             logging.info('End %s audio verification before suspend.' % _)
             logging.info('Start %s suspend/resume.' % _)
             self._suspender.suspend(_DEFAULT_SUSPEND_DURATION)
@@ -79,7 +83,7 @@
                     lambda x:self.check_recorded(x, _),
                     preserve_test_file=False,
                     num_channels=self._num_channels,
-                    record_command=self._cmd_rec)
+                    record_callback=record_callback)
             logging.info('End %s audio verification after suspend.' % _)
 
     def play_media(self):
diff --git a/client/site_tests/audiovideo_CRASFormatConversion/audiovideo_CRASFormatConversion.py b/client/site_tests/audiovideo_CRASFormatConversion/audiovideo_CRASFormatConversion.py
index 5deee8f..8617bd5 100755
--- a/client/site_tests/audiovideo_CRASFormatConversion/audiovideo_CRASFormatConversion.py
+++ b/client/site_tests/audiovideo_CRASFormatConversion/audiovideo_CRASFormatConversion.py
@@ -114,6 +114,9 @@
         noise_proc = subprocess.Popen(shlex.split(cmd))
         noise_proc.wait();
 
+        def record_callback(filename):
+            audio_helper.record_sample(filename, self._rec_cmd)
+
         # Try all sample rate pairs.
         for primary in _TEST_SAMPLE_RATES:
             for secondary in _TEST_SAMPLE_RATES:
@@ -124,7 +127,7 @@
                                                             primary, secondary),
                         lambda out: audio_helper.check_audio_rms(
                                 out, sox_threshold=_MIN_SOX_RMS_VALUE),
-                        record_command=self._rec_cmd)
+                        record_callback=record_callback)
 
         # Record at all sample rates
         for rate in _TEST_SAMPLE_RATES:
diff --git a/client/site_tests/audiovideo_LineOutToMicInLoopback/audiovideo_LineOutToMicInLoopback.py b/client/site_tests/audiovideo_LineOutToMicInLoopback/audiovideo_LineOutToMicInLoopback.py
index 7e46ddd..35105e9 100755
--- a/client/site_tests/audiovideo_LineOutToMicInLoopback/audiovideo_LineOutToMicInLoopback.py
+++ b/client/site_tests/audiovideo_LineOutToMicInLoopback/audiovideo_LineOutToMicInLoopback.py
@@ -70,11 +70,14 @@
                        (self._record_duration, self._wav_path))
                 utils.system(cmd)
 
+            def record_callback(filename):
+                audio_helper.record_sample(filename, rec_cmd);
+
             audio_helper.loopback_test_channels(noise_file.name,
                                                 self.resultsdir,
                                                 play_wav,
                                                 num_channels=self._num_channels,
-                                                record_command=rec_cmd)
+                                                record_callback=record_callback)
 
     def loopback_test_cras(self):
         """Uses cras_test_client to test audio on CRAS."""
@@ -96,8 +99,11 @@
                        (self._record_duration, self._wav_path))
                 utils.system(cmd)
 
+            def record_callback(filename):
+                audio_helper.record_sample(filename, rec_cmd);
+
             audio_helper.loopback_test_channels(noise_file.name,
                                                 self.resultsdir,
                                                 play_wav,
                                                 num_channels=self._num_channels,
-                                                record_command=rec_cmd)
+                                                record_callback=record_callback)
diff --git a/client/site_tests/desktopui_AudioFeedback/desktopui_AudioFeedback.py b/client/site_tests/desktopui_AudioFeedback/desktopui_AudioFeedback.py
index d1be8dc..811b991 100644
--- a/client/site_tests/desktopui_AudioFeedback/desktopui_AudioFeedback.py
+++ b/client/site_tests/desktopui_AudioFeedback/desktopui_AudioFeedback.py
@@ -86,11 +86,17 @@
             # will be overriden by Chrome.
             audio_helper.set_volume_levels(self._volume_level, self._capture_gain)
 
+            def record_callback(filename):
+                audio_helper.record_sample(filename, self._rec_cmd)
+
+            def mix_callback(filename):
+                utils.system("%s %s" % (self._mix_cmd, filename))
+
             # Play the same video to test all channels.
             self.play_video(lambda: audio_helper.loopback_test_channels(
                                             noise_file_name,
                                             self.resultsdir,
                                             num_channels=self._num_channels,
-                                            record_command=self._rec_cmd,
-                                            mix_command=self._mix_cmd),
+                                            record_callback=record_callback,
+                                            mix_callback=mix_callback),
                             cr.browser.tabs[0])
diff --git a/client/site_tests/desktopui_MediaAudioFeedback/desktopui_MediaAudioFeedback.py b/client/site_tests/desktopui_MediaAudioFeedback/desktopui_MediaAudioFeedback.py
index 6920d0d..d6da7b8 100644
--- a/client/site_tests/desktopui_MediaAudioFeedback/desktopui_MediaAudioFeedback.py
+++ b/client/site_tests/desktopui_MediaAudioFeedback/desktopui_MediaAudioFeedback.py
@@ -4,6 +4,7 @@
 
 import logging, tempfile
 
+from autotest_lib.client.bin import utils
 from autotest_lib.client.common_lib import error
 from autotest_lib.client.cros import cros_ui_test, httpd
 from autotest_lib.client.cros.audio import audio_helper
@@ -75,6 +76,13 @@
         with tempfile.NamedTemporaryFile(mode='w+t') as noise_file:
             logging.info('Noise file: %s', noise_file.name)
             audio_helper.record_sample(noise_file.name, self._rec_cmd)
+
+            def record_callback(filename):
+                audio_helper.record_sample(filename, self._rec_cmd)
+
+            def mix_callback(filename):
+                utils.system("%s %s" % (self._mix_cmd, filename))
+
             # Test each media file for all channels.
             for media_file in _MEDIA_FORMATS:
                 audio_helper.loopback_test_channels(noise_file.name,
@@ -82,8 +90,8 @@
                         lambda channel: self.play_media(media_file),
                         self.wait_player_end_then_check_recorded,
                         num_channels=self._num_channels,
-                        record_command=self._rec_cmd,
-                        mix_command=self._mix_cmd)
+                        record_callback=record_callback,
+                        mix_callback=mix_callback)
 
     def wait_player_end_then_check_recorded(self, sox_output):
         """Wait for player ends playing and then check for recorded result.
diff --git a/client/site_tests/factory_AudioLoop/factory_AudioLoop.py b/client/site_tests/factory_AudioLoop/factory_AudioLoop.py
index 1310fd0..21aebbf 100755
--- a/client/site_tests/factory_AudioLoop/factory_AudioLoop.py
+++ b/client/site_tests/factory_AudioLoop/factory_AudioLoop.py
@@ -136,8 +136,11 @@
 
     def audio_loopback(self):
         for input_device in self._input_devices:
-            rec_cmd = 'arecord -D %s -f dat -d %f' % (input_device,
-                                                      self._duration)
+
+            def record_callback(filename):
+                rec_cmd = 'arecord -D %s -f dat -d %f' % (input_device,
+                                                          self._duration)
+                audio_helper.record_sample(filename, rec_cmd)
 
             # TODO(hychao): split deps and I/O devices to different
             # utils so we can setup deps only once.
@@ -154,7 +157,7 @@
                                                               self._freq,
                                                               self._duration),
                             self.check_recorded_audio,
-                            record_command=rec_cmd)
+                            record_callback=record_callback)
 
         if self._test_result:
             self.ui.CallJSFunction('testPassResult')
diff --git a/client/site_tests/factory_Connector/factory_Connector.py b/client/site_tests/factory_Connector/factory_Connector.py
index 8c67afd..62fe8c0 100644
--- a/client/site_tests/factory_Connector/factory_Connector.py
+++ b/client/site_tests/factory_Connector/factory_Connector.py
@@ -499,14 +499,17 @@
                 errors.append('Frequency not match, expect %d but got %d' %
                         (test_freq, freq))
 
-        rec_cmd = 'arecord -D hw:0,0 -d %d -f dat' % loop_duration
+        def record_callback(filename):
+            rec_cmd = 'arecord -D hw:0,0 -d %d -f dat' % loop_duration
+            audio_helper.record_sample(filename, rec_cmd)
+
         with tempfile.NamedTemporaryFile(mode='w+t') as noise_file:
             audio_helper.record_sample(noise_file.name)
             audio_helper.loopback_test_channels(noise_file.name,
                     self.resultsdir,
                     lambda ch: playback_sine(),
                     check_loop_output,
-                    record_command=rec_cmd)
+                    record_callback=record_callback)
         return errors
 
     def run_once(self, config_file):