在Raspberry Pi上,我尝试使用pygame.mixer播放一些声音,并且在主线程以外的任何线程上播放它时遇到问题。例如,我连接了一个键盘,当按下一个键(检测线程为螺纹)时,它会触发一个回调,并在该回调中尝试播放声音。但是当它尝试时,大部分时间都失败了:
Fatal Python error: (pygame parachute) Segmentation Fault
如果我在主线程上运行它,它可以正常工作。有没有理由为什么单独的线程不起作用或者这样做?请参阅下面的代码,在线程上调用的方法是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)])
旁注:这似乎只发生在ogg文件中,而不是wav。
Matrix键盘处理程序:
#!/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()
虽然我从不直接生成新线程,但是在它自己的内部GPIO中断回调的上下文中,通过键盘()类调用keypadCallback()。所以,我认为这意味着keypadCallback()实际上不在主线程上。