1#!/usr/bin/env python
2# Copyright 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 copy
10import numpy as np
11import struct
12import StringIO
13
14
15"""The dict containing information on how to parse sample from raw data.
16
17Keys: The sample format as in aplay command.
18Values: A dict containing:
19    message: Human-readable sample format.
20    dtype_str: Data type used in numpy dtype.  Check
21               https://docs.scipy.org/doc/numpy/reference/arrays.dtypes.html
22               for supported data type.
23    size_bytes: Number of bytes for one sample.
24"""
25SAMPLE_FORMATS = dict(
26        S32_LE=dict(
27                message='Signed 32-bit integer, little-endian',
28                dtype_str='<i',
29                size_bytes=4),
30        S16_LE=dict(
31                message='Signed 16-bit integer, little-endian',
32                dtype_str='<i',
33                size_bytes=2))
34
35
36def get_maximum_value_from_sample_format(sample_format):
37    """Gets the maximum value from sample format.
38
39    @param sample_format: A key in SAMPLE_FORMAT.
40
41    @returns: The maximum value the sample can hold + 1.
42
43    """
44    size_bits = SAMPLE_FORMATS[sample_format]['size_bytes'] * 8
45    return 1 << (size_bits - 1)
46
47
48class AudioRawDataError(Exception):
49    """Error in AudioRawData."""
50    pass
51
52
53class AudioRawData(object):
54    """The abstraction of audio raw data.
55
56    @property channel: The number of channels.
57    @property channel_data: A list of lists containing samples in each channel.
58                            E.g., The third sample in the second channel is
59                            channel_data[1][2].
60    @property sample_format: The sample format which should be one of the keys
61                             in audio_data.SAMPLE_FORMATS.
62    """
63    def __init__(self, binary, channel, sample_format):
64        """Initializes an AudioRawData.
65
66        @param binary: A string containing binary data. If binary is not None,
67                       The samples in binary will be parsed and be filled into
68                       channel_data.
69        @param channel: The number of channels.
70        @param sample_format: One of the keys in audio_data.SAMPLE_FORMATS.
71        """
72        self.channel = channel
73        self.channel_data = [[] for _ in xrange(self.channel)]
74        self.sample_format = sample_format
75        if binary:
76            self.read_binary(binary)
77
78
79    def read_binary(self, binary):
80        """Reads samples from binary and fills channel_data.
81
82        Reads samples of fixed width from binary string into a numpy array
83        and shapes them into each channel.
84
85        @param binary: A string containing binary data.
86        """
87        sample_format_dict = SAMPLE_FORMATS[self.sample_format]
88
89        # The data type used in numpy fromstring function. For example,
90        # <i4 for 32-bit signed int.
91        np_dtype = '%s%d' % (sample_format_dict['dtype_str'],
92                             sample_format_dict['size_bytes'])
93
94        # Reads data from a string into 1-D array.
95        np_array = np.fromstring(binary, dtype=np_dtype)
96        n_frames = len(np_array) / self.channel
97        # Reshape np_array into an array of shape (n_frames, channel).
98        np_array = np_array.reshape(n_frames, self.channel)
99        # Transpose np_arrya so it becomes of shape (channel, n_frames).
100        self.channel_data = np_array.transpose()
101