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_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
);
114 psong
->samplerate
= inf
->vi
.rate
;
115 psong
->channels
= inf
->vi
.channels
;
117 if(inf
->vi
.bitrate_nominal
> 0)
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;
125 int upper_rate
, lower_rate
;
127 DPRINTF(E_DEBUG
, L_SCANNER
, "Nominal bitrate not set\n");
133 if(inf
->vi
.bitrate_upper
> 0)
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
;
141 DPRINTF(E_DEBUG
, L_SCANNER
, "Upper bitrate not set\n");
144 if(inf
->vi
.bitrate_lower
> 0)
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
;;
152 DPRINTF(E_DEBUG
, 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_DEBUG
, 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 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
;
189 DPRINTF(E_WARN
, L_SCANNER
, "Malformed vorbis strem.\n");
191 inf
->bytes
+= page
->header_len
+ page
->body_len
;
196 _ogg_vorbis_end(ogg_stream_processor
*stream
, struct song_metadata
*psong
)
198 ogg_misc_vorbis_info
*inf
= stream
->data
;
199 double bitrate
, time
;
201 time
= (double)inf
->lastgranulepos
/ inf
->vi
.rate
;
202 bitrate
= inf
->bytes
* 8 / time
/ 1000;
206 if(psong
->bitrate
<= 0)
208 psong
->bitrate
= bitrate
* 1000;
210 psong
->song_length
= time
* 1000;
213 vorbis_comment_clear(&inf
->vc
);
214 vorbis_info_clear(&inf
->vi
);
220 _ogg_process_null(ogg_stream_processor
*stream
, ogg_page
*page
, struct song_metadata
*psong
)
226 _ogg_process_other(ogg_stream_processor
*stream
, ogg_page
*page
, struct song_metadata
*psong
)
228 ogg_stream_pagein(&stream
->os
, page
);
232 _ogg_free_stream_set(ogg_stream_set
*set
)
236 for(i
= 0; i
< set
->used
; i
++)
238 if(!set
->streams
[i
].end
)
241 if(set
->streams
[i
].process_end
)
242 set
->streams
[i
].process_end(&set
->streams
[i
], NULL
);
244 ogg_stream_clear(&set
->streams
[i
].os
);
252 _ogg_streams_open(ogg_stream_set
*set
)
257 for(i
= 0; i
< set
->used
; i
++)
259 if(!set
->streams
[i
].end
)
267 _ogg_null_start(ogg_stream_processor
*stream
)
269 stream
->process_end
= NULL
;
270 stream
->type
= "invalid";
271 stream
->process_page
= _ogg_process_null
;
275 _ogg_other_start(ogg_stream_processor
*stream
, char *type
)
280 stream
->type
= "unknown";
281 stream
->process_page
= _ogg_process_other
;
282 stream
->process_end
= NULL
;
286 _ogg_vorbis_start(ogg_stream_processor
*stream
)
288 ogg_misc_vorbis_info
*info
;
290 stream
->type
= "vorbis";
291 stream
->process_page
= _ogg_vorbis_process
;
292 stream
->process_end
= _ogg_vorbis_end
;
294 stream
->data
= calloc(1, sizeof(ogg_misc_vorbis_info
));
298 vorbis_comment_init(&info
->vc
);
299 vorbis_info_init(&info
->vi
);
302 static ogg_stream_processor
*
303 _ogg_find_stream_processor(ogg_stream_set
*set
, ogg_page
*page
)
305 ogg_uint32_t serial
= ogg_page_serialno(page
);
309 ogg_stream_processor
*stream
;
311 for(i
= 0; i
< set
->used
; i
++)
313 if(serial
== set
->streams
[i
].serial
)
315 stream
= &(set
->streams
[i
]);
321 stream
->isillegal
= 1;
322 stream
->constraint_violated
= CONSTRAINT_PAGE_AFTER_EOS
;
327 stream
->start
= ogg_page_bos(page
);
328 stream
->end
= ogg_page_eos(page
);
329 stream
->serial
= serial
;
333 if(_ogg_streams_open(set
) && !set
->in_headers
)
335 constraint
= CONSTRAINT_MUXING_VIOLATED
;
341 if(set
->allocated
< set
->used
)
342 stream
= &set
->streams
[set
->used
];
346 set
->streams
= realloc(set
->streams
, sizeof(ogg_stream_processor
) * set
->allocated
);
347 stream
= &set
->streams
[set
->used
];
350 stream
->num
= set
->used
; // count from 1
353 stream
->isillegal
= invalid
;
354 stream
->constraint_violated
= constraint
;
360 ogg_stream_init(&stream
->os
, serial
);
361 ogg_stream_pagein(&stream
->os
, page
);
362 res
= ogg_stream_packetout(&stream
->os
, &packet
);
365 DPRINTF(E_WARN
, L_SCANNER
, "Invalid header page, no packet found\n");
366 _ogg_null_start(stream
);
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");
373 _ogg_other_start(stream
, NULL
);
375 res
= ogg_stream_packetout(&stream
->os
, &packet
);
378 DPRINTF(E_WARN
, L_SCANNER
, "Invalid header page in stream %d, "
379 "contains multiple packets\n", stream
->num
);
382 /* re-init, ready for processing */
383 ogg_stream_clear(&stream
->os
);
384 ogg_stream_init(&stream
->os
, serial
);
387 stream
->start
= ogg_page_bos(page
);
388 stream
->end
= ogg_page_eos(page
);
389 stream
->serial
= serial
;
395 _ogg_get_next_page(FILE *f
, ogg_sync_state
*sync
, ogg_page
*page
,
396 ogg_int64_t
*written
)
402 while((ret
= ogg_sync_pageout(sync
, page
)) <= 0)
405 DPRINTF(E_WARN
, L_SCANNER
, "Hole in data found at approximate offset %lld bytes. Corrupted ogg.\n", *written
);
407 buffer
= ogg_sync_buffer(sync
, 4500); // chunk=4500
408 bytes
= fread(buffer
, 1, 4500, f
);
411 ogg_sync_wrote(sync
, 0);
414 ogg_sync_wrote(sync
, bytes
);
423 _get_oggfileinfo(char *filename
, struct song_metadata
*psong
)
425 FILE *file
= fopen(filename
, "rb");
428 ogg_stream_set
*processors
= _ogg_create_stream_set();
430 ogg_int64_t written
= 0;
434 DPRINTF(E_FATAL
, L_SCANNER
,
435 "Error opening input file \"%s\": %s\n", filename
, strerror(errno
));
436 _ogg_free_stream_set(processors
);
440 DPRINTF(E_INFO
, L_SCANNER
, "Processing file \"%s\"...\n\n", filename
);
442 ogg_sync_init(&sync
);
444 while(_ogg_get_next_page(file
, &sync
, &page
, &written
))
446 ogg_stream_processor
*p
= _ogg_find_stream_processor(processors
, &page
);
451 DPRINTF(E_FATAL
, L_SCANNER
, "Could not find a processor for stream, bailing\n");
452 _ogg_free_stream_set(processors
);
456 if(p
->isillegal
&& !p
->shownillegal
)
459 switch(p
->constraint_violated
)
461 case CONSTRAINT_PAGE_AFTER_EOS
:
462 constraint
= "Page found for stream after EOS flag";
464 case CONSTRAINT_MUXING_VIOLATED
:
465 constraint
= "Ogg muxing constraints violated, new "
466 "stream before EOS of all previous streams";
469 constraint
= "Error unknown.";
472 DPRINTF(E_WARN
, L_SCANNER
,
473 "Warning: illegally placed page(s) for logical stream %d\n"
474 "This indicates a corrupt ogg file: %s.\n",
484 DPRINTF(E_DEBUG
, L_SCANNER
, "New logical stream (#%d, serial: %08x): type %s\n",
485 p
->num
, p
->serial
, p
->type
);
487 DPRINTF(E_WARN
, L_SCANNER
,
488 "stream start flag not set on stream %d\n",
492 DPRINTF(E_WARN
, L_SCANNER
, "stream start flag found in mid-stream "
493 "on stream %d\n", p
->num
);
495 if(p
->seqno
++ != ogg_page_pageno(&page
))
498 DPRINTF(E_WARN
, L_SCANNER
,
499 "sequence number gap in stream %d. Got page %ld "
500 "when expecting page %ld. Indicates missing data.\n",
501 p
->num
, ogg_page_pageno(&page
), p
->seqno
- 1);
502 p
->seqno
= ogg_page_pageno(&page
);
510 p
->process_page(p
, &page
, psong
);
515 p
->process_end(p
, psong
);
516 DPRINTF(E_DEBUG
, L_SCANNER
, "Logical stream %d ended\n", p
->num
);
518 p
->constraint_violated
= CONSTRAINT_PAGE_AFTER_EOS
;
523 _ogg_free_stream_set(processors
);
525 ogg_sync_clear(&sync
);
531 DPRINTF(E_ERROR
, L_SCANNER
, "No ogg data found in file \"%s\".\n", filename
);