ffmpeg / libavformat / oggdec.c @ 73823cb9
History | View | Annotate | Download (14.7 KB)
1 |
/*
|
---|---|
2 |
* Ogg bitstream support
|
3 |
* Luca Barbato <lu_zero@gentoo.org>
|
4 |
* Based on tcvp implementation
|
5 |
*
|
6 |
*/
|
7 |
|
8 |
/**
|
9 |
Copyright (C) 2005 Michael Ahlberg, Måns Rullgård
|
10 |
|
11 |
Permission is hereby granted, free of charge, to any person
|
12 |
obtaining a copy of this software and associated documentation
|
13 |
files (the "Software"), to deal in the Software without
|
14 |
restriction, including without limitation the rights to use, copy,
|
15 |
modify, merge, publish, distribute, sublicense, and/or sell copies
|
16 |
of the Software, and to permit persons to whom the Software is
|
17 |
furnished to do so, subject to the following conditions:
|
18 |
|
19 |
The above copyright notice and this permission notice shall be
|
20 |
included in all copies or substantial portions of the Software.
|
21 |
|
22 |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
23 |
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
24 |
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
25 |
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
26 |
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
27 |
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
28 |
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
29 |
DEALINGS IN THE SOFTWARE.
|
30 |
**/
|
31 |
|
32 |
|
33 |
#include <stdio.h> |
34 |
#include "oggdec.h" |
35 |
#include "avformat.h" |
36 |
|
37 |
#define MAX_PAGE_SIZE 65307 |
38 |
#define DECODER_BUFFER_SIZE MAX_PAGE_SIZE
|
39 |
|
40 |
static const struct ogg_codec * const ogg_codecs[] = { |
41 |
&ff_dirac_codec, |
42 |
&ff_speex_codec, |
43 |
&ff_vorbis_codec, |
44 |
&ff_theora_codec, |
45 |
&ff_flac_codec, |
46 |
&ff_old_dirac_codec, |
47 |
&ff_old_flac_codec, |
48 |
&ff_ogm_video_codec, |
49 |
&ff_ogm_audio_codec, |
50 |
&ff_ogm_text_codec, |
51 |
&ff_ogm_old_codec, |
52 |
NULL
|
53 |
}; |
54 |
|
55 |
//FIXME We could avoid some structure duplication
|
56 |
static int |
57 |
ogg_save (AVFormatContext * s) |
58 |
{ |
59 |
struct ogg *ogg = s->priv_data;
|
60 |
struct ogg_state *ost =
|
61 |
av_malloc(sizeof (*ost) + (ogg->nstreams-1) * sizeof (*ogg->streams)); |
62 |
int i;
|
63 |
ost->pos = url_ftell (s->pb); |
64 |
ost->curidx = ogg->curidx; |
65 |
ost->next = ogg->state; |
66 |
ost->nstreams = ogg->nstreams; |
67 |
memcpy(ost->streams, ogg->streams, ogg->nstreams * sizeof(*ogg->streams));
|
68 |
|
69 |
for (i = 0; i < ogg->nstreams; i++){ |
70 |
struct ogg_stream *os = ogg->streams + i;
|
71 |
os->buf = av_malloc (os->bufsize); |
72 |
memset (os->buf, 0, os->bufsize);
|
73 |
memcpy (os->buf, ost->streams[i].buf, os->bufpos); |
74 |
} |
75 |
|
76 |
ogg->state = ost; |
77 |
|
78 |
return 0; |
79 |
} |
80 |
|
81 |
static int |
82 |
ogg_restore (AVFormatContext * s, int discard)
|
83 |
{ |
84 |
struct ogg *ogg = s->priv_data;
|
85 |
ByteIOContext *bc = s->pb; |
86 |
struct ogg_state *ost = ogg->state;
|
87 |
int i;
|
88 |
|
89 |
if (!ost)
|
90 |
return 0; |
91 |
|
92 |
ogg->state = ost->next; |
93 |
|
94 |
if (!discard){
|
95 |
for (i = 0; i < ogg->nstreams; i++) |
96 |
av_free (ogg->streams[i].buf); |
97 |
|
98 |
url_fseek (bc, ost->pos, SEEK_SET); |
99 |
ogg->curidx = ost->curidx; |
100 |
ogg->nstreams = ost->nstreams; |
101 |
memcpy(ogg->streams, ost->streams, |
102 |
ost->nstreams * sizeof(*ogg->streams));
|
103 |
} |
104 |
|
105 |
av_free (ost); |
106 |
|
107 |
return 0; |
108 |
} |
109 |
|
110 |
static int |
111 |
ogg_reset (struct ogg * ogg)
|
112 |
{ |
113 |
int i;
|
114 |
|
115 |
for (i = 0; i < ogg->nstreams; i++){ |
116 |
struct ogg_stream *os = ogg->streams + i;
|
117 |
os->bufpos = 0;
|
118 |
os->pstart = 0;
|
119 |
os->psize = 0;
|
120 |
os->granule = -1;
|
121 |
os->lastpts = AV_NOPTS_VALUE; |
122 |
os->lastdts = AV_NOPTS_VALUE; |
123 |
os->sync_pos = -1;
|
124 |
os->page_pos = 0;
|
125 |
os->nsegs = 0;
|
126 |
os->segp = 0;
|
127 |
os->incomplete = 0;
|
128 |
} |
129 |
|
130 |
ogg->curidx = -1;
|
131 |
|
132 |
return 0; |
133 |
} |
134 |
|
135 |
static const struct ogg_codec * |
136 |
ogg_find_codec (uint8_t * buf, int size)
|
137 |
{ |
138 |
int i;
|
139 |
|
140 |
for (i = 0; ogg_codecs[i]; i++) |
141 |
if (size >= ogg_codecs[i]->magicsize &&
|
142 |
!memcmp (buf, ogg_codecs[i]->magic, ogg_codecs[i]->magicsize)) |
143 |
return ogg_codecs[i];
|
144 |
|
145 |
return NULL; |
146 |
} |
147 |
|
148 |
static int |
149 |
ogg_find_stream (struct ogg * ogg, int serial) |
150 |
{ |
151 |
int i;
|
152 |
|
153 |
for (i = 0; i < ogg->nstreams; i++) |
154 |
if (ogg->streams[i].serial == serial)
|
155 |
return i;
|
156 |
|
157 |
return -1; |
158 |
} |
159 |
|
160 |
static int |
161 |
ogg_new_stream (AVFormatContext * s, uint32_t serial) |
162 |
{ |
163 |
|
164 |
struct ogg *ogg = s->priv_data;
|
165 |
int idx = ogg->nstreams++;
|
166 |
AVStream *st; |
167 |
struct ogg_stream *os;
|
168 |
|
169 |
ogg->streams = av_realloc (ogg->streams, |
170 |
ogg->nstreams * sizeof (*ogg->streams));
|
171 |
memset (ogg->streams + idx, 0, sizeof (*ogg->streams)); |
172 |
os = ogg->streams + idx; |
173 |
os->serial = serial; |
174 |
os->bufsize = DECODER_BUFFER_SIZE; |
175 |
os->buf = av_malloc(os->bufsize); |
176 |
os->header = -1;
|
177 |
|
178 |
st = av_new_stream (s, idx); |
179 |
if (!st)
|
180 |
return AVERROR(ENOMEM);
|
181 |
|
182 |
av_set_pts_info(st, 64, 1, 1000000); |
183 |
|
184 |
return idx;
|
185 |
} |
186 |
|
187 |
static int |
188 |
ogg_new_buf(struct ogg *ogg, int idx) |
189 |
{ |
190 |
struct ogg_stream *os = ogg->streams + idx;
|
191 |
uint8_t *nb = av_malloc(os->bufsize); |
192 |
int size = os->bufpos - os->pstart;
|
193 |
if(os->buf){
|
194 |
memcpy(nb, os->buf + os->pstart, size); |
195 |
av_free(os->buf); |
196 |
} |
197 |
os->buf = nb; |
198 |
os->bufpos = size; |
199 |
os->pstart = 0;
|
200 |
|
201 |
return 0; |
202 |
} |
203 |
|
204 |
static int |
205 |
ogg_read_page (AVFormatContext * s, int *str)
|
206 |
{ |
207 |
ByteIOContext *bc = s->pb; |
208 |
struct ogg *ogg = s->priv_data;
|
209 |
struct ogg_stream *os;
|
210 |
int i = 0; |
211 |
int flags, nsegs;
|
212 |
uint64_t gp; |
213 |
uint32_t serial; |
214 |
uint32_t seq; |
215 |
uint32_t crc; |
216 |
int size, idx;
|
217 |
uint8_t sync[4];
|
218 |
int sp = 0; |
219 |
|
220 |
if (get_buffer (bc, sync, 4) < 4) |
221 |
return -1; |
222 |
|
223 |
do{
|
224 |
int c;
|
225 |
|
226 |
if (sync[sp & 3] == 'O' && |
227 |
sync[(sp + 1) & 3] == 'g' && |
228 |
sync[(sp + 2) & 3] == 'g' && sync[(sp + 3) & 3] == 'S') |
229 |
break;
|
230 |
|
231 |
c = url_fgetc (bc); |
232 |
if (c < 0) |
233 |
return -1; |
234 |
sync[sp++ & 3] = c;
|
235 |
}while (i++ < MAX_PAGE_SIZE);
|
236 |
|
237 |
if (i >= MAX_PAGE_SIZE){
|
238 |
av_log (s, AV_LOG_INFO, "ogg, can't find sync word\n");
|
239 |
return -1; |
240 |
} |
241 |
|
242 |
if (url_fgetc (bc) != 0) /* version */ |
243 |
return -1; |
244 |
|
245 |
flags = url_fgetc (bc); |
246 |
gp = get_le64 (bc); |
247 |
serial = get_le32 (bc); |
248 |
seq = get_le32 (bc); |
249 |
crc = get_le32 (bc); |
250 |
nsegs = url_fgetc (bc); |
251 |
|
252 |
idx = ogg_find_stream (ogg, serial); |
253 |
if (idx < 0){ |
254 |
idx = ogg_new_stream (s, serial); |
255 |
if (idx < 0) |
256 |
return -1; |
257 |
} |
258 |
|
259 |
os = ogg->streams + idx; |
260 |
os->page_pos = url_ftell(bc) - 27;
|
261 |
|
262 |
if(os->psize > 0) |
263 |
ogg_new_buf(ogg, idx); |
264 |
|
265 |
if (get_buffer (bc, os->segments, nsegs) < nsegs)
|
266 |
return -1; |
267 |
|
268 |
os->nsegs = nsegs; |
269 |
os->segp = 0;
|
270 |
|
271 |
size = 0;
|
272 |
for (i = 0; i < nsegs; i++) |
273 |
size += os->segments[i]; |
274 |
|
275 |
if (flags & OGG_FLAG_CONT || os->incomplete){
|
276 |
if (!os->psize){
|
277 |
while (os->segp < os->nsegs){
|
278 |
int seg = os->segments[os->segp++];
|
279 |
os->pstart += seg; |
280 |
if (seg < 255) |
281 |
break;
|
282 |
} |
283 |
os->sync_pos = os->page_pos; |
284 |
} |
285 |
}else{
|
286 |
os->psize = 0;
|
287 |
os->sync_pos = os->page_pos; |
288 |
} |
289 |
|
290 |
if (os->bufsize - os->bufpos < size){
|
291 |
uint8_t *nb = av_malloc (os->bufsize *= 2);
|
292 |
memcpy (nb, os->buf, os->bufpos); |
293 |
av_free (os->buf); |
294 |
os->buf = nb; |
295 |
} |
296 |
|
297 |
if (get_buffer (bc, os->buf + os->bufpos, size) < size)
|
298 |
return -1; |
299 |
|
300 |
os->bufpos += size; |
301 |
os->granule = gp; |
302 |
os->flags = flags; |
303 |
|
304 |
if (str)
|
305 |
*str = idx; |
306 |
|
307 |
return 0; |
308 |
} |
309 |
|
310 |
static int |
311 |
ogg_packet (AVFormatContext * s, int *str, int *dstart, int *dsize, int64_t *fpos) |
312 |
{ |
313 |
struct ogg *ogg = s->priv_data;
|
314 |
int idx, i;
|
315 |
struct ogg_stream *os;
|
316 |
int complete = 0; |
317 |
int segp = 0, psize = 0; |
318 |
|
319 |
#if 0
|
320 |
av_log (s, AV_LOG_DEBUG, "ogg_packet: curidx=%i\n", ogg->curidx);
|
321 |
#endif
|
322 |
|
323 |
do{
|
324 |
idx = ogg->curidx; |
325 |
|
326 |
while (idx < 0){ |
327 |
if (ogg_read_page (s, &idx) < 0) |
328 |
return -1; |
329 |
} |
330 |
|
331 |
os = ogg->streams + idx; |
332 |
|
333 |
#if 0
|
334 |
av_log (s, AV_LOG_DEBUG,
|
335 |
"ogg_packet: idx=%d pstart=%d psize=%d segp=%d nsegs=%d\n",
|
336 |
idx, os->pstart, os->psize, os->segp, os->nsegs);
|
337 |
#endif
|
338 |
|
339 |
if (!os->codec){
|
340 |
if (os->header < 0){ |
341 |
os->codec = ogg_find_codec (os->buf, os->bufpos); |
342 |
if (!os->codec){
|
343 |
os->header = 0;
|
344 |
return 0; |
345 |
} |
346 |
}else{
|
347 |
return 0; |
348 |
} |
349 |
} |
350 |
|
351 |
segp = os->segp; |
352 |
psize = os->psize; |
353 |
|
354 |
while (os->segp < os->nsegs){
|
355 |
int ss = os->segments[os->segp++];
|
356 |
os->psize += ss; |
357 |
if (ss < 255){ |
358 |
complete = 1;
|
359 |
break;
|
360 |
} |
361 |
} |
362 |
|
363 |
if (!complete && os->segp == os->nsegs){
|
364 |
ogg->curidx = -1;
|
365 |
os->incomplete = 1;
|
366 |
} |
367 |
}while (!complete);
|
368 |
|
369 |
#if 0
|
370 |
av_log (s, AV_LOG_DEBUG,
|
371 |
"ogg_packet: idx %i, frame size %i, start %i\n",
|
372 |
idx, os->psize, os->pstart);
|
373 |
#endif
|
374 |
|
375 |
ogg->curidx = idx; |
376 |
os->incomplete = 0;
|
377 |
|
378 |
if (!ogg->headers){
|
379 |
int hdr = os->codec->header (s, idx);
|
380 |
os->header = os->seq; |
381 |
if (!hdr){
|
382 |
os->segp = segp; |
383 |
os->psize = psize; |
384 |
ogg->headers = 1;
|
385 |
}else{
|
386 |
os->pstart += os->psize; |
387 |
os->psize = 0;
|
388 |
} |
389 |
} |
390 |
|
391 |
if (os->header > -1 && os->seq > os->header){ |
392 |
os->pflags = 0;
|
393 |
os->pduration = 0;
|
394 |
if (os->codec && os->codec->packet)
|
395 |
os->codec->packet (s, idx); |
396 |
if (str)
|
397 |
*str = idx; |
398 |
if (dstart)
|
399 |
*dstart = os->pstart; |
400 |
if (dsize)
|
401 |
*dsize = os->psize; |
402 |
if (fpos)
|
403 |
*fpos = os->sync_pos; |
404 |
os->pstart += os->psize; |
405 |
os->psize = 0;
|
406 |
os->sync_pos = os->page_pos; |
407 |
} |
408 |
|
409 |
// determine whether there are more complete packets in this page
|
410 |
// if not, the page's granule will apply to this packet
|
411 |
os->page_end = 1;
|
412 |
for (i = os->segp; i < os->nsegs; i++)
|
413 |
if (os->segments[i] < 255) { |
414 |
os->page_end = 0;
|
415 |
break;
|
416 |
} |
417 |
|
418 |
os->seq++; |
419 |
if (os->segp == os->nsegs)
|
420 |
ogg->curidx = -1;
|
421 |
|
422 |
return 0; |
423 |
} |
424 |
|
425 |
static int |
426 |
ogg_get_headers (AVFormatContext * s) |
427 |
{ |
428 |
struct ogg *ogg = s->priv_data;
|
429 |
|
430 |
do{
|
431 |
if (ogg_packet (s, NULL, NULL, NULL, NULL) < 0) |
432 |
return -1; |
433 |
}while (!ogg->headers);
|
434 |
|
435 |
#if 0
|
436 |
av_log (s, AV_LOG_DEBUG, "found headers\n");
|
437 |
#endif
|
438 |
|
439 |
return 0; |
440 |
} |
441 |
|
442 |
static uint64_t
|
443 |
ogg_gptopts (AVFormatContext * s, int i, uint64_t gp, int64_t *dts)
|
444 |
{ |
445 |
struct ogg *ogg = s->priv_data;
|
446 |
struct ogg_stream *os = ogg->streams + i;
|
447 |
uint64_t pts = AV_NOPTS_VALUE; |
448 |
|
449 |
if(os->codec->gptopts){
|
450 |
pts = os->codec->gptopts(s, i, gp, dts); |
451 |
} else {
|
452 |
pts = gp; |
453 |
if (dts)
|
454 |
*dts = pts; |
455 |
} |
456 |
|
457 |
return pts;
|
458 |
} |
459 |
|
460 |
|
461 |
static int |
462 |
ogg_get_length (AVFormatContext * s) |
463 |
{ |
464 |
struct ogg *ogg = s->priv_data;
|
465 |
int idx = -1, i; |
466 |
int64_t size, end; |
467 |
|
468 |
if(url_is_streamed(s->pb))
|
469 |
return 0; |
470 |
|
471 |
// already set
|
472 |
if (s->duration != AV_NOPTS_VALUE)
|
473 |
return 0; |
474 |
|
475 |
size = url_fsize(s->pb); |
476 |
if(size < 0) |
477 |
return 0; |
478 |
end = size > MAX_PAGE_SIZE? size - MAX_PAGE_SIZE: 0;
|
479 |
|
480 |
ogg_save (s); |
481 |
url_fseek (s->pb, end, SEEK_SET); |
482 |
|
483 |
while (!ogg_read_page (s, &i)){
|
484 |
if (ogg->streams[i].granule != -1 && ogg->streams[i].granule != 0 && |
485 |
ogg->streams[i].codec) |
486 |
idx = i; |
487 |
} |
488 |
|
489 |
if (idx != -1){ |
490 |
s->streams[idx]->duration = |
491 |
ogg_gptopts (s, idx, ogg->streams[idx].granule, NULL);
|
492 |
} |
493 |
|
494 |
ogg->size = size; |
495 |
ogg_restore (s, 0);
|
496 |
|
497 |
return 0; |
498 |
} |
499 |
|
500 |
|
501 |
static int |
502 |
ogg_read_header (AVFormatContext * s, AVFormatParameters * ap) |
503 |
{ |
504 |
struct ogg *ogg = s->priv_data;
|
505 |
int i;
|
506 |
ogg->curidx = -1;
|
507 |
//linear headers seek from start
|
508 |
if (ogg_get_headers (s) < 0){ |
509 |
return -1; |
510 |
} |
511 |
|
512 |
for (i = 0; i < ogg->nstreams; i++) |
513 |
if (ogg->streams[i].header < 0) |
514 |
ogg->streams[i].codec = NULL;
|
515 |
|
516 |
//linear granulepos seek from end
|
517 |
ogg_get_length (s); |
518 |
|
519 |
//fill the extradata in the per codec callbacks
|
520 |
return 0; |
521 |
} |
522 |
|
523 |
|
524 |
static int |
525 |
ogg_read_packet (AVFormatContext * s, AVPacket * pkt) |
526 |
{ |
527 |
struct ogg *ogg;
|
528 |
struct ogg_stream *os;
|
529 |
int idx = -1; |
530 |
int pstart, psize;
|
531 |
int64_t fpos; |
532 |
|
533 |
//Get an ogg packet
|
534 |
do{
|
535 |
if (ogg_packet (s, &idx, &pstart, &psize, &fpos) < 0) |
536 |
return AVERROR(EIO);
|
537 |
}while (idx < 0 || !s->streams[idx]); |
538 |
|
539 |
ogg = s->priv_data; |
540 |
os = ogg->streams + idx; |
541 |
|
542 |
//Alloc a pkt
|
543 |
if (av_new_packet (pkt, psize) < 0) |
544 |
return AVERROR(EIO);
|
545 |
pkt->stream_index = idx; |
546 |
memcpy (pkt->data, os->buf + pstart, psize); |
547 |
|
548 |
if (os->lastpts != AV_NOPTS_VALUE) {
|
549 |
pkt->pts = os->lastpts; |
550 |
os->lastpts = AV_NOPTS_VALUE; |
551 |
} |
552 |
if (os->lastdts != AV_NOPTS_VALUE) {
|
553 |
pkt->dts = os->lastdts; |
554 |
os->lastdts = AV_NOPTS_VALUE; |
555 |
} |
556 |
if (os->page_end) {
|
557 |
if (os->granule != -1LL) { |
558 |
if (os->codec && os->codec->granule_is_start)
|
559 |
pkt->pts = ogg_gptopts(s, idx, os->granule, &pkt->dts); |
560 |
else
|
561 |
os->lastpts = ogg_gptopts(s, idx, os->granule, &os->lastdts); |
562 |
os->granule = -1LL;
|
563 |
} else
|
564 |
av_log(s, AV_LOG_WARNING, "Packet is missing granule\n");
|
565 |
} |
566 |
|
567 |
pkt->flags = os->pflags; |
568 |
pkt->duration = os->pduration; |
569 |
pkt->pos = fpos; |
570 |
|
571 |
return psize;
|
572 |
} |
573 |
|
574 |
|
575 |
static int |
576 |
ogg_read_close (AVFormatContext * s) |
577 |
{ |
578 |
struct ogg *ogg = s->priv_data;
|
579 |
int i;
|
580 |
|
581 |
for (i = 0; i < ogg->nstreams; i++){ |
582 |
av_free (ogg->streams[i].buf); |
583 |
av_free (ogg->streams[i].private); |
584 |
} |
585 |
av_free (ogg->streams); |
586 |
return 0; |
587 |
} |
588 |
|
589 |
|
590 |
static int64_t
|
591 |
ogg_read_timestamp (AVFormatContext * s, int stream_index, int64_t * pos_arg,
|
592 |
int64_t pos_limit) |
593 |
{ |
594 |
struct ogg *ogg = s->priv_data;
|
595 |
ByteIOContext *bc = s->pb; |
596 |
int64_t pts = AV_NOPTS_VALUE; |
597 |
int i;
|
598 |
url_fseek(bc, *pos_arg, SEEK_SET); |
599 |
while (url_ftell(bc) < pos_limit && !ogg_read_page (s, &i)) {
|
600 |
if (ogg->streams[i].granule != -1 && ogg->streams[i].granule != 0 && |
601 |
ogg->streams[i].codec && i == stream_index) { |
602 |
pts = ogg_gptopts(s, i, ogg->streams[i].granule, NULL);
|
603 |
// FIXME: this is the position of the packet after the one with above
|
604 |
// pts.
|
605 |
*pos_arg = url_ftell(bc); |
606 |
break;
|
607 |
} |
608 |
} |
609 |
ogg_reset(ogg); |
610 |
return pts;
|
611 |
} |
612 |
|
613 |
static int ogg_probe(AVProbeData *p) |
614 |
{ |
615 |
if (p->buf[0] == 'O' && p->buf[1] == 'g' && |
616 |
p->buf[2] == 'g' && p->buf[3] == 'S' && |
617 |
p->buf[4] == 0x0 && p->buf[5] <= 0x7 ) |
618 |
return AVPROBE_SCORE_MAX;
|
619 |
else
|
620 |
return 0; |
621 |
} |
622 |
|
623 |
AVInputFormat ogg_demuxer = { |
624 |
"ogg",
|
625 |
NULL_IF_CONFIG_SMALL("Ogg"),
|
626 |
sizeof (struct ogg), |
627 |
ogg_probe, |
628 |
ogg_read_header, |
629 |
ogg_read_packet, |
630 |
ogg_read_close, |
631 |
NULL,
|
632 |
ogg_read_timestamp, |
633 |
.extensions = "ogg",
|
634 |
.metadata_conv = ff_vorbiscomment_metadata_conv, |
635 |
}; |