~kokoto-java/kazam/depencie-and-stuff

« back to all changes in this revision

Viewing changes to kazam/backend/gstreamer.py

  • Committer: David Klasinc
  • Date: 2012-11-11 19:36:05 UTC
  • mfrom: (193.2.82 unstable)
  • Revision ID: bigwhale@lubica.net-20121111193605-vh3fxglxwfhyh8ne
Merged from unstable branch.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- coding: utf-8 -*-
2
 
#
3
 
#       gstreamer.py
4
 
#
5
 
#       Copyright 2012 David Klasinc <bigwhale@lubica.net>
6
 
#
7
 
#       This program is free software; you can redistribute it and/or modify
8
 
#       it under the terms of the GNU General Public License as published by
9
 
#       the Free Software Foundation; either version 3 of the License, or
10
 
#       (at your option) any later version.
11
 
#
12
 
#       This program is distributed in the hope that it will be useful,
13
 
#       but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 
#       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 
#       GNU General Public License for more details.
16
 
#
17
 
#       You should have received a copy of the GNU General Public License
18
 
#       along with this program; if not, write to the Free Software
19
 
#       Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20
 
#       MA 02110-1301, USA.
21
 
 
22
 
import os
23
 
import logging
24
 
logger = logging.getLogger("GStreamer")
25
 
 
26
 
import tempfile
27
 
import multiprocessing
28
 
 
29
 
#
30
 
# This needs to be set before we load GStreamer modules!
31
 
#
32
 
os.environ["GST_DEBUG_DUMP_DOT_DIR"] = "/tmp"
33
 
os.putenv("GST_DEBUG_DUMP_DOT_DIR", "/tmp")
34
 
 
35
 
import pygst
36
 
pygst.require("0.10")
37
 
import gst
38
 
 
39
 
from gi.repository import GObject
40
 
 
41
 
from subprocess import Popen
42
 
from kazam.backend.constants import *
43
 
from kazam.utils import *
44
 
 
45
 
 
46
 
class Screencast(GObject.GObject):
47
 
    __gsignals__ = {
48
 
        "flush-done" : (GObject.SIGNAL_RUN_LAST,
49
 
                        None,
50
 
                        (),
51
 
            ),
52
 
        }
53
 
    def __init__(self, debug):
54
 
        GObject.GObject.__init__(self)
55
 
        self.tempfile = tempfile.mktemp(prefix="kazam_", suffix=".movie")
56
 
        self.muxer_tempfile = "{0}.mux".format(self.tempfile)
57
 
        self.pipeline = gst.Pipeline("Kazam")
58
 
        self.debug = debug
59
 
 
60
 
    def setup_sources(self,
61
 
                      video_source,
62
 
                      audio_source,
63
 
                      audio2_source,
64
 
                      codec,
65
 
                      capture_cursor,
66
 
                      framerate,
67
 
                      region,
68
 
                      test,
69
 
                      dist):
70
 
 
71
 
 
72
 
        self.codec = codec
73
 
        # Get the number of cores available then use all except one for encoding
74
 
        self.cores = multiprocessing.cpu_count()
75
 
 
76
 
        if self.cores > 1:
77
 
            self.cores -= 1
78
 
 
79
 
        self.audio_source = audio_source
80
 
        self.audio2_source = audio2_source
81
 
        self.video_source = video_source
82
 
        self.capture_cursor = capture_cursor
83
 
        self.framerate = framerate
84
 
        self.region = region
85
 
        self.test = test
86
 
        self.dist = dist
87
 
 
88
 
        logger.debug("Capture Cursor: {0}".format(capture_cursor))
89
 
        logger.debug("Framerate : {0}".format(capture_cursor))
90
 
        logger.debug("Codec: {0}".format(get_codec(codec)[1]))
91
 
 
92
 
        if self.video_source:
93
 
            self.setup_video_source()
94
 
 
95
 
        if self.audio_source:
96
 
            self.setup_audio_source()
97
 
 
98
 
        if self.audio2_source:
99
 
            self.setup_audio2_source()
100
 
 
101
 
        self.setup_filesink()
102
 
        self.setup_pipeline()
103
 
 
104
 
        self.bus = self.pipeline.get_bus()
105
 
        self.bus.add_signal_watch()
106
 
        self.bus.connect("message", self.on_message)
107
 
 
108
 
    def setup_video_source(self):
109
 
 
110
 
        if self.test:
111
 
            self.videosrc = gst.element_factory_make("videotestsrc", "video_src")
112
 
            self.videosrc.set_property("pattern", "smpte")
113
 
        else:
114
 
            self.videosrc = gst.element_factory_make("ximagesrc", "video_src")
115
 
 
116
 
        if self.region:
117
 
            startx = self.region[0] if self.region[0] > 0 else 0
118
 
            starty = self.region[1] if self.region[1] > 0 else 0
119
 
            endx = self.region[2]
120
 
            endy = self.region[3]
121
 
        else:
122
 
            startx = self.video_source['x']
123
 
            starty = self.video_source['y']
124
 
            width = self.video_source['width']
125
 
            height = self.video_source['height']
126
 
            endx = startx + width - 1
127
 
            endy = starty + height - 1
128
 
 
129
 
        #
130
 
        # H264 requirement is that video dimensions are divisible by 2.
131
 
        # If they are not, we have to get rid of that extra pixel.
132
 
        #
133
 
        if not abs(startx - endx) % 2 and self.codec == CODEC_H264:
134
 
            endx -= 1
135
 
 
136
 
        if not abs(starty - endy) % 2 and self.codec == CODEC_H264:
137
 
            endy -= 1
138
 
 
139
 
        logger.debug("Coordinates: {0} {1} {2} {3}".format(startx, starty, endx, endy))
140
 
 
141
 
        if self.test:
142
 
            logger.info("Using test signal instead of screen capture.")
143
 
            self.vid_caps = gst.Caps("video/x-raw-rgb, framerate={0}/1, width={1}, height={2}".format(
144
 
                  self.framerate,
145
 
                  endx - startx,
146
 
                  endy - starty))
147
 
            self.vid_caps_filter = gst.element_factory_make("capsfilter", "vid_filter")
148
 
            self.vid_caps_filter.set_property("caps", self.vid_caps)
149
 
        else:
150
 
            self.videosrc.set_property("startx", startx)
151
 
            self.videosrc.set_property("starty", starty)
152
 
            self.videosrc.set_property("endx", endx)
153
 
            self.videosrc.set_property("endy", endy)
154
 
            self.videosrc.set_property("use-damage", False)
155
 
            self.videosrc.set_property("show-pointer", self.capture_cursor)
156
 
 
157
 
            self.vid_caps = gst.Caps("video/x-raw-rgb, framerate={0}/1".format(self.framerate))
158
 
            self.vid_caps_filter = gst.element_factory_make("capsfilter", "vid_filter")
159
 
            self.vid_caps_filter.set_property("caps", self.vid_caps)
160
 
 
161
 
        self.ffmpegcolor = gst.element_factory_make("ffmpegcolorspace", "ffmpeg")
162
 
        self.videorate = gst.element_factory_make("videorate", "video_rate")
163
 
 
164
 
        logger.debug("Codec: {0}".format(get_codec(self.codec)[2]))
165
 
 
166
 
        if self.codec is not CODEC_RAW:
167
 
            self.videnc = gst.element_factory_make(get_codec(self.codec)[1], "video_encoder")
168
 
 
169
 
        if self.codec == CODEC_RAW:
170
 
            self.mux = gst.element_factory_make("avimux", "muxer")
171
 
        elif self.codec == CODEC_VP8:
172
 
            if self.dist[0] == 'Ubuntu':
173
 
                self.videnc.set_property("speed", 6)
174
 
            elif self.dist[0] == 'LinuxMint':
175
 
                self.videnc.set_property("speed", 2)
176
 
            self.videnc.set_property("max-latency", 1)
177
 
            self.videnc.set_property("quality", 8)
178
 
            self.videnc.set_property("threads", self.cores)
179
 
            self.mux = gst.element_factory_make("webmmux", "muxer")
180
 
        elif self.codec == CODEC_H264:
181
 
            self.videnc.set_property("speed-preset", "veryfast")
182
 
            self.videnc.set_property("pass", 4)
183
 
            self.videnc.set_property("quantizer", 15)
184
 
            self.videnc.set_property("threads", self.cores)
185
 
            self.videnc.set_property("option-string", "colormatrix=bt709")
186
 
            self.mux = gst.element_factory_make("mp4mux", "muxer")
187
 
            self.mux.set_property("faststart", 1)
188
 
            self.mux.set_property("faststart-file", self.muxer_tempfile)
189
 
            self.mux.set_property("streamable", 1)
190
 
        elif self.codec == CODEC_HUFF:
191
 
            self.mux = gst.element_factory_make("avimux", "muxer")
192
 
            self.videnc.set_property("bitrate", 500000)
193
 
        elif self.codec == CODEC_JPEG:
194
 
            self.mux = gst.element_factory_make("avimux", "muxer")
195
 
 
196
 
        self.vid_in_queue = gst.element_factory_make("queue", "queue_v1")
197
 
        self.vid_out_queue = gst.element_factory_make("queue", "queue_v2")
198
 
 
199
 
    def setup_audio_source(self):
200
 
        logger.debug("Audio1 Source:\n  {0}".format(self.audio_source))
201
 
        self.audiosrc = gst.element_factory_make("pulsesrc", "audio_src")
202
 
        self.audiosrc.set_property("device", self.audio_source)
203
 
        self.aud_caps = gst.Caps("audio/x-raw-int")
204
 
        self.aud_caps_filter = gst.element_factory_make("capsfilter", "aud_filter")
205
 
        self.aud_caps_filter.set_property("caps", self.aud_caps)
206
 
        self.audioconv = gst.element_factory_make("audioconvert", "audio_conv")
207
 
        if self.codec == CODEC_VP8:
208
 
            self.audioenc = gst.element_factory_make("vorbisenc", "audio_encoder")
209
 
            self.audioenc.set_property("quality", 1)
210
 
        else:
211
 
            self.audioenc = gst.element_factory_make("lamemp3enc", "audio_encoder")
212
 
            self.audioenc.set_property("quality", 0)
213
 
 
214
 
        self.aud_in_queue = gst.element_factory_make("queue", "queue_a_in")
215
 
        self.aud_out_queue = gst.element_factory_make("queue", "queue_a_out")
216
 
 
217
 
    def setup_audio2_source(self):
218
 
        logger.debug("Audio2 Source:\n  {0}".format(self.audio2_source))
219
 
        self.audiomixer = gst.element_factory_make("adder", "audiomixer")
220
 
        self.audio2src = gst.element_factory_make("pulsesrc", "audio2_src")
221
 
        self.audio2src.set_property("device", self.audio2_source)
222
 
        self.aud2_caps = gst.Caps("audio/x-raw-int")
223
 
        self.aud2_caps_filter = gst.element_factory_make("capsfilter", "aud2_filter")
224
 
        self.aud2_caps_filter.set_property("caps", self.aud2_caps)
225
 
        self.audio2conv = gst.element_factory_make("audioconvert", "audio2_conv")
226
 
 
227
 
        self.aud2_in_queue = gst.element_factory_make("queue", "queue_a2_in")
228
 
 
229
 
    def setup_filesink(self):
230
 
        logger.debug("Filesink: {0}".format(self.tempfile))
231
 
        self.sink = gst.element_factory_make("filesink", "sink")
232
 
        self.sink.set_property("location", self.tempfile)
233
 
        self.file_queue = gst.element_factory_make("queue", "queue_file")
234
 
 
235
 
    #
236
 
    # One day, this horrific code will be optimised... I promise!
237
 
    #
238
 
    def setup_pipeline(self):
239
 
        if self.video_source and not self.audio_source and not self.audio2_source:
240
 
            logger.debug("Pipline - Video only".format(self.audio_source))
241
 
            if self.codec == CODEC_RAW:
242
 
                self.pipeline.add(self.videosrc, self.vid_in_queue, self.videorate,
243
 
                                  self.vid_caps_filter, self.ffmpegcolor,
244
 
                                  self.vid_out_queue, self.mux,
245
 
                                  self.sink)
246
 
                gst.element_link_many(self.videosrc, self.vid_in_queue,
247
 
                                      self.videorate, self.vid_caps_filter,
248
 
                                      self.ffmpegcolor, self.vid_out_queue,
249
 
                                      self.mux, self.sink)
250
 
            else:
251
 
                self.pipeline.add(self.videosrc, self.vid_in_queue, self.videorate,
252
 
                                  self.vid_caps_filter, self.ffmpegcolor,
253
 
                                  self.videnc, self.vid_out_queue, self.mux,
254
 
                                  self.sink)
255
 
                gst.element_link_many(self.videosrc, self.vid_in_queue,
256
 
                                      self.videorate, self.vid_caps_filter,
257
 
                                      self.ffmpegcolor, self.videnc,
258
 
                                      self.vid_out_queue, self.mux,
259
 
                                      self.sink)
260
 
 
261
 
        elif self.video_source and self.audio_source and not self.audio2_source:
262
 
            logger.debug("Pipline - Video + Audio".format(self.audio_source))
263
 
            if self.codec == CODEC_RAW:
264
 
                self.pipeline.add(self.videosrc, self.vid_in_queue, self.videorate,
265
 
                                  self.vid_caps_filter, self.ffmpegcolor,
266
 
                                  self.audiosrc, self.aud_in_queue,
267
 
                                  self.aud_caps_filter, self.vid_out_queue,
268
 
                                  self.aud_out_queue, self.audioconv,
269
 
                                  self.audioenc, self.mux, self.file_queue, self.sink)
270
 
                gst.element_link_many(self.videosrc, self.vid_in_queue,
271
 
                                      self.videorate, self.vid_caps_filter,
272
 
                                      self.ffmpegcolor,
273
 
                                      self.vid_out_queue, self.mux)
274
 
            else:
275
 
                self.pipeline.add(self.videosrc, self.vid_in_queue, self.videorate,
276
 
                                  self.vid_caps_filter, self.ffmpegcolor,
277
 
                                  self.videnc, self.audiosrc, self.aud_in_queue,
278
 
                                  self.aud_caps_filter, self.vid_out_queue,
279
 
                                  self.aud_out_queue, self.audioconv,
280
 
                                  self.audioenc, self.mux, self.file_queue, self.sink)
281
 
                gst.element_link_many(self.videosrc, self.vid_in_queue,
282
 
                                      self.videorate, self.vid_caps_filter,
283
 
                                      self.ffmpegcolor, self.videnc,
284
 
                                      self.vid_out_queue, self.mux)
285
 
 
286
 
            gst.element_link_many(self.audiosrc, self.aud_in_queue,
287
 
                                  self.aud_caps_filter,
288
 
                                  self.audioconv, self.audioenc,
289
 
                                  self.aud_out_queue, self.mux)
290
 
 
291
 
            gst.element_link_many(self.mux, self.file_queue, self.sink)
292
 
 
293
 
        elif self.video_source and self.audio_source and self.audio2_source:
294
 
            logger.debug("Pipline - Video + Dual Audio".format(self.audio_source))
295
 
 
296
 
            if self.codec == CODEC_RAW:
297
 
                self.pipeline.add(self.videosrc, self.vid_in_queue, self.videorate,
298
 
                                  self.vid_caps_filter, self.ffmpegcolor,
299
 
                                  self.audiosrc, self.aud_in_queue,
300
 
                                  self.aud_caps_filter, self.vid_out_queue,
301
 
                                  self.aud_out_queue, self.audioconv,
302
 
                                  self.audioenc, self.audiomixer, self.aud2_in_queue,
303
 
                                  self.audio2src, self.aud2_caps_filter,
304
 
                                  self.mux, self.file_queue, self.sink)
305
 
 
306
 
                gst.element_link_many(self.videosrc, self.vid_in_queue,
307
 
                                      self.videorate, self.vid_caps_filter,
308
 
                                      self.ffmpegcolor, self.vid_out_queue,
309
 
                                      self.mux)
310
 
 
311
 
            else:
312
 
                self.pipeline.add(self.videosrc, self.vid_in_queue, self.videorate,
313
 
                                  self.vid_caps_filter, self.ffmpegcolor, self.videnc,
314
 
                                  self.audiosrc, self.aud_in_queue,
315
 
                                  self.aud_caps_filter, self.vid_out_queue,
316
 
                                  self.aud_out_queue, self.audioconv,
317
 
                                  self.audioenc, self.audiomixer, self.aud2_in_queue,
318
 
                                  self.audio2src, self.aud2_caps_filter,
319
 
                                  self.mux, self.file_queue, self.sink)
320
 
 
321
 
                gst.element_link_many(self.videosrc, self.vid_in_queue,
322
 
                                      self.videorate, self.vid_caps_filter,
323
 
                                      self.ffmpegcolor, self.videnc,
324
 
                                      self.vid_out_queue, self.mux)
325
 
 
326
 
            gst.element_link_many(self.audiosrc, self.aud_in_queue,
327
 
                                  self.aud_caps_filter, self.audiomixer)
328
 
 
329
 
            gst.element_link_many(self.audio2src, self.aud2_in_queue,
330
 
                                  self.aud2_caps_filter, self.audiomixer)
331
 
 
332
 
            gst.element_link_many(self.audiomixer, self.audioconv,
333
 
                                  self.audioenc, self.aud_out_queue, self.mux)
334
 
 
335
 
            gst.element_link_many(self.mux, self.file_queue, self.sink)
336
 
 
337
 
    def start_recording(self):
338
 
        if self.debug:
339
 
            logger.debug("Generating dot file.")
340
 
            gst.DEBUG_BIN_TO_DOT_FILE(self.pipeline,
341
 
                                      gst.DEBUG_GRAPH_SHOW_MEDIA_TYPE |
342
 
                                      gst.DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS |
343
 
                                      gst.DEBUG_GRAPH_SHOW_STATES,
344
 
                                      "kazam_debug")
345
 
 
346
 
 
347
 
        logger.debug("Setting STATE_PLAYING")
348
 
        self.pipeline.set_state(gst.STATE_PLAYING)
349
 
 
350
 
    def pause_recording(self):
351
 
        logger.debug("Setting STATE_PAUSED")
352
 
        self.pipeline.set_state(gst.STATE_PAUSED)
353
 
 
354
 
    def unpause_recording(self):
355
 
        logger.debug("Setting STATE_PLAYING - UNPAUSE")
356
 
        self.pipeline.set_state(gst.STATE_PLAYING)
357
 
 
358
 
    def stop_recording(self):
359
 
        logger.debug("Sending new EOS event")
360
 
        self.pipeline.send_event(gst.event_new_eos())
361
 
        #
362
 
        # TODO: Improve this ;)
363
 
        #
364
 
        if self.debug:
365
 
            logger.debug("Generating PNG file from DOT file.")
366
 
            if os.path.isfile("/usr/bin/dot"):
367
 
                os.system("/usr/bin/dot" + " -Tpng -o " + "/tmp/kazam_pipeline.png" + " " + "/tmp/kazam_debug.dot")
368
 
            elif os.path.isfile("/usr/local/bin/dot"):
369
 
                os.system("/usr/local/bin/dot" + " -Tpng -o " + "/tmp/kazam_pipeline.png" + " " + "/tmp/kazam_debug.dot")
370
 
            else:
371
 
                logger.debug("Program 'dot' not found. Unable to generate PNG.")
372
 
 
373
 
    def get_tempfile(self):
374
 
        return self.tempfile
375
 
 
376
 
    def get_audio_recorded(self):
377
 
        return self.audio
378
 
 
379
 
    def on_message(self, bus, message):
380
 
        t = message.type
381
 
        if t == gst.MESSAGE_EOS:
382
 
            logger.debug("Received EOS, setting pipeline to NULL.")
383
 
            self.pipeline.set_state(gst.STATE_NULL)
384
 
            logger.debug("Emitting flush-done.")
385
 
            self.emit("flush-done")
386
 
        elif t == gst.MESSAGE_ERROR:
387
 
            logger.debug("Received an error message.")
388
 
 
389
 
 
390
 
def detect_codecs():
391
 
    codecs_supported = []
392
 
 
393
 
    for codec in CODEC_LIST:
394
 
        if codec[0]:
395
 
            try:
396
 
                codec_test = gst.element_factory_make(codec[1], "video_encoder")
397
 
            except gst.ElementNotFoundError:
398
 
                logger.info("Unable to find {0} GStreamer plugin - support disabled.".format(codec))
399
 
                codec_test = None
400
 
 
401
 
            if codec_test:
402
 
                codecs_supported.append(codec[0])
403
 
                logger.info("Supported encoder: {0}.".format(codec[2]))
404
 
        else:
405
 
            codecs_supported.append(codec[0])
406
 
            logger.info("Supported encoder: {0}.".format(codec[2]))
407
 
 
408
 
    return codecs_supported
409
 
 
410
 
 
411
 
def get_codec(codec):
412
 
    for c in CODEC_LIST:
413
 
        if c[0] == codec:
414
 
            return c
415
 
    return None
416
 
 
417
 
 
418