"Fossies" - the Fresh Open Source Software Archive

Member "minidlna-1.3.0/tagutils/tagutils-ogg.c" (24 Nov 2020, 12299 Bytes) of package /linux/privat/minidlna-1.3.0.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "tagutils-ogg.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 1.2.1_vs_1.3.0.

    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 
   27 typedef 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 
   48 typedef struct {
   49     ogg_stream_processor *streams;
   50     int allocated;
   51     int used;
   52 
   53     int in_headers;
   54 } ogg_stream_set;
   55 
   56 typedef 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 
   70 static 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 
   82 static 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_MAXDEBUG, L_SCANNER, "Vorbis headers parsed for stream %d, "
  110                     "information follows...\n", stream->num);
  111                 DPRINTF(E_MAXDEBUG, L_SCANNER, "Channels: %d\n", inf->vi.channels);
  112                 DPRINTF(E_MAXDEBUG, 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_MAXDEBUG, 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_MAXDEBUG, 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_MAXDEBUG, 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_MAXDEBUG, L_SCANNER, "Upper bitrate not set\n");
  142                     }
  143 
  144                     if(inf->vi.bitrate_lower > 0)
  145                     {
  146                         DPRINTF(E_MAXDEBUG, 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_MAXDEBUG, 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_MAXDEBUG, 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             inf->lastgranulepos = gp;
  183         }
  184         else
  185         {
  186             DPRINTF(E_WARN, L_SCANNER, "Malformed vorbis strem.\n");
  187         }
  188         inf->bytes += page->header_len + page->body_len;
  189     }
  190 }
  191 
  192 static void
  193 _ogg_vorbis_end(ogg_stream_processor *stream, struct song_metadata *psong)
  194 {
  195     ogg_misc_vorbis_info *inf = stream->data;
  196     double bitrate, time;
  197 
  198     time = (double)inf->lastgranulepos / inf->vi.rate;
  199     bitrate = inf->bytes * 8 / time / 1000;
  200 
  201     if(psong != NULL)
  202     {
  203         if(psong->bitrate <= 0)
  204         {
  205             psong->bitrate = bitrate * 1000;
  206         }
  207         psong->song_length = time * 1000;
  208     }
  209 
  210     vorbis_comment_clear(&inf->vc);
  211     vorbis_info_clear(&inf->vi);
  212 
  213     free(stream->data);
  214 }
  215 
  216 static void
  217 _ogg_process_null(ogg_stream_processor *stream, ogg_page *page, struct song_metadata *psong)
  218 {
  219     // invalid stream
  220 }
  221 
  222 static void
  223 _ogg_process_other(ogg_stream_processor *stream, ogg_page *page, struct song_metadata *psong)
  224 {
  225     ogg_stream_pagein(&stream->os, page);
  226 }
  227 
  228 static void
  229 _ogg_free_stream_set(ogg_stream_set *set)
  230 {
  231     int i;
  232 
  233     for(i = 0; i < set->used; i++)
  234     {
  235         if(!set->streams[i].end)
  236         {
  237             // no EOS
  238             if(set->streams[i].process_end)
  239                 set->streams[i].process_end(&set->streams[i], NULL);
  240         }
  241         ogg_stream_clear(&set->streams[i].os);
  242     }
  243 
  244     free(set->streams);
  245     free(set);
  246 }
  247 
  248 static int
  249 _ogg_streams_open(ogg_stream_set *set)
  250 {
  251     int i;
  252     int res = 0;
  253 
  254     for(i = 0; i < set->used; i++)
  255     {
  256         if(!set->streams[i].end)
  257             res++;
  258     }
  259 
  260     return res;
  261 }
  262 
  263 static void
  264 _ogg_null_start(ogg_stream_processor *stream)
  265 {
  266     stream->process_end = NULL;
  267     stream->type = "invalid";
  268     stream->process_page = _ogg_process_null;
  269 }
  270 
  271 static void
  272 _ogg_other_start(ogg_stream_processor *stream, char *type)
  273 {
  274     if(type)
  275         stream->type = type;
  276     else
  277         stream->type = "unknown";
  278     stream->process_page = _ogg_process_other;
  279     stream->process_end = NULL;
  280 }
  281 
  282 static void
  283 _ogg_vorbis_start(ogg_stream_processor *stream)
  284 {
  285     ogg_misc_vorbis_info *info;
  286 
  287     stream->type = "vorbis";
  288     stream->process_page = _ogg_vorbis_process;
  289     stream->process_end = _ogg_vorbis_end;
  290 
  291     stream->data = calloc(1, sizeof(ogg_misc_vorbis_info));
  292 
  293     info = stream->data;
  294 
  295     vorbis_comment_init(&info->vc);
  296     vorbis_info_init(&info->vi);
  297 }
  298 
  299 static ogg_stream_processor *
  300 _ogg_find_stream_processor(ogg_stream_set *set, ogg_page *page)
  301 {
  302     ogg_uint32_t serial = ogg_page_serialno(page);
  303     int i;
  304     int invalid = 0;
  305     int constraint = 0;
  306     ogg_stream_processor *stream;
  307 
  308     for(i = 0; i < set->used; i++)
  309     {
  310         if(serial == set->streams[i].serial)
  311         {
  312             stream = &(set->streams[i]);
  313 
  314             set->in_headers = 0;
  315 
  316             if(stream->end)
  317             {
  318                 stream->isillegal = 1;
  319                 stream->constraint_violated = CONSTRAINT_PAGE_AFTER_EOS;
  320                 return stream;
  321             }
  322 
  323             stream->isnew = 0;
  324             stream->start = ogg_page_bos(page);
  325             stream->end = ogg_page_eos(page);
  326             stream->serial = serial;
  327             return stream;
  328         }
  329     }
  330     if(_ogg_streams_open(set) && !set->in_headers)
  331     {
  332         constraint = CONSTRAINT_MUXING_VIOLATED;
  333         invalid = 1;
  334     }
  335 
  336     set->in_headers = 1;
  337 
  338     if(set->allocated < set->used)
  339         stream = &set->streams[set->used];
  340     else
  341     {
  342         set->allocated += 5;
  343         set->streams = realloc(set->streams, sizeof(ogg_stream_processor) * set->allocated);
  344         stream = &set->streams[set->used];
  345     }
  346     set->used++;
  347     stream->num = set->used;                // count from 1
  348 
  349     stream->isnew = 1;
  350     stream->isillegal = invalid;
  351     stream->constraint_violated = constraint;
  352 
  353     {
  354         int res;
  355         ogg_packet packet;
  356 
  357         ogg_stream_init(&stream->os, serial);
  358         ogg_stream_pagein(&stream->os, page);
  359         res = ogg_stream_packetout(&stream->os, &packet);
  360         if(res <= 0)
  361         {
  362             DPRINTF(E_WARN, L_SCANNER, "Invalid header page, no packet found\n");
  363             _ogg_null_start(stream);
  364         }
  365         else if(packet.bytes >= 7 && memcmp(packet.packet, "\001vorbis", 7) == 0)
  366             _ogg_vorbis_start(stream);
  367         else if(packet.bytes >= 8 && memcmp(packet.packet, "OggMIDI\0", 8) == 0)
  368             _ogg_other_start(stream, "MIDI");
  369         else
  370             _ogg_other_start(stream, NULL);
  371 
  372         res = ogg_stream_packetout(&stream->os, &packet);
  373         if(res > 0)
  374         {
  375             DPRINTF(E_WARN, L_SCANNER, "Invalid header page in stream %d, "
  376                 "contains multiple packets\n", stream->num);
  377         }
  378 
  379         /* re-init, ready for processing */
  380         ogg_stream_clear(&stream->os);
  381         ogg_stream_init(&stream->os, serial);
  382     }
  383 
  384     stream->start = ogg_page_bos(page);
  385     stream->end = ogg_page_eos(page);
  386     stream->serial = serial;
  387 
  388     return stream;
  389 }
  390 
  391 static int
  392 _ogg_get_next_page(FILE *f, ogg_sync_state *sync, ogg_page *page,
  393            ogg_int64_t *written)
  394 {
  395     int ret;
  396     char *buffer;
  397     int bytes;
  398 
  399     while((ret = ogg_sync_pageout(sync, page)) <= 0)
  400     {
  401         if(ret < 0)
  402             DPRINTF(E_WARN, L_SCANNER, "Hole in data found at approximate offset %lld bytes. Corrupted ogg.\n",
  403                 (long long)*written);
  404 
  405         buffer = ogg_sync_buffer(sync, 4500); // chunk=4500
  406         bytes = fread(buffer, 1, 4500, f);
  407         if(bytes <= 0)
  408         {
  409             ogg_sync_wrote(sync, 0);
  410             return 0;
  411         }
  412         ogg_sync_wrote(sync, bytes);
  413         *written += bytes;
  414     }
  415 
  416     return 1;
  417 }
  418 
  419 
  420 static int
  421 _get_oggfileinfo(char *filename, struct song_metadata *psong)
  422 {
  423     FILE *file = fopen(filename, "rb");
  424     ogg_sync_state sync;
  425     ogg_page page;
  426     ogg_stream_set *processors = _ogg_create_stream_set();
  427     int gotpage = 0;
  428     ogg_int64_t written = 0;
  429 
  430     if(!file)
  431     {
  432         DPRINTF(E_FATAL, L_SCANNER,
  433             "Error opening input file \"%s\": %s\n", filename,  strerror(errno));
  434         _ogg_free_stream_set(processors);
  435         return -1;
  436     }
  437 
  438     DPRINTF(E_MAXDEBUG, L_SCANNER, "Processing file \"%s\"...\n", filename);
  439 
  440     ogg_sync_init(&sync);
  441 
  442     while(_ogg_get_next_page(file, &sync, &page, &written))
  443     {
  444         ogg_stream_processor *p = _ogg_find_stream_processor(processors, &page);
  445         gotpage = 1;
  446 
  447         if(!p)
  448         {
  449             DPRINTF(E_FATAL, L_SCANNER, "Could not find a processor for stream, bailing\n");
  450             _ogg_free_stream_set(processors);
  451             fclose(file);
  452             return -1;
  453         }
  454 
  455         if(p->isillegal && !p->shownillegal)
  456         {
  457             char *constraint;
  458             switch(p->constraint_violated)
  459             {
  460             case CONSTRAINT_PAGE_AFTER_EOS:
  461                 constraint = "Page found for stream after EOS flag";
  462                 break;
  463             case CONSTRAINT_MUXING_VIOLATED:
  464                 constraint = "Ogg muxing constraints violated, new "
  465                          "stream before EOS of all previous streams";
  466                 break;
  467             default:
  468                 constraint = "Error unknown.";
  469             }
  470 
  471             DPRINTF(E_WARN, L_SCANNER,
  472                 "Warning: illegally placed page(s) for logical stream %d\n"
  473                 "This indicates a corrupt ogg file: %s.\n",
  474                 p->num, constraint);
  475             p->shownillegal = 1;
  476 
  477             if(!p->isnew)
  478                 continue;
  479         }
  480 
  481         if(p->isnew)
  482         {
  483             DPRINTF(E_MAXDEBUG, L_SCANNER, "New logical stream (#%d, serial: %08x): type %s\n",
  484                 p->num, p->serial, p->type);
  485             if(!p->start)
  486                 DPRINTF(E_WARN, L_SCANNER,
  487                     "stream start flag not set on stream %d\n",
  488                     p->num);
  489         }
  490         else if(p->start)
  491             DPRINTF(E_WARN, L_SCANNER, "stream start flag found in mid-stream "
  492                 "on stream %d\n", p->num);
  493 
  494         if(p->seqno++ != ogg_page_pageno(&page))
  495         {
  496             if(!p->lostseq)
  497                 DPRINTF(E_WARN, L_SCANNER,
  498                     "sequence number gap in stream %d. Got page %ld "
  499                     "when expecting page %ld. Indicates missing data.\n",
  500                     p->num, ogg_page_pageno(&page), p->seqno - 1);
  501             p->seqno = ogg_page_pageno(&page);
  502             p->lostseq = 1;
  503         }
  504         else
  505             p->lostseq = 0;
  506 
  507         if(!p->isillegal)
  508         {
  509             p->process_page(p, &page, psong);
  510 
  511             if(p->end)
  512             {
  513                 if(p->process_end)
  514                     p->process_end(p, psong);
  515                 DPRINTF(E_MAXDEBUG, L_SCANNER, "Logical stream %d ended\n", p->num);
  516                 p->isillegal = 1;
  517                 p->constraint_violated = CONSTRAINT_PAGE_AFTER_EOS;
  518             }
  519         }
  520     }
  521 
  522     _ogg_free_stream_set(processors);
  523 
  524     ogg_sync_clear(&sync);
  525 
  526     fclose(file);
  527 
  528     if(!gotpage)
  529     {
  530         DPRINTF(E_ERROR, L_SCANNER, "No ogg data found in file \"%s\".\n", filename);
  531         return -1;
  532     }
  533 
  534     return 0;
  535 }