Commit | Line | Data |
---|---|---|
2ba45a60 DM |
1 | /* |
2 | * MP3 muxer | |
3 | * Copyright (c) 2003 Fabrice Bellard | |
4 | * | |
5 | * This file is part of FFmpeg. | |
6 | * | |
7 | * FFmpeg is free software; you can redistribute it and/or | |
8 | * modify it under the terms of the GNU Lesser General Public | |
9 | * License as published by the Free Software Foundation; either | |
10 | * version 2.1 of the License, or (at your option) any later version. | |
11 | * | |
12 | * FFmpeg is distributed in the hope that it will be useful, | |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
15 | * Lesser General Public License for more details. | |
16 | * | |
17 | * You should have received a copy of the GNU Lesser General Public | |
18 | * License along with FFmpeg; if not, write to the Free Software | |
19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
20 | */ | |
21 | ||
22 | #include "avformat.h" | |
23 | #include "avio_internal.h" | |
24 | #include "id3v1.h" | |
25 | #include "id3v2.h" | |
26 | #include "rawenc.h" | |
27 | #include "libavutil/avstring.h" | |
28 | #include "libavcodec/mpegaudio.h" | |
29 | #include "libavcodec/mpegaudiodata.h" | |
30 | #include "libavcodec/mpegaudiodecheader.h" | |
31 | #include "libavutil/intreadwrite.h" | |
32 | #include "libavutil/opt.h" | |
33 | #include "libavutil/dict.h" | |
34 | #include "libavutil/avassert.h" | |
35 | ||
36 | static int id3v1_set_string(AVFormatContext *s, const char *key, | |
37 | uint8_t *buf, int buf_size) | |
38 | { | |
39 | AVDictionaryEntry *tag; | |
40 | if ((tag = av_dict_get(s->metadata, key, NULL, 0))) | |
41 | av_strlcpy(buf, tag->value, buf_size); | |
42 | return !!tag; | |
43 | } | |
44 | ||
45 | static int id3v1_create_tag(AVFormatContext *s, uint8_t *buf) | |
46 | { | |
47 | AVDictionaryEntry *tag; | |
48 | int i, count = 0; | |
49 | ||
50 | memset(buf, 0, ID3v1_TAG_SIZE); /* fail safe */ | |
51 | buf[0] = 'T'; | |
52 | buf[1] = 'A'; | |
53 | buf[2] = 'G'; | |
54 | /* we knowingly overspecify each tag length by one byte to compensate for the mandatory null byte added by av_strlcpy */ | |
55 | count += id3v1_set_string(s, "TIT2", buf + 3, 30 + 1); //title | |
56 | count += id3v1_set_string(s, "TPE1", buf + 33, 30 + 1); //author|artist | |
57 | count += id3v1_set_string(s, "TALB", buf + 63, 30 + 1); //album | |
58 | count += id3v1_set_string(s, "TDRL", buf + 93, 4 + 1); //date | |
59 | count += id3v1_set_string(s, "comment", buf + 97, 30 + 1); | |
60 | if ((tag = av_dict_get(s->metadata, "TRCK", NULL, 0))) { //track | |
61 | buf[125] = 0; | |
62 | buf[126] = atoi(tag->value); | |
63 | count++; | |
64 | } | |
65 | buf[127] = 0xFF; /* default to unknown genre */ | |
66 | if ((tag = av_dict_get(s->metadata, "TCON", NULL, 0))) { //genre | |
67 | for(i = 0; i <= ID3v1_GENRE_MAX; i++) { | |
68 | if (!av_strcasecmp(tag->value, ff_id3v1_genre_str[i])) { | |
69 | buf[127] = i; | |
70 | count++; | |
71 | break; | |
72 | } | |
73 | } | |
74 | } | |
75 | return count; | |
76 | } | |
77 | ||
78 | #define XING_NUM_BAGS 400 | |
79 | #define XING_TOC_SIZE 100 | |
80 | // maximum size of the xing frame: offset/Xing/flags/frames/size/TOC | |
81 | #define XING_MAX_SIZE (32 + 4 + 4 + 4 + 4 + XING_TOC_SIZE) | |
82 | ||
83 | typedef struct MP3Context { | |
84 | const AVClass *class; | |
85 | ID3v2EncContext id3; | |
86 | int id3v2_version; | |
87 | int write_id3v1; | |
88 | int write_xing; | |
89 | ||
90 | /* xing header */ | |
91 | int64_t xing_offset; | |
92 | int32_t frames; | |
93 | int32_t size; | |
94 | uint32_t want; | |
95 | uint32_t seen; | |
96 | uint32_t pos; | |
97 | uint64_t bag[XING_NUM_BAGS]; | |
98 | int initial_bitrate; | |
99 | int has_variable_bitrate; | |
100 | ||
101 | /* index of the audio stream */ | |
102 | int audio_stream_idx; | |
103 | /* number of attached pictures we still need to write */ | |
104 | int pics_to_write; | |
105 | ||
106 | /* audio packets are queued here until we get all the attached pictures */ | |
107 | AVPacketList *queue, *queue_end; | |
108 | } MP3Context; | |
109 | ||
110 | static const uint8_t xing_offtbl[2][2] = {{32, 17}, {17, 9}}; | |
111 | ||
112 | /* | |
113 | * Write an empty XING header and initialize respective data. | |
114 | */ | |
115 | static int mp3_write_xing(AVFormatContext *s) | |
116 | { | |
117 | MP3Context *mp3 = s->priv_data; | |
118 | AVCodecContext *codec = s->streams[mp3->audio_stream_idx]->codec; | |
119 | int32_t header; | |
120 | MPADecodeHeader mpah; | |
121 | int srate_idx, i, channels; | |
122 | int bitrate_idx; | |
123 | int best_bitrate_idx = -1; | |
124 | int best_bitrate_error = INT_MAX; | |
125 | int xing_offset; | |
126 | int ver = 0; | |
127 | int bytes_needed; | |
128 | const char *vendor = (s->flags & AVFMT_FLAG_BITEXACT) ? "Lavf" : LIBAVFORMAT_IDENT; | |
129 | ||
130 | if (!s->pb->seekable || !mp3->write_xing) | |
131 | return 0; | |
132 | ||
133 | for (i = 0; i < FF_ARRAY_ELEMS(avpriv_mpa_freq_tab); i++) { | |
134 | const uint16_t base_freq = avpriv_mpa_freq_tab[i]; | |
135 | ||
136 | if (codec->sample_rate == base_freq) ver = 0x3; // MPEG 1 | |
137 | else if (codec->sample_rate == base_freq / 2) ver = 0x2; // MPEG 2 | |
138 | else if (codec->sample_rate == base_freq / 4) ver = 0x0; // MPEG 2.5 | |
139 | else continue; | |
140 | ||
141 | srate_idx = i; | |
142 | break; | |
143 | } | |
144 | if (i == FF_ARRAY_ELEMS(avpriv_mpa_freq_tab)) { | |
145 | av_log(s, AV_LOG_WARNING, "Unsupported sample rate, not writing Xing header.\n"); | |
146 | return -1; | |
147 | } | |
148 | ||
149 | switch (codec->channels) { | |
150 | case 1: channels = MPA_MONO; break; | |
151 | case 2: channels = MPA_STEREO; break; | |
152 | default: av_log(s, AV_LOG_WARNING, "Unsupported number of channels, " | |
153 | "not writing Xing header.\n"); | |
154 | return -1; | |
155 | } | |
156 | ||
157 | /* dummy MPEG audio header */ | |
158 | header = 0xffU << 24; // sync | |
159 | header |= (0x7 << 5 | ver << 3 | 0x1 << 1 | 0x1) << 16; // sync/audio-version/layer 3/no crc*/ | |
160 | header |= (srate_idx << 2) << 8; | |
161 | header |= channels << 6; | |
162 | ||
163 | for (bitrate_idx = 1; bitrate_idx < 15; bitrate_idx++) { | |
164 | int bit_rate = 1000 * avpriv_mpa_bitrate_tab[ver != 3][3 - 1][bitrate_idx]; | |
165 | int error = FFABS(bit_rate - codec->bit_rate); | |
166 | ||
167 | if (error < best_bitrate_error) { | |
168 | best_bitrate_error = error; | |
169 | best_bitrate_idx = bitrate_idx; | |
170 | } | |
171 | } | |
172 | av_assert0(best_bitrate_idx >= 0); | |
173 | ||
174 | for (bitrate_idx = best_bitrate_idx; ; bitrate_idx++) { | |
175 | int32_t mask = bitrate_idx << (4 + 8); | |
176 | if (15 == bitrate_idx) | |
177 | return -1; | |
178 | header |= mask; | |
179 | ||
180 | avpriv_mpegaudio_decode_header(&mpah, header); | |
181 | xing_offset=xing_offtbl[mpah.lsf == 1][mpah.nb_channels == 1]; | |
182 | bytes_needed = 4 // header | |
183 | + xing_offset | |
184 | + 4 // xing tag | |
185 | + 4 // frames/size/toc flags | |
186 | + 4 // frames | |
187 | + 4 // size | |
188 | + XING_TOC_SIZE // toc | |
189 | + 24 | |
190 | ; | |
191 | ||
192 | if (bytes_needed <= mpah.frame_size) | |
193 | break; | |
194 | ||
195 | header &= ~mask; | |
196 | } | |
197 | ||
198 | avio_wb32(s->pb, header); | |
199 | ||
200 | ffio_fill(s->pb, 0, xing_offset); | |
201 | mp3->xing_offset = avio_tell(s->pb); | |
202 | ffio_wfourcc(s->pb, "Xing"); | |
203 | avio_wb32(s->pb, 0x01 | 0x02 | 0x04); // frames / size / TOC | |
204 | ||
205 | mp3->size = mpah.frame_size; | |
206 | mp3->want=1; | |
207 | mp3->seen=0; | |
208 | mp3->pos=0; | |
209 | ||
210 | avio_wb32(s->pb, 0); // frames | |
211 | avio_wb32(s->pb, 0); // size | |
212 | ||
213 | // toc | |
214 | for (i = 0; i < XING_TOC_SIZE; ++i) | |
215 | avio_w8(s->pb, (uint8_t)(255 * i / XING_TOC_SIZE)); | |
216 | ||
217 | for (i = 0; i < strlen(vendor); ++i) | |
218 | avio_w8(s->pb, vendor[i]); | |
219 | for (; i < 21; ++i) | |
220 | avio_w8(s->pb, 0); | |
221 | avio_wb24(s->pb, FFMAX(codec->delay - 528 - 1, 0)<<12); | |
222 | ||
223 | ffio_fill(s->pb, 0, mpah.frame_size - bytes_needed); | |
224 | ||
225 | return 0; | |
226 | } | |
227 | ||
228 | /* | |
229 | * Add a frame to XING data. | |
230 | * Following lame's "VbrTag.c". | |
231 | */ | |
232 | static void mp3_xing_add_frame(MP3Context *mp3, AVPacket *pkt) | |
233 | { | |
234 | int i; | |
235 | ||
236 | mp3->frames++; | |
237 | mp3->seen++; | |
238 | mp3->size += pkt->size; | |
239 | ||
240 | if (mp3->want == mp3->seen) { | |
241 | mp3->bag[mp3->pos] = mp3->size; | |
242 | ||
243 | if (XING_NUM_BAGS == ++mp3->pos) { | |
244 | /* shrink table to half size by throwing away each second bag. */ | |
245 | for (i = 1; i < XING_NUM_BAGS; i += 2) | |
246 | mp3->bag[i >> 1] = mp3->bag[i]; | |
247 | ||
248 | /* double wanted amount per bag. */ | |
249 | mp3->want *= 2; | |
250 | /* adjust current position to half of table size. */ | |
251 | mp3->pos = XING_NUM_BAGS / 2; | |
252 | } | |
253 | ||
254 | mp3->seen = 0; | |
255 | } | |
256 | } | |
257 | ||
258 | static int mp3_write_audio_packet(AVFormatContext *s, AVPacket *pkt) | |
259 | { | |
260 | MP3Context *mp3 = s->priv_data; | |
261 | ||
262 | if (pkt->data && pkt->size >= 4) { | |
263 | MPADecodeHeader mpah; | |
264 | int av_unused base; | |
265 | uint32_t h; | |
266 | ||
267 | h = AV_RB32(pkt->data); | |
268 | if (ff_mpa_check_header(h) == 0) { | |
269 | avpriv_mpegaudio_decode_header(&mpah, h); | |
270 | if (!mp3->initial_bitrate) | |
271 | mp3->initial_bitrate = mpah.bit_rate; | |
272 | if ((mpah.bit_rate == 0) || (mp3->initial_bitrate != mpah.bit_rate)) | |
273 | mp3->has_variable_bitrate = 1; | |
274 | } else { | |
275 | av_log(s, AV_LOG_WARNING, "Audio packet of size %d (starting with %08X...) " | |
276 | "is invalid, writing it anyway.\n", pkt->size, h); | |
277 | } | |
278 | ||
279 | #ifdef FILTER_VBR_HEADERS | |
280 | /* filter out XING and INFO headers. */ | |
281 | base = 4 + xing_offtbl[mpah.lsf == 1][mpah.nb_channels == 1]; | |
282 | ||
283 | if (base + 4 <= pkt->size) { | |
284 | uint32_t v = AV_RB32(pkt->data + base); | |
285 | ||
286 | if (MKBETAG('X','i','n','g') == v || MKBETAG('I','n','f','o') == v) | |
287 | return 0; | |
288 | } | |
289 | ||
290 | /* filter out VBRI headers. */ | |
291 | base = 4 + 32; | |
292 | ||
293 | if (base + 4 <= pkt->size && MKBETAG('V','B','R','I') == AV_RB32(pkt->data + base)) | |
294 | return 0; | |
295 | #endif | |
296 | ||
297 | if (mp3->xing_offset) | |
298 | mp3_xing_add_frame(mp3, pkt); | |
299 | } | |
300 | ||
301 | return ff_raw_write_packet(s, pkt); | |
302 | } | |
303 | ||
304 | static int mp3_queue_flush(AVFormatContext *s) | |
305 | { | |
306 | MP3Context *mp3 = s->priv_data; | |
307 | AVPacketList *pktl; | |
308 | int ret = 0, write = 1; | |
309 | ||
310 | ff_id3v2_finish(&mp3->id3, s->pb, s->metadata_header_padding); | |
311 | mp3_write_xing(s); | |
312 | ||
313 | while ((pktl = mp3->queue)) { | |
314 | if (write && (ret = mp3_write_audio_packet(s, &pktl->pkt)) < 0) | |
315 | write = 0; | |
316 | av_free_packet(&pktl->pkt); | |
317 | mp3->queue = pktl->next; | |
318 | av_freep(&pktl); | |
319 | } | |
320 | mp3->queue_end = NULL; | |
321 | return ret; | |
322 | } | |
323 | ||
324 | static void mp3_update_xing(AVFormatContext *s) | |
325 | { | |
326 | MP3Context *mp3 = s->priv_data; | |
327 | int i; | |
328 | ||
329 | /* replace "Xing" identification string with "Info" for CBR files. */ | |
330 | if (!mp3->has_variable_bitrate) { | |
331 | avio_seek(s->pb, mp3->xing_offset, SEEK_SET); | |
332 | ffio_wfourcc(s->pb, "Info"); | |
333 | } | |
334 | ||
335 | avio_seek(s->pb, mp3->xing_offset + 8, SEEK_SET); | |
336 | avio_wb32(s->pb, mp3->frames); | |
337 | avio_wb32(s->pb, mp3->size); | |
338 | ||
339 | avio_w8(s->pb, 0); // first toc entry has to be zero. | |
340 | ||
341 | for (i = 1; i < XING_TOC_SIZE; ++i) { | |
342 | int j = i * mp3->pos / XING_TOC_SIZE; | |
343 | int seek_point = 256LL * mp3->bag[j] / mp3->size; | |
344 | avio_w8(s->pb, FFMIN(seek_point, 255)); | |
345 | } | |
346 | ||
347 | avio_seek(s->pb, 0, SEEK_END); | |
348 | } | |
349 | ||
350 | static int mp3_write_trailer(struct AVFormatContext *s) | |
351 | { | |
352 | uint8_t buf[ID3v1_TAG_SIZE]; | |
353 | MP3Context *mp3 = s->priv_data; | |
354 | ||
355 | if (mp3->pics_to_write) { | |
356 | av_log(s, AV_LOG_WARNING, "No packets were sent for some of the " | |
357 | "attached pictures.\n"); | |
358 | mp3_queue_flush(s); | |
359 | } | |
360 | ||
361 | /* write the id3v1 tag */ | |
362 | if (mp3->write_id3v1 && id3v1_create_tag(s, buf) > 0) { | |
363 | avio_write(s->pb, buf, ID3v1_TAG_SIZE); | |
364 | } | |
365 | ||
366 | if (mp3->xing_offset) | |
367 | mp3_update_xing(s); | |
368 | ||
369 | return 0; | |
370 | } | |
371 | ||
372 | static int query_codec(enum AVCodecID id, int std_compliance) | |
373 | { | |
374 | const CodecMime *cm= ff_id3v2_mime_tags; | |
375 | while(cm->id != AV_CODEC_ID_NONE) { | |
376 | if(id == cm->id) | |
377 | return MKTAG('A', 'P', 'I', 'C'); | |
378 | cm++; | |
379 | } | |
380 | return -1; | |
381 | } | |
382 | ||
383 | #if CONFIG_MP2_MUXER | |
384 | AVOutputFormat ff_mp2_muxer = { | |
385 | .name = "mp2", | |
386 | .long_name = NULL_IF_CONFIG_SMALL("MP2 (MPEG audio layer 2)"), | |
387 | .mime_type = "audio/mpeg", | |
388 | .extensions = "mp2,m2a,mpa", | |
389 | .audio_codec = AV_CODEC_ID_MP2, | |
390 | .video_codec = AV_CODEC_ID_NONE, | |
391 | .write_packet = ff_raw_write_packet, | |
392 | .flags = AVFMT_NOTIMESTAMPS, | |
393 | }; | |
394 | #endif | |
395 | ||
396 | #if CONFIG_MP3_MUXER | |
397 | ||
398 | static const AVOption options[] = { | |
399 | { "id3v2_version", "Select ID3v2 version to write. Currently 3 and 4 are supported.", | |
400 | offsetof(MP3Context, id3v2_version), AV_OPT_TYPE_INT, {.i64 = 4}, 0, 4, AV_OPT_FLAG_ENCODING_PARAM}, | |
401 | { "write_id3v1", "Enable ID3v1 writing. ID3v1 tags are written in UTF-8 which may not be supported by most software.", | |
402 | offsetof(MP3Context, write_id3v1), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM}, | |
403 | { "write_xing", "Write the Xing header containing file duration.", | |
404 | offsetof(MP3Context, write_xing), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM}, | |
405 | { NULL }, | |
406 | }; | |
407 | ||
408 | static const AVClass mp3_muxer_class = { | |
409 | .class_name = "MP3 muxer", | |
410 | .item_name = av_default_item_name, | |
411 | .option = options, | |
412 | .version = LIBAVUTIL_VERSION_INT, | |
413 | }; | |
414 | ||
415 | static int mp3_write_packet(AVFormatContext *s, AVPacket *pkt) | |
416 | { | |
417 | MP3Context *mp3 = s->priv_data; | |
418 | ||
419 | if (pkt->stream_index == mp3->audio_stream_idx) { | |
420 | if (mp3->pics_to_write) { | |
421 | /* buffer audio packets until we get all the pictures */ | |
422 | AVPacketList *pktl = av_mallocz(sizeof(*pktl)); | |
423 | int ret; | |
424 | if (!pktl) { | |
425 | av_log(s, AV_LOG_WARNING, "Not enough memory to buffer audio. Skipping picture streams\n"); | |
426 | mp3->pics_to_write = 0; | |
427 | mp3_queue_flush(s); | |
428 | return mp3_write_audio_packet(s, pkt); | |
429 | } | |
430 | ||
431 | ret = av_copy_packet(&pktl->pkt, pkt); | |
432 | if (ret < 0) { | |
433 | av_freep(&pktl); | |
434 | return ret; | |
435 | } | |
436 | ||
437 | if (mp3->queue_end) | |
438 | mp3->queue_end->next = pktl; | |
439 | else | |
440 | mp3->queue = pktl; | |
441 | mp3->queue_end = pktl; | |
442 | } else | |
443 | return mp3_write_audio_packet(s, pkt); | |
444 | } else { | |
445 | int ret; | |
446 | ||
447 | /* warn only once for each stream */ | |
448 | if (s->streams[pkt->stream_index]->nb_frames == 1) { | |
449 | av_log(s, AV_LOG_WARNING, "Got more than one picture in stream %d," | |
450 | " ignoring.\n", pkt->stream_index); | |
451 | } | |
452 | if (!mp3->pics_to_write || s->streams[pkt->stream_index]->nb_frames >= 1) | |
453 | return 0; | |
454 | ||
455 | if ((ret = ff_id3v2_write_apic(s, &mp3->id3, pkt)) < 0) | |
456 | return ret; | |
457 | mp3->pics_to_write--; | |
458 | ||
459 | /* flush the buffered audio packets */ | |
460 | if (!mp3->pics_to_write && | |
461 | (ret = mp3_queue_flush(s)) < 0) | |
462 | return ret; | |
463 | } | |
464 | ||
465 | return 0; | |
466 | } | |
467 | ||
468 | /** | |
469 | * Write an ID3v2 header at beginning of stream | |
470 | */ | |
471 | ||
472 | static int mp3_write_header(struct AVFormatContext *s) | |
473 | { | |
474 | MP3Context *mp3 = s->priv_data; | |
475 | int ret, i; | |
476 | ||
477 | if (mp3->id3v2_version && | |
478 | mp3->id3v2_version != 3 && | |
479 | mp3->id3v2_version != 4) { | |
480 | av_log(s, AV_LOG_ERROR, "Invalid ID3v2 version requested: %d. Only " | |
481 | "3, 4 or 0 (disabled) are allowed.\n", mp3->id3v2_version); | |
482 | return AVERROR(EINVAL); | |
483 | } | |
484 | ||
485 | /* check the streams -- we want exactly one audio and arbitrary number of | |
486 | * video (attached pictures) */ | |
487 | mp3->audio_stream_idx = -1; | |
488 | for (i = 0; i < s->nb_streams; i++) { | |
489 | AVStream *st = s->streams[i]; | |
490 | if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO) { | |
491 | if (mp3->audio_stream_idx >= 0 || st->codec->codec_id != AV_CODEC_ID_MP3) { | |
492 | av_log(s, AV_LOG_ERROR, "Invalid audio stream. Exactly one MP3 " | |
493 | "audio stream is required.\n"); | |
494 | return AVERROR(EINVAL); | |
495 | } | |
496 | mp3->audio_stream_idx = i; | |
497 | } else if (st->codec->codec_type != AVMEDIA_TYPE_VIDEO) { | |
498 | av_log(s, AV_LOG_ERROR, "Only audio streams and pictures are allowed in MP3.\n"); | |
499 | return AVERROR(EINVAL); | |
500 | } | |
501 | } | |
502 | if (mp3->audio_stream_idx < 0) { | |
503 | av_log(s, AV_LOG_ERROR, "No audio stream present.\n"); | |
504 | return AVERROR(EINVAL); | |
505 | } | |
506 | mp3->pics_to_write = s->nb_streams - 1; | |
507 | ||
508 | if (mp3->pics_to_write && !mp3->id3v2_version) { | |
509 | av_log(s, AV_LOG_ERROR, "Attached pictures were requested, but the " | |
510 | "ID3v2 header is disabled.\n"); | |
511 | return AVERROR(EINVAL); | |
512 | } | |
513 | ||
514 | if (mp3->id3v2_version) { | |
515 | ff_id3v2_start(&mp3->id3, s->pb, mp3->id3v2_version, ID3v2_DEFAULT_MAGIC); | |
516 | ret = ff_id3v2_write_metadata(s, &mp3->id3); | |
517 | if (ret < 0) | |
518 | return ret; | |
519 | } | |
520 | ||
521 | if (!mp3->pics_to_write) { | |
522 | if (mp3->id3v2_version) | |
523 | ff_id3v2_finish(&mp3->id3, s->pb, s->metadata_header_padding); | |
524 | mp3_write_xing(s); | |
525 | } | |
526 | ||
527 | return 0; | |
528 | } | |
529 | ||
530 | AVOutputFormat ff_mp3_muxer = { | |
531 | .name = "mp3", | |
532 | .long_name = NULL_IF_CONFIG_SMALL("MP3 (MPEG audio layer 3)"), | |
533 | .mime_type = "audio/mpeg", | |
534 | .extensions = "mp3", | |
535 | .priv_data_size = sizeof(MP3Context), | |
536 | .audio_codec = AV_CODEC_ID_MP3, | |
537 | .video_codec = AV_CODEC_ID_PNG, | |
538 | .write_header = mp3_write_header, | |
539 | .write_packet = mp3_write_packet, | |
540 | .write_trailer = mp3_write_trailer, | |
541 | .query_codec = query_codec, | |
542 | .flags = AVFMT_NOTIMESTAMPS, | |
543 | .priv_class = &mp3_muxer_class, | |
544 | }; | |
545 | #endif |