audio_test_utils.py revision a975ead3e88aeedb1923fcfc153b32d7452dc6ee
1# Copyright 2015 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""This module provides the test utilities for audio tests using chameleon."""
6
7# TODO (cychiang) Move test utilities from chameleon_audio_helpers
8# to this module.
9
10import logging
11import multiprocessing
12import os
13import pprint
14import re
15import time
16from contextlib import contextmanager
17
18from autotest_lib.client.common_lib import error
19from autotest_lib.client.cros import constants
20from autotest_lib.client.cros.audio import audio_analysis
21from autotest_lib.client.cros.audio import audio_data
22from autotest_lib.client.cros.audio import audio_helper
23from autotest_lib.client.cros.audio import audio_quality_measurement
24from autotest_lib.client.cros.chameleon import chameleon_audio_ids
25
26CHAMELEON_AUDIO_IDS_TO_CRAS_NODE_TYPES = {
27       chameleon_audio_ids.CrosIds.HDMI: 'HDMI',
28       chameleon_audio_ids.CrosIds.HEADPHONE: 'HEADPHONE',
29       chameleon_audio_ids.CrosIds.EXTERNAL_MIC: 'MIC',
30       chameleon_audio_ids.CrosIds.SPEAKER: 'INTERNAL_SPEAKER',
31       chameleon_audio_ids.CrosIds.INTERNAL_MIC: 'INTERNAL_MIC',
32       chameleon_audio_ids.CrosIds.BLUETOOTH_HEADPHONE: 'BLUETOOTH',
33       chameleon_audio_ids.CrosIds.BLUETOOTH_MIC: 'BLUETOOTH',
34       chameleon_audio_ids.CrosIds.USBIN: 'USB',
35       chameleon_audio_ids.CrosIds.USBOUT: 'USB',
36}
37
38
39def cros_port_id_to_cras_node_type(port_id):
40    """Gets Cras node type from Cros port id.
41
42    @param port_id: A port id defined in chameleon_audio_ids.CrosIds.
43
44    @returns: A Cras node type defined in cras_utils.CRAS_NODE_TYPES.
45
46    """
47    return CHAMELEON_AUDIO_IDS_TO_CRAS_NODE_TYPES[port_id]
48
49
50def check_output_port(audio_facade, port_id):
51    """Checks selected output node on Cros device is correct for a port.
52
53    @param port_id: A port id defined in chameleon_audio_ids.CrosIds.
54
55    """
56    output_node_type = cros_port_id_to_cras_node_type(port_id)
57    check_audio_nodes(audio_facade, ([output_node_type], None))
58
59
60def check_input_port(audio_facade, port_id):
61    """Checks selected input node on Cros device is correct for a port.
62
63    @param port_id: A port id defined in chameleon_audio_ids.CrosIds.
64
65    """
66    input_node_type = cros_port_id_to_cras_node_type(port_id)
67    check_audio_nodes(audio_facade, (None, [input_node_type]))
68
69
70def check_audio_nodes(audio_facade, audio_nodes):
71    """Checks the node selected by Cros device is correct.
72
73    @param audio_facade: A RemoteAudioFacade to access audio functions on
74                         Cros device.
75
76    @param audio_nodes: A tuple (out_audio_nodes, in_audio_nodes) containing
77                        expected selected output and input nodes.
78
79    @raises: error.TestFail if the nodes selected by Cros device are not expected.
80
81    """
82    curr_out_nodes, curr_in_nodes = audio_facade.get_selected_node_types()
83    out_audio_nodes, in_audio_nodes = audio_nodes
84    if (in_audio_nodes != None and
85        sorted(curr_in_nodes) != sorted(in_audio_nodes)):
86        raise error.TestFail('Wrong input node(s) selected %s '
87                'instead %s!' % (str(curr_in_nodes), str(in_audio_nodes)))
88    if (out_audio_nodes != None and
89        sorted(curr_out_nodes) != sorted(out_audio_nodes)):
90        raise error.TestFail('Wrong output node(s) selected %s '
91                'instead %s!' % (str(curr_out_nodes), str(out_audio_nodes)))
92
93
94def check_plugged_nodes(audio_facade, audio_nodes):
95    """Checks the nodes that are currently plugged on Cros device are correct.
96
97    @param audio_facade: A RemoteAudioFacade to access audio functions on
98                         Cros device.
99
100    @param audio_nodes: A tuple (out_audio_nodes, in_audio_nodes) containing
101                        expected plugged output and input nodes.
102
103    @raises: error.TestFail if the plugged nodes on Cros device are not expected.
104
105    """
106    curr_out_nodes, curr_in_nodes = audio_facade.get_plugged_node_types()
107    out_audio_nodes, in_audio_nodes = audio_nodes
108    if (in_audio_nodes != None and
109        sorted(curr_in_nodes) != sorted(in_audio_nodes)):
110        raise error.TestFail('Wrong input node(s) plugged %s '
111                'instead %s!' % (str(curr_in_nodes), str(in_audio_nodes)))
112    if (out_audio_nodes != None and
113        sorted(curr_out_nodes) != sorted(out_audio_nodes)):
114        raise error.TestFail('Wrong output node(s) plugged %s '
115                'instead %s!' % (str(curr_out_nodes), str(out_audio_nodes)))
116
117
118def bluetooth_nodes_plugged(audio_facade):
119    """Checks bluetooth nodes are plugged.
120
121    @param audio_facade: A RemoteAudioFacade to access audio functions on
122                         Cros device.
123
124    @raises: error.TestFail if either input or output bluetooth node is
125             not plugged.
126
127    """
128    curr_out_nodes, curr_in_nodes = audio_facade.get_plugged_node_types()
129    return 'BLUETOOTH' in curr_out_nodes and 'BLUETOOTH' in curr_in_nodes
130
131
132def _get_board_name(host):
133    """Gets the board name.
134
135    @param host: The CrosHost object.
136
137    @returns: The board name.
138
139    """
140    return host.get_board().split(':')[1]
141
142
143def has_internal_speaker(host):
144    """Checks if the Cros device has speaker.
145
146    @param host: The CrosHost object.
147
148    @returns: True if Cros device has internal speaker. False otherwise.
149
150    """
151    board_name = _get_board_name(host)
152    if host.get_board_type() == 'CHROMEBOX' and board_name != 'stumpy':
153        logging.info('Board %s does not have speaker.', board_name)
154        return False
155    return True
156
157
158def has_internal_microphone(host):
159    """Checks if the Cros device has internal microphone.
160
161    @param host: The CrosHost object.
162
163    @returns: True if Cros device has internal microphone. False otherwise.
164
165    """
166    board_name = _get_board_name(host)
167    if host.get_board_type() == 'CHROMEBOX':
168        logging.info('Board %s does not have internal microphone.', board_name)
169        return False
170    return True
171
172
173def has_headphone(host):
174    """Checks if the Cros device has headphone.
175
176    @param host: The CrosHost object.
177
178    @returns: True if Cros device has headphone. False otherwise.
179
180    """
181    board_name = _get_board_name(host)
182    if host.get_board_type() == 'CHROMEBIT':
183        logging.info('Board %s does not have headphone.', board_name)
184        return False
185    return True
186
187
188def suspend_resume(host, suspend_time_secs, resume_network_timeout_secs=50):
189    """Performs the suspend/resume on Cros device.
190
191    @param suspend_time_secs: Time in seconds to let Cros device suspend.
192    @resume_network_timeout_secs: Time in seconds to let Cros device resume and
193                                  obtain network.
194    """
195    def action_suspend():
196        """Calls the host method suspend."""
197        host.suspend(suspend_time=suspend_time_secs)
198
199    boot_id = host.get_boot_id()
200    proc = multiprocessing.Process(target=action_suspend)
201    logging.info("Suspending...")
202    proc.daemon = True
203    proc.start()
204    host.test_wait_for_sleep(suspend_time_secs / 3)
205    logging.info("DUT suspended! Waiting to resume...")
206    host.test_wait_for_resume(
207            boot_id, suspend_time_secs + resume_network_timeout_secs)
208    logging.info("DUT resumed!")
209
210
211def dump_cros_audio_logs(host, audio_facade, directory, suffix=''):
212    """Dumps logs for audio debugging from Cros device.
213
214    @param host: The CrosHost object.
215    @param audio_facade: A RemoteAudioFacade to access audio functions on
216                         Cros device.
217    @directory: The directory to dump logs.
218
219    """
220    def get_file_path(name):
221        """Gets file path to dump logs.
222
223        @param name: The file name.
224
225        @returns: The file path with an optional suffix.
226
227        """
228        file_name = '%s.%s' % (name, suffix) if suffix else name
229        file_path = os.path.join(directory, file_name)
230        return file_path
231
232    audio_facade.dump_diagnostics(get_file_path('audio_diagnostics.txt'))
233
234    host.get_file('/var/log/messages', get_file_path('messages'))
235
236    host.get_file(constants.MULTIMEDIA_XMLRPC_SERVER_LOG_FILE,
237                  get_file_path('multimedia_xmlrpc_server.log'))
238
239
240def examine_audio_diagnostics(path):
241    """Examines audio diagnostic content.
242
243    @param path: Path to audio diagnostic file.
244
245    @returns: Warning messages or ''.
246
247    """
248    warning_msgs = []
249    line_number = 1
250
251    underrun_pattern = re.compile('num_underruns: (\d*)')
252
253    with open(path) as f:
254        for line in f.readlines():
255
256            # Check for number of underruns.
257            search_result = underrun_pattern.search(line)
258            if search_result:
259                num_underruns = int(search_result.group(1))
260                if num_underruns != 0:
261                    warning_msgs.append(
262                            'Found %d underrun at line %d: %s' % (
263                                    num_underruns, line_number, line))
264
265            # TODO(cychiang) add other check like maximum client reply delay.
266            line_number = line_number + 1
267
268    if warning_msgs:
269        return ('Found issue in audio diganostics result : %s' %
270                '\n'.join(warning_msgs))
271
272    logging.info('audio_diagnostic result looks fine')
273    return ''
274
275
276@contextmanager
277def monitor_no_nodes_changed(audio_facade, callback=None):
278    """Context manager to monitor nodes changed signal on Cros device.
279
280    Starts the counter in the beginning. Stops the counter in the end to make
281    sure there is no NodesChanged signal during the try block.
282
283    E.g. with monitor_no_nodes_changed(audio_facade):
284             do something on playback/recording
285
286    @param audio_facade: A RemoteAudioFacade to access audio functions on
287                         Cros device.
288    @param fail_callback: The callback to call before raising TestFail
289                          when there is unexpected NodesChanged signals.
290
291    @raises: error.TestFail if there is NodesChanged signal on
292             Cros device during the context.
293
294    """
295    try:
296        audio_facade.start_counting_signal('NodesChanged')
297        yield
298    finally:
299        count = audio_facade.stop_counting_signal()
300        if count:
301            message = 'Got %d unexpected NodesChanged signal' % count
302            logging.error(message)
303            if callback:
304                callback()
305            raise error.TestFail(message)
306
307
308# The second dominant frequency should have energy less than -26dB of the
309# first dominant frequency in the spectrum.
310_DEFAULT_SECOND_PEAK_RATIO = 0.05
311
312# Tolerate more noise for bluetooth audio using HSP.
313_HSP_SECOND_PEAK_RATIO = 0.2
314
315# Tolerate more noise for speaker.
316_SPEAKER_SECOND_PEAK_RATIO = 0.1
317
318# Tolerate more noise for internal microphone.
319_INTERNAL_MIC_SECOND_PEAK_RATIO = 0.2
320
321# maximum tolerant noise level
322DEFAULT_TOLERANT_NOISE_LEVEL = 0.01
323
324# If relative error of two durations is less than 0.2,
325# they will be considered equivalent.
326DEFAULT_EQUIVALENT_THRESHOLD = 0.2
327
328# The frequency at lower than _DC_FREQ_THRESHOLD should have coefficient
329# smaller than _DC_COEFF_THRESHOLD.
330_DC_FREQ_THRESHOLD = 0.001
331_DC_COEFF_THRESHOLD = 0.01
332
333def get_second_peak_ratio(source_id, recorder_id, is_hsp=False):
334    """Gets the second peak ratio suitable for use case.
335
336    @param source_id: ID defined in chameleon_audio_ids for source widget.
337    @param recorder_id: ID defined in chameleon_audio_ids for recorder widget.
338    @param is_hsp: For bluetooth HSP use case.
339
340    @returns: A float for proper second peak ratio to be used in
341              check_recorded_frequency.
342    """
343    if is_hsp:
344        return _HSP_SECOND_PEAK_RATIO
345    elif source_id == chameleon_audio_ids.CrosIds.SPEAKER:
346        return _SPEAKER_SECOND_PEAK_RATIO
347    elif recorder_id == chameleon_audio_ids.CrosIds.INTERNAL_MIC:
348        return _INTERNAL_MIC_SECOND_PEAK_RATIO
349    else:
350        return _DEFAULT_SECOND_PEAK_RATIO
351
352
353# The deviation of estimated dominant frequency from golden frequency.
354DEFAULT_FREQUENCY_DIFF_THRESHOLD = 5
355
356def check_recorded_frequency(
357        golden_file, recorder,
358        second_peak_ratio=_DEFAULT_SECOND_PEAK_RATIO,
359        frequency_diff_threshold=DEFAULT_FREQUENCY_DIFF_THRESHOLD,
360        ignore_frequencies=None, check_anomaly=False, check_artifacts=False,
361        mute_durations=None, volume_changes=None,
362        tolerant_noise_level=DEFAULT_TOLERANT_NOISE_LEVEL):
363    """Checks if the recorded data contains sine tone of golden frequency.
364
365    @param golden_file: An AudioTestData object that serves as golden data.
366    @param recorder: An AudioWidget used in the test to record data.
367    @param second_peak_ratio: The test fails when the second dominant
368                              frequency has coefficient larger than this
369                              ratio of the coefficient of first dominant
370                              frequency.
371    @param frequency_diff_threshold: The maximum difference between estimated
372                                     frequency of test signal and golden
373                                     frequency. This value should be small for
374                                     signal passed through line.
375    @param ignore_frequencies: A list of frequencies to be ignored. The
376                               component in the spectral with frequency too
377                               close to the frequency in the list will be
378                               ignored. The comparison of frequencies uses
379                               frequency_diff_threshold as well.
380    @param check_anomaly: True to check anomaly in the signal.
381    @param check_artifacts: True to check artifacts in the signal.
382    @param mute_durations: Each duration of mute in seconds in the signal.
383    @param volume_changes: A list containing alternative -1 for decreasing
384                           volume and +1 for increasing volume.
385    @param tolerant_noise_level: The maximum noise level can be tolerated
386
387    @returns: A list containing tuples of (dominant_frequency, coefficient) for
388              valid channels. Coefficient can be a measure of signal magnitude
389              on that dominant frequency. Invalid channels where golden_channel
390              is None are ignored.
391
392    @raises error.TestFail if the recorded data does not contain sine tone of
393            golden frequency.
394
395    """
396    if not ignore_frequencies:
397        ignore_frequencies = []
398
399    # Also ignore harmonics of ignore frequencies.
400    ignore_frequencies_harmonics = []
401    for ignore_freq in ignore_frequencies:
402        ignore_frequencies_harmonics += [ignore_freq * n for n in xrange(1, 4)]
403
404    data_format = recorder.data_format
405    recorded_data = audio_data.AudioRawData(
406            binary=recorder.get_binary(),
407            channel=data_format['channel'],
408            sample_format=data_format['sample_format'])
409
410    errors = []
411    dominant_spectrals = []
412
413    for test_channel, golden_channel in enumerate(recorder.channel_map):
414        if golden_channel is None:
415            logging.info('Skipped channel %d', test_channel)
416            continue
417
418        signal = recorded_data.channel_data[test_channel]
419        saturate_value = audio_data.get_maximum_value_from_sample_format(
420                data_format['sample_format'])
421        logging.debug('Channel %d max signal: %f', test_channel, max(signal))
422        normalized_signal = audio_analysis.normalize_signal(
423                signal, saturate_value)
424        logging.debug('saturate_value: %f', saturate_value)
425        logging.debug('max signal after normalized: %f', max(normalized_signal))
426        spectral = audio_analysis.spectral_analysis(
427                normalized_signal, data_format['rate'])
428        logging.debug('spectral: %s', spectral)
429
430        if not spectral:
431            errors.append(
432                    'Channel %d: Can not find dominant frequency.' %
433                            test_channel)
434
435        golden_frequency = golden_file.frequencies[golden_channel]
436        logging.debug('Checking channel %s spectral %s against frequency %s',
437                test_channel, spectral, golden_frequency)
438
439        dominant_frequency = spectral[0][0]
440
441        if (abs(dominant_frequency - golden_frequency) >
442            frequency_diff_threshold):
443            errors.append(
444                    'Channel %d: Dominant frequency %s is away from golden %s' %
445                    (test_channel, dominant_frequency, golden_frequency))
446
447        if check_anomaly:
448            detected_anomaly = audio_analysis.anomaly_detection(
449                    signal=normalized_signal,
450                    rate=data_format['rate'],
451                    freq=golden_frequency)
452            if detected_anomaly:
453                errors.append(
454                        'Channel %d: Detect anomaly near these time: %s' %
455                        (test_channel, detected_anomaly))
456            else:
457                logging.info(
458                        'Channel %d: Quality is good as there is no anomaly',
459                        test_channel)
460
461        if check_artifacts or mute_durations or volume_changes:
462            result = audio_quality_measurement.quality_measurement(
463                                        normalized_signal,
464                                        data_format['rate'],
465                                        dominant_frequency=dominant_frequency)
466            logging.debug('Quality measurement result:\n%s', pprint.pformat(result))
467            if check_artifacts:
468                if len(result['artifacts']['noise_before_playback']) > 0:
469                    errors.append(
470                        'Channel %d: Detects artifacts before playing near'
471                        ' these time and duration: %s' %
472                        (test_channel,
473                         str(result['artifacts']['noise_before_playback'])))
474
475                if len(result['artifacts']['noise_after_playback']) > 0:
476                    errors.append(
477                        'Channel %d: Detects artifacts after playing near'
478                        ' these time and duration: %s' %
479                        (test_channel,
480                         str(result['artifacts']['noise_after_playback'])))
481
482            if mute_durations:
483                delays = result['artifacts']['delay_during_playback']
484                delay_durations = []
485                for x in delays:
486                    delay_durations.append(x[1])
487                mute_matched, delay_matched = longest_common_subsequence(
488                        mute_durations,
489                        delay_durations,
490                        DEFAULT_EQUIVALENT_THRESHOLD)
491
492                # updated delay list
493                new_delays = [delays[i]
494                                for i in delay_matched if not delay_matched[i]]
495
496                result['artifacts']['delay_during_playback'] = new_delays
497
498                unmatched_mutes = [mute_durations[i]
499                                for i in mute_matched if not mute_matched[i]]
500
501                if len(unmatched_mutes) > 0:
502                    errors.append(
503                        'Channel %d: Unmatched mute duration: %s' %
504                        (test_channel, unmatched_mutes))
505
506            if check_artifacts:
507                if len(result['artifacts']['delay_during_playback']) > 0:
508                    errors.append(
509                        'Channel %d: Detects delay during playing near'
510                        ' these time and duration: %s' %
511                        (test_channel,
512                         result['artifacts']['delay_during_playback']))
513
514                if len(result['artifacts']['burst_during_playback']) > 0:
515                    errors.append(
516                        'Channel %d: Detects burst/pop near these time: %s' %
517                        (test_channel,
518                         result['artifacts']['burst_during_playback']))
519
520                if result['equivalent_noise_level'] > tolerant_noise_level:
521                    errors.append(
522                        'Channel %d: noise level is higher than tolerant'
523                        ' noise level: %f > %f' %
524                        (test_channel,
525                         result['equivalent_noise_level'],
526                         tolerant_noise_level))
527
528            if volume_changes:
529                matched = True
530                volume_changing = result['volume_changes']
531                if len(volume_changing) != len(volume_changes):
532                    matched = False
533                else:
534                    for i in xrange(len(volume_changing)):
535                        if volume_changing[i][1] != volume_changes[i]:
536                            matched = False
537                            break
538                if not matched:
539                    errors.append(
540                        'Channel %d: volume changing is not as expected, '
541                        'found changing time and events are: %s while '
542                        'expected changing events are %s'%
543                        (test_channel,
544                         volume_changing,
545                         volume_changes))
546
547        # Filter out the harmonics resulted from imperfect sin wave.
548        # This list is different for different channels.
549        harmonics = [dominant_frequency * n for n in xrange(2, 10)]
550
551        def should_be_ignored(frequency):
552            """Checks if frequency is close to any frequency in ignore list.
553
554            The ignore list is harmonics of frequency to be ignored
555            (like power noise), plus harmonics of dominant frequencies,
556            plus DC.
557
558            @param frequency: The frequency to be tested.
559
560            @returns: True if the frequency should be ignored. False otherwise.
561
562            """
563            for ignore_frequency in (ignore_frequencies_harmonics + harmonics
564                                     + [0.0]):
565                if (abs(frequency - ignore_frequency) <
566                    frequency_diff_threshold):
567                    logging.debug('Ignore frequency: %s', frequency)
568                    return True
569
570        # Checks DC is small enough.
571        for freq, coeff in spectral:
572            if freq < _DC_FREQ_THRESHOLD and coeff > _DC_COEFF_THRESHOLD:
573                errors.append(
574                        'Channel %d: Found large DC coefficient: '
575                        '(%f Hz, %f)' % (test_channel, freq, coeff))
576
577        # Filter out the frequencies to be ignored.
578        spectral = [x for x in spectral if not should_be_ignored(x[0])]
579
580        if len(spectral) > 1:
581            first_coeff = spectral[0][1]
582            second_coeff = spectral[1][1]
583            if second_coeff > first_coeff * second_peak_ratio:
584                errors.append(
585                        'Channel %d: Found large second dominant frequencies: '
586                        '%s' % (test_channel, spectral))
587
588        dominant_spectrals.append(spectral[0])
589
590    if errors:
591        raise error.TestFail(', '.join(errors))
592
593    return dominant_spectrals
594
595
596def longest_common_subsequence(list1, list2, equivalent_threshold):
597    """Finds longest common subsequence of list1 and list2
598
599    Such as list1: [0.3, 0.4],
600            list2: [0.001, 0.299, 0.002, 0.401, 0.001]
601            equivalent_threshold: 0.001
602    it will return matched1: [True, True],
603                   matched2: [False, True, False, True, False]
604
605    @param list1: a list of integer or float value
606    @param list2: a list of integer or float value
607    @param equivalent_threshold: two values are considered equivalent if their
608                                 relative error is less than
609                                 equivalent_threshold.
610
611    @returns: a tuple of list (matched_1, matched_2) indicating each item
612              of list1 and list2 are matched or not.
613
614    """
615    length1, length2 = len(list1), len(list2)
616    matching = [[0] * (length2 + 1)] * (length1 + 1)
617    # matching[i][j] is the maximum number of matched pairs for first i items
618    # in list1 and first j items in list2.
619    for i in xrange(length1):
620        for j in xrange(length2):
621            # Maximum matched pairs may be obtained without
622            # i-th item in list1 or without j-th item in list2
623            matching[i + 1][j + 1] = max(matching[i + 1][j],
624                                         matching[i][j + 1])
625            diff = abs(list1[i] - list2[j])
626            relative_error = diff / list1[i]
627            # If i-th item in list1 can be matched to j-th item in list2
628            if relative_error < equivalent_threshold:
629                matching[i + 1][j + 1] = matching[i][j] + 1
630
631    # Backtracking which item in list1 and list2 are matched
632    matched1 = [False] * length1
633    matched2 = [False] * length2
634    i, j = length1, length2
635    while i > 0 and j > 0:
636        # Maximum number is obtained by matching i-th item in list1
637        # and j-th one in list2.
638        if matching[i][j] == matching[i - 1][j - 1] + 1:
639            matched1[i - 1] = True
640            matched2[j - 1] = True
641            i, j = i - 1, j - 1
642        elif matching[i][j] == matching[i - 1][j]:
643            i -= 1
644        else:
645            j -= 1
646    return (matched1, matched2)
647
648
649def switch_to_hsp(audio_facade):
650    """Switches to HSP profile.
651
652    Selects bluetooth microphone and runs a recording process on Cros device.
653    This triggers bluetooth profile be switched from A2DP to HSP.
654    Note the user can call stop_recording on audio facade to stop the recording
655    process, or let multimedia_xmlrpc_server terminates it in its cleanup.
656
657    """
658    audio_facade.set_chrome_active_node_type(None, 'BLUETOOTH')
659    check_audio_nodes(audio_facade, (None, ['BLUETOOTH']))
660    audio_facade.start_recording(
661            dict(file_type='raw', sample_format='S16_LE', channel=2,
662                 rate=48000))
663
664
665def compare_recorded_correlation(golden_file, recorder, parameters=None):
666    """Checks recorded audio in an AudioInputWidget against a golden file.
667
668    Compares recorded data with golden data by cross correlation method.
669    Refer to audio_helper.compare_data for details of comparison.
670
671    @param golden_file: An AudioTestData object that serves as golden data.
672    @param recorder: An AudioInputWidget that has recorded some audio data.
673    @param parameters: A dict containing parameters for method.
674
675    """
676    logging.info('Comparing recorded data with golden file %s ...',
677                 golden_file.path)
678    audio_helper.compare_data_correlation(
679            golden_file.get_binary(), golden_file.data_format,
680            recorder.get_binary(), recorder.data_format, recorder.channel_map,
681            parameters)
682