oggspots.c 11.9 KB
Newer Older
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
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
/*****************************************************************************
 * oggspots.c: OggSpots decoder module.
 *****************************************************************************
 * Copyright (C) 2016 VLC authors and VideoLAN
 * $Id$
 *
 * Authors: Michael Taenzer <neo@nhng.de>
 *
 * This program 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.
 *
 * This program 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 program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <vlc_common.h>
#include <vlc_plugin.h>
#include <vlc_codec.h>
#include <vlc_image.h>

#include <assert.h>
#include <limits.h>

/*****************************************************************************
 * decoder_sys_t : oggspots decoder descriptor
 *****************************************************************************/
struct decoder_sys_t
{
    /* Module mode */
    bool b_packetizer;

    /*
     * Input properties
     */
    bool b_has_headers;

    /*
     * Image handler
     */
    image_handler_t* p_image;

    /*
     * Common properties
     */
    mtime_t i_pts;
};

/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
static int  OpenDecoder   (vlc_object_t*);
static int  OpenPacketizer(vlc_object_t*);
static void CloseDecoder  (vlc_object_t*);

static void*      DecodeBlock  (decoder_t*, block_t**);
static int        ProcessHeader(decoder_t*);
static void*      ProcessPacket(decoder_t*, block_t**);
static void       Flush        (decoder_t*);
static picture_t* DecodePacket (decoder_t*, block_t*);


/*****************************************************************************
 * Module descriptor
 *****************************************************************************/

vlc_module_begin ()
    set_category(CAT_INPUT)
    set_subcategory(SUBCAT_INPUT_VCODEC)
    set_shortname("OggSpots")
    set_description(N_("OggSpots video decoder"))
    set_capability("decoder", 10)
    set_callbacks(OpenDecoder, CloseDecoder)
    add_shortcut("oggspots")

    add_submodule ()
    set_description(N_("OggSpots video packetizer"))
    set_capability("packetizer", 10)
    set_callbacks(OpenPacketizer, CloseDecoder)
    add_shortcut("oggspots")
vlc_module_end ()

/*****************************************************************************
 * OpenDecoder: probe the decoder and return score
 *****************************************************************************/
static int OpenDecoder(vlc_object_t* p_this)
{
    decoder_t* p_dec = (decoder_t*)p_this;
    decoder_sys_t* p_sys;

    if (p_dec->fmt_in.i_codec != VLC_CODEC_OGGSPOTS) {
        return VLC_EGENERIC;
    }

    /* Allocate the memory needed to store the decoder's structure */
    p_sys = malloc(sizeof(*p_sys));
    if (p_sys == NULL) {
        return VLC_ENOMEM;
    }
    p_dec->p_sys = p_sys;
    p_sys->b_packetizer = false;
    p_sys->b_has_headers = false;
    p_sys->i_pts = VLC_TS_INVALID;

    /* Set output properties */
    p_dec->fmt_out.i_cat = VIDEO_ES;
    p_dec->fmt_out.i_codec = VLC_CODEC_RGBA;

    /* Initialize image handler */
    p_sys->p_image = image_HandlerCreate(p_dec);
    if (p_sys->p_image == NULL) {
        free(p_sys);
        return VLC_ENOMEM;
    }

    /* Set callbacks */
    p_dec->pf_decode_video = (picture_t*(*)(decoder_t*, block_t**))
        DecodeBlock;
    p_dec->pf_packetize    = (block_t*(*)(decoder_t*, block_t**))
        DecodeBlock;
    p_dec->pf_flush        = Flush;

    return VLC_SUCCESS;
}

static int OpenPacketizer(vlc_object_t* p_this)
{
    decoder_t* p_dec = (decoder_t*)p_this;

    int i_ret = OpenDecoder(p_this);

    if (i_ret == VLC_SUCCESS) {
        p_dec->p_sys->b_packetizer = true;
        p_dec->fmt_out.i_codec = VLC_CODEC_OGGSPOTS;
    }

    return i_ret;
}

/****************************************************************************
 * DecodeBlock: the whole thing
 ****************************************************************************
 * This function must be fed with ogg packets.
 ****************************************************************************/
static void* DecodeBlock(decoder_t* p_dec, block_t** pp_block)
{
    decoder_sys_t* p_sys = p_dec->p_sys;

    if (!pp_block || !*pp_block) return NULL;

    /* Check for headers */
    if (!p_sys->b_has_headers) {
        if (ProcessHeader(p_dec)) {
            block_Release(*pp_block);
            return NULL;
        }
        p_sys->b_has_headers = true;
    }

    return ProcessPacket(p_dec, pp_block);
}

/*****************************************************************************
 * ProcessHeader: process OggSpots header.
 *****************************************************************************/
static int ProcessHeader(decoder_t* p_dec)
{
    decoder_sys_t* p_sys = p_dec->p_sys;
    const uint8_t* p_extra;
    int i_major;
    int i_minor;
    uint64_t i_granulerate_numerator;
    uint64_t i_granulerate_denominator;

    /* The OggSpots header is always 52 bytes */
    if (p_dec->fmt_in.i_extra != 52) {
        return VLC_EGENERIC;
    }
    p_extra = p_dec->fmt_in.p_extra;

    /* Identification string */
    if ( memcmp(p_extra, "SPOTS\0\0", 8) ) {
        return VLC_EGENERIC;
    }

    /* Version number */
    i_major = GetWLE(&p_extra[ 8]); /* major version num */
    i_minor = GetWLE(&p_extra[10]); /* minor version num */
    if (i_major != 0 || i_minor != 1) {
        return VLC_EGENERIC;
    }

    /* Granule rate */
    i_granulerate_numerator   = GetQWLE(&p_extra[12]);
    i_granulerate_denominator = GetQWLE(&p_extra[20]);
    if (i_granulerate_numerator == 0 || i_granulerate_denominator == 0) {
        return VLC_EGENERIC;
    }

    /* The OggSpots spec contained an error and there are implementations out
     * there that used the wrong value. So we detect that case and switch
     * numerator and denominator in that case */
    if (i_granulerate_numerator == 1 && i_granulerate_denominator == 30) {
        i_granulerate_numerator   = 30;
        i_granulerate_denominator = 1;
    }

    /* Normalize granulerate */
    vlc_ureduce(&p_dec->fmt_in.video.i_frame_rate,
                &p_dec->fmt_in.video.i_frame_rate_base,
                i_granulerate_numerator, i_granulerate_denominator, 0);

    /* Image format */
    if (!p_sys->b_packetizer) {
        if ( memcmp(&p_extra[32], "PNG", 3) && memcmp(&p_extra[32], "JPEG", 4) ) {
            char psz_image_type[8+1];
            strncpy(psz_image_type, (char*)&p_extra[32], 8);
            psz_image_type[sizeof(psz_image_type)-1] = '\0';

            msg_Warn(p_dec, "Unsupported image format: %s", psz_image_type);
        }
    }

    /* Dimensions */
    p_dec->fmt_out.video.i_width  = p_dec->fmt_out.video.i_visible_width  =
            GetWLE(&p_extra[40]);
    p_dec->fmt_out.video.i_height = p_dec->fmt_out.video.i_visible_height =
            GetWLE(&p_extra[42]);

    /* We assume square pixels */
    p_dec->fmt_out.video.i_sar_num = 1;
    p_dec->fmt_out.video.i_sar_den = 1;

    /* We don't implement background color, alignment and options at the
     * moment because the former doesn't seem necessary right now and the
     * latter are underspecified. */

    if (p_sys->b_packetizer) {
        p_dec->fmt_out.i_extra = p_dec->fmt_in.i_extra;
        p_dec->fmt_out.p_extra = xrealloc(p_dec->fmt_out.p_extra,
                                          p_dec->fmt_out.i_extra);
        memcpy(p_dec->fmt_out.p_extra,
               p_dec->fmt_in.p_extra, p_dec->fmt_out.i_extra);
    }

    return VLC_SUCCESS;
}

/*****************************************************************************
 * Flush:
 *****************************************************************************/
static void Flush(decoder_t* p_dec)
{
    decoder_sys_t* p_sys = p_dec->p_sys;

    p_sys->i_pts = VLC_TS_INVALID;
}

/*****************************************************************************
 * ProcessPacket: processes an OggSpots packet.
 *****************************************************************************/
static void* ProcessPacket(decoder_t* p_dec, block_t** pp_block)
{
    decoder_sys_t* p_sys = p_dec->p_sys;
    block_t* p_block = *pp_block;
    void* p_buf;

    *pp_block = NULL; /* To avoid being fed the same packet again */

    if (!p_block)
        return NULL;

    if ( (p_block->i_flags & BLOCK_FLAG_DISCONTINUITY) != 0 ) {
        p_sys->i_pts = p_block->i_pts;
    }

    if ( (p_block->i_flags & BLOCK_FLAG_CORRUPTED) != 0 ) {
        block_Release(p_block);
        return NULL;
    }

    /* Date management */
    if (p_block->i_pts > VLC_TS_INVALID && p_block->i_pts != p_sys->i_pts) {
        p_sys->i_pts = p_block->i_pts;
    }

    if (p_sys->b_packetizer) {
        /* Date management */
        /* FIXME: This is copied from theora but it looks wrong.
         * p_block->i_length will always be zero. */
        p_block->i_dts = p_block->i_pts = p_sys->i_pts;

        p_block->i_length = p_sys->i_pts - p_block->i_pts;

        p_buf = p_block;
    }
    else {
        p_buf = DecodePacket(p_dec, p_block);
    }

    return p_buf;
}

/*****************************************************************************
 * DecodePacket: decodes an OggSpots packet.
 *****************************************************************************/
static picture_t* DecodePacket(decoder_t* p_dec, block_t* p_block)
{
    decoder_sys_t* p_sys = p_dec->p_sys;
    uint32_t i_img_offset;
    picture_t* p_pic;

    if (p_block->i_buffer < 20) {
        msg_Dbg(p_dec, "Packet too short");
        goto error;
    }

    /* Byte offset */
    i_img_offset = GetDWLE(p_block->p_buffer);
    if (i_img_offset < 20) {
        msg_Dbg(p_dec, "Invalid byte offset");
        goto error;
    }

    /* Image format */
    if ( !memcmp(&p_block->p_buffer[4], "PNG", 3) ) {
        p_dec->fmt_in.video.i_chroma = VLC_CODEC_PNG;
    }
    else if ( !memcmp(&p_block->p_buffer[4], "JPEG", 4) ) {
        p_dec->fmt_in.video.i_chroma = VLC_CODEC_JPEG;
    }
    else {
        char psz_image_type[8+1];
        strncpy(psz_image_type, (char*)&p_block->p_buffer[4], 8);
        psz_image_type[sizeof(psz_image_type)-1] = '\0';

        msg_Dbg(p_dec, "Unsupported image format: %s", psz_image_type);
        goto error;
    }

    /* We currently ignore the rest of the header and let the image format
     * handle the details */

    p_block->i_buffer -= i_img_offset;
    p_block->p_buffer += i_img_offset;

    p_pic = image_Read(p_sys->p_image, p_block,
                       &p_dec->fmt_in.video,
                       &p_dec->fmt_out.video);
    if (p_pic == NULL) {
        return NULL;
    }

    p_pic->b_force = true;
    p_dec->fmt_out.i_codec = p_dec->fmt_out.video.i_chroma;
    decoder_UpdateVideoFormat(p_dec);

    return p_pic;

error:
    block_Release(p_block);
    return NULL;
}

/*****************************************************************************
 * CloseDecoder: OggSpots decoder destruction
 *****************************************************************************/
static void CloseDecoder(vlc_object_t* p_this)
{
    decoder_t* p_dec = (decoder_t*)p_this;
    decoder_sys_t* p_sys = p_dec->p_sys;

    image_HandlerDelete(p_sys->p_image);
    free(p_sys);
}