2

On the Raspberry Pi, I'm trying to play some sounds using pygame.mixer and am having a problem when playing them on any thread other than the main thread. For example, I have a keypad hooked up and when a key is pressed (for which the detection is threaded) it fires a callback and in that callback it tries to play the sound. But when it tries it, most of the time, fails with:

Fatal Python error: (pygame parachute) Segmentation Fault

If I run it on the main thread, it works fine. Is there a reason why separate threads doesn't work or a way to do it? See code below, the method being called on the thread is audio_handler.playTone()

import pygame.mixer as mixer
import glob

class audio_handler:
    def __init__(self):
        mixer.init(channels = 2)
        mixer.set_num_channels(3)
        self._chanTones = mixer.Channel(0)
        self._chanMusic = mixer.Channel(1)
        self._chanRinger = mixer.Channel(2)
        self._dtmfTones = {}
        self._songs = []
        self._musicDir = "/home/pi/music/"

        self.loadDTMF()
        #self.loadSongs()

    def cleanup(self):
        mixer.quit()

    def loadDTMF(self):
        self._dtmfTones["0"] = mixer.Sound("./DTMF/0.ogg")
        self._dtmfTones["1"] = mixer.Sound("./DTMF/1.ogg")
        self._dtmfTones["2"] = mixer.Sound("./DTMF/2.ogg")
        self._dtmfTones["3"] = mixer.Sound("./DTMF/3.ogg")
        self._dtmfTones["4"] = mixer.Sound("./DTMF/4.ogg")
        self._dtmfTones["5"] = mixer.Sound("./DTMF/5.ogg")
        self._dtmfTones["6"] = mixer.Sound("./DTMF/6.ogg")
        self._dtmfTones["7"] = mixer.Sound("./DTMF/7.ogg")
        self._dtmfTones["8"] = mixer.Sound("./DTMF/8.ogg")
        self._dtmfTones["9"] = mixer.Sound("./DTMF/9.ogg")
        self._dtmfTones["*"] = mixer.Sound("./DTMF/star.ogg")
        self._dtmfTones["#"] = mixer.Sound("./DTMF/pound.ogg")

    def loadSongs(self):
        for file in glob.glob(self._musicDir + "*.ogg"):
            self._songs.append(file)

        print str(len(self._songs)) + " songs loaded."

    def playTone(self, tone):
        if self._dtmfTones.has_key(str(tone)):
            self._dtmfTones[str(tone)].play()
            self._chanTones.set_volume(1.0, 0.0)
            self._chanTones.play(self._dtmfTones[str(tone)])

Side Note: This seems to only happen with ogg files, not wav.

Matrix Keypad Handler:

#!/usr/bin/python

import RPi.GPIO as GPIO
import time

class keypad():
    def __init__(self, callback):
        GPIO.setmode(GPIO.BCM)
        self._count = 0
        self._inInterrupt = False
        self._callback = callback

        # CONSTANTS 
        self.KEYPAD = [
            [1,2,3],
            [4,5,6],
            [7,8,9],
            ["*",0,"#"]
        ]

        self.ROW         = [18,23,24,25]
        self.COLUMN      = [4,17,22]

        self.__setInterruptMode()

    def __colInt(self, channel):
        time.sleep(0.05) #give it a moment to settle
        if GPIO.input(channel) > 0:
            return

        #remove interrupts temporarily
        for c in range(len(self.COLUMN)):
            GPIO.remove_event_detect(self.COLUMN[c])

        #get column number
        colVal = -1
        for c in range(len(self.COLUMN)):
            if channel == self.COLUMN[c]:
                colVal = c

        #continue if valid column (it should always be)
        if colVal >=0 and colVal < len(self.COLUMN):

            #set rows as intputs
            for r in range(len(self.ROW)):
                GPIO.setup(self.ROW[r], GPIO.IN, pull_up_down=GPIO.PUD_UP)

            #set triggered column as low output
            GPIO.setup(channel, GPIO.OUT, initial=GPIO.LOW)

            # Scan rows for pushed key/button
            rowVal = -1
            for r in range(len(self.ROW)):
                tmpRead = GPIO.input(self.ROW[r])
                if tmpRead == 0:
                    rowVal = r
                    break

            #continue if row is valid (possible that it might not be if the key was very quickly released)
            if rowVal >= 0 and rowVal < len(self.ROW):
                #send key info right away
                self._callback(self.KEYPAD[rowVal][colVal])
                #This avoids nasty boucning errors when the key is released
                #By waiting for the rising edge before re-enabling interrupts it 
                #avoids interrupts fired due to bouncing on key release and 
                #any repeated interrupts that would otherwise fire.
                try:
                    GPIO.wait_for_edge(self.ROW[rowVal], GPIO.RISING)
                    self.__setInterruptMode()
                except RuntimeError:
                    pass

                return

            else:
                print "Invalid Row!"
        else:
            print "Invalid Col!"

        #re-enable interrupts
        self.__setInterruptMode()

    def __changeWrapper(self, channel):
        #if there is already another interrupt going on (multiple key press or something)
        #return right away to avoid collisions
        if self._inInterrupt:
            return;

        self._inInterrupt = True
        self.__colInt(channel) #handle the actual interrupt
        self._inInterrupt = False

    def __setInterruptMode(self):
        #set the first row as output low
        #only first one needed as it will ground to all columns
        for r in range(len(self.ROW)):
            GPIO.setup(self.ROW[r], GPIO.OUT, initial=GPIO.LOW)

        #set columns as inputs and attach interrupt handlers on rising edge
        for c in range(len(self.COLUMN)):
            GPIO.setup(self.COLUMN[c], GPIO.IN, pull_up_down=GPIO.PUD_UP)
            GPIO.add_event_detect(self.COLUMN[c], GPIO.FALLING, bouncetime=250, callback=self.__changeWrapper)


    def cleanup(self):
        GPIO.cleanup()
        print "Cleanup done!"

import time     
if __name__ == '__main__':
    audio = audio_handler()
    def keypadCallback(value):
        audio.playTone(value)
        print "Keypad: " + value

    key = keypad(keypadCallback)

    try:
        while True:
            time.sleep(1)

    except KeyboardInterrupt:
        key.cleanup()

While a new thread is never spawned directly by me, keypadCallback() is called via the keypad() class in the context of it's own internal GPIO interrupt callback. So, I assume that means that keypadCallback() is, in fact, not on the main thread.

Adam Haile
  • 30,705
  • 58
  • 191
  • 286
  • Just curious, but why do you need to use or why are you multi threading? Is there a specific reason that you need to use multithreading on your application? – KodyVanRy Mar 27 '14 at 04:20
  • Mainly so that the main interface is responsive. The keypad detection all takes place on a separate thread and provides callbacks. And the sound stuff was getting fired off in that callback, hence running on the keypad thread. I need the sound to occur right away and didn't want to wait for a loop on the main thread to fire. – Adam Haile Mar 27 '14 at 16:14
  • That new side not is an interesting side note. Do wav files work correctly on the main thread? or is it also just ogg files on the main thread? – KodyVanRy Mar 27 '14 at 16:34
  • Wav works fine anywhere. – Adam Haile Mar 27 '14 at 16:35
  • Oh yes sorry I got that backwards. Do ogg files work correctly on the main thread? or just wav? – KodyVanRy Mar 27 '14 at 16:36
  • Oh man... I'm an idiot. Looked back at the rest of the code and the keypad handling stuff isn't on a separate thread. It was at one point. But now it just fires a callback when it gets a GPIO interrupt. When I try to call playTone in *that* is when it blows up. And yes, ogg works file when I just do while True: audio.playTone(1) time.sleep(1) – Adam Haile Mar 27 '14 at 16:49
  • Alright glad you got it working :) – KodyVanRy Mar 27 '14 at 17:21
  • Ah... nope. It's still broken. I just wrongly said it was running on a different thread. It is still failing when run from that callback though. – Adam Haile Mar 27 '14 at 17:22
  • Could you show me some of your code relating to calling the callbacks along with the creating thread. And managing the thread? – KodyVanRy Mar 27 '14 at 17:38
  • See updated code and comments above. – Adam Haile Mar 27 '14 at 17:43
  • Now I'm assuming all of the audios (if in wav format) will play if you press a key on the keypad? – KodyVanRy Mar 27 '14 at 17:47
  • Yeah... seems to work in wav just fine. But generally yes, the idea is that a key is pressed and its tone plays. Ogg, however, causes a seg fault. – Adam Haile Mar 27 '14 at 17:52
  • Ok can you read the raw bytes or does even that not work? Right after the `self._dtmfTones["0"] = mixer.Sound("./DTMF/0.ogg")` put `self._dtmfTones["0"].get_raw()` and post results. – KodyVanRy Mar 27 '14 at 17:58
  • ok... will do tonight. thanks :) – Adam Haile Mar 27 '14 at 17:59
  • hmmm... sadly it looks like get_raw is new in 1.9.2 which isn't out for the Pi yet :( – Adam Haile Mar 28 '14 at 03:07
  • However, new details... it is this that is screwing with it: self._chanTones.set_volume(1.0, 0.0) If I don't make that call, it runs just fine, but with it the script seg faults. I'm using that so that I can output some sounds over one speaker and others (via another channel) over the other. – Adam Haile Mar 28 '14 at 03:13
  • So I'm guessing you need to keep that line in order to have it run how you want? – KodyVanRy Mar 28 '14 at 03:31
  • Yeah... pretty much. Or some other way of controlling which speaker the sound comes out of. Couldn't figure out any other way to do it aside from tweaking the volume per channel. But I need some sounds to come out of the left speaker and others the right but never both. – Adam Haile Mar 28 '14 at 03:32
  • Although it may be a bit more sloppy i would say have it the latter way, just wondering, is there a certain way you want the sounds to come from left or right such as sounds 1-5 through the left and 6-11 through the right speaker? – KodyVanRy Mar 28 '14 at 03:35
  • THis is all embedded in an old phone (long story). Left speaker (in the handset) needs to play dial tones and music and right speaker (in the phone body) needs to be the "ringer" that simulates the phone ringing. Hence the need to speaker specific control... – Adam Haile Mar 28 '14 at 03:37
  • OK to be honest what i would try if i were you is, because you said wav files work anywhere, to use wav files by converting the ogg files to wav files – KodyVanRy Mar 28 '14 at 03:45
  • A good program to do this would probably be audacity – KodyVanRy Mar 28 '14 at 03:45
  • Ha... just tried Wav again... and it turns out that I never tried wav with the volume, that was new with the ogg. still fails with Wav and volume tweak :( besides, I need a lot of music on here and wav would be too big :( – Adam Haile Mar 28 '14 at 03:46
  • Gotcha, um let me ask my dad, he's a computer guy and might have a but of insight, because as far as i can tell your code is right, he might have a work around. – KodyVanRy Mar 28 '14 at 03:48
  • Can you try and call `pygame.mixer.quit()` and tell me if you get a segmentation fault? – KodyVanRy Mar 28 '14 at 03:55
  • I call it already in my cleanup() method and it works fine... – Adam Haile Mar 28 '14 at 03:56
  • K and how exactly did you build/install pygame and what version are you running? – KodyVanRy Mar 28 '14 at 03:57
  • 1.9.1 and it's what came on Raspbian (latest version downloaded yesterday) on my Pi – Adam Haile Mar 28 '14 at 03:58
  • Yeah I'm gonna ask my dad in the morning and see what he says, but until then if you keep trying good luck, and I'll keep thinking about it til i talk to him, this is kinda bugging me that i can't figure it out. – KodyVanRy Mar 28 '14 at 04:06
  • Thanks for the dedication. Much appreciated! – Adam Haile Mar 28 '14 at 04:07
  • Ok sorry for not being around for a little while, I've been experimenting a little bit, I haven't found anything to solve the problem, but I did find that I have this same problem in loading images IF the other class is in a separate file. So are the two classes in the same python file or not? – KodyVanRy Apr 03 '14 at 19:42
  • They are in separate files... that would be really weird :P – Adam Haile Apr 03 '14 at 20:41
  • Oh wait you said it doesn't work when in the main file... – KodyVanRy Apr 03 '14 at 21:31
  • Could you upload one of the sounds to dropbox so i can test with that ogg and other oggs? – KodyVanRy Apr 03 '14 at 23:28

0 Answers0