xref: /aosp_15_r20/external/autotest/client/cros/audio/audio_data.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li#!/usr/bin/env python3
2*9c5db199SXin Li# Copyright 2014 The Chromium OS Authors. All rights reserved.
3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be
4*9c5db199SXin Li# found in the LICENSE file.
5*9c5db199SXin Li
6*9c5db199SXin Li"""This module provides abstraction of audio data."""
7*9c5db199SXin Li
8*9c5db199SXin Lifrom __future__ import absolute_import
9*9c5db199SXin Lifrom __future__ import division
10*9c5db199SXin Lifrom __future__ import print_function
11*9c5db199SXin Liimport contextlib
12*9c5db199SXin Liimport copy
13*9c5db199SXin Liimport numpy as np
14*9c5db199SXin Liimport struct
15*9c5db199SXin Lifrom six.moves import range
16*9c5db199SXin Liimport six
17*9c5db199SXin Li
18*9c5db199SXin Li
19*9c5db199SXin Li"""The dict containing information on how to parse sample from raw data.
20*9c5db199SXin Li
21*9c5db199SXin LiKeys: The sample format as in aplay command.
22*9c5db199SXin LiValues: A dict containing:
23*9c5db199SXin Li    message: Human-readable sample format.
24*9c5db199SXin Li    dtype_str: Data type used in numpy dtype.  Check
25*9c5db199SXin Li               https://docs.scipy.org/doc/numpy/reference/arrays.dtypes.html
26*9c5db199SXin Li               for supported data type.
27*9c5db199SXin Li    size_bytes: Number of bytes for one sample.
28*9c5db199SXin Li"""
29*9c5db199SXin LiSAMPLE_FORMATS = dict(
30*9c5db199SXin Li        S32_LE=dict(
31*9c5db199SXin Li                message='Signed 32-bit integer, little-endian',
32*9c5db199SXin Li                dtype_str='<i',
33*9c5db199SXin Li                size_bytes=4),
34*9c5db199SXin Li        S16_LE=dict(
35*9c5db199SXin Li                message='Signed 16-bit integer, little-endian',
36*9c5db199SXin Li                dtype_str='<i',
37*9c5db199SXin Li                size_bytes=2))
38*9c5db199SXin Li
39*9c5db199SXin Li
40*9c5db199SXin Lidef get_maximum_value_from_sample_format(sample_format):
41*9c5db199SXin Li    """Gets the maximum value from sample format.
42*9c5db199SXin Li
43*9c5db199SXin Li    @param sample_format: A key in SAMPLE_FORMAT.
44*9c5db199SXin Li
45*9c5db199SXin Li    @returns: The maximum value the sample can hold + 1.
46*9c5db199SXin Li
47*9c5db199SXin Li    """
48*9c5db199SXin Li    size_bits = SAMPLE_FORMATS[sample_format]['size_bytes'] * 8
49*9c5db199SXin Li    return 1 << (size_bits - 1)
50*9c5db199SXin Li
51*9c5db199SXin Li
52*9c5db199SXin Liclass AudioRawDataError(Exception):
53*9c5db199SXin Li    """Error in AudioRawData."""
54*9c5db199SXin Li    pass
55*9c5db199SXin Li
56*9c5db199SXin Li
57*9c5db199SXin Liclass AudioRawData(object):
58*9c5db199SXin Li    """The abstraction of audio raw data.
59*9c5db199SXin Li
60*9c5db199SXin Li    @property channel: The number of channels.
61*9c5db199SXin Li    @property channel_data: A list of lists containing samples in each channel.
62*9c5db199SXin Li                            E.g., The third sample in the second channel is
63*9c5db199SXin Li                            channel_data[1][2].
64*9c5db199SXin Li    @property sample_format: The sample format which should be one of the keys
65*9c5db199SXin Li                             in audio_data.SAMPLE_FORMATS.
66*9c5db199SXin Li    """
67*9c5db199SXin Li    def __init__(self, binary, channel, sample_format):
68*9c5db199SXin Li        """Initializes an AudioRawData.
69*9c5db199SXin Li
70*9c5db199SXin Li        @param binary: A string containing binary data. If binary is not None,
71*9c5db199SXin Li                       The samples in binary will be parsed and be filled into
72*9c5db199SXin Li                       channel_data.
73*9c5db199SXin Li        @param channel: The number of channels.
74*9c5db199SXin Li        @param sample_format: One of the keys in audio_data.SAMPLE_FORMATS.
75*9c5db199SXin Li        """
76*9c5db199SXin Li        self.channel = channel
77*9c5db199SXin Li        self.channel_data = [[] for _ in range(self.channel)]
78*9c5db199SXin Li        self.sample_format = sample_format
79*9c5db199SXin Li        if binary:
80*9c5db199SXin Li            self.read_binary(binary)
81*9c5db199SXin Li
82*9c5db199SXin Li
83*9c5db199SXin Li    def read_binary(self, binary):
84*9c5db199SXin Li        """Reads samples from binary and fills channel_data.
85*9c5db199SXin Li
86*9c5db199SXin Li        Reads samples of fixed width from binary string into a numpy array
87*9c5db199SXin Li        and shapes them into each channel.
88*9c5db199SXin Li
89*9c5db199SXin Li        @param binary: A string containing binary data.
90*9c5db199SXin Li        """
91*9c5db199SXin Li        sample_format_dict = SAMPLE_FORMATS[self.sample_format]
92*9c5db199SXin Li
93*9c5db199SXin Li        # The data type used in numpy fromstring function. For example,
94*9c5db199SXin Li        # <i4 for 32-bit signed int.
95*9c5db199SXin Li        np_dtype = '%s%d' % (sample_format_dict['dtype_str'],
96*9c5db199SXin Li                             sample_format_dict['size_bytes'])
97*9c5db199SXin Li
98*9c5db199SXin Li        # Reads data from a string into 1-D array.
99*9c5db199SXin Li        np_array = np.fromstring(binary, dtype=np_dtype)
100*9c5db199SXin Li        n_frames = len(np_array) // self.channel
101*9c5db199SXin Li        # Reshape np_array into an array of shape (n_frames, channel).
102*9c5db199SXin Li        np_array = np_array.reshape(n_frames, self.channel)
103*9c5db199SXin Li        # Transpose np_arrya so it becomes of shape (channel, n_frames).
104*9c5db199SXin Li        self.channel_data = np_array.transpose()
105