blob: 7ded30760431803d3a8076cb26f941c461a7e3aa [file] [log] [blame]
Cheng-Yi Chiang348270a2014-09-01 19:59:38 +08001#!/usr/bin/env python
2# Copyright (c) 2014 The Chromium OS 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"""This module provides abstraction of audio data."""
7
8import contextlib
9import struct
10import StringIO
11
12
13"""The dict containing information on how to parse sample from raw data.
14
15Keys: The sample format as in aplay command.
16Values: A dict containing:
17 message: Human-readable sample format.
18 struct_format: Format used in struct.unpack.
19 size_bytes: Number of bytes for one sample.
20"""
21SAMPLE_FORMATS = dict(
22 S32_LE=dict(
23 message='Signed 32-bit integer, little-endian',
24 struct_format='<i',
25 size_bytes=4),
26 S16_LE=dict(
27 message='Signed 16-bit integer, little-endian',
28 struct_format='<h',
29 size_bytes=2))
30
31
32class AudioRawData(object):
33 """The abstraction of audio raw data.
34
35 @property channel: The number of channels.
36 @property channel_data: A list of lists containing samples in each channel.
37 E.g., The third sample in the second channel is
38 channel_data[1][2].
39 @property sample_format: The sample format which should be one of the keys
40 in audio_data.SAMPLE_FORMATS.
41 """
42 def __init__(self, binary, channel, sample_format):
43 """Initializes an AudioRawData.
44
45 @param binary: A string containing binary data.
46 @param channel: The number of channels.
47 @param sample_format: One of the keys in audio_data.SAMPLE_FORMATS.
48 """
49 self.channel = channel
50 self.channel_data = [[] for _ in xrange(self.channel)]
51 self.sample_format = sample_format
52 self.read_binary(binary)
53
54
55 def read_one_sample(self, handle):
56 """Reads one sample from handle.
57
58 @param handle: A handle that supports read() method.
59
60 @return: A number read from file handle based on sample format.
61 None if there is no data to read.
62 """
63 data = handle.read(SAMPLE_FORMATS[self.sample_format]['size_bytes'])
64 if data == '':
65 return None
66 number, = struct.unpack(
67 SAMPLE_FORMATS[self.sample_format]['struct_format'], data)
68 return number
69
70
71 def read_binary(self, binary):
72 """Reads samples from binary and fills channel_data.
73
74 Reads one sample for each channel and repeats until the end of
75 input binary.
76
77 @param binary: A string containing binary data.
78 """
79 channel_index = 0
80 with contextlib.closing(StringIO.StringIO(binary)) as f:
81 number = self.read_one_sample(f)
82 while number is not None:
83 self.channel_data[channel_index].append(number)
84 channel_index = (channel_index + 1) % self.channel
85 number = self.read_one_sample(f)