python-sounddevice icon indicating copy to clipboard operation
python-sounddevice copied to clipboard

callback is not called in RawOutputStream when using some devices

Open padmalcom opened this issue 5 years ago • 1 comments

Hi, I have a simple application playing a stream (see code below). That works well when I use my laptop's speakers but when I switch to a bluetooth headset, the callback function is never called (I checked by using print statements) - this consequently leads to a Queue.Full exception. The headset works well in all other cases, even in the same application. I use sd.default.device['output'] as device ID, so I expect sounddevice to detect the correct device.

Could it be that the sd.default has to be updated when I start playing a new file/stream??

Thanks for your time and your great library btw :-)

def _play_stream(self, source):
		_q = Queue(maxsize=20)
		
		def _callback_stream(outdata, frames, time, status):
			if status.output_underflow:
				raise sd.CallbackAbort
			assert not status
			try:
				data = _q.get_nowait()
				#data = data
			except queue.Empty as e:
				raise sd.CallbackAbort from e
			assert len(data) == len(outdata)
			outdata[:] = data
		
		try:
			info = ffmpeg.probe(source)
		except ffmpeg.Error as e:
			logger.error(e)

		streams = info.get('streams', [])
		if len(streams) != 1:
			logger.error('There must be exactly one stream available')

		stream = streams[0]

		if stream.get('codec_type') != 'audio':
			logger.error('The stream must be an audio stream')

		channels = stream['channels']
		samplerate = float(stream['sample_rate'])
		
		try:
			process = ffmpeg.input(source).filter('volume', self._volume).output(
				'pipe:',
				format='f32le',
				acodec='pcm_f32le',
				ac=channels,
				ar=samplerate,
				loglevel='quiet',
			).run_async(pipe_stdout=True)
			stream = sd.RawOutputStream(
				samplerate=samplerate, blocksize=1024,
				device=sd.default.device['output'], channels=channels, dtype='float32',
				callback=_callback_stream)
			read_size = 1024 * channels * stream.samplesize
			for _ in range(20):
				_q.put_nowait(process.stdout.read(read_size))
			logger.info("Starte Stream ...")
			with stream:
				timeout = 1024 * 20 / samplerate
				while True:
					_q.put(process.stdout.read(read_size), timeout=timeout)
		#except KeyboardInterrupt:
		#	logger.error('\nInterrupted by user')
		except queue.Full as e:
			# A timeout occurred, i.e. there was an error in the callback
			logger.error("Queue ist voll: {}", e)
		except Exception as e:
			logger.error(e)

padmalcom avatar Dec 11 '20 09:12 padmalcom

I use sd.default.device['output'] as device ID, so I expect sounddevice to detect the correct device.

The selection of the default device, and in fact any device handling is done by the underlying PortAudio library.

Could it be that the sd.default has to be updated when I start playing a new file/stream??

I don't know, probably.

One thing you could try is to call sd._terminate(); sd._initialize() after switching the device. See also https://python-sounddevice.readthedocs.io/en/0.4.1/api/expert-mode.html.

If this doesn't work, it might be a limitation of the PortAudio library. You could ask in their mailing list about it: http://portaudio.com/contacts.html

mgeier avatar Dec 12 '20 11:12 mgeier