Line | Branch | Exec | Source |
---|---|---|---|
1 | /* | ||
2 | * AIFF/AIFF-C muxer | ||
3 | * Copyright (c) 2006 Patrick Guimond | ||
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 <stdint.h> | ||
23 | |||
24 | #include "libavutil/intfloat.h" | ||
25 | #include "libavutil/opt.h" | ||
26 | #include "libavcodec/packet_internal.h" | ||
27 | #include "avformat.h" | ||
28 | #include "internal.h" | ||
29 | #include "aiff.h" | ||
30 | #include "avio_internal.h" | ||
31 | #include "isom.h" | ||
32 | #include "id3v2.h" | ||
33 | #include "mux.h" | ||
34 | |||
35 | typedef struct AIFFOutputContext { | ||
36 | const AVClass *class; | ||
37 | int64_t form; | ||
38 | int64_t frames; | ||
39 | int64_t ssnd; | ||
40 | int audio_stream_idx; | ||
41 | PacketList pict_list; | ||
42 | int write_id3v2; | ||
43 | int id3v2_version; | ||
44 | } AIFFOutputContext; | ||
45 | |||
46 | 3 | static int put_id3v2_tags(AVFormatContext *s, AIFFOutputContext *aiff) | |
47 | { | ||
48 | int ret; | ||
49 | uint64_t pos, end, size; | ||
50 | 3 | ID3v2EncContext id3v2 = { 0 }; | |
51 | 3 | AVIOContext *pb = s->pb; | |
52 | 3 | PacketListEntry *list_entry = aiff->pict_list.head; | |
53 | |||
54 |
1/6✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
3 | if (!s->metadata && !s->nb_chapters && !list_entry) |
55 | ✗ | return 0; | |
56 | |||
57 | 3 | avio_wb32(pb, MKBETAG('I', 'D', '3', ' ')); | |
58 | 3 | avio_wb32(pb, 0); | |
59 | 3 | pos = avio_tell(pb); | |
60 | |||
61 | 3 | ff_id3v2_start(&id3v2, pb, aiff->id3v2_version, ID3v2_DEFAULT_MAGIC); | |
62 | 3 | ff_id3v2_write_metadata(s, &id3v2); | |
63 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 3 times.
|
8 | while (list_entry) { |
64 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
|
5 | if ((ret = ff_id3v2_write_apic(s, &id3v2, &list_entry->pkt)) < 0) |
65 | ✗ | return ret; | |
66 | 5 | list_entry = list_entry->next; | |
67 | } | ||
68 | 3 | ff_id3v2_finish(&id3v2, pb, s->metadata_header_padding); | |
69 | |||
70 | 3 | end = avio_tell(pb); | |
71 | 3 | size = end - pos; | |
72 | |||
73 | /* Update chunk size */ | ||
74 | 3 | avio_seek(pb, pos - 4, SEEK_SET); | |
75 | 3 | avio_wb32(pb, size); | |
76 | 3 | avio_seek(pb, end, SEEK_SET); | |
77 | |||
78 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
|
3 | if (size & 1) |
79 | 1 | avio_w8(pb, 0); | |
80 | |||
81 | 3 | return 0; | |
82 | } | ||
83 | |||
84 | 24 | static void put_meta(AVFormatContext *s, const char *key, uint32_t id) | |
85 | { | ||
86 | AVDictionaryEntry *tag; | ||
87 | 24 | AVIOContext *pb = s->pb; | |
88 | |||
89 |
2/2✓ Branch 1 taken 4 times.
✓ Branch 2 taken 20 times.
|
24 | if (tag = av_dict_get(s->metadata, key, NULL, 0)) { |
90 | 4 | size_t size = strlen(tag->value); | |
91 | |||
92 | // AIFF tags are zero-padded to an even length. | ||
93 | // So simply copy the terminating \0 if the length is odd. | ||
94 | 4 | size = FFALIGN(size, 2); | |
95 | |||
96 | 4 | avio_wb32(pb, id); | |
97 | 4 | avio_wb32(pb, size); | |
98 | 4 | avio_write(pb, tag->value, size); | |
99 | } | ||
100 | 24 | } | |
101 | |||
102 | 6 | static int aiff_write_header(AVFormatContext *s) | |
103 | { | ||
104 | 6 | AIFFOutputContext *aiff = s->priv_data; | |
105 | 6 | AVIOContext *pb = s->pb; | |
106 | AVCodecParameters *par; | ||
107 | uint64_t sample_rate; | ||
108 | 6 | int i, aifc = 0; | |
109 | |||
110 | 6 | aiff->audio_stream_idx = -1; | |
111 |
2/2✓ Branch 0 taken 11 times.
✓ Branch 1 taken 6 times.
|
17 | for (i = 0; i < s->nb_streams; i++) { |
112 | 11 | AVStream *st = s->streams[i]; | |
113 |
3/4✓ Branch 0 taken 6 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
|
11 | if (aiff->audio_stream_idx < 0 && st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { |
114 | 6 | aiff->audio_stream_idx = i; | |
115 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | } else if (st->codecpar->codec_type != AVMEDIA_TYPE_VIDEO) { |
116 | ✗ | av_log(s, AV_LOG_ERROR, "AIFF allows only one audio stream and a picture.\n"); | |
117 | ✗ | return AVERROR(EINVAL); | |
118 | } | ||
119 | } | ||
120 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (aiff->audio_stream_idx < 0) { |
121 | ✗ | av_log(s, AV_LOG_ERROR, "No audio stream present.\n"); | |
122 | ✗ | return AVERROR(EINVAL); | |
123 | } | ||
124 | |||
125 | 6 | par = s->streams[aiff->audio_stream_idx]->codecpar; | |
126 | |||
127 | /* First verify if format is ok */ | ||
128 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (!par->codec_tag) |
129 | ✗ | return AVERROR(EINVAL); | |
130 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
|
6 | if (par->codec_tag != MKTAG('N','O','N','E')) |
131 | 3 | aifc = 1; | |
132 | |||
133 | /* FORM AIFF header */ | ||
134 | 6 | ffio_wfourcc(pb, "FORM"); | |
135 | 6 | aiff->form = avio_tell(pb); | |
136 | 6 | avio_wb32(pb, 0); /* file length */ | |
137 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
|
6 | ffio_wfourcc(pb, aifc ? "AIFC" : "AIFF"); |
138 | |||
139 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
|
6 | if (aifc) { // compressed audio |
140 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | if (!par->block_align) { |
141 | ✗ | av_log(s, AV_LOG_ERROR, "block align not set\n"); | |
142 | ✗ | return AVERROR(EINVAL); | |
143 | } | ||
144 | /* Version chunk */ | ||
145 | 3 | ffio_wfourcc(pb, "FVER"); | |
146 | 3 | avio_wb32(pb, 4); | |
147 | 3 | avio_wb32(pb, 0xA2805140); | |
148 | } | ||
149 | |||
150 |
3/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 5 times.
|
6 | if (par->ch_layout.order == AV_CHANNEL_ORDER_NATIVE && par->ch_layout.nb_channels > 2) { |
151 | 1 | ffio_wfourcc(pb, "CHAN"); | |
152 | 1 | avio_wb32(pb, 12); | |
153 | 1 | ff_mov_write_chan(pb, par->ch_layout.u.mask); | |
154 | } | ||
155 | |||
156 | 6 | put_meta(s, "title", MKBETAG('N', 'A', 'M', 'E')); | |
157 | 6 | put_meta(s, "author", MKBETAG('A', 'U', 'T', 'H')); | |
158 | 6 | put_meta(s, "copyright", MKBETAG('(', 'c', ')', ' ')); | |
159 | 6 | put_meta(s, "comment", MKBETAG('A', 'N', 'N', 'O')); | |
160 | |||
161 | /* Common chunk */ | ||
162 | 6 | ffio_wfourcc(pb, "COMM"); | |
163 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
|
6 | avio_wb32(pb, aifc ? 24 : 18); /* size */ |
164 | 6 | avio_wb16(pb, par->ch_layout.nb_channels); /* Number of channels */ | |
165 | |||
166 | 6 | aiff->frames = avio_tell(pb); | |
167 | 6 | avio_wb32(pb, 0); /* Number of frames */ | |
168 | |||
169 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (!par->bits_per_coded_sample) |
170 | ✗ | par->bits_per_coded_sample = av_get_bits_per_sample(par->codec_id); | |
171 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (!par->bits_per_coded_sample) { |
172 | ✗ | av_log(s, AV_LOG_ERROR, "could not compute bits per sample\n"); | |
173 | ✗ | return AVERROR(EINVAL); | |
174 | } | ||
175 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (!par->block_align) |
176 | ✗ | par->block_align = (par->bits_per_coded_sample * par->ch_layout.nb_channels) >> 3; | |
177 | |||
178 | 6 | avio_wb16(pb, par->bits_per_coded_sample); /* Sample size */ | |
179 | |||
180 | 6 | sample_rate = av_double2int(par->sample_rate); | |
181 | 6 | avio_wb16(pb, (sample_rate >> 52) + (16383 - 1023)); | |
182 | 6 | avio_wb64(pb, UINT64_C(1) << 63 | sample_rate << 11); | |
183 | |||
184 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
|
6 | if (aifc) { |
185 | 3 | avio_wl32(pb, par->codec_tag); | |
186 | 3 | avio_wb16(pb, 0); | |
187 | } | ||
188 | |||
189 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | if ( (par->codec_tag == MKTAG('Q','D','M','2') |
190 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
6 | || par->codec_tag == MKTAG('Q','c','l','p')) && par->extradata_size) { |
191 | ✗ | ffio_wfourcc(pb, "wave"); | |
192 | ✗ | avio_wb32(pb, par->extradata_size); | |
193 | ✗ | avio_write(pb, par->extradata, par->extradata_size); | |
194 | } | ||
195 | |||
196 | /* Sound data chunk */ | ||
197 | 6 | ffio_wfourcc(pb, "SSND"); | |
198 | 6 | aiff->ssnd = avio_tell(pb); /* Sound chunk size */ | |
199 | 6 | avio_wb32(pb, 0); /* Sound samples data size */ | |
200 | 6 | avio_wb32(pb, 0); /* Data offset */ | |
201 | 6 | avio_wb32(pb, 0); /* Block-size (block align) */ | |
202 | |||
203 | 6 | avpriv_set_pts_info(s->streams[aiff->audio_stream_idx], 64, 1, | |
204 | 6 | s->streams[aiff->audio_stream_idx]->codecpar->sample_rate); | |
205 | |||
206 | 6 | return 0; | |
207 | } | ||
208 | |||
209 | 8450 | static int aiff_write_packet(AVFormatContext *s, AVPacket *pkt) | |
210 | { | ||
211 | 8450 | AIFFOutputContext *aiff = s->priv_data; | |
212 | 8450 | AVIOContext *pb = s->pb; | |
213 |
2/2✓ Branch 0 taken 8445 times.
✓ Branch 1 taken 5 times.
|
8450 | if (pkt->stream_index == aiff->audio_stream_idx) |
214 | 8445 | avio_write(pb, pkt->data, pkt->size); | |
215 | else { | ||
216 | /* warn only once for each stream */ | ||
217 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | if (s->streams[pkt->stream_index]->nb_frames == 1) { |
218 | ✗ | av_log(s, AV_LOG_WARNING, "Got more than one picture in stream %d," | |
219 | " ignoring.\n", pkt->stream_index); | ||
220 | } | ||
221 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | if (s->streams[pkt->stream_index]->nb_frames >= 1) |
222 | ✗ | return 0; | |
223 | |||
224 | 5 | return avpriv_packet_list_put(&aiff->pict_list, pkt, NULL, 0); | |
225 | } | ||
226 | |||
227 | 8445 | return 0; | |
228 | } | ||
229 | |||
230 | 6 | static int aiff_write_trailer(AVFormatContext *s) | |
231 | { | ||
232 | 6 | int ret = 0; | |
233 | 6 | AVIOContext *pb = s->pb; | |
234 | 6 | AIFFOutputContext *aiff = s->priv_data; | |
235 | 6 | AVCodecParameters *par = s->streams[aiff->audio_stream_idx]->codecpar; | |
236 | |||
237 | /* Chunks sizes must be even */ | ||
238 | int64_t file_size, data_size; | ||
239 | 6 | data_size = avio_tell(pb); | |
240 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (data_size & 1) |
241 | ✗ | avio_w8(pb, 0); | |
242 | |||
243 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | if (s->pb->seekable & AVIO_SEEKABLE_NORMAL) { |
244 | /* Write ID3 tags */ | ||
245 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
|
6 | if (aiff->write_id3v2) |
246 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
|
3 | if ((ret = put_id3v2_tags(s, aiff)) < 0) |
247 | ✗ | return ret; | |
248 | |||
249 | /* File length */ | ||
250 | 6 | file_size = avio_tell(pb); | |
251 | 6 | avio_seek(pb, aiff->form, SEEK_SET); | |
252 | 6 | avio_wb32(pb, file_size - aiff->form - 4); | |
253 | |||
254 | /* Number of sample frames */ | ||
255 | 6 | avio_seek(pb, aiff->frames, SEEK_SET); | |
256 | 6 | avio_wb32(pb, (data_size - aiff->ssnd - 12) / par->block_align); | |
257 | |||
258 | /* Sound Data chunk size */ | ||
259 | 6 | avio_seek(pb, aiff->ssnd, SEEK_SET); | |
260 | 6 | avio_wb32(pb, data_size - aiff->ssnd - 4); | |
261 | } | ||
262 | |||
263 | 6 | return ret; | |
264 | } | ||
265 | |||
266 | 6 | static void aiff_deinit(AVFormatContext *s) | |
267 | { | ||
268 | 6 | AIFFOutputContext *aiff = s->priv_data; | |
269 | |||
270 | 6 | avpriv_packet_list_free(&aiff->pict_list); | |
271 | 6 | } | |
272 | |||
273 | #define OFFSET(x) offsetof(AIFFOutputContext, x) | ||
274 | #define ENC AV_OPT_FLAG_ENCODING_PARAM | ||
275 | static const AVOption options[] = { | ||
276 | { "write_id3v2", "Enable ID3 tags writing.", | ||
277 | OFFSET(write_id3v2), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, ENC }, | ||
278 | { "id3v2_version", "Select ID3v2 version to write. Currently 3 and 4 are supported.", | ||
279 | OFFSET(id3v2_version), AV_OPT_TYPE_INT, {.i64 = 4}, 3, 4, ENC }, | ||
280 | { NULL }, | ||
281 | }; | ||
282 | |||
283 | static const AVClass aiff_muxer_class = { | ||
284 | .class_name = "AIFF muxer", | ||
285 | .item_name = av_default_item_name, | ||
286 | .option = options, | ||
287 | .version = LIBAVUTIL_VERSION_INT, | ||
288 | }; | ||
289 | |||
290 | const FFOutputFormat ff_aiff_muxer = { | ||
291 | .p.name = "aiff", | ||
292 | .p.long_name = NULL_IF_CONFIG_SMALL("Audio IFF"), | ||
293 | .p.mime_type = "audio/aiff", | ||
294 | .p.extensions = "aif,aiff,afc,aifc", | ||
295 | .priv_data_size = sizeof(AIFFOutputContext), | ||
296 | .p.audio_codec = AV_CODEC_ID_PCM_S16BE, | ||
297 | .p.video_codec = AV_CODEC_ID_PNG, | ||
298 | .write_header = aiff_write_header, | ||
299 | .write_packet = aiff_write_packet, | ||
300 | .write_trailer = aiff_write_trailer, | ||
301 | .deinit = aiff_deinit, | ||
302 | .p.codec_tag = ff_aiff_codec_tags_list, | ||
303 | .p.priv_class = &aiff_muxer_class, | ||
304 | }; | ||
305 |