1 //=========================================================================
2 // FILENAME : tagutils-ogg.c
3 // DESCRIPTION : Ogg metadata reader
4 //=========================================================================
5 // Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved.
6 //=========================================================================
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.
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.
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/>.
24 * This file is derived from mt-daap project.
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
*);
31 int constraint_violated
;
46 } ogg_stream_processor
;
49 ogg_stream_processor
*streams
;
61 ogg_int64_t lastgranulepos
;
62 ogg_int64_t firstgranulepos
;
65 } ogg_misc_vorbis_info
;
67 #define CONSTRAINT_PAGE_AFTER_EOS 1
68 #define CONSTRAINT_MUXING_VIOLATED 2
70 static ogg_stream_set
*
71 _ogg_create_stream_set(void)
73 ogg_stream_set
*set
= calloc(1, sizeof(ogg_stream_set
));
75 set
->streams
= calloc(5, sizeof(ogg_stream_processor
));
83 _ogg_vorbis_process(ogg_stream_processor
*stream
, ogg_page
*page
,
84 struct song_metadata
*psong
)
87 ogg_misc_vorbis_info
*inf
= stream
->data
;
90 ogg_stream_pagein(&stream
->os
, page
);
91 if(inf
->doneheaders
< 3)
94 while(ogg_stream_packetout(&stream
->os
, &packet
) > 0)
96 if(inf
->doneheaders
< 3)
98 if(vorbis_synthesis_headerin(&inf
->vi
, &inf
->vc
, &packet
) < 0)
100 DPRINTF(E_WARN
, L_SCANNER
, "Could not decode vorbis header "
101 "packet - invalid vorbis stream (%d)\n", stream
->num
);
105 if(inf
->doneheaders
== 3)
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
);
114 psong
->samplerate
= inf
->vi
.rate
;
115 psong
->channels
= inf
->vi
.channels
;
117 if(inf
->vi
.bitrate_nominal
> 0)
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;
125 int upper_rate
, lower_rate
;
127 DPRINTF(E_MAXDEBUG
, L_SCANNER
, "Nominal bitrate not set\n");
133 if(inf
->vi
.bitrate_upper
> 0)
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
;
141 DPRINTF(E_MAXDEBUG
, L_SCANNER
, "Upper bitrate not set\n");
144 if(inf
->vi
.bitrate_lower
> 0)
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
;;
152 DPRINTF(E_MAXDEBUG
, L_SCANNER
, "Lower bitrate not set\n");
155 if(upper_rate
&& lower_rate
)
157 psong
->bitrate
= (upper_rate
+ lower_rate
) / 2;
161 psong
->bitrate
= upper_rate
+ lower_rate
;
165 if(inf
->vc
.comments
> 0)
166 DPRINTF(E_MAXDEBUG
, L_SCANNER
,
167 "User comments section follows...\n");
169 for(i
= 0; i
< inf
->vc
.comments
; i
++)
171 vc_scan(psong
, inf
->vc
.user_comments
[i
], inf
->vc
.comment_lengths
[i
]);
179 ogg_int64_t gp
= ogg_page_granulepos(page
);
182 inf
->lastgranulepos
= gp
;
186 DPRINTF(E_WARN
, L_SCANNER
, "Malformed vorbis strem.\n");
188 inf
->bytes
+= page
->header_len
+ page
->body_len
;
193 _ogg_vorbis_end(ogg_stream_processor
*stream
, struct song_metadata
*psong
)
195 ogg_misc_vorbis_info
*inf
= stream
->data
;
196 double bitrate
, time
;
198 time
= (double)inf
->lastgranulepos
/ inf
->vi
.rate
;
199 bitrate
= inf
->bytes
* 8 / time
/ 1000;
203 if(psong
->bitrate
<= 0)
205 psong
->bitrate
= bitrate
* 1000;
207 psong
->song_length
= time
* 1000;
210 vorbis_comment_clear(&inf
->vc
);
211 vorbis_info_clear(&inf
->vi
);
217 _ogg_process_null(ogg_stream_processor
*stream
, ogg_page
*page
, struct song_metadata
*psong
)
223 _ogg_process_other(ogg_stream_processor
*stream
, ogg_page
*page
, struct song_metadata
*psong
)
225 ogg_stream_pagein(&stream
->os
, page
);
229 _ogg_free_stream_set(ogg_stream_set
*set
)
233 for(i
= 0; i
< set
->used
; i
++)
235 if(!set
->streams
[i
].end
)
238 if(set
->streams
[i
].process_end
)
239 set
->streams
[i
].process_end(&set
->streams
[i
], NULL
);
241 ogg_stream_clear(&set
->streams
[i
].os
);
249 _ogg_streams_open(ogg_stream_set
*set
)
254 for(i
= 0; i
< set
->used
; i
++)
256 if(!set
->streams
[i
].end
)
264 _ogg_null_start(ogg_stream_processor
*stream
)
266 stream
->process_end
= NULL
;
267 stream
->type
= "invalid";
268 stream
->process_page
= _ogg_process_null
;
272 _ogg_other_start(ogg_stream_processor
*stream
, char *type
)
277 stream
->type
= "unknown";
278 stream
->process_page
= _ogg_process_other
;
279 stream
->process_end
= NULL
;
283 _ogg_vorbis_start(ogg_stream_processor
*stream
)
285 ogg_misc_vorbis_info
*info
;
287 stream
->type
= "vorbis";
288 stream
->process_page
= _ogg_vorbis_process
;
289 stream
->process_end
= _ogg_vorbis_end
;
291 stream
->data
= calloc(1, sizeof(ogg_misc_vorbis_info
));
295 vorbis_comment_init(&info
->vc
);
296 vorbis_info_init(&info
->vi
);
299 static ogg_stream_processor
*
300 _ogg_find_stream_processor(ogg_stream_set
*set
, ogg_page
*page
)
302 ogg_uint32_t serial
= ogg_page_serialno(page
);
306 ogg_stream_processor
*stream
;
308 for(i
= 0; i
< set
->used
; i
++)
310 if(serial
== set
->streams
[i
].serial
)
312 stream
= &(set
->streams
[i
]);
318 stream
->isillegal
= 1;
319 stream
->constraint_violated
= CONSTRAINT_PAGE_AFTER_EOS
;
324 stream
->start
= ogg_page_bos(page
);
325 stream
->end
= ogg_page_eos(page
);
326 stream
->serial
= serial
;
330 if(_ogg_streams_open(set
) && !set
->in_headers
)
332 constraint
= CONSTRAINT_MUXING_VIOLATED
;
338 if(set
->allocated
< set
->used
)
339 stream
= &set
->streams
[set
->used
];
343 set
->streams
= realloc(set
->streams
, sizeof(ogg_stream_processor
) * set
->allocated
);
344 stream
= &set
->streams
[set
->used
];
347 stream
->num
= set
->used
; // count from 1
350 stream
->isillegal
= invalid
;
351 stream
->constraint_violated
= constraint
;
357 ogg_stream_init(&stream
->os
, serial
);
358 ogg_stream_pagein(&stream
->os
, page
);
359 res
= ogg_stream_packetout(&stream
->os
, &packet
);
362 DPRINTF(E_WARN
, L_SCANNER
, "Invalid header page, no packet found\n");
363 _ogg_null_start(stream
);
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");
370 _ogg_other_start(stream
, NULL
);
372 res
= ogg_stream_packetout(&stream
->os
, &packet
);
375 DPRINTF(E_WARN
, L_SCANNER
, "Invalid header page in stream %d, "
376 "contains multiple packets\n", stream
->num
);
379 /* re-init, ready for processing */
380 ogg_stream_clear(&stream
->os
);
381 ogg_stream_init(&stream
->os
, serial
);
384 stream
->start
= ogg_page_bos(page
);
385 stream
->end
= ogg_page_eos(page
);
386 stream
->serial
= serial
;
392 _ogg_get_next_page(FILE *f
, ogg_sync_state
*sync
, ogg_page
*page
,
393 ogg_int64_t
*written
)
399 while((ret
= ogg_sync_pageout(sync
, page
)) <= 0)
402 DPRINTF(E_WARN
, L_SCANNER
, "Hole in data found at approximate offset %lld bytes. Corrupted ogg.\n",
403 (long long)*written
);
405 buffer
= ogg_sync_buffer(sync
, 4500); // chunk=4500
406 bytes
= fread(buffer
, 1, 4500, f
);
409 ogg_sync_wrote(sync
, 0);
412 ogg_sync_wrote(sync
, bytes
);
421 _get_oggfileinfo(char *filename
, struct song_metadata
*psong
)
423 FILE *file
= fopen(filename
, "rb");
426 ogg_stream_set
*processors
= _ogg_create_stream_set();
428 ogg_int64_t written
= 0;
432 DPRINTF(E_FATAL
, L_SCANNER
,
433 "Error opening input file \"%s\": %s\n", filename
, strerror(errno
));
434 _ogg_free_stream_set(processors
);
438 DPRINTF(E_MAXDEBUG
, L_SCANNER
, "Processing file \"%s\"...\n", filename
);
440 ogg_sync_init(&sync
);
442 while(_ogg_get_next_page(file
, &sync
, &page
, &written
))
444 ogg_stream_processor
*p
= _ogg_find_stream_processor(processors
, &page
);
449 DPRINTF(E_FATAL
, L_SCANNER
, "Could not find a processor for stream, bailing\n");
450 _ogg_free_stream_set(processors
);
455 if(p
->isillegal
&& !p
->shownillegal
)
458 switch(p
->constraint_violated
)
460 case CONSTRAINT_PAGE_AFTER_EOS
:
461 constraint
= "Page found for stream after EOS flag";
463 case CONSTRAINT_MUXING_VIOLATED
:
464 constraint
= "Ogg muxing constraints violated, new "
465 "stream before EOS of all previous streams";
468 constraint
= "Error unknown.";
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",
483 DPRINTF(E_MAXDEBUG
, L_SCANNER
, "New logical stream (#%d, serial: %08x): type %s\n",
484 p
->num
, p
->serial
, p
->type
);
486 DPRINTF(E_WARN
, L_SCANNER
,
487 "stream start flag not set on stream %d\n",
491 DPRINTF(E_WARN
, L_SCANNER
, "stream start flag found in mid-stream "
492 "on stream %d\n", p
->num
);
494 if(p
->seqno
++ != ogg_page_pageno(&page
))
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
);
509 p
->process_page(p
, &page
, psong
);
514 p
->process_end(p
, psong
);
515 DPRINTF(E_MAXDEBUG
, L_SCANNER
, "Logical stream %d ended\n", p
->num
);
517 p
->constraint_violated
= CONSTRAINT_PAGE_AFTER_EOS
;
522 _ogg_free_stream_set(processors
);
524 ogg_sync_clear(&sync
);
530 DPRINTF(E_ERROR
, L_SCANNER
, "No ogg data found in file \"%s\".\n", filename
);