summaryrefslogtreecommitdiff
path: root/src/media-engines/gstreamer/rygel-gst-transcoder.vala
blob: 8920109afa86d394bff3f9cb757c1efffeadf169 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
/*
 * Copyright (C) 2009-2012 Nokia Corporation.
 * Copyright (C) 2012 Intel Corporation.
 * Copyright (C) 2013 Cable Television Laboratories, Inc.
 *
 * Author: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
 *                               <zeeshan.ali@nokia.com>
 *         Jens Georg <jensg@openismus.com>
 *         Prasanna Modem <prasanna@ecaspia.com>
 *
 * This file is part of Rygel.
 *
 * Rygel is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * Rygel is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

using Gst;
using Gst.PbUtils;
using GUPnP;

public errordomain Rygel.GstTranscoderError {
    CANT_TRANSCODE
}

/**
 * The base Transcoder class used by gstreamer media engine.
 * Each implementation derives from it and must
 * implement get_resources_for_item and get_encoding_profile methods.
 */
internal abstract class Rygel.GstTranscoder : GLib.Object {
    public string name { get; construct; }
    public string mime_type { get; construct; }
    public string dlna_profile { get; construct; }
    public string extension { get; construct; }

    public string preset { get;
                           protected set;
                           default =  DEFAULT_ENCODING_PRESET; }

    private const string DECODE_BIN = "decodebin";
    private const string ENCODE_BIN = "encodebin";
    private const string DEFAULT_ENCODING_PRESET = "Rygel DLNA preset";

    dynamic Element decoder;
    dynamic Element encoder;

    private bool link_failed;

    protected GstTranscoder (string name,
                             string mime_type,
                             string dlna_profile,
                             string extension) {
        GLib.Object (name : name,
                     mime_type : mime_type,
                     dlna_profile : dlna_profile,
                     extension : extension);
    }

    public override void constructed () {
        base.constructed ();

        this.link_failed = true;
    }

    /**
     * Get the supported (transcoded) MediaResource for the given content item
     *
     * @return A MediaResources or null if the transcoder cannot
     * transcode this media item
     */
    public virtual MediaResource? get_resource_for_item (MediaFileItem item) {
        MediaResource res = new MediaResource(this.name);

        res.mime_type = this.mime_type;
        res.dlna_profile = this.dlna_profile;
        res.extension = this.extension;
        res.dlna_conversion = DLNAConversion.TRANSCODED;
        res.dlna_flags = DLNAFlags.DLNA_V15
                         | DLNAFlags.STREAMING_TRANSFER_MODE
                         | DLNAFlags.BACKGROUND_TRANSFER_MODE
                         | DLNAFlags.CONNECTION_STALL;
        // For transcoded content only support time seek
        res.dlna_operation = DLNAOperation.TIMESEEK;

        // Retrieve the duration from primary media resource
        if (item is AudioItem) {
            res.duration = (item as AudioItem).duration;
        }

        return res;
    }

    /**
     * Gets a numeric value that gives an gives an estimate of how hard
     * it would be for this transcoder to trancode @item to the target profile of
     * this transcoder.
     *
     * @param item the media item to calculate the distance for
     *
     * @return      the distance from the @item, uint.MIN if providing such a
     *              value is impossible or uint.MAX if it doesn't make any
     *              sense to use this transcoder for @item
     */
    public abstract uint get_distance (MediaFileItem item);

    /**
     * Creates a transcoding source.
     *
     * @param src the media item to create the transcoding source for
     * @param src the original (non-transcoding) source
     *
     * @return      the new transcoding source
     */
    public GstDataSource create_source (MediaFileItem item, DataSource src) throws Error {
        // We can only link GStreamer data sources together
        assert (src is GstDataSource);

        var orig_source = src as GstDataSource;

        this.decoder = GstUtils.create_element (DECODE_BIN,
                                                DECODE_BIN);
        this.encoder = GstUtils.create_element (ENCODE_BIN,
                                                ENCODE_BIN);

        encoder.profile = this.get_encoding_profile (item);
        if (encoder.profile == null) {
            var message = _("Could not create a transcoder configuration. Your GStreamer installation might be missing a plug-in");

            throw new GstTranscoderError.CANT_TRANSCODE (message);
        }

        debug ("%s using the following encoding profile:",
               this.get_class ().get_type ().name ());
        GstUtils.dump_encoding_profile (encoder.profile);

        var bin = new Bin ("transcoder-source");
        bin.add_many (orig_source.src, decoder, encoder);

        orig_source.src.link (decoder);

        decoder.pad_added.connect (this.on_decoder_pad_added);
        decoder.no_more_pads.connect (this.on_no_more_pads);

        var pad = encoder.get_static_pad ("src");
        var ghost = new GhostPad (null, pad);
        bin.add_pad (ghost);

        // Hook up resource from original resource
        var new_source = new GstDataSource.from_element (bin);
        new_source.res = orig_source.res;

        return new_source;
    }

    /**
     * Gets the Gst.EncodingProfile for this transcoder.
     *
     * @return      the Gst.EncodingProfile for this transcoder.
     */
    protected abstract EncodingProfile get_encoding_profile
                                        (MediaFileItem item);

    private void on_decoder_pad_added (Element decodebin, Pad new_pad) {
        Gst.Pad sinkpad;

        sinkpad = this.encoder.get_compatible_pad (new_pad, null);

        if (sinkpad == null) {
            var caps = new_pad.query_caps (null);
            Signal.emit_by_name (this.encoder, "request-pad", caps, out sinkpad);
        }

        if (sinkpad == null) {
            debug ("No compatible encodebin pad found for pad '%s', ignoring..",
                   new_pad.name);

            return;
        }

        var pad_link_ok = (new_pad.link (sinkpad) == PadLinkReturn.OK);
        if (!pad_link_ok) {
            warning ("Failed to link pad '%s' to '%s'",
                     new_pad.name,
                     sinkpad.name);
        } else {
            this.link_failed = false;
        }
    }

    private const string DESCRIPTION = "Encoder and decoder are not " +
                                       "compatible";

    private void on_no_more_pads (Element decodebin) {
        // We haven't found any pads we could link
        if (this.link_failed) {
            // Signalize that error
            var bin = this.encoder.get_parent () as Bin;
            var error = new IOError.FAILED ("Could not link");
            var message = new Message.error (bin,
                                             error,
                                             DESCRIPTION);


            var bus = bin.get_bus ();
            bus.post (message);
        }
    }

    public bool transcoding_necessary (MediaFileItem item) {
        return !(this.mime_type_is_a (this.mime_type, item.mime_type) &&
                 this.dlna_profile == item.dlna_profile);
    }

    protected bool mime_type_is_a (string mime_type1, string mime_type2) {
        string content_type1 = ContentType.get_mime_type (mime_type1);
        string content_type2 = ContentType.get_mime_type (mime_type2);

        return ContentType.is_a (content_type1, content_type2);
    }

}