aiffenc.c 9.85 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
/*
 * AIFF/AIFF-C muxer
 * Copyright (c) 2006  Patrick Guimond
 *
 * This file is part of FFmpeg.
 *
 * FFmpeg is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * FFmpeg is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with FFmpeg; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

22 23
#include <stdint.h>

24
#include "libavutil/intfloat.h"
25
#include "libavutil/opt.h"
26
#include "avformat.h"
27
#include "internal.h"
28
#include "aiff.h"
29
#include "avio_internal.h"
30
#include "isom.h"
31
#include "id3v2.h"
32

33
typedef struct AIFFOutputContext {
34
    const AVClass *class;
35 36 37
    int64_t form;
    int64_t frames;
    int64_t ssnd;
38 39 40 41
    int audio_stream_idx;
    AVPacketList *pict_list;
    int write_id3v2;
    int id3v2_version;
42 43
} AIFFOutputContext;

44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
static int put_id3v2_tags(AVFormatContext *s, AIFFOutputContext *aiff)
{
    int ret;
    uint64_t pos, end, size;
    ID3v2EncContext id3v2 = { 0 };
    AVIOContext *pb = s->pb;
    AVPacketList *pict_list = aiff->pict_list;

    if (!pb->seekable)
        return 0;

    if (!s->metadata && !aiff->pict_list)
        return 0;

    avio_wl32(pb, MKTAG('I', 'D', '3', ' '));
    avio_wb32(pb, 0);
    pos = avio_tell(pb);

    ff_id3v2_start(&id3v2, pb, aiff->id3v2_version, ID3v2_DEFAULT_MAGIC);
    ff_id3v2_write_metadata(s, &id3v2);
    while (pict_list) {
        if ((ret = ff_id3v2_write_apic(s, &id3v2, &pict_list->pkt)) < 0)
            return ret;
        pict_list = pict_list->next;
    }
69
    ff_id3v2_finish(&id3v2, pb, s->metadata_header_padding);
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84

    end = avio_tell(pb);
    size = end - pos;

    /* Update chunk size */
    avio_seek(pb, pos - 4, SEEK_SET);
    avio_wb32(pb, size);
    avio_seek(pb, end, SEEK_SET);

    if (size & 1)
        avio_w8(pb, 0);

    return 0;
}

Paul B Mahol's avatar
Paul B Mahol committed
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
static void put_meta(AVFormatContext *s, const char *key, uint32_t id)
{
    AVDictionaryEntry *tag;
    AVIOContext *pb = s->pb;

    if (tag = av_dict_get(s->metadata, key, NULL, 0)) {
        int size = strlen(tag->value);

        avio_wl32(pb, id);
        avio_wb32(pb, FFALIGN(size, 2));
        avio_write(pb, tag->value, size);
        if (size & 1)
            avio_w8(pb, 0);
    }
}

101 102 103
static int aiff_write_header(AVFormatContext *s)
{
    AIFFOutputContext *aiff = s->priv_data;
104
    AVIOContext *pb = s->pb;
105
    AVCodecContext *enc;
106
    uint64_t sample_rate;
107 108 109 110 111 112 113 114
    int i, aifc = 0;

    aiff->audio_stream_idx = -1;
    for (i = 0; i < s->nb_streams; i++) {
        AVStream *st = s->streams[i];
        if (aiff->audio_stream_idx < 0 && st->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
            aiff->audio_stream_idx = i;
        } else if (st->codec->codec_type != AVMEDIA_TYPE_VIDEO) {
115
            av_log(s, AV_LOG_ERROR, "AIFF allows only one audio stream and a picture.\n");
116 117 118 119 120 121 122 123 124
            return AVERROR(EINVAL);
        }
    }
    if (aiff->audio_stream_idx < 0) {
        av_log(s, AV_LOG_ERROR, "No audio stream present.\n");
        return AVERROR(EINVAL);
    }

    enc = s->streams[aiff->audio_stream_idx]->codec;
125 126 127 128 129 130 131 132

    /* First verify if format is ok */
    if (!enc->codec_tag)
        return -1;
    if (enc->codec_tag != MKTAG('N','O','N','E'))
        aifc = 1;

    /* FORM AIFF header */
133
    ffio_wfourcc(pb, "FORM");
134
    aiff->form = avio_tell(pb);
135
    avio_wb32(pb, 0);                    /* file length */
136
    ffio_wfourcc(pb, aifc ? "AIFC" : "AIFF");
137 138 139 140 141 142 143

    if (aifc) { // compressed audio
        if (!enc->block_align) {
            av_log(s, AV_LOG_ERROR, "block align not set\n");
            return -1;
        }
        /* Version chunk */
144
        ffio_wfourcc(pb, "FVER");
145 146
        avio_wb32(pb, 4);
        avio_wb32(pb, 0xA2805140);
147 148
    }

149 150 151 152 153 154
    if (enc->channels > 2 && enc->channel_layout) {
        ffio_wfourcc(pb, "CHAN");
        avio_wb32(pb, 12);
        ff_mov_write_chan(pb, enc->channel_layout);
    }

Paul B Mahol's avatar
Paul B Mahol committed
155 156 157 158 159
    put_meta(s, "title",     MKTAG('N', 'A', 'M', 'E'));
    put_meta(s, "author",    MKTAG('A', 'U', 'T', 'H'));
    put_meta(s, "copyright", MKTAG('(', 'c', ')', ' '));
    put_meta(s, "comment",   MKTAG('A', 'N', 'N', 'O'));

160
    /* Common chunk */
161
    ffio_wfourcc(pb, "COMM");
162 163
    avio_wb32(pb, aifc ? 24 : 18); /* size */
    avio_wb16(pb, enc->channels);  /* Number of channels */
164

165
    aiff->frames = avio_tell(pb);
166
    avio_wb32(pb, 0);              /* Number of frames */
167 168 169 170 171 172 173 174 175 176

    if (!enc->bits_per_coded_sample)
        enc->bits_per_coded_sample = av_get_bits_per_sample(enc->codec_id);
    if (!enc->bits_per_coded_sample) {
        av_log(s, AV_LOG_ERROR, "could not compute bits per sample\n");
        return -1;
    }
    if (!enc->block_align)
        enc->block_align = (enc->bits_per_coded_sample * enc->channels) >> 3;

177
    avio_wb16(pb, enc->bits_per_coded_sample); /* Sample size */
178

179 180 181
    sample_rate = av_double2int(enc->sample_rate);
    avio_wb16(pb, (sample_rate >> 52) + (16383 - 1023));
    avio_wb64(pb, UINT64_C(1) << 63 | sample_rate << 11);
182 183

    if (aifc) {
184 185
        avio_wl32(pb, enc->codec_tag);
        avio_wb16(pb, 0);
186 187
    }

188 189 190 191 192 193
    if (enc->codec_tag == MKTAG('Q','D','M','2') && enc->extradata_size) {
        ffio_wfourcc(pb, "wave");
        avio_wb32(pb, enc->extradata_size);
        avio_write(pb, enc->extradata, enc->extradata_size);
    }

194
    /* Sound data chunk */
195
    ffio_wfourcc(pb, "SSND");
196
    aiff->ssnd = avio_tell(pb);         /* Sound chunk size */
197 198 199
    avio_wb32(pb, 0);                    /* Sound samples data size */
    avio_wb32(pb, 0);                    /* Data offset */
    avio_wb32(pb, 0);                    /* Block-size (block align) */
200

201 202
    avpriv_set_pts_info(s->streams[aiff->audio_stream_idx], 64, 1,
                        s->streams[aiff->audio_stream_idx]->codec->sample_rate);
203 204

    /* Data is starting here */
205
    avio_flush(pb);
206 207 208 209

    return 0;
}

210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
static int aiff_write_packet(AVFormatContext *s, AVPacket *pkt)
{
    AIFFOutputContext *aiff = s->priv_data;
    AVIOContext *pb = s->pb;
    if (pkt->stream_index == aiff->audio_stream_idx)
        avio_write(pb, pkt->data, pkt->size);
    else {
        int ret;
        AVPacketList *pict_list, *last;

        if (s->streams[pkt->stream_index]->codec->codec_type != AVMEDIA_TYPE_VIDEO)
            return 0;

        /* warn only once for each stream */
        if (s->streams[pkt->stream_index]->nb_frames == 1) {
            av_log(s, AV_LOG_WARNING, "Got more than one picture in stream %d,"
                   " ignoring.\n", pkt->stream_index);
        }
        if (s->streams[pkt->stream_index]->nb_frames >= 1)
            return 0;

        pict_list = av_mallocz(sizeof(AVPacketList));
        if (!pict_list)
            return AVERROR(ENOMEM);

        if ((ret = av_copy_packet(&pict_list->pkt, pkt)) < 0) {
            av_freep(&pict_list);
            return ret;
        }

        if (!aiff->pict_list)
            aiff->pict_list = pict_list;
        else {
            last = aiff->pict_list;
            while (last->next)
                last = last->next;
            last->next = pict_list;
        }
    }

    return 0;
}

253 254
static int aiff_write_trailer(AVFormatContext *s)
{
255
    int ret;
256
    AVIOContext *pb = s->pb;
257
    AIFFOutputContext *aiff = s->priv_data;
258 259
    AVPacketList *pict_list = aiff->pict_list;
    AVCodecContext *enc = s->streams[aiff->audio_stream_idx]->codec;
260 261 262

    /* Chunks sizes must be even */
    int64_t file_size, end_size;
263
    end_size = file_size = avio_tell(pb);
264
    if (file_size & 1) {
265
        avio_w8(pb, 0);
266 267 268
        end_size++;
    }

269
    if (s->pb->seekable) {
270
        /* Number of sample frames */
271
        avio_seek(pb, aiff->frames, SEEK_SET);
272
        avio_wb32(pb, (file_size-aiff->ssnd-12)/enc->block_align);
273 274

        /* Sound Data chunk size */
275
        avio_seek(pb, aiff->ssnd, SEEK_SET);
276
        avio_wb32(pb, file_size - aiff->ssnd - 4);
277 278

        /* return to the end */
279
        avio_seek(pb, end_size, SEEK_SET);
280

281 282 283 284 285 286 287 288 289 290
        /* Write ID3 tags */
        if (aiff->write_id3v2)
            if ((ret = put_id3v2_tags(s, aiff)) < 0)
                return ret;

        /* File length */
        file_size = avio_tell(pb);
        avio_seek(pb, aiff->form, SEEK_SET);
        avio_wb32(pb, file_size - aiff->form - 4);

291
        avio_flush(pb);
292 293
    }

294 295 296 297 298 299 300
    while (pict_list) {
        AVPacketList *next = pict_list->next;
        av_free_packet(&pict_list->pkt);
        av_freep(&pict_list);
        pict_list = next;
    }

301 302 303
    return 0;
}

304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
#define OFFSET(x) offsetof(AIFFOutputContext, x)
#define ENC AV_OPT_FLAG_ENCODING_PARAM
static const AVOption options[] = {
    { "write_id3v2", "Enable ID3 tags writing.",
      OFFSET(write_id3v2), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, ENC },
    { "id3v2_version", "Select ID3v2 version to write. Currently 3 and 4 are supported.",
      OFFSET(id3v2_version), AV_OPT_TYPE_INT, {.i64 = 4}, 3, 4, ENC },
    { NULL },
};

static const AVClass aiff_muxer_class = {
    .class_name     = "AIFF muxer",
    .item_name      = av_default_item_name,
    .option         = options,
    .version        = LIBAVUTIL_VERSION_INT,
};

321
AVOutputFormat ff_aiff_muxer = {
322 323 324 325 326
    .name              = "aiff",
    .long_name         = NULL_IF_CONFIG_SMALL("Audio IFF"),
    .mime_type         = "audio/aiff",
    .extensions        = "aif,aiff,afc,aifc",
    .priv_data_size    = sizeof(AIFFOutputContext),
327
    .audio_codec       = AV_CODEC_ID_PCM_S16BE,
328
    .video_codec       = AV_CODEC_ID_PNG,
329
    .write_header      = aiff_write_header,
330
    .write_packet      = aiff_write_packet,
331
    .write_trailer     = aiff_write_trailer,
332
    .codec_tag         = (const AVCodecTag* const []){ ff_codec_aiff_tags, 0 },
333
    .priv_class        = &aiff_muxer_class,
334
};