Package flumotion :: Package component :: Package decoders :: Package generic :: Module generic
[hide private]

Source Code for Module flumotion.component.decoders.generic.generic

  1  # -*- Mode: Python -*- 
  2  # vi:si:et:sw=4:sts=4:ts=4 
  3  # 
  4  # Flumotion - a streaming media server 
  5  # Copyright (C) 2004,2005,2006,2007 Fluendo, S.L. (www.fluendo.com). 
  6  # All rights reserved. 
  7   
  8  # This file may be distributed and/or modified under the terms of 
  9  # the GNU General Public License version 2 as published by 
 10  # the Free Software Foundation. 
 11  # This file is distributed without any warranty; without even the implied 
 12  # warranty of merchantability or fitness for a particular purpose. 
 13  # See "LICENSE.GPL" in the source distribution for more information. 
 14   
 15  # Licensees having purchased or holding a valid Flumotion Advanced 
 16  # Streaming Server license may use this file in accordance with the 
 17  # Flumotion Advanced Streaming Server Commercial License Agreement. 
 18  # See "LICENSE.Flumotion" in the source distribution for more information. 
 19   
 20  # Headers in this file shall remain intact. 
 21   
 22  import gst 
 23  import gobject 
 24   
 25  from flumotion.component import decodercomponent as dc 
 26  from flumotion.common import messages 
 27  from flumotion.common.i18n import N_, gettexter 
 28   
 29  T_ = gettexter() 
 30   
 31  __version__ = "$Rev: 7162 $" 
 32   
 33  BASIC_AUDIO_CAPS = "audio/x-raw-int;audio/x-raw-float" 
 34  BASIC_VIDEO_CAPS = "video/x-raw-yuv;video/x-raw-rgb" 
 35   
 36  # FIXME: The GstAutoplugSelectResult enum has no bindings in gst-python. 
 37  # Replace this when the enum is exposed in the bindings. 
 38   
 39  GST_AUTOPLUG_SELECT_TRY = 0 
 40  GST_AUTOPLUG_SELECT_SKIP = 2 
 41   
 42   
43 -class FeederInfo(object):
44
45 - def __init__(self, name, caps, linked=False):
46 self.name = name 47 self.caps = caps
48 49
50 -class SyncKeeper(gst.Element):
51 __gstdetails__ = ('SyncKeeper', 'Generic', 52 'Retimestamp the output to be contiguous and maintain ' 53 'the sync', 'Xavier Queralt') 54 _audiosink = gst.PadTemplate("audio-in", 55 gst.PAD_SINK, 56 gst.PAD_ALWAYS, 57 gst.caps_from_string(BASIC_AUDIO_CAPS)) 58 _videosink = gst.PadTemplate("video-in", 59 gst.PAD_SINK, 60 gst.PAD_ALWAYS, 61 gst.caps_from_string(BASIC_VIDEO_CAPS)) 62 _audiosrc = gst.PadTemplate("audio-out", 63 gst.PAD_SRC, 64 gst.PAD_ALWAYS, 65 gst.caps_from_string(BASIC_AUDIO_CAPS)) 66 _videosrc = gst.PadTemplate("video-out", 67 gst.PAD_SRC, 68 gst.PAD_ALWAYS, 69 gst.caps_from_string(BASIC_VIDEO_CAPS)) 70 71 nextVideoTs = 0 72 nextAudioTs = 0 73 videoReceived = False 74 audioReceived = False 75 audioDiscontBuffer = None 76 videoDiscontBuffer = None 77 78 sendVideoNewSegment = True 79 sendAudioNewSegment = True 80 videoPadding = 0 81 audioPadding = 0 82 resetReceived = False 83
84 - def __init__(self):
85 gst.Element.__init__(self) 86 87 self.audiosink = gst.Pad(self._audiosink, "audio-in") 88 self.audiosink.set_chain_function(self.chainfunc) 89 self.audiosink.set_event_function(self.eventfunc) 90 self.add_pad(self.audiosink) 91 self.videosink = gst.Pad(self._videosink, "video-in") 92 self.videosink.set_chain_function(self.chainfunc) 93 self.videosink.set_event_function(self.eventfunc) 94 self.add_pad(self.videosink) 95 96 self.audiosrc = gst.Pad(self._audiosrc, "audio-out") 97 self.add_pad(self.audiosrc) 98 self.videosrc = gst.Pad(self._videosrc, "video-out") 99 self.add_pad(self.videosrc)
100
101 - def push_video_buffer(self, buffer):
102 buffer.timestamp += self.videoPadding 103 self.nextVideoTs = buffer.timestamp + buffer.duration 104 if self.sendVideoNewSegment: 105 self.videosrc.push_event( 106 gst.event_new_new_segment(True, 1.0, gst.FORMAT_TIME, 107 buffer.timestamp, -1, 0)) 108 self.sendVideoNewSegment = False 109 110 self.log( 111 "Output video timestamp: %s, %s" % (gst.TIME_ARGS(buffer.timestamp), 112 gst.TIME_ARGS(buffer.duration))) 113 self.videosrc.push(buffer)
114
115 - def push_audio_buffer(self, buffer):
116 buffer.timestamp += self.audioPadding 117 self.nextAudioTs = buffer.timestamp + buffer.duration 118 if self.sendAudioNewSegment: 119 self.audiosrc.push_event( 120 gst.event_new_new_segment(True, 1.0, gst.FORMAT_TIME, 121 buffer.timestamp, -1, 0)) 122 self.sendAudioNewSegment = False 123 124 self.log( 125 "Output audio timestamp: %s, %s" % (gst.TIME_ARGS(buffer.timestamp), 126 gst.TIME_ARGS(buffer.duration))) 127 self.audiosrc.push(buffer)
128
129 - def fixAVPadding(self, videoBuffer=None, audioBuffer=None):
130 if not self.resetReceived: 131 return False 132 if not videoBuffer or not audioBuffer: 133 return False 134 diff = audioBuffer.timestamp - videoBuffer.timestamp 135 newStart = max(self.nextVideoTs, self.nextAudioTs) 136 137 if diff > 0: 138 self.videoPadding = newStart - videoBuffer.timestamp 139 self.audioPadding = newStart + diff - audioBuffer.timestamp 140 else: 141 self.videoPadding = newStart + diff - videoBuffer.timestamp 142 self.audioPadding = newStart - audioBuffer.timestamp 143 144 self.resetReceived = False 145 146 return True
147
148 - def chainfunc(self, pad, buffer):
149 if pad.get_name() == 'audio-in': 150 self.log( 151 "Input audio timestamp: %s, %s" % (gst.TIME_ARGS(buffer.timestamp), 152 gst.TIME_ARGS(buffer.duration))) 153 if self.fixAVPadding(self.videoDiscontBuffer, buffer): 154 self.push_video_buffer(self.videoDiscontBuffer) 155 self.videoDiscontBuffer = None 156 157 # Check contiguous buffer 158 self.audioReceived = True 159 if self.videoReceived: 160 self.push_audio_buffer(buffer) 161 elif self.resetReceived: 162 self.audioDiscontBuffer = buffer 163 164 return gst.FLOW_OK 165 elif pad.get_name() == 'video-in': 166 self.log( 167 "Input video timestamp: %s, %s" % (gst.TIME_ARGS(buffer.timestamp), 168 gst.TIME_ARGS(buffer.duration))) 169 170 if self.fixAVPadding(buffer, self.audioDiscontBuffer): 171 self.push_audio_buffer(self.audioDiscontBuffer) 172 self.audioDiscontBuffer = None 173 174 self.videoReceived = True 175 if self.audioReceived: 176 self.push_video_buffer(buffer) 177 elif self.resetReceived: 178 self.videoDiscontBuffer = buffer 179 180 return gst.FLOW_OK 181 else: 182 return gst.FLOW_ERROR
183
184 - def eventfunc(self, pad, event):
185 self.debug("Received event %r from %s" % (event, event.src)) 186 if event.type == gst.EVENT_NEWSEGMENT: 187 return False 188 if event.type != gst.EVENT_CUSTOM_DOWNSTREAM: 189 return True 190 if event.get_structure().get_name() != 'flumotion-reset': 191 return True 192 self.resetReceived = True 193 self.videoReceived = False 194 self.audioReceived = False 195 self.sendVideoNewSegment = True 196 self.sendAudioNewSegment = True 197 198 self.audiosrc.push_event(event) 199 self.videosrc.push_event(event) 200 return True
201 202 gobject.type_register(SyncKeeper) 203 gst.element_register(SyncKeeper, "synckeeper", gst.RANK_MARGINAL) 204 205
206 -class GenericDecoder(dc.DecoderComponent):
207 """ 208 Generic decoder component using decodebin2. 209 210 It listen to the custom gstreamer event flumotion-reset, 211 and reset the decoding pipeline by removing the old one 212 and creating a new one. 213 214 Sub-classes must override _get_feeders_info() and return 215 a list of FeederInfo instances that describe the decoder 216 output. 217 218 When reset, if the new decoded pads do not match the 219 previously negotiated caps, feeder will not be connected, 220 and the decoder will go sad. 221 """ 222 223 logCategory = "gen-decoder" 224 feeder_tmpl = ("identity name=%(ename)s single-segment=true " 225 "silent=true ! %(caps)s ! @feeder:%(pad)s@") 226 227 ### Public Methods ### 228
229 - def init(self):
230 self._feeders_info = None # {FEEDER_NAME: FeederInfo}
231
232 - def get_pipeline_string(self, properties):
233 # Retrieve feeder info and build a dict out of it 234 finfo = self._get_feeders_info() 235 assert finfo, "No feeder info specified" 236 self._feeders_info = dict([(i.name, i) for i in finfo]) 237 238 pipeline_parts = [self._get_base_pipeline_string()] 239 240 for i in self._feeders_info.values(): 241 ename = self._get_output_element_name(i.name) 242 pipeline_parts.append( 243 self.feeder_tmpl % dict(ename=ename, caps=i.caps, pad=i.name)) 244 245 pipeline_str = " ".join(pipeline_parts) 246 self.log("Decoder pipeline: %s", pipeline_str) 247 248 self._blacklist = properties.get('blacklist', []) 249 250 return pipeline_str
251
252 - def configure_pipeline(self, pipeline, properties):
253 dc.DecoderComponent.configure_pipeline(self, pipeline, 254 properties) 255 256 decoder = self.pipeline.get_by_name("decoder") 257 decoder.connect('autoplug-select', self._autoplug_select_cb)
258 259 ### Protected Methods ## 260
262 return 'decodebin2 name=decoder'
263
264 - def _get_feeders_info(self):
265 """ 266 Must be overridden to returns a tuple of FeederInfo. 267 """ 268 return None
269 270 ### Private Methods ### 271
272 - def _get_output_element_name(self, feed_name):
273 return "%s-output" % feed_name
274 275 ### Callbacks ### 276
277 - def _autoplug_select_cb(self, decoder, pad, caps, factory):
278 if factory.get_name() in self._blacklist: 279 self.log("Skipping element %s because it's in the blacklist", 280 factory.get_name()) 281 return GST_AUTOPLUG_SELECT_SKIP 282 return GST_AUTOPLUG_SELECT_TRY
283 284
285 -class SingleGenericDecoder(GenericDecoder):
286 287 logCategory = "sgen-decoder" 288 289 _caps_lookup = {'audio': BASIC_AUDIO_CAPS, 290 'video': BASIC_VIDEO_CAPS} 291
292 - def init(self):
293 self._media_type = None
294
295 - def check_properties(self, properties, addMessage):
296 media_type = properties.get("media-type") 297 if media_type not in ["audio", "video"]: 298 msg = 'Property media-type can only be "audio" or "video"' 299 m = messages.Error(T_(N_(msg)), mid="error-decoder-media-type") 300 addMessage(m) 301 else: 302 self._media_type = media_type
303
304 - def _get_feeders_info(self):
305 caps = self._caps_lookup[self._media_type] 306 return FeederInfo('default', caps),
307 308
309 -class AVGenericDecoder(GenericDecoder):
310 311 logCategory = "avgen-decoder" 312 feeder_tmpl = ("identity name=%(ename)s silent=true ! %(caps)s ! " 313 "sync.%(pad)s-in sync.%(pad)s-out ! @feeder:%(pad)s@") 314
315 - def _get_feeders_info(self):
316 return (FeederInfo('audio', BASIC_AUDIO_CAPS), 317 FeederInfo('video', BASIC_VIDEO_CAPS))
318
320 return 'decodebin2 name=decoder synckeeper name=sync'
321