def refill(self, write_size): assert _debug_media('refill', write_size) with self._lock: while write_size > self.min_buffer_size: audio_data = self.source_group.get_audio_data(write_size) if not audio_data: assert _debug_media('No audio data left') if self._has_underrun(): assert _debug_media('Underrun') MediaEvent(0, 'on_eos')._sync_dispatch_to_player( self.player) MediaEvent( 0, 'on_source_group_eos')._sync_dispatch_to_player( self.player) break assert _debug_media('Writing {} bytes'.format( audio_data.length)) self._queue_events(audio_data) self._queue_audio_data(audio_data) self._update_write_cursor(audio_data) write_size -= audio_data.length # Check for underrun stopping playback with self.driver: if self._playing and not self.source.is_playing: assert _debug_media('underrun') self.source.play()
def test_add_events(self): buf = EventBuffer() self.assertIsNone(buf.get_next_event_timestamp()) event1 = MediaEvent(.1, 'Event1') event2 = MediaEvent(.5, 'Event2') data = AudioData('', 0, 0., 1., [event1, event2]) buf.add_events(data) self.assertAlmostEqual(.1, buf.get_next_event_timestamp())
def _consume_data(self): """Consume content of packets that should have been played back up to now.""" with self._thread.condition: offset = self._calculate_offset() self._audio_buffer.consume_audio_data(offset) self._update_time() if self._audio_buffer.is_empty(): if _debug: print('Out of packets') timestamp = self.get_time() MediaEvent(timestamp, 'on_eos')._sync_dispatch_to_player(self.player) MediaEvent(timestamp, 'on_source_group_eos')._sync_dispatch_to_player(self.player)
def refill(self, write_size): with self._lock: while write_size > 0: if _debug: print('refill, write_size =', write_size) # Get next audio packet (or remains of last one) if self._next_audio_data: audio_data = self._next_audio_data self._next_audio_data = None else: audio_data = self.source_group.get_audio_data(write_size) # Write it, or silence if there are no more packets if audio_data: # Add events for event in audio_data.events: event_cursor = self._write_cursor + event.timestamp * \ self.source_group.audio_format.bytes_per_second self._events.append((event_cursor, event)) # Add timestamp (at end of this data packet) ts_cursor = self._write_cursor + audio_data.length self._timestamps.append( (ts_cursor, audio_data.timestamp + audio_data.duration)) # Write data if _debug: print('write', audio_data.length) length = min(write_size, audio_data.length) self.write(audio_data, length) if audio_data.length: self._next_audio_data = audio_data write_size -= length else: # Write silence if self._eos_cursor is None: self._eos_cursor = self._write_cursor self._events.append( (self._eos_cursor, MediaEvent(0, 'on_eos'))) self._events.append( (self._eos_cursor, MediaEvent(0, 'on_source_group_eos'))) self._events.sort() if self._write_cursor > self._eos_cursor + self._buffer_size: self.stop() else: self.write(None, write_size) write_size = 0
def test_consume_non_str_data(self): audio_format = AudioFormat(1, 8, 11025) duration = 1.0 length = int(duration * audio_format.bytes_per_second) data = self.generate_random_string_data(length) non_str_data = self.create_string_buffer(data) audio_data = AudioData(non_str_data, length, 0.0, duration, (MediaEvent(0.0, 'event'), )) chunk_duration = 0.1 chunk_size = int(chunk_duration * audio_format.bytes_per_second) self.assertLessEqual(chunk_size, length) audio_data.consume(chunk_size, audio_format) self.assertEqual(audio_data.length, length - chunk_size) self.assertAlmostEqual(audio_data.duration, duration - chunk_duration, places=2) self.assertAlmostEqual(audio_data.timestamp, chunk_duration, places=2) self.assertEqual(audio_data.get_string_data(), data[chunk_size:]) self.assertTupleEqual(audio_data.events, ())
def _get_new_audiodata(self): assert _debug_media('Getting new audio data buffer.') self._audiodata_buffer = self.source_group.get_audio_data( self.ideal_buffer_size) if self._audiodata_buffer is not None: assert _debug_media('New audio data available: {} bytes'.format( self._audiodata_buffer.length)) self._queue_events(self._audiodata_buffer) else: assert _debug_media('No audio data left') if self._has_underrun(): assert _debug_media('Underrun') MediaEvent(0, 'on_eos')._sync_dispatch_to_player(self.player) MediaEvent(0, 'on_source_group_eos')._sync_dispatch_to_player( self.player)
def test_get_time_to_next_event(self): buf = EventBuffer() self.assertIsNone(buf.get_next_event_timestamp()) event1 = MediaEvent(.2, 'Event1') event2 = MediaEvent(.2, 'Event2') data1 = AudioData('', 0, 0., 1., [event1, event2]) buf.add_events(data1) event3 = MediaEvent(.3, 'Event3') event4 = MediaEvent(.4, 'Event4') data2 = AudioData('', 0, 1., 1., [event3, event4]) buf.add_events(data2) self.assertAlmostEqual(.2, buf.get_time_to_next_event(0.)) self.assertAlmostEqual(.1, buf.get_time_to_next_event(.1)) buf.get_expired_events(.2) self.assertAlmostEqual(1.1, buf.get_time_to_next_event(.2)) self.assertAlmostEqual(.1, buf.get_time_to_next_event(1.2))
def test_get_multiple_events(self): buf = EventBuffer() self.assertIsNone(buf.get_next_event_timestamp()) event1 = MediaEvent(.2, 'Event1') event2 = MediaEvent(.2, 'Event2') data1 = AudioData('', 0, 0., 1., [event1, event2]) buf.add_events(data1) event3 = MediaEvent(.3, 'Event3') event4 = MediaEvent(.4, 'Event4') data2 = AudioData('', 0, 1., 1., [event3, event4]) buf.add_events(data2) expired_events = buf.get_expired_events(0.) self.assertListEqual([], expired_events) expired_events = buf.get_expired_events(.2) self.assertListEqual([event1, event2], expired_events) expired_events = buf.get_expired_events(1.6) self.assertListEqual([event3, event4], expired_events)
def test_get_expired_events(self): buf = EventBuffer() self.assertIsNone(buf.get_next_event_timestamp()) event1 = MediaEvent(.1, 'Event1') event2 = MediaEvent(.5, 'Event2') data = AudioData('', 0, 0., 1., [event1, event2]) buf.add_events(data) expired_events = buf.get_expired_events(0.) self.assertListEqual([], expired_events) expired_events = buf.get_expired_events(.1) self.assertListEqual([event1], expired_events) expired_events = buf.get_expired_events(.1) self.assertListEqual([], expired_events) expired_events = buf.get_expired_events(.6) self.assertListEqual([event2], expired_events) expired_events = buf.get_expired_events(.6) self.assertListEqual([], expired_events)
def _get_new_audiodata(self): assert _debug('Getting new audio data buffer.') compensation_time = self.get_audio_time_diff() self._audiodata_buffer = self.playlist.get_audio_data( self.ideal_buffer_size, compensation_time) if self._audiodata_buffer is not None: assert _debug('New audio data available: {} bytes'.format( self._audiodata_buffer.length)) self._queue_events(self._audiodata_buffer) else: assert _debug('No audio data left') if self._has_underrun(): assert _debug('Underrun') MediaEvent(0, 'on_eos')._sync_dispatch_to_player(self.player)
def test_consume_only_events(self): audio_format = AudioFormat(1, 8, 11025) duration = 1.0 length = int(duration * audio_format.bytes_per_second) data = self.generate_random_string_data(length) audio_data = AudioData(data, length, 0.0, duration, (MediaEvent(0.0, 'event'),)) chunk_size = 0 audio_data.consume(chunk_size, audio_format) self.assertEqual(audio_data.length, length) self.assertAlmostEqual(audio_data.duration, duration, places=2) self.assertAlmostEqual(audio_data.timestamp, 0., places=2) self.assertEqual(audio_data.get_string_data(), data) self.assertTupleEqual(audio_data.events, ())
def get_audio_data(self, bytes): """Get next audio packet. :Parameters: `bytes` : int Hint for preferred size of audio packet; may be ignored. :rtype: `AudioData` :return: Audio data, or None if there is no more data. """ if not self._sources: return None data = self._sources[0].get_audio_data(bytes) eos = False while not data: eos = True if self._loop and not self._advance_after_eos: self._timestamp_offset += self._sources[0].duration self._dequeued_durations.insert(0, self._sources[0].duration) self._sources[0].seek(0) else: self._advance_after_eos = False # Advance source if there's something to advance to. # Otherwise leave last source paused at EOS. if len(self._sources) > 1: self._advance() else: return None data = self._sources[0].get_audio_data(bytes) # TODO method rename data.timestamp += self._timestamp_offset if eos: if _debug: print('adding on_eos event to audio data') data.events.append(MediaEvent(0, 'on_eos')) return data
def _dispatch_new_event(self, event_name): MediaEvent(0, event_name)._sync_dispatch_to_player(self.player)
def refill_source_player(self): """Obtains audio data from the source, puts it into a buffer to submit to the voice. Unlike the other drivers this does not carve pieces of audio from the buffer and slowly consume it. This submits the buffer retrieved from the decoder in it's entirety. """ buffers_queued = self._xa2_source_voice.buffers_queued # Free any buffers that have ended. while len(self._buffers) > buffers_queued: # Clean out any buffers that have played. buffer = self._buffers.pop(0) self._play_cursor += buffer.AudioBytes del buffer # Does this remove AudioData within the buffer? Let GC remove or explicit remove? # We have to wait for all of the buffers we are flushing to end before we restart next buffer. # When voice reaches 0 buffers, it is available for re-use. if self._flushing: if buffers_queued == 0: self._flushing = False # This is required because the next call to play will come before all flushes are done. # Restart at next available opportunity. pyglet.clock.schedule_once(self._restart, 0) return if self._deleted: if buffers_queued == 0: self._deleted = False self._xa2_driver.return_voice(self._xa2_source_voice) return # Wait for the playback to hit 0 buffers before we eos. if self.buffer_end_submitted: if buffers_queued == 0: self._xa2_source_voice.stop() MediaEvent(0, "on_eos")._sync_dispatch_to_player(self.player) else: current_buffers = [] while buffers_queued < self.max_buffer_count: audio_data = self.source.get_audio_data(self._buffer_size, 0.0) if audio_data: assert _debug( 'Xaudio2: audio data - length: {}, duration: {}, buffer size: {}' .format(audio_data.length, audio_data.duration, self._buffer_size)) if audio_data.length == 0: # Sometimes audio data has 0 length at the front? continue x2_buffer = self._xa2_driver.create_buffer(audio_data) current_buffers.append(x2_buffer) self._write_cursor += x2_buffer.AudioBytes # We've pushed this many bytes into the source player. self._add_audiodata_events(audio_data) self._add_audiodata_timestamp(audio_data) buffers_queued += 1 else: # End of audio data, set last packet as end. self.buffer_end_submitted = True break # We submit the buffers here, just in-case the end of stream was found. for cx2_buffer in current_buffers: self._xa2_source_voice.submit_buffer(cx2_buffer) # Store buffers temporarily, otherwise they get GC'd. self._buffers.extend(current_buffers) self._dispatch_pending_events()
def _add_event_at_write_index(self, event_name): assert _debug('PulseAudioPlayer: Add event at index {}'.format( self._write_index)) self._events.append((self._write_index, MediaEvent(0., event_name)))