aiffenc.c 9.93 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
    AVCodecParameters *par;
106
    uint64_t sample_rate;
107 108 109 110 111
    int i, aifc = 0;

    aiff->audio_stream_idx = -1;
    for (i = 0; i < s->nb_streams; i++) {
        AVStream *st = s->streams[i];
112
        if (aiff->audio_stream_idx < 0 && st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
113
            aiff->audio_stream_idx = i;
114
        } else if (st->codecpar->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
            return AVERROR(EINVAL);
        }
    }
    if (aiff->audio_stream_idx < 0) {
        av_log(s, AV_LOG_ERROR, "No audio stream present.\n");
        return AVERROR(EINVAL);
    }

124
    par = s->streams[aiff->audio_stream_idx]->codecpar;
125 126

    /* First verify if format is ok */
127
    if (!par->codec_tag)
128
        return -1;
129
    if (par->codec_tag != MKTAG('N','O','N','E'))
130 131 132
        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

    if (aifc) { // compressed audio
139
        if (!par->block_align) {
140 141 142 143
            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
    if (par->channels > 2 && par->channel_layout) {
150 151
        ffio_wfourcc(pb, "CHAN");
        avio_wb32(pb, 12);
152
        ff_mov_write_chan(pb, par->channel_layout);
153 154
    }

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
    avio_wb32(pb, aifc ? 24 : 18); /* size */
163
    avio_wb16(pb, par->channels);  /* Number of channels */
164

165
    aiff->frames = avio_tell(pb);
166
    avio_wb32(pb, 0);              /* Number of frames */
167

168 169 170
    if (!par->bits_per_coded_sample)
        par->bits_per_coded_sample = av_get_bits_per_sample(par->codec_id);
    if (!par->bits_per_coded_sample) {
171 172 173
        av_log(s, AV_LOG_ERROR, "could not compute bits per sample\n");
        return -1;
    }
174 175
    if (!par->block_align)
        par->block_align = (par->bits_per_coded_sample * par->channels) >> 3;
176

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

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

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

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

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

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

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

    return 0;
}

211 212 213 214 215 216 217 218 219 220
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;

221
        if (s->streams[pkt->stream_index]->codecpar->codec_type != AVMEDIA_TYPE_VIDEO)
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 253
            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;
}

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

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

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

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

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

282 283 284 285 286 287 288 289 290 291
        /* 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);

292
        avio_flush(pb);
293 294
    }

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

302 303 304
    return 0;
}

305 306 307 308
#define OFFSET(x) offsetof(AIFFOutputContext, x)
#define ENC AV_OPT_FLAG_ENCODING_PARAM
static const AVOption options[] = {
    { "write_id3v2", "Enable ID3 tags writing.",
309
      OFFSET(write_id3v2), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, ENC },
310 311 312 313 314 315 316 317 318 319 320 321
    { "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,
};

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