问题
I'm trying to use PyAudio to simply reproduce a wav file using the non-blocking IO code available here: PyAudio documentation.
Differently from the documentation, I'm trying to use numpy input data instead of bytes, so i'm using librosa
to load my .wav file instead of wave
as shown in the documentation.
My code is the following one, it's self contained and reproducible, you just have to change the filename
with the one of a wave audio you want to reproduce:
import pyaudio
import wave
import time
import numpy as np
import scipy.io.wavfile as sw
import librosa
import sys
from scipy.io.wavfile import write
############ Global variables ###################
filename = '../wav/The_Weeknd.wav' #Test file
#Conversion from np to pyAudio types
np_to_pa_format = {
np.dtype('float32') : pyaudio.paFloat32,
np.dtype('int32') : pyaudio.paInt32,
np.dtype('int16') : pyaudio.paInt16,
np.dtype('int8') : pyaudio.paInt8,
np.dtype('uint8') : pyaudio.paUInt8
}
np_type_to_sample_width = {
np.dtype('float32') : 4,
np.dtype('int32') : 4,
np.dtype('int16') : 3,
np.dtype('int8') : 1,
np.dtype('uint8') : 1
}
STEREO = 2 #channels
#################################################
# Simple class which reads an input test wav file and reproduce it in a real time fashion. Used to test real time functioning.
class Player:
# Loading the input test file. Crop to 30 seconds length
def __init__(self):
self.input_array, self.sample_rate = librosa.load(filename, sr=44100, dtype=np.float32, offset = 30, duration=30)
print(self.sample_rate)
print(self.input_array.shape)
self.cycle_count = 0
def pyaudio_callback(self,in_data, frame_count, time_info, status):
audio_size = np.shape(self.input_array)[0]
#print(audio_size)
print(frame_count)
if frame_count*self.cycle_count > audio_size:
# Processing is complete.
print('processing complete')
return (None, pyaudio.paComplete)
elif frame_count*(self.cycle_count+1) > audio_size:
# Last frame to process.
print('1 left frame')
frames_left = audio_size - frame_count*self.cycle_count
else:
# Every other frame.
print('everyotherframe')
frames_left = frame_count
data = self.input_array[frame_count*self.cycle_count:frame_count*self.cycle_count+frames_left]
write('test.wav', 44100, data) #Saves correctly the file!
print(data.shape)
out_data = data.tobytes()
print('printing length: ',len(out_data))
self.cycle_count+=1
print(self.cycle_count)
print(pyaudio.paContinue)
return (out_data, pyaudio.paContinue)
def start_non_blocking_processing(self, save_output=True, frame_count=2**20, listen_output=True):
'''
Non blocking mode works on a different thread, therefore, the main thread must be kept active with, for example:
while processing():
time.sleep(1)
'''
self.save_output = save_output
self.frame_count = frame_count
# Initiate PyAudio
self.pa = pyaudio.PyAudio()
# Open stream using callback
self.stream = self.pa.open(format=np_to_pa_format[self.input_array.dtype],
channels=STEREO,
rate=self.sample_rate,
output=listen_output,
input=not listen_output,
stream_callback=self.pyaudio_callback,
frames_per_buffer=frame_count)
# Start the stream
self.stream.start_stream()
def processing(self):
'''
Returns true if the PyAudio stream is still active in non blocking mode.
MUST be called AFTER self.start_non_blocking_processing.
'''
return self.stream.is_active()
def terminate_processing(self):
'''
Terminates stream opened by self.start_non_blocking_processing.
MUST be called AFTER self.processing returns False.
'''
# Stop stream.
self.stream.stop_stream()
self.stream.close()
# Close PyAudio.
self.pa.terminate()
# Resets count.
self.cycle_count = 0
# Resets output.
self.output_array = np.array([[], []], dtype=self.input_array.dtype).T
if __name__ == "__main__":
print('RUNNING MAIN')
player = Player()
player.start_non_blocking_processing()
while(player.processing()):
time.sleep(0.1)
player.terminate_processing()
Basically I followed the documentation tutorial but I re-wrote the code in a more object oriented way (since i'll need it for a bigger project).
I'm able to reproduce the audio, but I noticed that:
- It's pitched higher than it should be
- It reproduces only a single frame, the variable
pyaudio.paContinue
is always 0, thus my code executes only a single "window" of audio.
I've been looking around for a solution, but there is only an answer to a similar problem (here: callback called only once) and I haven't been able to solve my problem.
RECAP OF THE PROBLEM: my callback function is called only once (because pyaudio.paContinue
is always 0) and I can't figure out how to solve this problem.
NB: The code has been inspired by https://github.com/grupo-1-ASSD-E2/ASSD-TP4
EDIT: I added a test write to check if the numpy array containing the audio (data
variable in the code) is correct, and it is. The write
function correctly generates a .wav file with the expected audio.
EDIT 2: It seems normal that pyaudio.paContinue
has 0 value, it is the intended behaviour for "keep processing", as mentioned here: pyAudio Documentation. So I don't really know why my audio stops after 1 iteration on the callback function
回答1:
I solved the problem. I was declaring channels
inside the pa.open
function as STEREO, while I was using a MONO file. librosa.read
automatically converts input wav into mono, even if they are stereo files. So basically my stream
object was expecting 2 channels (interleaved) but it was getting only 1.
The full working code is the following:
#https://realpython.com/playing-and-recording-sound-python/#pyaudio
import pyaudio
import wave
import time
import numpy as np
import scipy.io.wavfile as sw
import librosa
import sys
from scipy.io.wavfile import write
############ Global variables ###################
filename = '../wav/The_Weeknd.wav' #Test file
chunk = 512 #frame size
#Conversion from np to pyAudio types
np_to_pa_format = {
np.dtype('float32') : pyaudio.paFloat32,
np.dtype('int32') : pyaudio.paInt32,
np.dtype('int16') : pyaudio.paInt16,
np.dtype('int8') : pyaudio.paInt8,
np.dtype('uint8') : pyaudio.paUInt8
}
np_type_to_sample_width = {
np.dtype('float32') : 4,
np.dtype('int32') : 4,
np.dtype('int16') : 3,
np.dtype('int8') : 1,
np.dtype('uint8') : 1
}
STEREO = 2 #channels
#################################################
# Simple class which reads an input test wav file and reproduce it in a real time fashion. Used to test real time functioning.
class Player:
# Loading the input test file. Crop to 30 seconds length
def __init__(self):
self.input_array, self.sample_rate = librosa.load(filename, sr=44100, dtype=np.float32, duration=60)
#print(self.sample_rate)
#print(self.input_array.shape)
self.cycle_count = 0
def pyaudio_callback(self,in_data, frame_count, time_info, status):
audio_size = np.shape(self.input_array)[0]
#print(audio_size)
print('frame count: ', frame_count)
if frame_count*self.cycle_count > audio_size:
# Processing is complete.
print('processing complete')
return (None, pyaudio.paComplete)
elif frame_count*(self.cycle_count+1) > audio_size:
# Last frame to process.
print('1 left frame')
frames_left = audio_size - frame_count*self.cycle_count
else:
# Every other frame.
print('everyotherframe')
frames_left = frame_count
data = self.input_array[frame_count*self.cycle_count:frame_count*self.cycle_count+frames_left]
print('len of data', data.shape)
#write('test.wav', 44100, data) #Saves correctly the file!
out_data = data.astype(np.float32).tobytes()
print('printing length: ',len(out_data))
#print(out_data)
self.cycle_count+=1
print(self.cycle_count)
print('pyaudio continue value: ',pyaudio.paContinue)
return (out_data, pyaudio.paContinue)
def start_non_blocking_processing(self, save_output=True, frame_count=2**10, listen_output=True):
'''
Non blocking mode works on a different thread, therefore, the main thread must be kept active with, for example:
while processing():
time.sleep(1)
'''
self.save_output = save_output
self.frame_count = frame_count
# Initiate PyAudio
self.pa = pyaudio.PyAudio()
# Open stream using callback
self.stream = self.pa.open(format=np_to_pa_format[self.input_array.dtype],
channels=1,
rate=self.sample_rate,
output=listen_output,
input=not listen_output,
stream_callback=self.pyaudio_callback,
frames_per_buffer=frame_count)
# Start the stream
self.stream.start_stream()
def processing(self):
'''
Returns true if the PyAudio stream is still active in non blocking mode.
MUST be called AFTER self.start_non_blocking_processing.
'''
return self.stream.is_active()
def terminate_processing(self):
'''
Terminates stream opened by self.start_non_blocking_processing.
MUST be called AFTER self.processing returns False.
'''
# Stop stream.
self.stream.stop_stream()
self.stream.close()
# Close PyAudio.
self.pa.terminate()
# Resets count.
self.cycle_count = 0
# Resets output.
self.output_array = np.array([[], []], dtype=self.input_array.dtype).T
if __name__ == "__main__":
print('RUNNING MAIN')
player = Player()
player.start_non_blocking_processing()
while(player.processing()):
time.sleep(0.1)
player.terminate_processing()
来源:https://stackoverflow.com/questions/65467212/pyaudio-callback-function-called-only-once