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