当前位置: 代码迷 >> 综合 >> Python 音频处理:wave
  详细解决方案

Python 音频处理:wave

热度:44   发布时间:2023-12-15 05:17:52.0

wave模块

  • 1. 概述
    • 1.1 Wave_read对象
    • 1.2 Wave_write 对象
  • 2. 实际使用中的问题
    • 2.1 音频保存
  • 3. 源代码:`Lib/wave.py`

1. 概述

wave 模块提供了一个处理 WAV 声音格式的便利接口。它不支持压缩/解压,但是支持单声道/立体声

用法:wave.open(file, mode=None),其中,moderbwb

  • rb:生成 wav_read 对象
  • wb:生成 wav_write 对象
    注意不支持同时读写

注:关于 rwrbwb
rw是普通读和写文件(简单理解为人工编写的文件);
rbwb是读写二进制文件(简单理解为可以操作图片等非手工编写的文件)

1.1 Wave_read对象

import wave# wave.read 对象方法wr = wave.open('/Users/robin/Desktop/WavTest.wav', 'rb')print(wr.getnchannels()) # 返回声道数量(1 为单声道,2 为立体声)
print(wr.getsampwidth()) # 返回采样字节长度
print(wr.getframerate()) # 返回采样频率
print(wr.getnframes()) # 返回音频总的帧数print(wr.getparams()) # 返回一个 namedtuple() (nchannels, sampwidth, framerate, nframes, comptype, compname),与 get*() 方法的输出相同。print(wr.readframes(n))
1
2
16000
103765
_wave_params(nchannels=1, sampwidth=2, framerate=16000, nframes=103765, comptype='NONE', compname='not compressed')
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0
...

1.2 Wave_write 对象

# wave.write 对象方法
#(1)open 创建文件
ww = wave.open('/Users/robin/Desktop/WavTest.wav', 'wb')#(2)set 设置参数
ww.setchannels(n) # 设置通道数
ww.setsampwidth(n) # 设置采样字节长度为 n
ww.setframerate(n) # 设置采样频率为 nww.setparams(n) # 设置所有形参,(nchannels, sampwidth, framerate, nframes, comptype, compname),每项的值应可用于 set*() 方法。# (3) writeframes 写入数据流
ww.writeframes(data) # 写入音频帧并确保 nframes 是正确的# (4) close 关闭
ww.close()

2. 实际使用中的问题

2.1 音频保存

1、setsampwidth采样字节长度 or 采样位数、量化位数

表示用多少bit表达一次采样所采集的数据,通常有8bit、16bit、24bit和32bit等几种,
需要注意的是,在设置setsampwidth时,采用的单位是bytes而不是bit。比如,16 bits per sample (= 2 bytes),应该设置setsampwidth=2而不是setsampwidth=16,否则可能会报错:raise Error('sample width not specified')

代码示例:

channel = 1
bits = 2  # 16 bits = 2 bytes(wave模块中使用byte!!!注意除以8进行转换)
rates = 16000# read(or resample) wav and save
def write_wav(file_name, input_data):wavfile = wave.open(file_name, 'wb')  # 创建(空)文件wavfile.setparams((channel, bits,  rates, 0, 'NONE', 'NONE'))print("wav文件信息:", wavfile.getparams()[:4])wavfile.writeframes(input_data)  # 写入数据wavfile.close()

3. 源代码:Lib/wave.py

"""Stuff to parse WAVE files. Usage. Reading WAVE files:f = wave.open(file, 'r') where file is either the name of a file or an open file pointer. The open file pointer must have methods read(), seek(), and close(). When the setpos() and rewind() methods are not used, the seek() method is not necessary. This returns an instance of a class with the following public methods:getnchannels() -- returns number of audio channels (1 formono, 2 for stereo)getsampwidth() -- returns sample width in bytesgetframerate() -- returns sampling frequencygetnframes() -- returns number of audio framesgetcomptype() -- returns compression type ('NONE' for linear samples)getcompname() -- returns human-readable version ofcompression type ('not compressed' linear samples)getparams() -- returns a namedtuple consisting of all of theabove in the above ordergetmarkers() -- returns None (for compatibility with theaifc module)getmark(id) -- raises an error since the mark does notexist (for compatibility with the aifc module)readframes(n) -- returns at most n frames of audiorewind() -- rewind to the beginning of the audio streamsetpos(pos) -- seek to the specified positiontell() -- return the current positionclose() -- close the instance (make it unusable) The position returned by tell() and the position given to setpos() are compatible and have nothing to do with the actual position in the file. The close() method is called automatically when the class instance is destroyed. Writing WAVE files:f = wave.open(file, 'w') where file is either the name of a file or an open file pointer. The open file pointer must have methods write(), tell(), seek(), and close(). This returns an instance of a class with the following public methods:setnchannels(n) -- set the number of channelssetsampwidth(n) -- set the sample widthsetframerate(n) -- set the frame ratesetnframes(n) -- set the number of framessetcomptype(type, name)-- set the compression type and thehuman-readable compression typesetparams(tuple)-- set all parameters at oncetell() -- return current position in output filewriteframesraw(data)-- write audio frames without patching up thefile headerwriteframes(data)-- write audio frames and patch up the file headerclose() -- patch up the file header and close theoutput file You should set the parameters before the first writeframesraw or writeframes. The total number of frames does not need to be set, but when it is set to the correct value, the header does not have to be patched up. It is best to first set all parameters, perhaps possibly the compression type, and then write audio frames using writeframesraw. When all frames have been written, either call writeframes(b'') or close() to patch up the sizes in the header. The close() method is called automatically when the class instance is destroyed. """from chunk import Chunk
from collections import namedtuple
import audioop
import builtins
import struct
import sys__all__ = ["open", "Error", "Wave_read", "Wave_write"]class Error(Exception):passWAVE_FORMAT_PCM = 0x0001_array_fmts = None, 'b', 'h', None, 'i'_wave_params = namedtuple('_wave_params','nchannels sampwidth framerate nframes comptype compname')class Wave_read:"""Variables used in this class:These variables are available to the user though appropriatemethods of this class:_file -- the open file with methods read(), close(), and seek()set through the __init__() method_nchannels -- the number of audio channelsavailable through the getnchannels() method_nframes -- the number of audio framesavailable through the getnframes() method_sampwidth -- the number of bytes per audio sampleavailable through the getsampwidth() method_framerate -- the sampling frequencyavailable through the getframerate() method_comptype -- the AIFF-C compression type ('NONE' if AIFF)available through the getcomptype() method_compname -- the human-readable AIFF-C compression typeavailable through the getcomptype() method_soundpos -- the position in the audio streamavailable through the tell() method, set through thesetpos() methodThese variables are used internally only:_fmt_chunk_read -- 1 iff the FMT chunk has been read_data_seek_needed -- 1 iff positioned correctly in audiofile for readframes()_data_chunk -- instantiation of a chunk class for the DATA chunk_framesize -- size of one frame in the file"""def initfp(self, file):self._convert = Noneself._soundpos = 0self._file = Chunk(file, bigendian = 0)if self._file.getname() != b'RIFF':raise Error('file does not start with RIFF id')if self._file.read(4) != b'WAVE':raise Error('not a WAVE file')self._fmt_chunk_read = 0self._data_chunk = Nonewhile 1:self._data_seek_needed = 1try:chunk = Chunk(self._file, bigendian = 0)except EOFError:breakchunkname = chunk.getname()if chunkname == b'fmt ':self._read_fmt_chunk(chunk)self._fmt_chunk_read = 1elif chunkname == b'data':if not self._fmt_chunk_read:raise Error('data chunk before fmt chunk')self._data_chunk = chunkself._nframes = chunk.chunksize // self._framesizeself._data_seek_needed = 0breakchunk.skip()if not self._fmt_chunk_read or not self._data_chunk:raise Error('fmt chunk and/or data chunk missing')def __init__(self, f):self._i_opened_the_file = Noneif isinstance(f, str):f = builtins.open(f, 'rb')self._i_opened_the_file = f# else, assume it is an open file object alreadytry:self.initfp(f)except:if self._i_opened_the_file:f.close()raisedef __del__(self):self.close()def __enter__(self):return selfdef __exit__(self, *args):self.close()## User visible methods.#def getfp(self):return self._filedef rewind(self):self._data_seek_needed = 1self._soundpos = 0def close(self):self._file = Nonefile = self._i_opened_the_fileif file:self._i_opened_the_file = Nonefile.close()def tell(self):return self._soundposdef getnchannels(self):return self._nchannelsdef getnframes(self):return self._nframesdef getsampwidth(self):return self._sampwidthdef getframerate(self):return self._frameratedef getcomptype(self):return self._comptypedef getcompname(self):return self._compnamedef getparams(self):return _wave_params(self.getnchannels(), self.getsampwidth(),self.getframerate(), self.getnframes(),self.getcomptype(), self.getcompname())def getmarkers(self):return Nonedef getmark(self, id):raise Error('no marks')def setpos(self, pos):if pos < 0 or pos > self._nframes:raise Error('position not in range')self._soundpos = posself._data_seek_needed = 1def readframes(self, nframes):if self._data_seek_needed:self._data_chunk.seek(0, 0)pos = self._soundpos * self._framesizeif pos:self._data_chunk.seek(pos, 0)self._data_seek_needed = 0if nframes == 0:return b''data = self._data_chunk.read(nframes * self._framesize)if self._sampwidth != 1 and sys.byteorder == 'big':data = audioop.byteswap(data, self._sampwidth)if self._convert and data:data = self._convert(data)self._soundpos = self._soundpos + len(data) // (self._nchannels * self._sampwidth)return data## Internal methods.#def _read_fmt_chunk(self, chunk):try:wFormatTag, self._nchannels, self._framerate, dwAvgBytesPerSec, wBlockAlign = struct.unpack_from('<HHLLH', chunk.read(14))except struct.error:raise EOFError from Noneif wFormatTag == WAVE_FORMAT_PCM:try:sampwidth = struct.unpack_from('<H', chunk.read(2))[0]except struct.error:raise EOFError from Noneself._sampwidth = (sampwidth + 7) // 8if not self._sampwidth:raise Error('bad sample width')else:raise Error('unknown format: %r' % (wFormatTag,))if not self._nchannels:raise Error('bad # of channels')self._framesize = self._nchannels * self._sampwidthself._comptype = 'NONE'self._compname = 'not compressed'class Wave_write:"""Variables used in this class:These variables are user settable through appropriate methodsof this class:_file -- the open file with methods write(), close(), tell(), seek()set through the __init__() method_comptype -- the AIFF-C compression type ('NONE' in AIFF)set through the setcomptype() or setparams() method_compname -- the human-readable AIFF-C compression typeset through the setcomptype() or setparams() method_nchannels -- the number of audio channelsset through the setnchannels() or setparams() method_sampwidth -- the number of bytes per audio sampleset through the setsampwidth() or setparams() method_framerate -- the sampling frequencyset through the setframerate() or setparams() method_nframes -- the number of audio frames written to the headerset through the setnframes() or setparams() methodThese variables are used internally only:_datalength -- the size of the audio samples written to the header_nframeswritten -- the number of frames actually written_datawritten -- the size of the audio samples actually written"""def __init__(self, f):self._i_opened_the_file = Noneif isinstance(f, str):f = builtins.open(f, 'wb')self._i_opened_the_file = ftry:self.initfp(f)except:if self._i_opened_the_file:f.close()raisedef initfp(self, file):self._file = fileself._convert = Noneself._nchannels = 0self._sampwidth = 0self._framerate = 0self._nframes = 0self._nframeswritten = 0self._datawritten = 0self._datalength = 0self._headerwritten = Falsedef __del__(self):self.close()def __enter__(self):return selfdef __exit__(self, *args):self.close()## User visible methods.#def setnchannels(self, nchannels):if self._datawritten:raise Error('cannot change parameters after starting to write')if nchannels < 1:raise Error('bad # of channels')self._nchannels = nchannelsdef getnchannels(self):if not self._nchannels:raise Error('number of channels not set')return self._nchannelsdef setsampwidth(self, sampwidth):if self._datawritten:raise Error('cannot change parameters after starting to write')if sampwidth < 1 or sampwidth > 4:raise Error('bad sample width')self._sampwidth = sampwidthdef getsampwidth(self):if not self._sampwidth:raise Error('sample width not set')return self._sampwidthdef setframerate(self, framerate):if self._datawritten:raise Error('cannot change parameters after starting to write')if framerate <= 0:raise Error('bad frame rate')self._framerate = int(round(framerate))def getframerate(self):if not self._framerate:raise Error('frame rate not set')return self._frameratedef setnframes(self, nframes):if self._datawritten:raise Error('cannot change parameters after starting to write')self._nframes = nframesdef getnframes(self):return self._nframeswrittendef setcomptype(self, comptype, compname):if self._datawritten:raise Error('cannot change parameters after starting to write')if comptype not in ('NONE',):raise Error('unsupported compression type')self._comptype = comptypeself._compname = compnamedef getcomptype(self):return self._comptypedef getcompname(self):return self._compnamedef setparams(self, params):nchannels, sampwidth, framerate, nframes, comptype, compname = paramsif self._datawritten:raise Error('cannot change parameters after starting to write')self.setnchannels(nchannels)self.setsampwidth(sampwidth)self.setframerate(framerate)self.setnframes(nframes)self.setcomptype(comptype, compname)def getparams(self):if not self._nchannels or not self._sampwidth or not self._framerate:raise Error('not all parameters set')return _wave_params(self._nchannels, self._sampwidth, self._framerate,self._nframes, self._comptype, self._compname)def setmark(self, id, pos, name):raise Error('setmark() not supported')def getmark(self, id):raise Error('no marks')def getmarkers(self):return Nonedef tell(self):return self._nframeswrittendef writeframesraw(self, data):if not isinstance(data, (bytes, bytearray)):data = memoryview(data).cast('B')self._ensure_header_written(len(data))nframes = len(data) // (self._sampwidth * self._nchannels)if self._convert:data = self._convert(data)if self._sampwidth != 1 and sys.byteorder == 'big':data = audioop.byteswap(data, self._sampwidth)self._file.write(data)self._datawritten += len(data)self._nframeswritten = self._nframeswritten + nframesdef writeframes(self, data):self.writeframesraw(data)if self._datalength != self._datawritten:self._patchheader()def close(self):try:if self._file:self._ensure_header_written(0)if self._datalength != self._datawritten:self._patchheader()self._file.flush()finally:self._file = Nonefile = self._i_opened_the_fileif file:self._i_opened_the_file = Nonefile.close()## Internal methods.#def _ensure_header_written(self, datasize):if not self._headerwritten:if not self._nchannels:raise Error('# channels not specified')if not self._sampwidth:raise Error('sample width not specified')if not self._framerate:raise Error('sampling rate not specified')self._write_header(datasize)def _write_header(self, initlength):assert not self._headerwrittenself._file.write(b'RIFF')if not self._nframes:self._nframes = initlength // (self._nchannels * self._sampwidth)self._datalength = self._nframes * self._nchannels * self._sampwidthtry:self._form_length_pos = self._file.tell()except (AttributeError, OSError):self._form_length_pos = Noneself._file.write(struct.pack('<L4s4sLHHLLHH4s',36 + self._datalength, b'WAVE', b'fmt ', 16,WAVE_FORMAT_PCM, self._nchannels, self._framerate,self._nchannels * self._framerate * self._sampwidth,self._nchannels * self._sampwidth,self._sampwidth * 8, b'data'))if self._form_length_pos is not None:self._data_length_pos = self._file.tell()self._file.write(struct.pack('<L', self._datalength))self._headerwritten = Truedef _patchheader(self):assert self._headerwrittenif self._datawritten == self._datalength:returncurpos = self._file.tell()self._file.seek(self._form_length_pos, 0)self._file.write(struct.pack('<L', 36 + self._datawritten))self._file.seek(self._data_length_pos, 0)self._file.write(struct.pack('<L', self._datawritten))self._file.seek(curpos, 0)self._datalength = self._datawrittendef open(f, mode=None):if mode is None:if hasattr(f, 'mode'):mode = f.modeelse:mode = 'rb'if mode in ('r', 'rb'):return Wave_read(f)elif mode in ('w', 'wb'):return Wave_write(f)else:raise Error("mode must be 'r', 'rb', 'w', or 'wb'")

参考:

  1. wave — 读写WAV格式文件
  2. python 3x - about writing with the python wave module