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 struct 11import StringIO 12 13 14"""The dict containing information on how to parse sample from raw data. 15 16Keys: The sample format as in aplay command. 17Values: A dict containing: 18 message: Human-readable sample format. 19 struct_format: Format used in struct.unpack. 20 size_bytes: Number of bytes for one sample. 21""" 22SAMPLE_FORMATS = dict( 23 S32_LE=dict( 24 message='Signed 32-bit integer, little-endian', 25 struct_format='<i', 26 size_bytes=4), 27 S16_LE=dict( 28 message='Signed 16-bit integer, little-endian', 29 struct_format='<h', 30 size_bytes=2)) 31 32 33def get_maximum_value_from_sample_format(sample_format): 34 """Gets the maximum value from sample format. 35 36 @param sample_format: A key in SAMPLE_FORMAT. 37 38 @returns: The maximum value the sample can hold + 1. 39 40 """ 41 size_bits = SAMPLE_FORMATS[sample_format]['size_bytes'] * 8 42 return 1 << (size_bits - 1) 43 44 45class AudioRawDataError(Exception): 46 """Error in AudioRawData.""" 47 pass 48 49 50class AudioRawData(object): 51 """The abstraction of audio raw data. 52 53 @property channel: The number of channels. 54 @property channel_data: A list of lists containing samples in each channel. 55 E.g., The third sample in the second channel is 56 channel_data[1][2]. 57 @property sample_format: The sample format which should be one of the keys 58 in audio_data.SAMPLE_FORMATS. 59 """ 60 def __init__(self, binary, channel, sample_format): 61 """Initializes an AudioRawData. 62 63 @param binary: A string containing binary data. If binary is not None, 64 The samples in binary will be parsed and be filled into 65 channel_data. 66 @param channel: The number of channels. 67 @param sample_format: One of the keys in audio_data.SAMPLE_FORMATS. 68 """ 69 self.channel = channel 70 self.channel_data = [[] for _ in xrange(self.channel)] 71 self.sample_format = sample_format 72 if binary: 73 self.read_binary(binary) 74 75 76 def read_one_sample(self, handle): 77 """Reads one sample from handle. 78 79 @param handle: A handle that supports read() method. 80 81 @return: A number read from file handle based on sample format. 82 None if there is no data to read. 83 """ 84 data = handle.read(SAMPLE_FORMATS[self.sample_format]['size_bytes']) 85 if data == '': 86 return None 87 number, = struct.unpack( 88 SAMPLE_FORMATS[self.sample_format]['struct_format'], data) 89 return number 90 91 92 def read_binary(self, binary): 93 """Reads samples from binary and fills channel_data. 94 95 Reads one sample for each channel and repeats until the end of 96 input binary. 97 98 @param binary: A string containing binary data. 99 """ 100 channel_index = 0 101 with contextlib.closing(StringIO.StringIO(binary)) as f: 102 number = self.read_one_sample(f) 103 while number is not None: 104 self.channel_data[channel_index].append(number) 105 channel_index = (channel_index + 1) % self.channel 106 number = self.read_one_sample(f) 107 108 109 def copy_channel_data(self, channel_data): 110 """Copies channel data and updates channel number. 111 112 @param channel_data: A list of list. The channel data to be copied. 113 114 """ 115 self.channel_data = copy.deepcopy(channel_data) 116 self.channel = len(self.channel_data) 117 118 119 def write_to_file(self, file_path): 120 """Writes channel data to file. 121 122 Writes samples in each channel into file in index-first sequence. 123 E.g. (index_0, ch_0), (index_0, ch_1), ... ,(index_0, ch_N), 124 (index_1, ch_0), (index_1, ch_1), ... ,(index_1, ch_N). 125 126 @param file_path: The path to the file. 127 128 """ 129 lengths = [len(self.channel_data[ch]) 130 for ch in xrange(self.channel)] 131 if len(set(lengths)) != 1: 132 raise AudioRawDataError( 133 'Channel lengths are not the same: %r' % lengths) 134 length = lengths[0] 135 136 with open(file_path, 'wb') as f: 137 for index in xrange(length): 138 for ch in xrange(self.channel): 139 f.write(struct.pack( 140 SAMPLE_FORMATS[self.sample_format]['struct_format'], 141 self.channel_data[ch][index])) 142