pombreda / pgreloaded

Automatically exported from code.google.com/p/pgreloaded
Other
0 stars 0 forks source link

Using SoundSink.process_source more than once on source results in exception and failure to play sound #17

Closed GoogleCodeExporter closed 9 years ago

GoogleCodeExporter commented 9 years ago
What steps will reproduce the problem?
1. Create an app to play the sound using: a SoundSink, SoundSource, and 
SoundData
2. Play the sound loaded from file and queued in the SoundSource by telling 
SoundSink to process_source the sound.
3. On the second attempt to process the sound an error will occur in 
process_source about buffers and such.

What is the expected output? What do you see instead?
I get a backtrace:
Traceback (most recent call last):
  File "C:\Users\demolishun\Documents\Programming\python\vlc\custom_vlc.pyw", line 174, in onEventTimer
    self.playFeedbackAudio("breath1.wav")
  File "C:\Users\demolishun\Documents\Programming\python\vlc\custom_vlc.pyw", line 238, in playFeedbackAudio
    self.asink.process(None,[self.asource])
  File "C:\Python27\lib\site-packages\pygame2\audio\__init__.py", line 257, in process
    process_source(source)
  File "C:\Python27\lib\site-packages\pygame2\audio\__init__.py", line 219, in process_source
    self._create_buffers(source) # modified to fix buffer error
  File "C:\Python27\lib\site-packages\pygame2\audio\__init__.py", line 166, in _create_buffers
    snddata.frequency)
  File "C:\Python27\lib\site-packages\pygame2\openal\al.py", line 712, in buffer_data
    _raise_error_or_continue()
  File "C:\Python27\lib\site-packages\pygame2\openal\al.py", line 138, in _raise_error_or_continue
    raise OpenALError(_ERRMAP[errcode])
pygame2.openal.al.OpenALError: 'Invalid operation'

What version of the product are you using? On what operating system?
With which Python implementation?
NA

Please provide any additional information below.
The solution is to fix the buffer creation call:
def process_source(self, source):
        """Processes a SoundSource.

        Note: this does NOT activate the SoundSink. If another SoundSink
        is active, chances are good that the source is processed in that
        SoundSink.
        """
        ssid = source._ssid
        if ssid is None:
            ssid = self._create_source(source)
            self._create_buffers(source) # modified to fix buffer error

The latest version has the self._create_buffers un-indented so it is attempting 
to allocated buffers every call.  Indenting this call and putting under the 
protection of the ssid check seems to fix the problem and it seems to work as 
expected.

Original issue reported on code.google.com by demolish...@gmail.com on 6 Nov 2012 at 9:43

GoogleCodeExporter commented 9 years ago
There is still an issue with the buffer creation code for the source.  If I try 
and play the same SoundData on two different sources I get this error:
Traceback (most recent call last):
  File "C:\Users\demolishun\Documents\Programming\python\vlc\custom_vlc.pyw", line 714, in <module>
    player = Player("Simple PyVLC Player")
  File "C:\Users\demolishun\Documents\Programming\python\vlc\custom_vlc.pyw", line 253, in __init__
    self.playFeedbackAudio("breath1m.wav", 1, 1.0, 0.0, 1.0)
  File "C:\Users\demolishun\Documents\Programming\python\vlc\custom_vlc.pyw", line 414, in playFeedbackAudio
    self.asink.process_source(self.asources[source])
  File "C:\Python27\lib\site-packages\pygame2\audio\__init__.py", line 219, in process_source
    self._create_buffers(source) # modified to fix buffer error
  File "C:\Python27\lib\site-packages\pygame2\audio\__init__.py", line 166, in _create_buffers
    snddata.frequency)
  File "C:\Python27\lib\site-packages\pygame2\openal\al.py", line 712, in buffer_data
    _raise_error_or_continue()
  File "C:\Python27\lib\site-packages\pygame2\openal\al.py", line 138, in _raise_error_or_continue
    raise OpenALError(_ERRMAP[errcode])
OpenALError: 'Invalid operation'

I read on a website that OpenAL can play the same buffer on more than one 
source.  So this should be solvable.  Here is the link:
http://opensource.creative.com/pipermail/openal/2007-August/010624.html

Original comment by demolish...@gmail.com on 8 Nov 2012 at 4:44

GoogleCodeExporter commented 9 years ago
I found a way around this and I hope this does not cause a memory leak:
tsnd = pygame2.audio.SoundData(snd.format,snd.data[:],snd.size,snd.frequency)

Basically I take the snd which is a SoundData object and make a duplicate 
called tsnd.  Then I queue the tsnd into the source and I can play the sound at 
the same time on different sources.

Original comment by demolish...@gmail.com on 8 Nov 2012 at 4:56

GoogleCodeExporter commented 9 years ago
This is a bigger issue than I thought.  In order to solve this you must modify 
_create_buffers in the SoundSource object like so:
def _create_buffers(self, source):
        """Creates a new set of OpenAL buffers from the passed
        SoundSource."""
        queue = []
        for snddata in source._buffers:
            if snddata._bufid is None:
                bufid = None
                for kbufid, kdata in self._buffers.items():
                    if kdata is None:
                        # free buffer found
                        bufid = kbufid
                        break
                if bufid is not None:
                    snddata._bufid = bufid
                    self._buffers[bufid] = snddata
                else:
                    # No free buffer id, create a new one
                    bufid = al.gen_buffers(1)[0]
                    self._buffers[bufid] = snddata
                    snddata._bufid = bufid
                # Buffer id assigned, now buffer the data.
                # fhc indented
                al.buffer_data(snddata._bufid, snddata.format, snddata.data,
                           snddata.frequency)
            queue.append(snddata._bufid)
        # fhc added clearing of buffers
        al.source_i(source._ssid, al.AL_BUFFER, al.AL_NONE)
        al.source_queue_buffers(source._ssid, queue)

This does a couple of things.  It prevents an existing buffer from attempting 
to create a new buffer.  It also wipes out the already queued buffers on the 
source.  So when it rebuilds the queue there is not extra sounds attached.  
This change alleviates the need to copy sounds in order to play them.

Also, when you queue buffers using the SoundSource.queue() call you need to 
wipe out the existing internal _buffers if you don't want to play an existing 
sound anymore.  So doing this is sufficient if you made the changes above:  
SoundSource._buffers = [].

Original comment by demolish...@gmail.com on 8 Nov 2012 at 11:25

GoogleCodeExporter commented 9 years ago
Alright, that was not a good fix.  Instead I have moved the create buffers into 
the SoundSource where they belong.  This way the SoundSource can manage their 
own buffers.  The SoundSink now does nothing with buffers.  So remove the call 
in SoundSink and use this SoundSource instead:
class SoundSource(Component):
    """A sound source.

    The SoundSource is an object within the application world, that can emit
    sounds.
    """
    def __init__(self, gain=1.0, pitch=1.0, position=(0, 0, 0),
                 velocity=(0, 0, 0)):
        """Creates a new SoundSource."""
        super(SoundSource, self).__init__()
        self._ssid = None
        self._buffers = []
        self._buffids = {}
        self.gain = gain
        self.pitch = pitch
        self.position = position
        self.velocity = velocity
        self.request = SOURCE_NONE

    @property
    def ssid(self):
        """The OpenAL source id, if any."""
        return self._ssid

    def _create_buffers(self):
        """Creates a new set of OpenAL buffers from the passed
        SoundSource."""
        queue = []
        for snddata in self._buffers:
            if snddata._bufid is None:
                bufid = None
                for kbufid, kdata in self._buffids.items():
                    if kdata is None:
                        # free buffer found
                        bufid = kbufid
                        break
                if bufid is not None:
                    snddata._bufid = bufid
                    self._buffids[bufid] = snddata
                else:
                    # No free buffer id, create a new one
                    bufid = al.gen_buffers(1)[0]
                    self._buffids[bufid] = snddata
                    snddata._bufid = bufid
                # Buffer id assigned, now buffer the data.
                # fhc indented
                al.buffer_data(snddata._bufid, snddata.format, snddata.data,
                           snddata.frequency)
            queue.append(snddata._bufid)
        # fhc added clearing of buffers
        al.source_i(self._ssid, al.AL_BUFFER, al.AL_NONE)
        al.source_queue_buffers(self._ssid, queue)

    def dequeue(self, snd=None):
        """Removes SoundData from the playback queue for the source."""
        # dequeu all
        if snd is None:
            self._buffers = []
        # or attempt to remove a specific sound
        else:
            if snd in self._buffers:
                self._buffers.remove(snd)
        self._create_buffers()

    def queue(self, sounddata):
        """Appends a SoundData to the playback queue for the source."""
        if not isinstance(sounddata, SoundData):
            raise TypeError("sounddata must be a SoundData")
        self._buffers.append(sounddata)
        self._create_buffers()

This has fixed all the issues I had with playing sound and buffer management.

Original comment by demolish...@gmail.com on 9 Nov 2012 at 12:03

GoogleCodeExporter commented 9 years ago
As discussed off-list, the OpenAL/Audio wrappers are to be replaced in the 
foreseeable future with the implementation of http://code.google.com/p/py-al

I will close the issue for now.

Original comment by marcusvonappen@googlemail.com on 17 Nov 2012 at 8:23