[autotest] audio: Add audio_data module and AudioRawData class

Add audio_data module to abstract audio data.
Add AudioRawData class to provide access to audio raw data. AudioRawData
handles reading and parsing raw file into samples on each channel.

BUG=chromium:398806
TEST=This patch will be used in the upcoming audiovideo_AudioBasicHDMI
test.

Change-Id: I4e7d4b3ce2bc1201f03db60b788a01b3df8cbc01
Reviewed-on: https://chromium-review.googlesource.com/215870
Reviewed-by: Hsinyu Chao <hychao@chromium.org>
Tested-by: Cheng-Yi Chiang <cychiang@chromium.org>
Commit-Queue: Cheng-Yi Chiang <cychiang@chromium.org>
diff --git a/client/cros/audio/audio_data.py b/client/cros/audio/audio_data.py
new file mode 100644
index 0000000..7ded307
--- /dev/null
+++ b/client/cros/audio/audio_data.py
@@ -0,0 +1,85 @@
+#!/usr/bin/env python
+# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""This module provides abstraction of audio data."""
+
+import contextlib
+import struct
+import StringIO
+
+
+"""The dict containing information on how to parse sample from raw data.
+
+Keys: The sample format as in aplay command.
+Values: A dict containing:
+    message: Human-readable sample format.
+    struct_format: Format used in struct.unpack.
+    size_bytes: Number of bytes for one sample.
+"""
+SAMPLE_FORMATS = dict(
+        S32_LE=dict(
+                message='Signed 32-bit integer, little-endian',
+                struct_format='<i',
+                size_bytes=4),
+        S16_LE=dict(
+                message='Signed 16-bit integer, little-endian',
+                struct_format='<h',
+                size_bytes=2))
+
+
+class AudioRawData(object):
+    """The abstraction of audio raw data.
+
+    @property channel: The number of channels.
+    @property channel_data: A list of lists containing samples in each channel.
+                            E.g., The third sample in the second channel is
+                            channel_data[1][2].
+    @property sample_format: The sample format which should be one of the keys
+                             in audio_data.SAMPLE_FORMATS.
+    """
+    def __init__(self, binary, channel, sample_format):
+        """Initializes an AudioRawData.
+
+        @param binary: A string containing binary data.
+        @param channel: The number of channels.
+        @param sample_format: One of the keys in audio_data.SAMPLE_FORMATS.
+        """
+        self.channel = channel
+        self.channel_data = [[] for _ in xrange(self.channel)]
+        self.sample_format = sample_format
+        self.read_binary(binary)
+
+
+    def read_one_sample(self, handle):
+        """Reads one sample from handle.
+
+        @param handle: A handle that supports read() method.
+
+        @return: A number read from file handle based on sample format.
+                 None if there is no data to read.
+        """
+        data = handle.read(SAMPLE_FORMATS[self.sample_format]['size_bytes'])
+        if data == '':
+            return None
+        number, = struct.unpack(
+                SAMPLE_FORMATS[self.sample_format]['struct_format'], data)
+        return number
+
+
+    def read_binary(self, binary):
+        """Reads samples from binary and fills channel_data.
+
+        Reads one sample for each channel and repeats until the end of
+        input binary.
+
+        @param binary: A string containing binary data.
+        """
+        channel_index = 0
+        with contextlib.closing(StringIO.StringIO(binary)) as f:
+            number = self.read_one_sample(f)
+            while number is not None:
+                self.channel_data[channel_index].append(number)
+                channel_index = (channel_index + 1) % self.channel
+                number = self.read_one_sample(f)