def __init__(self, filename, file=None): if file is not None: raise NotImplementedError('TODO: Load from file stream') self._file = av.avbin_open_filename(asbytes_filename(filename)) if not self._file: raise AVbinException('Could not open "%s"' % filename) self._video_stream = None self._video_stream_index = -1 self._audio_stream = None self._audio_stream_index = -1 file_info = AVbinFileInfo() file_info.structure_size = ctypes.sizeof(file_info) av.avbin_file_info(self._file, ctypes.byref(file_info)) self._duration = timestamp_from_avbin(file_info.duration) self.info = SourceInfo() self.info.title = file_info.title self.info.author = file_info.author self.info.copyright = file_info.copyright self.info.comment = file_info.comment self.info.album = file_info.album self.info.year = file_info.year self.info.track = file_info.track self.info.genre = file_info.genre # Pick the first video and audio streams found, ignore others. for i in range(file_info.n_streams): info = AVbinStreamInfo8() info.structure_size = ctypes.sizeof(info) av.avbin_stream_info(self._file, i, info) if (info.type == AVBIN_STREAM_TYPE_VIDEO and not self._video_stream): stream = av.avbin_open_stream(self._file, i) if not stream: continue self.video_format = VideoFormat( width=info.u.video.width, height=info.u.video.height) if info.u.video.sample_aspect_num != 0: self.video_format.sample_aspect = ( float(info.u.video.sample_aspect_num) / info.u.video.sample_aspect_den) if _have_frame_rate: self.video_format.frame_rate = ( float(info.u.video.frame_rate_num) / info.u.video.frame_rate_den) self._video_stream = stream self._video_stream_index = i elif (info.type == AVBIN_STREAM_TYPE_AUDIO and info.u.audio.sample_bits in (8, 16) and info.u.audio.channels in (1, 2) and not self._audio_stream): stream = av.avbin_open_stream(self._file, i) if not stream: continue self.audio_format = AudioFormat( channels=info.u.audio.channels, sample_size=info.u.audio.sample_bits, sample_rate=info.u.audio.sample_rate) self._audio_stream = stream self._audio_stream_index = i self._packet = AVbinPacket() self._packet.structure_size = ctypes.sizeof(self._packet) self._packet.stream_index = -1 self._events = [] # Timestamp of last video packet added to decoder queue. self._video_timestamp = 0 self._buffered_audio_data = [] if self.audio_format: self._audio_buffer = \ (ctypes.c_uint8 * av.avbin_get_audio_buffer_size())() if self.video_format: self._video_packets = [] self._decode_thread = WorkerThread() self._decode_thread.start() self._condition = threading.Condition()
def __init__(self, filename, file=None): if file is not None: raise NotImplementedError('TODO: Load from file stream') self._file = av.avbin_open_filename(filename) if not self._file: raise AVbinException('Could not open "%s"' % filename) self._video_stream = None self._video_stream_index = -1 self._audio_stream = None self._audio_stream_index = -1 file_info = AVbinFileInfo() file_info.structure_size = ctypes.sizeof(file_info) av.avbin_file_info(self._file, ctypes.byref(file_info)) self._duration = timestamp_from_avbin(file_info.duration) self.info = SourceInfo() self.info.title = file_info.title self.info.author = file_info.author self.info.copyright = file_info.copyright self.info.comment = file_info.comment self.info.album = file_info.album self.info.year = file_info.year self.info.track = file_info.track self.info.genre = file_info.genre # Pick the first video and audio streams found, ignore others. for i in range(file_info.n_streams): info = AVbinStreamInfo8() info.structure_size = ctypes.sizeof(info) av.avbin_stream_info(self._file, i, info) if (info.type == AVBIN_STREAM_TYPE_VIDEO and not self._video_stream): stream = av.avbin_open_stream(self._file, i) if not stream: continue self.video_format = VideoFormat( width=info.u.video.width, height=info.u.video.height) if info.u.video.sample_aspect_num != 0: self.video_format.sample_aspect = ( float(info.u.video.sample_aspect_num) / info.u.video.sample_aspect_den) if _have_frame_rate: self.video_format.frame_rate = ( float(info.u.video.frame_rate_num) / info.u.video.frame_rate_den) self._video_stream = stream self._video_stream_index = i elif (info.type == AVBIN_STREAM_TYPE_AUDIO and info.u.audio.sample_bits in (8, 16) and info.u.audio.channels in (1, 2) and not self._audio_stream): stream = av.avbin_open_stream(self._file, i) if not stream: continue self.audio_format = AudioFormat( channels=info.u.audio.channels, sample_size=info.u.audio.sample_bits, sample_rate=info.u.audio.sample_rate) self._audio_stream = stream self._audio_stream_index = i self._packet = AVbinPacket() self._packet.structure_size = ctypes.sizeof(self._packet) self._packet.stream_index = -1 self._events = [] # Timestamp of last video packet added to decoder queue. self._video_timestamp = 0 self._buffered_audio_data = [] if self.audio_format: self._audio_buffer = \ (ctypes.c_uint8 * av.avbin_get_audio_buffer_size())() if self.video_format: self._video_packets = [] self._decode_thread = WorkerThread() self._decode_thread.start() self._condition = threading.Condition()
class AVbinSource(StreamingSource): def __init__(self, filename, file=None): if file is not None: raise NotImplementedError('TODO: Load from file stream') self._file = av.avbin_open_filename(asbytes_filename(filename)) if not self._file: raise AVbinException('Could not open "%s"' % filename) self._video_stream = None self._video_stream_index = -1 self._audio_stream = None self._audio_stream_index = -1 file_info = AVbinFileInfo() file_info.structure_size = ctypes.sizeof(file_info) av.avbin_file_info(self._file, ctypes.byref(file_info)) self._duration = timestamp_from_avbin(file_info.duration) self.info = SourceInfo() self.info.title = file_info.title self.info.author = file_info.author self.info.copyright = file_info.copyright self.info.comment = file_info.comment self.info.album = file_info.album self.info.year = file_info.year self.info.track = file_info.track self.info.genre = file_info.genre # Pick the first video and audio streams found, ignore others. for i in range(file_info.n_streams): info = AVbinStreamInfo8() info.structure_size = ctypes.sizeof(info) av.avbin_stream_info(self._file, i, info) if (info.type == AVBIN_STREAM_TYPE_VIDEO and not self._video_stream): stream = av.avbin_open_stream(self._file, i) if not stream: continue self.video_format = VideoFormat( width=info.u.video.width, height=info.u.video.height) if info.u.video.sample_aspect_num != 0: self.video_format.sample_aspect = ( float(info.u.video.sample_aspect_num) / info.u.video.sample_aspect_den) if _have_frame_rate: self.video_format.frame_rate = ( float(info.u.video.frame_rate_num) / info.u.video.frame_rate_den) self._video_stream = stream self._video_stream_index = i elif (info.type == AVBIN_STREAM_TYPE_AUDIO and info.u.audio.sample_bits in (8, 16) and info.u.audio.channels in (1, 2) and not self._audio_stream): stream = av.avbin_open_stream(self._file, i) if not stream: continue self.audio_format = AudioFormat( channels=info.u.audio.channels, sample_size=info.u.audio.sample_bits, sample_rate=info.u.audio.sample_rate) self._audio_stream = stream self._audio_stream_index = i self._packet = AVbinPacket() self._packet.structure_size = ctypes.sizeof(self._packet) self._packet.stream_index = -1 self._events = [] # Timestamp of last video packet added to decoder queue. self._video_timestamp = 0 self._buffered_audio_data = [] if self.audio_format: self._audio_buffer = \ (ctypes.c_uint8 * av.avbin_get_audio_buffer_size())() if self.video_format: self._video_packets = [] self._decode_thread = WorkerThread() self._decode_thread.start() self._condition = threading.Condition() def __del__(self): if _debug: print 'del avbin source' try: if self._video_stream: av.avbin_close_stream(self._video_stream) if self._audio_stream: av.avbin_close_stream(self._audio_stream) av.avbin_close_file(self._file) except: pass # XXX TODO call this / add to source api def delete(self): if self.video_format: self._decode_thread.stop() def seek(self, timestamp): if _debug: print 'AVbin seek', timestamp av.avbin_seek_file(self._file, timestamp_to_avbin(timestamp)) self._audio_packet_size = 0 del self._events[:] del self._buffered_audio_data[:] if self.video_format: self._video_timestamp = 0 self._condition.acquire() for packet in self._video_packets: packet.image = None self._condition.notify() self._condition.release() del self._video_packets[:] self._decode_thread.clear_jobs() def _get_packet(self): # Read a packet into self._packet. Returns True if OK, False if no # more packets are in stream. return av.avbin_read(self._file, self._packet) == AVBIN_RESULT_OK def _process_packet(self): # Returns (packet_type, packet), where packet_type = 'video' or # 'audio'; and packet is VideoPacket or AudioData. In either case, # packet is buffered or queued for decoding; no further action is # necessary. Returns (None, None) if packet was neither type. if self._packet.stream_index == self._video_stream_index: if self._packet.timestamp < 0: # XXX TODO # AVbin needs hack to decode timestamp for B frames in # some containers (OGG?). See # http://www.dranger.com/ffmpeg/tutorial05.html # For now we just drop these frames. return None, None video_packet = VideoPacket(self._packet) if _debug: print 'Created and queued frame %d (%f)' % \ (video_packet.id, video_packet.timestamp) self._video_timestamp = max(self._video_timestamp, video_packet.timestamp) self._video_packets.append(video_packet) self._decode_thread.put_job( lambda: self._decode_video_packet(video_packet)) return 'video', video_packet elif self._packet.stream_index == self._audio_stream_index: audio_data = self._decode_audio_packet() if audio_data: if _debug: print 'Got an audio packet at', audio_data.timestamp self._buffered_audio_data.append(audio_data) return 'audio', audio_data return None, None def get_audio_data(self, bytes): try: audio_data = self._buffered_audio_data.pop(0) audio_data_timeend = audio_data.timestamp + audio_data.duration except IndexError: audio_data = None audio_data_timeend = self._video_timestamp + 1 if _debug: print 'get_audio_data' have_video_work = False # Keep reading packets until we have an audio packet and all the # associated video packets have been enqueued on the decoder thread. while not audio_data or ( self._video_stream and self._video_timestamp < audio_data_timeend): if not self._get_packet(): break packet_type, packet = self._process_packet() if packet_type == 'video': have_video_work = True elif not audio_data and packet_type == 'audio': audio_data = self._buffered_audio_data.pop(0) if _debug: print 'Got requested audio packet at', audio_data.timestamp audio_data_timeend = audio_data.timestamp + audio_data.duration if have_video_work: # Give decoder thread a chance to run before we return this audio # data. time.sleep(0) if not audio_data: if _debug: print 'get_audio_data returning None' return None while self._events and self._events[0].timestamp <= audio_data_timeend: event = self._events.pop(0) if event.timestamp >= audio_data.timestamp: event.timestamp -= audio_data.timestamp audio_data.events.append(event) if _debug: print 'get_audio_data returning ts %f with events' % \ audio_data.timestamp, audio_data.events print 'remaining events are', self._events return audio_data def _decode_audio_packet(self): packet = self._packet size_out = ctypes.c_int(len(self._audio_buffer)) while True: audio_packet_ptr = ctypes.cast(packet.data, ctypes.c_void_p) audio_packet_size = packet.size used = av.avbin_decode_audio(self._audio_stream, audio_packet_ptr, audio_packet_size, self._audio_buffer, size_out) if used < 0: self._audio_packet_size = 0 break audio_packet_ptr.value += used audio_packet_size -= used if size_out.value <= 0: continue # XXX how did this ever work? replaced with copy below # buffer = ctypes.string_at(self._audio_buffer, size_out) # XXX to actually copy the data.. but it never used to crash, so # maybe I'm missing something buffer = ctypes.create_string_buffer(size_out.value) ctypes.memmove(buffer, self._audio_buffer, len(buffer)) buffer = buffer.raw duration = float(len(buffer)) / self.audio_format.bytes_per_second self._audio_packet_timestamp = \ timestamp = timestamp_from_avbin(packet.timestamp) return AudioData(buffer, len(buffer), timestamp, duration, []) def _decode_video_packet(self, packet): width = self.video_format.width height = self.video_format.height pitch = width * 3 buffer = (ctypes.c_uint8 * (pitch * height))() result = av.avbin_decode_video(self._video_stream, packet.data, packet.size, buffer) if result < 0: image_data = None else: image_data = image.ImageData(width, height, 'RGB', buffer, pitch) packet.image = image_data # Notify get_next_video_frame() that another one is ready. self._condition.acquire() self._condition.notify() self._condition.release() def _ensure_video_packets(self): '''Process packets until a video packet has been queued (and begun decoding). Return False if EOS. ''' if not self._video_packets: if _debug: print 'No video packets...' # Read ahead until we have another video packet self._get_packet() packet_type, _ = self._process_packet() while packet_type and packet_type != 'video': self._get_packet() packet_type, _ = self._process_packet() if not packet_type: return False if _debug: print 'Queued packet', _ return True def get_next_video_timestamp(self): if not self.video_format: return if self._ensure_video_packets(): if _debug: print 'Next video timestamp is', self._video_packets[0].timestamp return self._video_packets[0].timestamp def get_next_video_frame(self): if not self.video_format: return if self._ensure_video_packets(): packet = self._video_packets.pop(0) if _debug: print 'Waiting for', packet # Block until decoding is complete self._condition.acquire() while packet.image == 0: self._condition.wait() self._condition.release() if _debug: print 'Returning', packet return packet.image
class AVbinSource(StreamingSource): def __init__(self, filename, file=None): if file is not None: raise NotImplementedError('TODO: Load from file stream') self._file = av.avbin_open_filename(filename) if not self._file: raise AVbinException('Could not open "%s"' % filename) self._video_stream = None self._video_stream_index = -1 self._audio_stream = None self._audio_stream_index = -1 file_info = AVbinFileInfo() file_info.structure_size = ctypes.sizeof(file_info) av.avbin_file_info(self._file, ctypes.byref(file_info)) self._duration = timestamp_from_avbin(file_info.duration) self.info = SourceInfo() self.info.title = file_info.title self.info.author = file_info.author self.info.copyright = file_info.copyright self.info.comment = file_info.comment self.info.album = file_info.album self.info.year = file_info.year self.info.track = file_info.track self.info.genre = file_info.genre # Pick the first video and audio streams found, ignore others. for i in range(file_info.n_streams): info = AVbinStreamInfo8() info.structure_size = ctypes.sizeof(info) av.avbin_stream_info(self._file, i, info) if (info.type == AVBIN_STREAM_TYPE_VIDEO and not self._video_stream): stream = av.avbin_open_stream(self._file, i) if not stream: continue self.video_format = VideoFormat( width=info.u.video.width, height=info.u.video.height) if info.u.video.sample_aspect_num != 0: self.video_format.sample_aspect = ( float(info.u.video.sample_aspect_num) / info.u.video.sample_aspect_den) if _have_frame_rate: self.video_format.frame_rate = ( float(info.u.video.frame_rate_num) / info.u.video.frame_rate_den) self._video_stream = stream self._video_stream_index = i elif (info.type == AVBIN_STREAM_TYPE_AUDIO and info.u.audio.sample_bits in (8, 16) and info.u.audio.channels in (1, 2) and not self._audio_stream): stream = av.avbin_open_stream(self._file, i) if not stream: continue self.audio_format = AudioFormat( channels=info.u.audio.channels, sample_size=info.u.audio.sample_bits, sample_rate=info.u.audio.sample_rate) self._audio_stream = stream self._audio_stream_index = i self._packet = AVbinPacket() self._packet.structure_size = ctypes.sizeof(self._packet) self._packet.stream_index = -1 self._events = [] # Timestamp of last video packet added to decoder queue. self._video_timestamp = 0 self._buffered_audio_data = [] if self.audio_format: self._audio_buffer = \ (ctypes.c_uint8 * av.avbin_get_audio_buffer_size())() if self.video_format: self._video_packets = [] self._decode_thread = WorkerThread() self._decode_thread.start() self._condition = threading.Condition() def __del__(self): if _debug: print 'del avbin source' try: if self._video_stream: av.avbin_close_stream(self._video_stream) if self._audio_stream: av.avbin_close_stream(self._audio_stream) av.avbin_close_file(self._file) except: pass # XXX TODO call this / add to source api def delete(self): if self.video_format: self._decode_thread.stop() def seek(self, timestamp): if _debug: print 'AVbin seek', timestamp av.avbin_seek_file(self._file, timestamp_to_avbin(timestamp)) self._audio_packet_size = 0 del self._events[:] del self._buffered_audio_data[:] if self.video_format: self._video_timestamp = 0 self._condition.acquire() for packet in self._video_packets: packet.image = None self._condition.notify() self._condition.release() del self._video_packets[:] self._decode_thread.clear_jobs() def _get_packet(self): # Read a packet into self._packet. Returns True if OK, False if no # more packets are in stream. return av.avbin_read(self._file, self._packet) == AVBIN_RESULT_OK def _process_packet(self): # Returns (packet_type, packet), where packet_type = 'video' or # 'audio'; and packet is VideoPacket or AudioData. In either case, # packet is buffered or queued for decoding; no further action is # necessary. Returns (None, None) if packet was neither type. if self._packet.stream_index == self._video_stream_index: if self._packet.timestamp < 0: # XXX TODO # AVbin needs hack to decode timestamp for B frames in # some containers (OGG?). See # http://www.dranger.com/ffmpeg/tutorial05.html # For now we just drop these frames. return None, None video_packet = VideoPacket(self._packet) if _debug: print 'Created and queued frame %d (%f)' % \ (video_packet.id, video_packet.timestamp) self._video_timestamp = max(self._video_timestamp, video_packet.timestamp) self._video_packets.append(video_packet) self._decode_thread.put_job( lambda: self._decode_video_packet(video_packet)) return 'video', video_packet elif self._packet.stream_index == self._audio_stream_index: audio_data = self._decode_audio_packet() if audio_data: if _debug: print 'Got an audio packet at', audio_data.timestamp self._buffered_audio_data.append(audio_data) return 'audio', audio_data return None, None def get_audio_data(self, bytes): try: audio_data = self._buffered_audio_data.pop(0) audio_data_timeend = audio_data.timestamp + audio_data.duration except IndexError: audio_data = None audio_data_timeend = self._video_timestamp + 1 if _debug: print 'get_audio_data' have_video_work = False # Keep reading packets until we have an audio packet and all the # associated video packets have been enqueued on the decoder thread. while not audio_data or ( self._video_stream and self._video_timestamp < audio_data_timeend): if not self._get_packet(): break packet_type, packet = self._process_packet() if packet_type == 'video': have_video_work = True elif not audio_data and packet_type == 'audio': audio_data = self._buffered_audio_data.pop(0) if _debug: print 'Got requested audio packet at', audio_data.timestamp audio_data_timeend = audio_data.timestamp + audio_data.duration if have_video_work: # Give decoder thread a chance to run before we return this audio # data. time.sleep(0) if not audio_data: if _debug: print 'get_audio_data returning None' return None while self._events and self._events[0].timestamp <= audio_data_timeend: event = self._events.pop(0) if event.timestamp >= audio_data.timestamp: event.timestamp -= audio_data.timestamp audio_data.events.append(event) if _debug: print 'get_audio_data returning ts %f with events' % \ audio_data.timestamp, audio_data.events print 'remaining events are', self._events return audio_data def _decode_audio_packet(self): packet = self._packet size_out = ctypes.c_int(len(self._audio_buffer)) while True: audio_packet_ptr = ctypes.cast(packet.data, ctypes.c_void_p) audio_packet_size = packet.size used = av.avbin_decode_audio(self._audio_stream, audio_packet_ptr, audio_packet_size, self._audio_buffer, size_out) if used < 0: self._audio_packet_size = 0 break audio_packet_ptr.value += used audio_packet_size -= used if size_out.value <= 0: continue # XXX how did this ever work? replaced with copy below # buffer = ctypes.string_at(self._audio_buffer, size_out) # XXX to actually copy the data.. but it never used to crash, so # maybe I'm missing something buffer = ctypes.create_string_buffer(size_out.value) ctypes.memmove(buffer, self._audio_buffer, len(buffer)) buffer = buffer.raw duration = float(len(buffer)) / self.audio_format.bytes_per_second self._audio_packet_timestamp = \ timestamp = timestamp_from_avbin(packet.timestamp) return AudioData(buffer, len(buffer), timestamp, duration, []) def _decode_video_packet(self, packet): width = self.video_format.width height = self.video_format.height pitch = width * 3 buffer = (ctypes.c_uint8 * (pitch * height))() result = av.avbin_decode_video(self._video_stream, packet.data, packet.size, buffer) if result < 0: image_data = None else: image_data = image.ImageData(width, height, 'RGB', buffer, pitch) packet.image = image_data # Notify get_next_video_frame() that another one is ready. self._condition.acquire() self._condition.notify() self._condition.release() def _ensure_video_packets(self): '''Process packets until a video packet has been queued (and begun decoding). Return False if EOS. ''' if not self._video_packets: if _debug: print 'No video packets...' # Read ahead until we have another video packet self._get_packet() packet_type, _ = self._process_packet() while packet_type and packet_type != 'video': self._get_packet() packet_type, _ = self._process_packet() if not packet_type: return False if _debug: print 'Queued packet', _ return True old_stamp = None def get_next_video_timestamp(self): if not self.video_format: return if self._ensure_video_packets(): next_stamp = self._video_packets[0].timestamp if _debug: print 'Next video timestamp is', next_stamp if self.old_stamp == next_stamp: return self.old_stamp = next_stamp return next_stamp def get_next_video_frame(self): if not self.video_format: return if self._ensure_video_packets(): packet = self._video_packets.pop(0) if _debug: print 'Waiting for', packet # Block until decoding is complete self._condition.acquire() while packet.image == 0: self._condition.wait() self._condition.release() if _debug: print 'Returning', packet return packet.image