Skip to content

Commit

Permalink
fix support for 8-bit audio
Browse files Browse the repository at this point in the history
This solves the issue where 8-bit audio is inconsistently treated as unsigned and signed integers.
The audioop library only supports signed integers, so this fix converts to and from signed integers where necessary.

It removes places where the values are treated like unsigned as these partial fixes are no longer necessary.
This includes special handling in the rms and set_sample_width methods of the AudioSegment class.

It changes the codec used for reading from 8-bit wav files to pcm_u8 from pcm_s8
since pcm_u8 is the only valid codec for 8-bit wav files.
  • Loading branch information
GreyAlien502 committed Jan 20, 2020
1 parent 9e292e0 commit 3818779
Showing 1 changed file with 16 additions and 19 deletions.
35 changes: 16 additions & 19 deletions pydub/audio_segment.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,9 @@ def __init__(self, data=None, *args, **kwargs):
self.frame_rate = wav_data.sample_rate
self.frame_width = self.channels * self.sample_width
self._data = wav_data.raw_data
if self.sample_width == 1:
# convert from unsigned integers in wav
self._data = audioop.bias(self._data, 1, -128)

# Convert 24-bit audio to 32-bit audio.
# (stdlib audioop and array modules do not support 24-bit data)
Expand Down Expand Up @@ -685,7 +688,7 @@ def is_format(f):
else:
bits_per_sample = audio_streams[0]['bits_per_sample']
if bits_per_sample == 8:
acodec = 'pcm_s8'
acodec = 'pcm_u8'
else:
acodec = 'pcm_s%dle' % bits_per_sample

Expand Down Expand Up @@ -804,14 +807,19 @@ def export(self, out_f=None, format='mp3', codec=None, bitrate=None, parameters=
else:
data = NamedTemporaryFile(mode="wb", delete=False)

pcm_for_wav = self._data
if self.sample_width == 1:
# convert to unsigned integers for wav
pcm_for_wav = audioop.bias(self._data, 1, 128)

wave_data = wave.open(data, 'wb')
wave_data.setnchannels(self.channels)
wave_data.setsampwidth(self.sample_width)
wave_data.setframerate(self.frame_rate)
# For some reason packing the wave header struct with
# a float in python 2 doesn't throw an exception
wave_data.setnframes(int(self.frame_count()))
wave_data.writeframesraw(self._data)
wave_data.writeframesraw(pcm_for_wav)
wave_data.close()

# for wav files, we're done (wav data is written directly to out_f)
Expand Down Expand Up @@ -920,20 +928,12 @@ def set_sample_width(self, sample_width):
if sample_width == self.sample_width:
return self

data = self._data

if self.sample_width == 1:
data = audioop.bias(data, 1, -128)

if data:
data = audioop.lin2lin(data, self.sample_width, sample_width)

if sample_width == 1:
data = audioop.bias(data, 1, 128)

frame_width = self.channels * sample_width
return self._spawn(data, overrides={'sample_width': sample_width,
'frame_width': frame_width})

return self._spawn(
audioop.lin2lin(self._data, self.sample_width, sample_width),
overrides={'sample_width': sample_width, 'frame_width': frame_width}
)

def set_frame_rate(self, frame_rate):
if frame_rate == self.frame_rate:
Expand Down Expand Up @@ -1009,10 +1009,7 @@ def split_to_mono(self):

@property
def rms(self):
if self.sample_width == 1:
return self.set_sample_width(2).rms
else:
return audioop.rms(self._data, self.sample_width)
return audioop.rms(self._data, self.sample_width)

@property
def dBFS(self):
Expand Down

0 comments on commit 3818779

Please sign in to comment.