source: titan/minidlna-1.0.22/tagutils/tagutils-ogg.c @ 13567

Last change on this file since 13567 was 13567, checked in by obi, 11 years ago

[titan] add minidlna-1.0.22 first step

File size: 12.1 KB
Line 
1//=========================================================================
2// FILENAME     : tagutils-ogg.c
3// DESCRIPTION  : Ogg metadata reader
4//=========================================================================
5// Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved.
6//=========================================================================
7
8/*
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23/*
24 * This file is derived from mt-daap project.
25 */
26
27typedef struct _ogg_stream_processor {
28        void (*process_page)(struct _ogg_stream_processor *, ogg_page *, struct song_metadata *);
29        void (*process_end)(struct _ogg_stream_processor *, struct song_metadata *);
30        int isillegal;
31        int constraint_violated;
32        int shownillegal;
33        int isnew;
34        long seqno;
35        int lostseq;
36
37        int start;
38        int end;
39
40        int num;
41        char *type;
42
43        ogg_uint32_t serial;
44        ogg_stream_state os;
45        void *data;
46} ogg_stream_processor;
47
48typedef struct {
49        ogg_stream_processor *streams;
50        int allocated;
51        int used;
52
53        int in_headers;
54} ogg_stream_set;
55
56typedef struct {
57        vorbis_info vi;
58        vorbis_comment vc;
59
60        ogg_int64_t bytes;
61        ogg_int64_t lastgranulepos;
62        ogg_int64_t firstgranulepos;
63
64        int doneheaders;
65} ogg_misc_vorbis_info;
66
67#define CONSTRAINT_PAGE_AFTER_EOS   1
68#define CONSTRAINT_MUXING_VIOLATED  2
69
70static ogg_stream_set *
71_ogg_create_stream_set(void)
72{
73        ogg_stream_set *set = calloc(1, sizeof(ogg_stream_set));
74
75        set->streams = calloc(5, sizeof(ogg_stream_processor));
76        set->allocated = 5;
77        set->used = 0;
78
79        return set;
80}
81
82static void
83_ogg_vorbis_process(ogg_stream_processor *stream, ogg_page *page,
84                    struct song_metadata *psong)
85{
86        ogg_packet packet;
87        ogg_misc_vorbis_info *inf = stream->data;
88        int i, header = 0;
89
90        ogg_stream_pagein(&stream->os, page);
91        if(inf->doneheaders < 3)
92                header = 1;
93
94        while(ogg_stream_packetout(&stream->os, &packet) > 0)
95        {
96                if(inf->doneheaders < 3)
97                {
98                        if(vorbis_synthesis_headerin(&inf->vi, &inf->vc, &packet) < 0)
99                        {
100                                DPRINTF(E_WARN, L_SCANNER, "Could not decode vorbis header "
101                                        "packet - invalid vorbis stream (%d)\n", stream->num);
102                                continue;
103                        }
104                        inf->doneheaders++;
105                        if(inf->doneheaders == 3)
106                        {
107                                if(ogg_page_granulepos(page) != 0 || ogg_stream_packetpeek(&stream->os, NULL) == 1)
108                                        DPRINTF(E_WARN, L_SCANNER, "No header in vorbis stream %d\n", stream->num);
109                                DPRINTF(E_DEBUG, L_SCANNER, "Vorbis headers parsed for stream %d, "
110                                        "information follows...\n", stream->num);
111                                DPRINTF(E_DEBUG, L_SCANNER, "Channels: %d\n", inf->vi.channels);
112                                DPRINTF(E_DEBUG, L_SCANNER, "Rate: %ld\n\n", inf->vi.rate);
113
114                                psong->samplerate = inf->vi.rate;
115                                psong->channels = inf->vi.channels;
116
117                                if(inf->vi.bitrate_nominal > 0)
118                                {
119                                        DPRINTF(E_DEBUG, L_SCANNER, "Nominal bitrate: %f kb/s\n",
120                                                (double)inf->vi.bitrate_nominal / 1000.0);
121                                        psong->bitrate = inf->vi.bitrate_nominal / 1000;
122                                }
123                                else
124                                {
125                                        int upper_rate, lower_rate;
126
127                                        DPRINTF(E_DEBUG, L_SCANNER, "Nominal bitrate not set\n");
128
129                                        //
130                                        upper_rate = 0;
131                                        lower_rate = 0;
132
133                                        if(inf->vi.bitrate_upper > 0)
134                                        {
135                                                DPRINTF(E_DEBUG, L_SCANNER, "Upper bitrate: %f kb/s\n",
136                                                        (double)inf->vi.bitrate_upper / 1000.0);
137                                                upper_rate = inf->vi.bitrate_upper;
138                                        }
139                                        else
140                                        {
141                                                DPRINTF(E_DEBUG, L_SCANNER, "Upper bitrate not set\n");
142                                        }
143
144                                        if(inf->vi.bitrate_lower > 0)
145                                        {
146                                                DPRINTF(E_DEBUG, L_SCANNER, "Lower bitrate: %f kb/s\n",
147                                                        (double)inf->vi.bitrate_lower / 1000.0);
148                                                lower_rate = inf->vi.bitrate_lower;;
149                                        }
150                                        else
151                                        {
152                                                DPRINTF(E_DEBUG, L_SCANNER, "Lower bitrate not set\n");
153                                        }
154
155                                        if(upper_rate && lower_rate)
156                                        {
157                                                psong->bitrate = (upper_rate + lower_rate) / 2;
158                                        }
159                                        else
160                                        {
161                                                psong->bitrate = upper_rate + lower_rate;
162                                        }
163                                }
164
165                                if(inf->vc.comments > 0)
166                                        DPRINTF(E_DEBUG, L_SCANNER,
167                                                "User comments section follows...\n");
168
169                                for(i = 0; i < inf->vc.comments; i++)
170                                {
171                                        vc_scan(psong, inf->vc.user_comments[i], inf->vc.comment_lengths[i]);
172                                }
173                        }
174                }
175        }
176
177        if(!header)
178        {
179                ogg_int64_t gp = ogg_page_granulepos(page);
180                if(gp > 0)
181                {
182                        if(gp < inf->lastgranulepos)
183                                DPRINTF(E_WARN, L_SCANNER, "granulepos in stream %d decreases from %lld to %lld",
184                                        stream->num, inf->lastgranulepos, gp);
185                        inf->lastgranulepos = gp;
186                }
187                else
188                {
189                        DPRINTF(E_WARN, L_SCANNER, "Malformed vorbis strem.\n");
190                }
191                inf->bytes += page->header_len + page->body_len;
192        }
193}
194
195static void
196_ogg_vorbis_end(ogg_stream_processor *stream, struct song_metadata *psong)
197{
198        ogg_misc_vorbis_info *inf = stream->data;
199        double bitrate, time;
200
201        time = (double)inf->lastgranulepos / inf->vi.rate;
202        bitrate = inf->bytes * 8 / time / 1000;
203
204        if(psong != NULL)
205        {
206                if(psong->bitrate <= 0)
207                {
208                        psong->bitrate = bitrate * 1000;
209                }
210                psong->song_length = time * 1000;
211        }
212
213        vorbis_comment_clear(&inf->vc);
214        vorbis_info_clear(&inf->vi);
215
216        free(stream->data);
217}
218
219static void
220_ogg_process_null(ogg_stream_processor *stream, ogg_page *page, struct song_metadata *psong)
221{
222        // invalid stream
223}
224
225static void
226_ogg_process_other(ogg_stream_processor *stream, ogg_page *page, struct song_metadata *psong)
227{
228        ogg_stream_pagein(&stream->os, page);
229}
230
231static void
232_ogg_free_stream_set(ogg_stream_set *set)
233{
234        int i;
235
236        for(i = 0; i < set->used; i++)
237        {
238                if(!set->streams[i].end)
239                {
240                        // no EOS
241                        if(set->streams[i].process_end)
242                                set->streams[i].process_end(&set->streams[i], NULL);
243                }
244                ogg_stream_clear(&set->streams[i].os);
245        }
246
247        free(set->streams);
248        free(set);
249}
250
251static int
252_ogg_streams_open(ogg_stream_set *set)
253{
254        int i;
255        int res = 0;
256
257        for(i = 0; i < set->used; i++)
258        {
259                if(!set->streams[i].end)
260                        res++;
261        }
262
263        return res;
264}
265
266static void
267_ogg_null_start(ogg_stream_processor *stream)
268{
269        stream->process_end = NULL;
270        stream->type = "invalid";
271        stream->process_page = _ogg_process_null;
272}
273
274static void
275_ogg_other_start(ogg_stream_processor *stream, char *type)
276{
277        if(type)
278                stream->type = type;
279        else
280                stream->type = "unknown";
281        stream->process_page = _ogg_process_other;
282        stream->process_end = NULL;
283}
284
285static void
286_ogg_vorbis_start(ogg_stream_processor *stream)
287{
288        ogg_misc_vorbis_info *info;
289
290        stream->type = "vorbis";
291        stream->process_page = _ogg_vorbis_process;
292        stream->process_end = _ogg_vorbis_end;
293
294        stream->data = calloc(1, sizeof(ogg_misc_vorbis_info));
295
296        info = stream->data;
297
298        vorbis_comment_init(&info->vc);
299        vorbis_info_init(&info->vi);
300}
301
302static ogg_stream_processor *
303_ogg_find_stream_processor(ogg_stream_set *set, ogg_page *page)
304{
305        ogg_uint32_t serial = ogg_page_serialno(page);
306        int i;
307        int invalid = 0;
308        int constraint = 0;
309        ogg_stream_processor *stream;
310
311        for(i = 0; i < set->used; i++)
312        {
313                if(serial == set->streams[i].serial)
314                {
315                        stream = &(set->streams[i]);
316
317                        set->in_headers = 0;
318
319                        if(stream->end)
320                        {
321                                stream->isillegal = 1;
322                                stream->constraint_violated = CONSTRAINT_PAGE_AFTER_EOS;
323                                return stream;
324                        }
325
326                        stream->isnew = 0;
327                        stream->start = ogg_page_bos(page);
328                        stream->end = ogg_page_eos(page);
329                        stream->serial = serial;
330                        return stream;
331                }
332        }
333        if(_ogg_streams_open(set) && !set->in_headers)
334        {
335                constraint = CONSTRAINT_MUXING_VIOLATED;
336                invalid = 1;
337        }
338
339        set->in_headers = 1;
340
341        if(set->allocated < set->used)
342                stream = &set->streams[set->used];
343        else
344        {
345                set->allocated += 5;
346                set->streams = realloc(set->streams, sizeof(ogg_stream_processor) * set->allocated);
347                stream = &set->streams[set->used];
348        }
349        set->used++;
350        stream->num = set->used;                // count from 1
351
352        stream->isnew = 1;
353        stream->isillegal = invalid;
354        stream->constraint_violated = constraint;
355
356        {
357                int res;
358                ogg_packet packet;
359
360                ogg_stream_init(&stream->os, serial);
361                ogg_stream_pagein(&stream->os, page);
362                res = ogg_stream_packetout(&stream->os, &packet);
363                if(res <= 0)
364                {
365                        DPRINTF(E_WARN, L_SCANNER, "Invalid header page, no packet found\n");
366                        _ogg_null_start(stream);
367                }
368                else if(packet.bytes >= 7 && memcmp(packet.packet, "\001vorbis", 7) == 0)
369                        _ogg_vorbis_start(stream);
370                else if(packet.bytes >= 8 && memcmp(packet.packet, "OggMIDI\0", 8) == 0)
371                        _ogg_other_start(stream, "MIDI");
372                else
373                        _ogg_other_start(stream, NULL);
374
375                res = ogg_stream_packetout(&stream->os, &packet);
376                if(res > 0)
377                {
378                        DPRINTF(E_WARN, L_SCANNER, "Invalid header page in stream %d, "
379                                "contains multiple packets\n", stream->num);
380                }
381
382                /* re-init, ready for processing */
383                ogg_stream_clear(&stream->os);
384                ogg_stream_init(&stream->os, serial);
385        }
386
387        stream->start = ogg_page_bos(page);
388        stream->end = ogg_page_eos(page);
389        stream->serial = serial;
390
391        return stream;
392}
393
394static int
395_ogg_get_next_page(FILE *f, ogg_sync_state *sync, ogg_page *page,
396                   ogg_int64_t *written)
397{
398        int ret;
399        char *buffer;
400        int bytes;
401
402        while((ret = ogg_sync_pageout(sync, page)) <= 0)
403        {
404                if(ret < 0)
405                        DPRINTF(E_WARN, L_SCANNER, "Hole in data found at approximate offset %lld bytes. Corrupted ogg.\n", *written);
406
407                buffer = ogg_sync_buffer(sync, 4500); // chunk=4500
408                bytes = fread(buffer, 1, 4500, f);
409                if(bytes <= 0)
410                {
411                        ogg_sync_wrote(sync, 0);
412                        return 0;
413                }
414                ogg_sync_wrote(sync, bytes);
415                *written += bytes;
416        }
417
418        return 1;
419}
420
421
422static int
423_get_oggfileinfo(char *filename, struct song_metadata *psong)
424{
425        FILE *file = fopen(filename, "rb");
426        ogg_sync_state sync;
427        ogg_page page;
428        ogg_stream_set *processors = _ogg_create_stream_set();
429        int gotpage = 0;
430        ogg_int64_t written = 0;
431
432        if(!file)
433        {
434                DPRINTF(E_FATAL, L_SCANNER,
435                        "Error opening input file \"%s\": %s\n", filename,  strerror(errno));
436                _ogg_free_stream_set(processors);
437                return -1;
438        }
439
440        DPRINTF(E_INFO, L_SCANNER, "Processing file \"%s\"...\n\n", filename);
441
442        ogg_sync_init(&sync);
443
444        while(_ogg_get_next_page(file, &sync, &page, &written))
445        {
446                ogg_stream_processor *p = _ogg_find_stream_processor(processors, &page);
447                gotpage = 1;
448
449                if(!p)
450                {
451                        DPRINTF(E_FATAL, L_SCANNER, "Could not find a processor for stream, bailing\n");
452                        _ogg_free_stream_set(processors);
453                        fclose(file);
454                        return -1;
455                }
456
457                if(p->isillegal && !p->shownillegal)
458                {
459                        char *constraint;
460                        switch(p->constraint_violated)
461                        {
462                        case CONSTRAINT_PAGE_AFTER_EOS:
463                                constraint = "Page found for stream after EOS flag";
464                                break;
465                        case CONSTRAINT_MUXING_VIOLATED:
466                                constraint = "Ogg muxing constraints violated, new "
467                                             "stream before EOS of all previous streams";
468                                break;
469                        default:
470                                constraint = "Error unknown.";
471                        }
472
473                        DPRINTF(E_WARN, L_SCANNER,
474                                "Warning: illegally placed page(s) for logical stream %d\n"
475                                "This indicates a corrupt ogg file: %s.\n",
476                                p->num, constraint);
477                        p->shownillegal = 1;
478
479                        if(!p->isnew)
480                                continue;
481                }
482
483                if(p->isnew)
484                {
485                        DPRINTF(E_DEBUG, L_SCANNER, "New logical stream (#%d, serial: %08x): type %s\n",
486                                p->num, p->serial, p->type);
487                        if(!p->start)
488                                DPRINTF(E_WARN, L_SCANNER,
489                                        "stream start flag not set on stream %d\n",
490                                        p->num);
491                }
492                else if(p->start)
493                        DPRINTF(E_WARN, L_SCANNER, "stream start flag found in mid-stream "
494                                "on stream %d\n", p->num);
495
496                if(p->seqno++ != ogg_page_pageno(&page))
497                {
498                        if(!p->lostseq)
499                                DPRINTF(E_WARN, L_SCANNER,
500                                        "sequence number gap in stream %d. Got page %ld "
501                                        "when expecting page %ld. Indicates missing data.\n",
502                                        p->num, ogg_page_pageno(&page), p->seqno - 1);
503                        p->seqno = ogg_page_pageno(&page);
504                        p->lostseq = 1;
505                }
506                else
507                        p->lostseq = 0;
508
509                if(!p->isillegal)
510                {
511                        p->process_page(p, &page, psong);
512
513                        if(p->end)
514                        {
515                                if(p->process_end)
516                                        p->process_end(p, psong);
517                                DPRINTF(E_DEBUG, L_SCANNER, "Logical stream %d ended\n", p->num);
518                                p->isillegal = 1;
519                                p->constraint_violated = CONSTRAINT_PAGE_AFTER_EOS;
520                        }
521                }
522        }
523
524        _ogg_free_stream_set(processors);
525
526        ogg_sync_clear(&sync);
527
528        fclose(file);
529
530        if(!gotpage)
531        {
532                DPRINTF(E_ERROR, L_SCANNER, "No ogg data found in file \"%s\".\n", filename);
533                return -1;
534        }
535
536        return 0;
537}
Note: See TracBrowser for help on using the repository browser.