Go to most recent revision | Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
4349 | Serge | 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 | |||
89 | /* xing header */ |
||
90 | int64_t xing_offset; |
||
91 | int32_t frames; |
||
92 | int32_t size; |
||
93 | uint32_t want; |
||
94 | uint32_t seen; |
||
95 | uint32_t pos; |
||
96 | uint64_t bag[XING_NUM_BAGS]; |
||
97 | int initial_bitrate; |
||
98 | int has_variable_bitrate; |
||
99 | |||
100 | /* index of the audio stream */ |
||
101 | int audio_stream_idx; |
||
102 | /* number of attached pictures we still need to write */ |
||
103 | int pics_to_write; |
||
104 | |||
105 | /* audio packets are queued here until we get all the attached pictures */ |
||
106 | AVPacketList *queue, *queue_end; |
||
107 | } MP3Context; |
||
108 | |||
109 | static const uint8_t xing_offtbl[2][2] = {{32, 17}, {17, 9}}; |
||
110 | |||
111 | /* |
||
112 | * Write an empty XING header and initialize respective data. |
||
113 | */ |
||
114 | static int mp3_write_xing(AVFormatContext *s) |
||
115 | { |
||
116 | MP3Context *mp3 = s->priv_data; |
||
117 | AVCodecContext *codec = s->streams[mp3->audio_stream_idx]->codec; |
||
118 | int bitrate_idx; |
||
119 | int best_bitrate_idx = -1; |
||
120 | int best_bitrate_error= INT_MAX; |
||
121 | int xing_offset; |
||
122 | int32_t header, mask; |
||
123 | MPADecodeHeader c; |
||
124 | int srate_idx, ver = 0, i, channels; |
||
125 | int needed; |
||
126 | const char *vendor = (codec->flags & CODEC_FLAG_BITEXACT) ? "Lavf" : LIBAVFORMAT_IDENT; |
||
127 | |||
128 | if (!s->pb->seekable) |
||
129 | return 0; |
||
130 | |||
131 | for (i = 0; i < FF_ARRAY_ELEMS(avpriv_mpa_freq_tab); i++) { |
||
132 | const uint16_t base_freq = avpriv_mpa_freq_tab[i]; |
||
133 | |||
134 | if (codec->sample_rate == base_freq) ver = 0x3; // MPEG 1 |
||
135 | else if (codec->sample_rate == base_freq / 2) ver = 0x2; // MPEG 2 |
||
136 | else if (codec->sample_rate == base_freq / 4) ver = 0x0; // MPEG 2.5 |
||
137 | else continue; |
||
138 | |||
139 | srate_idx = i; |
||
140 | break; |
||
141 | } |
||
142 | if (i == FF_ARRAY_ELEMS(avpriv_mpa_freq_tab)) { |
||
143 | av_log(s, AV_LOG_WARNING, "Unsupported sample rate, not writing Xing header.\n"); |
||
144 | return -1; |
||
145 | } |
||
146 | |||
147 | switch (codec->channels) { |
||
148 | case 1: channels = MPA_MONO; break; |
||
149 | case 2: channels = MPA_STEREO; break; |
||
150 | default: av_log(s, AV_LOG_WARNING, "Unsupported number of channels, " |
||
151 | "not writing Xing header.\n"); |
||
152 | return -1; |
||
153 | } |
||
154 | |||
155 | /* dummy MPEG audio header */ |
||
156 | header = 0xffU << 24; // sync |
||
157 | header |= (0x7 << 5 | ver << 3 | 0x1 << 1 | 0x1) << 16; // sync/audio-version/layer 3/no crc*/ |
||
158 | header |= (srate_idx << 2) << 8; |
||
159 | header |= channels << 6; |
||
160 | |||
161 | for (bitrate_idx=1; bitrate_idx<15; bitrate_idx++) { |
||
162 | int error; |
||
163 | avpriv_mpegaudio_decode_header(&c, header | (bitrate_idx << (4+8))); |
||
164 | error= FFABS(c.bit_rate - codec->bit_rate); |
||
165 | if(error < best_bitrate_error){ |
||
166 | best_bitrate_error= error; |
||
167 | best_bitrate_idx = bitrate_idx; |
||
168 | } |
||
169 | } |
||
170 | av_assert0(best_bitrate_idx >= 0); |
||
171 | |||
172 | for (bitrate_idx= best_bitrate_idx;; bitrate_idx++) { |
||
173 | if (15 == bitrate_idx) |
||
174 | return -1; |
||
175 | mask = bitrate_idx << (4+8); |
||
176 | header |= mask; |
||
177 | avpriv_mpegaudio_decode_header(&c, header); |
||
178 | xing_offset=xing_offtbl[c.lsf == 1][c.nb_channels == 1]; |
||
179 | needed = 4 // header |
||
180 | + xing_offset |
||
181 | + 4 // xing tag |
||
182 | + 4 // frames/size/toc flags |
||
183 | + 4 // frames |
||
184 | + 4 // size |
||
185 | + XING_TOC_SIZE // toc |
||
186 | + 24 |
||
187 | ; |
||
188 | |||
189 | if (needed <= c.frame_size) |
||
190 | break; |
||
191 | header &= ~mask; |
||
192 | } |
||
193 | |||
194 | avio_wb32(s->pb, header); |
||
195 | |||
196 | ffio_fill(s->pb, 0, xing_offset); |
||
197 | mp3->xing_offset = avio_tell(s->pb); |
||
198 | ffio_wfourcc(s->pb, "Xing"); |
||
199 | avio_wb32(s->pb, 0x01 | 0x02 | 0x04); // frames / size / TOC |
||
200 | |||
201 | mp3->size = c.frame_size; |
||
202 | mp3->want=1; |
||
203 | mp3->seen=0; |
||
204 | mp3->pos=0; |
||
205 | |||
206 | avio_wb32(s->pb, 0); // frames |
||
207 | avio_wb32(s->pb, 0); // size |
||
208 | |||
209 | // toc |
||
210 | for (i = 0; i < XING_TOC_SIZE; ++i) |
||
211 | avio_w8(s->pb, (uint8_t)(255 * i / XING_TOC_SIZE)); |
||
212 | |||
213 | for (i = 0; i < strlen(vendor); ++i) |
||
214 | avio_w8(s->pb, vendor[i]); |
||
215 | for (; i < 21; ++i) |
||
216 | avio_w8(s->pb, 0); |
||
217 | avio_wb24(s->pb, FFMAX(codec->delay - 528 - 1, 0)<<12); |
||
218 | |||
219 | ffio_fill(s->pb, 0, c.frame_size - needed); |
||
220 | |||
221 | return 0; |
||
222 | } |
||
223 | |||
224 | /* |
||
225 | * Add a frame to XING data. |
||
226 | * Following lame's "VbrTag.c". |
||
227 | */ |
||
228 | static void mp3_xing_add_frame(MP3Context *mp3, AVPacket *pkt) |
||
229 | { |
||
230 | int i; |
||
231 | |||
232 | mp3->frames++; |
||
233 | mp3->seen++; |
||
234 | mp3->size += pkt->size; |
||
235 | |||
236 | if (mp3->want == mp3->seen) { |
||
237 | mp3->bag[mp3->pos] = mp3->size; |
||
238 | |||
239 | if (XING_NUM_BAGS == ++mp3->pos) { |
||
240 | /* shrink table to half size by throwing away each second bag. */ |
||
241 | for (i = 1; i < XING_NUM_BAGS; i += 2) |
||
242 | mp3->bag[i >> 1] = mp3->bag[i]; |
||
243 | |||
244 | /* double wanted amount per bag. */ |
||
245 | mp3->want *= 2; |
||
246 | /* adjust current position to half of table size. */ |
||
247 | mp3->pos = XING_NUM_BAGS / 2; |
||
248 | } |
||
249 | |||
250 | mp3->seen = 0; |
||
251 | } |
||
252 | } |
||
253 | |||
254 | static int mp3_write_audio_packet(AVFormatContext *s, AVPacket *pkt) |
||
255 | { |
||
256 | MP3Context *mp3 = s->priv_data; |
||
257 | |||
258 | if (pkt->data && pkt->size >= 4) { |
||
259 | MPADecodeHeader c; |
||
260 | int av_unused base; |
||
261 | uint32_t head = AV_RB32(pkt->data); |
||
262 | |||
263 | if (ff_mpa_check_header(head) < 0) { |
||
264 | av_log(s, AV_LOG_WARNING, "Audio packet of size %d (starting with %08X...) " |
||
265 | "is invalid, writing it anyway.\n", pkt->size, head); |
||
266 | return ff_raw_write_packet(s, pkt); |
||
267 | } |
||
268 | avpriv_mpegaudio_decode_header(&c, head); |
||
269 | |||
270 | if (!mp3->initial_bitrate) |
||
271 | mp3->initial_bitrate = c.bit_rate; |
||
272 | if ((c.bit_rate == 0) || (mp3->initial_bitrate != c.bit_rate)) |
||
273 | mp3->has_variable_bitrate = 1; |
||
274 | |||
275 | #ifdef FILTER_VBR_HEADERS |
||
276 | /* filter out XING and INFO headers. */ |
||
277 | base = 4 + xing_offtbl[c.lsf == 1][c.nb_channels == 1]; |
||
278 | |||
279 | if (base + 4 <= pkt->size) { |
||
280 | uint32_t v = AV_RB32(pkt->data + base); |
||
281 | |||
282 | if (MKBETAG('X','i','n','g') == v || MKBETAG('I','n','f','o') == v) |
||
283 | return 0; |
||
284 | } |
||
285 | |||
286 | /* filter out VBRI headers. */ |
||
287 | base = 4 + 32; |
||
288 | |||
289 | if (base + 4 <= pkt->size && MKBETAG('V','B','R','I') == AV_RB32(pkt->data + base)) |
||
290 | return 0; |
||
291 | #endif |
||
292 | |||
293 | if (mp3->xing_offset) |
||
294 | mp3_xing_add_frame(mp3, pkt); |
||
295 | } |
||
296 | |||
297 | return ff_raw_write_packet(s, pkt); |
||
298 | } |
||
299 | |||
300 | static int mp3_queue_flush(AVFormatContext *s) |
||
301 | { |
||
302 | MP3Context *mp3 = s->priv_data; |
||
303 | AVPacketList *pktl; |
||
304 | int ret = 0, write = 1; |
||
305 | |||
306 | ff_id3v2_finish(&mp3->id3, s->pb); |
||
307 | mp3_write_xing(s); |
||
308 | |||
309 | while ((pktl = mp3->queue)) { |
||
310 | if (write && (ret = mp3_write_audio_packet(s, &pktl->pkt)) < 0) |
||
311 | write = 0; |
||
312 | av_free_packet(&pktl->pkt); |
||
313 | mp3->queue = pktl->next; |
||
314 | av_freep(&pktl); |
||
315 | } |
||
316 | mp3->queue_end = NULL; |
||
317 | return ret; |
||
318 | } |
||
319 | |||
320 | static void mp3_update_xing(AVFormatContext *s) |
||
321 | { |
||
322 | MP3Context *mp3 = s->priv_data; |
||
323 | int i; |
||
324 | |||
325 | /* replace "Xing" identification string with "Info" for CBR files. */ |
||
326 | if (!mp3->has_variable_bitrate) { |
||
327 | avio_seek(s->pb, mp3->xing_offset, SEEK_SET); |
||
328 | ffio_wfourcc(s->pb, "Info"); |
||
329 | } |
||
330 | |||
331 | avio_seek(s->pb, mp3->xing_offset + 8, SEEK_SET); |
||
332 | avio_wb32(s->pb, mp3->frames); |
||
333 | avio_wb32(s->pb, mp3->size); |
||
334 | |||
335 | avio_w8(s->pb, 0); // first toc entry has to be zero. |
||
336 | |||
337 | for (i = 1; i < XING_TOC_SIZE; ++i) { |
||
338 | int j = i * mp3->pos / XING_TOC_SIZE; |
||
339 | int seek_point = 256LL * mp3->bag[j] / mp3->size; |
||
340 | avio_w8(s->pb, FFMIN(seek_point, 255)); |
||
341 | } |
||
342 | |||
343 | avio_seek(s->pb, 0, SEEK_END); |
||
344 | } |
||
345 | |||
346 | static int mp3_write_trailer(struct AVFormatContext *s) |
||
347 | { |
||
348 | uint8_t buf[ID3v1_TAG_SIZE]; |
||
349 | MP3Context *mp3 = s->priv_data; |
||
350 | |||
351 | if (mp3->pics_to_write) { |
||
352 | av_log(s, AV_LOG_WARNING, "No packets were sent for some of the " |
||
353 | "attached pictures.\n"); |
||
354 | mp3_queue_flush(s); |
||
355 | } |
||
356 | |||
357 | /* write the id3v1 tag */ |
||
358 | if (mp3->write_id3v1 && id3v1_create_tag(s, buf) > 0) { |
||
359 | avio_write(s->pb, buf, ID3v1_TAG_SIZE); |
||
360 | } |
||
361 | |||
362 | if (mp3->xing_offset) |
||
363 | mp3_update_xing(s); |
||
364 | |||
365 | return 0; |
||
366 | } |
||
367 | |||
368 | static int query_codec(enum AVCodecID id, int std_compliance) |
||
369 | { |
||
370 | const CodecMime *cm= ff_id3v2_mime_tags; |
||
371 | while(cm->id != AV_CODEC_ID_NONE) { |
||
372 | if(id == cm->id) |
||
373 | return MKTAG('A', 'P', 'I', 'C'); |
||
374 | cm++; |
||
375 | } |
||
376 | return -1; |
||
377 | } |
||
378 | |||
379 | #if CONFIG_MP2_MUXER |
||
380 | AVOutputFormat ff_mp2_muxer = { |
||
381 | .name = "mp2", |
||
382 | .long_name = NULL_IF_CONFIG_SMALL("MP2 (MPEG audio layer 2)"), |
||
383 | .mime_type = "audio/x-mpeg", |
||
384 | .extensions = "mp2,m2a,mpa", |
||
385 | .audio_codec = AV_CODEC_ID_MP2, |
||
386 | .video_codec = AV_CODEC_ID_NONE, |
||
387 | .write_packet = ff_raw_write_packet, |
||
388 | .flags = AVFMT_NOTIMESTAMPS, |
||
389 | }; |
||
390 | #endif |
||
391 | |||
392 | #if CONFIG_MP3_MUXER |
||
393 | |||
394 | static const AVOption options[] = { |
||
395 | { "id3v2_version", "Select ID3v2 version to write. Currently 3 and 4 are supported.", |
||
396 | offsetof(MP3Context, id3v2_version), AV_OPT_TYPE_INT, {.i64 = 4}, 3, 4, AV_OPT_FLAG_ENCODING_PARAM}, |
||
397 | { "write_id3v1", "Enable ID3v1 writing. ID3v1 tags are written in UTF-8 which may not be supported by most software.", |
||
398 | offsetof(MP3Context, write_id3v1), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM}, |
||
399 | { NULL }, |
||
400 | }; |
||
401 | |||
402 | static const AVClass mp3_muxer_class = { |
||
403 | .class_name = "MP3 muxer", |
||
404 | .item_name = av_default_item_name, |
||
405 | .option = options, |
||
406 | .version = LIBAVUTIL_VERSION_INT, |
||
407 | }; |
||
408 | |||
409 | static int mp3_write_packet(AVFormatContext *s, AVPacket *pkt) |
||
410 | { |
||
411 | MP3Context *mp3 = s->priv_data; |
||
412 | |||
413 | if (pkt->stream_index == mp3->audio_stream_idx) { |
||
414 | if (mp3->pics_to_write) { |
||
415 | /* buffer audio packets until we get all the pictures */ |
||
416 | AVPacketList *pktl = av_mallocz(sizeof(*pktl)); |
||
417 | if (!pktl) |
||
418 | return AVERROR(ENOMEM); |
||
419 | |||
420 | pktl->pkt = *pkt; |
||
421 | pktl->pkt.buf = av_buffer_ref(pkt->buf); |
||
422 | if (!pktl->pkt.buf) { |
||
423 | av_freep(&pktl); |
||
424 | return AVERROR(ENOMEM); |
||
425 | } |
||
426 | |||
427 | if (mp3->queue_end) |
||
428 | mp3->queue_end->next = pktl; |
||
429 | else |
||
430 | mp3->queue = pktl; |
||
431 | mp3->queue_end = pktl; |
||
432 | } else |
||
433 | return mp3_write_audio_packet(s, pkt); |
||
434 | } else { |
||
435 | int ret; |
||
436 | |||
437 | /* warn only once for each stream */ |
||
438 | if (s->streams[pkt->stream_index]->nb_frames == 1) { |
||
439 | av_log(s, AV_LOG_WARNING, "Got more than one picture in stream %d," |
||
440 | " ignoring.\n", pkt->stream_index); |
||
441 | } |
||
442 | if (!mp3->pics_to_write || s->streams[pkt->stream_index]->nb_frames >= 1) |
||
443 | return 0; |
||
444 | |||
445 | if ((ret = ff_id3v2_write_apic(s, &mp3->id3, pkt)) < 0) |
||
446 | return ret; |
||
447 | mp3->pics_to_write--; |
||
448 | |||
449 | /* flush the buffered audio packets */ |
||
450 | if (!mp3->pics_to_write && |
||
451 | (ret = mp3_queue_flush(s)) < 0) |
||
452 | return ret; |
||
453 | } |
||
454 | |||
455 | return 0; |
||
456 | } |
||
457 | |||
458 | /** |
||
459 | * Write an ID3v2 header at beginning of stream |
||
460 | */ |
||
461 | |||
462 | static int mp3_write_header(struct AVFormatContext *s) |
||
463 | { |
||
464 | MP3Context *mp3 = s->priv_data; |
||
465 | int ret, i; |
||
466 | |||
467 | /* check the streams -- we want exactly one audio and arbitrary number of |
||
468 | * video (attached pictures) */ |
||
469 | mp3->audio_stream_idx = -1; |
||
470 | for (i = 0; i < s->nb_streams; i++) { |
||
471 | AVStream *st = s->streams[i]; |
||
472 | if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO) { |
||
473 | if (mp3->audio_stream_idx >= 0 || st->codec->codec_id != AV_CODEC_ID_MP3) { |
||
474 | av_log(s, AV_LOG_ERROR, "Invalid audio stream. Exactly one MP3 " |
||
475 | "audio stream is required.\n"); |
||
476 | return AVERROR(EINVAL); |
||
477 | } |
||
478 | mp3->audio_stream_idx = i; |
||
479 | } else if (st->codec->codec_type != AVMEDIA_TYPE_VIDEO) { |
||
480 | av_log(s, AV_LOG_ERROR, "Only audio streams and pictures are allowed in MP3.\n"); |
||
481 | return AVERROR(EINVAL); |
||
482 | } |
||
483 | } |
||
484 | if (mp3->audio_stream_idx < 0) { |
||
485 | av_log(s, AV_LOG_ERROR, "No audio stream present.\n"); |
||
486 | return AVERROR(EINVAL); |
||
487 | } |
||
488 | mp3->pics_to_write = s->nb_streams - 1; |
||
489 | |||
490 | ff_id3v2_start(&mp3->id3, s->pb, mp3->id3v2_version, ID3v2_DEFAULT_MAGIC); |
||
491 | ret = ff_id3v2_write_metadata(s, &mp3->id3); |
||
492 | if (ret < 0) |
||
493 | return ret; |
||
494 | |||
495 | if (!mp3->pics_to_write) { |
||
496 | ff_id3v2_finish(&mp3->id3, s->pb); |
||
497 | mp3_write_xing(s); |
||
498 | } |
||
499 | |||
500 | return 0; |
||
501 | } |
||
502 | |||
503 | AVOutputFormat ff_mp3_muxer = { |
||
504 | .name = "mp3", |
||
505 | .long_name = NULL_IF_CONFIG_SMALL("MP3 (MPEG audio layer 3)"), |
||
506 | .mime_type = "audio/x-mpeg", |
||
507 | .extensions = "mp3", |
||
508 | .priv_data_size = sizeof(MP3Context), |
||
509 | .audio_codec = AV_CODEC_ID_MP3, |
||
510 | .video_codec = AV_CODEC_ID_PNG, |
||
511 | .write_header = mp3_write_header, |
||
512 | .write_packet = mp3_write_packet, |
||
513 | .write_trailer = mp3_write_trailer, |
||
514 | .query_codec = query_codec, |
||
515 | .flags = AVFMT_NOTIMESTAMPS, |
||
516 | .priv_class = &mp3_muxer_class, |
||
517 | }; |
||
518 | #endif>>>>>>>=>=>>>12); |