def get_buffer(self): if not self: buffer = al.ALuint() al.alGenBuffers(1, buffer) else: buffer = self.pop(0) return buffer
def __init__(self, source_group, player): super(OpenALAudioPlayer, self).__init__(source_group, player) audio_format = source_group.audio_format try: self._al_format = format_map[(audio_format.channels, audio_format.sample_size)] except KeyError: raise OpenALException('Unsupported audio format.') self._al_source = al.ALuint() al.alGenSources(1, self._al_source) # Lock policy: lock all instance vars (except constants). (AL calls # are locked on context). self._lock = threading.RLock() # Cursor positions, like DSound and Pulse drivers, refer to a # hypothetical infinite-length buffer. Cursor units are in bytes. # Cursor position of current (head) AL buffer self._buffer_cursor = 0 # Estimated playback cursor position (last seen) self._play_cursor = 0 # Cursor position of end of queued AL buffer. self._write_cursor = 0 # List of currently queued buffer sizes (in bytes) self._buffer_sizes = [] # List of currently queued buffer timestamps self._buffer_timestamps = [] # Timestamp at end of last written buffer (timestamp to return in case # of underrun) self._underrun_timestamp = None # List of (cursor, MediaEvent) self._events = [] # Desired play state (True even if stopped due to underrun) self._playing = False # Has source group EOS been seen (and hence, event added to queue)? self._eos = False # OpenAL 1.0 timestamp interpolation: system time of current buffer # playback (best guess) if not context.have_1_1: self._buffer_system_time = time.time() self.refill(self._ideal_buffer_size)
def dequeueBuffer(self, alSource, buffer): """A buffer has finished playing, free it.""" assert context._lock.locked() sourceBuffs = self._sources[alSource.value] if buffer in map(lambda x: x.value, sourceBuffs): #sourceBuffs.remove(buffer) self._sources[alSource.value] = [ b for b in sourceBuffs if b.value != buffer ] self._buffers.append(al.ALuint(buffer)) elif _debug_buffers: # This seems to be the problem with Mac OS X - The buffers are # dequeued, but they're not _actually_ buffers. In other words, # there's some leakage, so after awhile, things break. print("Bad buffer: " + str(buffer))
def refill(self, write_size): if _debug: print 'refill', write_size self._lock.acquire() while write_size > self._min_buffer_size: audio_data = self.source_group.get_audio_data(write_size) if not audio_data: self._eos = True self._events.append( (self._write_cursor, mt_media.MediaEvent(0, 'on_eos'))) self._events.append( (self._write_cursor, mt_media.MediaEvent(0, 'on_source_group_eos'))) break for event in audio_data.events: cursor = self._write_cursor + event.timestamp * \ self.source_group.audio_format.bytes_per_second self._events.append((cursor, event)) buffer = al.ALuint() context.lock() al.alGenBuffers(1, buffer) al.alBufferData(buffer, self._al_format, audio_data.data, audio_data.length, self.source_group.audio_format.sample_rate) al.alSourceQueueBuffers(self._al_source, 1, ctypes.byref(buffer)) context.unlock() self._write_cursor += audio_data.length self._buffer_sizes.append(audio_data.length) self._buffer_timestamps.append(audio_data.timestamp) write_size -= audio_data.length # Check for underrun stopping playback if self._playing: state = al.ALint() context.lock() al.alGetSourcei(self._al_source, al.AL_SOURCE_STATE, state) if state.value != al.AL_PLAYING: if _debug: print 'underrun' al.alSourcePlay(self._al_source) context.unlock() self._lock.release()
def getBuffers(self, alSource, i): """Returns an array containing i buffer names. The returned list must not be modified in any way, and may get changed by subsequent calls to getBuffers. """ assert context._lock.locked() buffs = [] try: while i > 0: b = self._buffers.pop() if not al.alIsBuffer(b): # Protect against implementations that DO free buffers # when they delete a source - carry on. if _debug_buffers: print("Found a bad buffer") continue buffs.append(b) i -= 1 except IndexError: while i > 0: buffer = al.ALuint() al.alGenBuffers(1, buffer) if _debug_buffers: error = al.alGetError() if error != 0: print("GEN BUFFERS: " + str(error)) buffs.append(buffer) i -= 1 alSourceVal = alSource.value if alSourceVal not in self._sources: self._sources[alSourceVal] = buffs else: self._sources[alSourceVal].extend(buffs) return buffs
return True if __name__ == '__main__': import sys # openal device = alc.alcOpenDevice(None) if not device: raise Exception('No OpenAL device.') alcontext = alc.alcCreateContext(device, None) alc.alcMakeContextCurrent(alcontext) source = al.ALuint() al.alGenSources(1, source) pool = BufferPool() # avcodec init() container = open(sys.argv[1]) stream = get_audio_stream(container) decoder = AVCodecDecoder(container, stream) while True: buffer = pool.get_buffer() if not decoder.fill_buffer(buffer): break
class AVCodecDecoder: def __init__(self, container, stream): self.codec = get_codec(container, stream) if self.codec.contents.channels == 1: self.buffer_format = al.AL_FORMAT_MONO16 elif self.codec.contents.channels == 2: self.buffer_format = al.AL_FORMAT_STEREO16 else: raise Exception('Invalid number of channels') self.sample_rate = self.codec.contents.sample_rate self.container = container self.stream = stream self.packet = avformat.AVPacket() self.sample_buffer = \ (ctypes.c_int16 * (avcodec.AVCODEC_MAX_AUDIO_FRAME_SIZE / 2))() self.read_packet() def read_packet(self): while True: if self.packet.data: self.packet.data = None self.packet.size = 0 if avformat.av_read_packet(self.container, self.packet) < 0: break if self.packet.stream_index == self.stream: break self.packet_data = self.packet.data self.packet_size = self.packet.size def fill_buffer(self, buffer): if self.packet_size <= 0: self.read_packet() if self.packet_size == 0: # EOS return False sample_buffer_size = ctypes.c_int() len = avcodec.avcodec_decode_audio(self.codec, self.sample_buffer, sample_buffer_size, self.packet_data, self.packet_size) if len < 0: # Error, skip frame raise Exception('frame error TODO') if sample_buffer_size.value > 0: al.alBufferData(buffer, self.buffer_format, self.sample_buffer, sample_buffer_size.value, self.sample_rate) # Advance buffer pointer self.packet_data = ctypes.c_uint8.from_address( ctypes.addressof(self.packet_data) + len) self.packet_size -= len return True import sys # openal device = alc.alcOpenDevice(None) if not device: raise Exception('No OpenAL device.') alcontext = alc.alcCreateContext(device, None) alc.alcMakeContextCurrent(alcontext) source = al.ALuint() al.alGenSources(1, source) pool = BufferPool() # avcodec init() container = open(sys.argv[1]) stream = get_audio_stream(container) decoder = AVCodecDecoder(container, stream) while True: buffer = pool.get_buffer() if not decoder.fill_buffer(buffer): break al.alSourceQueueBuffers(source, 1, buffer) al.alSourcePlay(source) while True: value = al.ALint() al.alGetSourcei(source, al.AL_SOURCE_STATE, value) if value.value == al.AL_STOPPED: break time.sleep(1)