| 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 2 times.
✓ Branch 1 taken 1 times.
|
3 | if (size & 1) |
| 79 | 2 | 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 |