Threading issue (using alsaaudio)


J

Jérôme

Hi all.

I am currently learning python (and pyGTK) and I'm having a hard time
understanding some threading stuff.

I'm writing here hoping I can get

- pointers to some documentation that could help me

- a lead concerning the specific problem described in the message

- a hint about which library to use for audio playback

As an exercise, I am trying to write an application that plays sounds through
the speakers. For that, I use alsaaudio (http://pyalsaaudio.sourceforge.net/).
The sound is just a sine wave. I guess using threading is the good way to go.
So I create a thread. The run() method calls PCM.write() from alsaudio to
send the sound to a playback buffer.

Once the thread is started, the button is disabled, and it is enabled again
when the thread is dead. This is done via GObject.idle_add(), which triggers
a regular call to the thread is_alive() function.

There's a design flow, here, as the run() method that calls pcm.write() exits
when the sound data is buffered (this is quick, say 0.1s), not when it is
actualy finished playing (this is long : 1s). Therefore, the thread dies long
before the sound is finished playing and the button is going to be enabled
too soon. But this is not what happens, and before finding another way to
proceed (this is likely to be a timer), I would like to understand what
actually happens (remember, this is just an exercise).

What happens is that the idle_add polling occurs as long as the run() method
is running, but then, it gets stuck until the sound has actually finished
playing. The button is enabled at the end of the playback (which is what I
wanted in the first place, except I'd like to understand why...). Besides, a
second click (on the then disabled button) during the playback is not
ignored, but generates another sound after the first.

It seems that while the thread is dead (or should be, as the last print has
been reached and executed), as long as the sound is being played, the
Gtk.main() is stuck and does not process either gtk events or idle tasks.

I don't understand that. I wonder what happens and in which thread lies my
program while the sound is playing, between the thread run() end and the
Gtk.main().

(There's a note in alsaaudio documentation about pcm.write normal and
non-block modes but I don't think it is relevant here because, as far as I
understand, the data sent does not fill the buffer so in both modes,
pcm.write() should exit without blocking. (I might be wrong about this.))

I tried to remove from the code all that has nothing to do with this issue,
and I left the prints that reveal what happens. Here it is :

---------------------------------------------------------------------------

#!/usr/bin/env python
# -*- coding: UTF8 -*-

from gi.repository import Gtk, GObject
import alsaaudio
from numpy import arange, sin, ceil, pi, float32, hstack
import threading
import time

#########################################################################
# audio functions
#########################################################################
class Alsa_play_note(threading.Thread):

def run(self):

print "Entering thread run() method"

length = 1 # 1 second
freq = 440

channels = 1
sample_size = 1 # bytes per sample
frame_size = channels * sample_size # bytes per frame
frame_rate = 44000 # frames per second
period_size = int(frame_rate * length)

pcm = alsaaudio.PCM(alsaaudio.PCM_PLAYBACK, alsaaudio.PCM_NORMAL)
#pcm = alsaaudio.PCM(alsaaudio.PCM_PLAYBACK, alsaaudio.PCM_NONBLOCK)
pcm.setchannels(channels)
pcm.setrate(frame_rate)
pcm.setformat(alsaaudio.PCM_FORMAT_FLOAT_LE)
pcm.setperiodsize(period_size)

nb_samp = int(ceil(frame_rate / freq))
sample = arange(0, nb_samp, dtype=float32)
sample *= pi * 2 / 44100 * freq
sample = sin(sample)
(nw, ex) = divmod(period_size * frame_size, nb_samp)
wave_data = sample
for i in range(nw-2):
wave_data = hstack((wave_data, sample))
wave_data = hstack((wave_data, sample[:ex]))

print "run() -> entering pcm.write"
#time.sleep(1)
a = pcm.write(wave_data.tostring())
#print a
print "run() -> pcm.write done"

print "Leaving thread run() method"

def __init__(self):
threading.Thread.__init__(self)


#########################################################################
# class Keyboard
#########################################################################
class Keyboard:

#####################################################################
# Play note
#####################################################################
def play_note(self, widget):

print "Entering play_note() callback"
self._button.set_sensitive(False)

print "Creating thread"
self._alsa_thread = Alsa_play_note()
print "Calling thread.start()"
self._alsa_thread.start()
print "Back from thread.start()"

GObject.idle_add(self._poll_alsa_play_in_progress)
print "GObject.idle_add() done"

#####################################################################
# Poll alsa play in progress
#####################################################################
def _poll_alsa_play_in_progress (self):
print "."
if (self._alsa_thread.is_alive()):
return True
print "Button enabled"
self._button.set_sensitive(True)
return False

#####################################################################
# Init
#####################################################################
def __init__(self):

# Declare thread handler
self._alsa_thread = 0

# Create window
###############
self._window = Gtk.Window()
self._window.connect("destroy", Gtk.main_quit)
self._window.connect("destroy", Gtk.main_quit)

# Key button
############

self._button = Gtk.Button()
self._button.set_size_request(400, 100)
self._button.set_label("A")
self._button.show()

# Connect handler
self._handler = self._button.connect("clicked", self.play_note)

# Show everything
#################
self._window.add(self._button)
self._window.show()

#########################################################################
# main
#########################################################################
def main():

GObject.threads_init()
Gtk.main()
return 0

if __name__ == "__main__":
Keyboard()
main()


---------------------------------------------------------------------------

I'd be thankful for any tip. I've been pulling my hair quite some time with
these threading issues.

Regarding python and pyGTK, I'm generally refering to :

Python 2.7 documentation
http://docs.python.org/

python GTK+3 tutorial (not really complete)
http://readthedocs.org/docs/python-gtk-3-tutorial/en/latest/index.html

python GTK2.0 tutorial
http://pygtk.org/pygtk2tutorial/index.html

I may be missing some documentation / tutorials. Anything else I should read
extensively before asking here ?

Regarding the playback library, I've been searching before choosing alsaaudio.
I chose ALSA because I understand OSS is deprecated, or on its way to be. And
alsaaudio seemed to be the project with the most recent updates. Yet it does
not pretend to be (or aim at being) complete. Was that a sensible choice ?
Would you recommend something else ?

The learning curve is sometimes steep... I would be thankful for any lead.
 
Ad

Advertisements


Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top