Skip to content
Snippets Groups Projects
aiffdec.c 12.9 KiB
Newer Older
  • Learn to ignore specific revisions
  •  * 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
     */
    
    #include "libavutil/intreadwrite.h"
    
    #include "libavutil/mathematics.h"
    
    #include "libavutil/dict.h"
    
    #include "internal.h"
    
    #include "pcm.h"
    
    #include "id3v2.h"
    
    typedef struct AIFFInputContext {
    
        int block_duration;
    
    static enum AVCodecID aiff_codec_get_id(int bps)
    
            return AV_CODEC_ID_PCM_S8;
    
            return AV_CODEC_ID_PCM_S16BE;
    
            return AV_CODEC_ID_PCM_S24BE;
    
            return AV_CODEC_ID_PCM_S32BE;
    
        return AV_CODEC_ID_NONE;
    
    static int get_tag(AVIOContext *pb, uint32_t * tag)
    
        if (avio_feof(pb))
    
        *tag = avio_rl32(pb);
        size = avio_rb32(pb);
    
    
        if (size < 0)
            size = 0x7fffffff;
    
        return size;
    }
    
    /* Metadata string read */
    
    static void get_meta(AVFormatContext *s, const char *key, int size)
    
        if (str) {
            int res = avio_read(s->pb, str, size);
            if (res < 0){
                av_free(str);
                return;
            }
            size += (size&1)-res;
            str[res] = 0;
    
            av_dict_set(&s->metadata, key, str, AV_DICT_DONT_STRDUP_VAL);
    
        }else
            size+= size&1;
    
        avio_skip(s->pb, size);
    
    }
    
    /* Returns the number of sound data frames or negative on error */
    
    static int get_aiff_header(AVFormatContext *s, int size,
    
                                        unsigned version)
    
        AVIOContext *pb        = s->pb;
    
        AVCodecParameters *par = s->streams[0]->codecpar;
    
        AIFFInputContext *aiff = s->priv_data;
    
        int exp;
        uint64_t val;
    
        par->codec_type = AVMEDIA_TYPE_AUDIO;
        par->channels = avio_rb16(pb);
    
        num_frames = avio_rb32(pb);
    
        par->bits_per_coded_sample = avio_rb16(pb);
    
        exp = avio_rb16(pb) - 16383 - 63;
    
        val = avio_rb64(pb);
    
        if (exp <-63 || exp >63) {
            av_log(s, AV_LOG_ERROR, "exp %d is out of range\n", exp);
            return AVERROR_INVALIDDATA;
        }
        if (exp >= 0)
            sample_rate = val << exp;
        else
            sample_rate = (val + (1ULL<<(-exp-1))) >> -exp;
    
        par->sample_rate = sample_rate;
    
        if (size < 4) {
            version = AIFF;
        } else if (version == AIFF_C_VERSION1) {
    
            par->codec_tag = avio_rl32(pb);
            par->codec_id  = ff_codec_get_id(ff_codec_aiff_tags, par->codec_tag);
    
            if (par->codec_id == AV_CODEC_ID_NONE) {
    
                av_get_codec_tag_string(tag, sizeof(tag), par->codec_tag);
    
                avpriv_request_sample(s, "unknown or unsupported codec tag: %s", tag);
            }
    
        if (version != AIFF_C_VERSION1 || par->codec_id == AV_CODEC_ID_PCM_S16BE) {
            par->codec_id = aiff_codec_get_id(par->bits_per_coded_sample);
            par->bits_per_coded_sample = av_get_bits_per_sample(par->codec_id);
    
            switch (par->codec_id) {
    
            case AV_CODEC_ID_PCM_F32BE:
            case AV_CODEC_ID_PCM_F64BE:
            case AV_CODEC_ID_PCM_S16LE:
            case AV_CODEC_ID_PCM_ALAW:
            case AV_CODEC_ID_PCM_MULAW:
    
                aiff->block_duration = 1;
    
    Baptiste Coudurier's avatar
    Baptiste Coudurier committed
                break;
    
            case AV_CODEC_ID_ADPCM_IMA_QT:
    
                par->block_align = 34 * par->channels;
    
    Baptiste Coudurier's avatar
    Baptiste Coudurier committed
                break;
    
            case AV_CODEC_ID_MACE3:
    
                par->block_align = 2 * par->channels;
    
            case AV_CODEC_ID_ADPCM_G726LE:
    
                par->bits_per_coded_sample = 5;
    
            case AV_CODEC_ID_ADPCM_IMA_WS:
    
            case AV_CODEC_ID_ADPCM_G722:
    
            case AV_CODEC_ID_MACE6:
    
            case AV_CODEC_ID_SDX2_DPCM:
    
                par->block_align = 1 * par->channels;
    
    Baptiste Coudurier's avatar
    Baptiste Coudurier committed
                break;
    
            case AV_CODEC_ID_GSM:
    
                par->block_align = 33;
    
    Baptiste Coudurier's avatar
    Baptiste Coudurier committed
            default:
    
                aiff->block_duration = 1;
    
    Baptiste Coudurier's avatar
    Baptiste Coudurier committed
                break;
    
            if (par->block_align > 0)
                aiff->block_duration = av_get_audio_frame_duration2(par,
                                                                    par->block_align);
    
        }
    
        /* Block align needs to be computed in all cases, as the definition
         * is specific to applications -> here we use the WAVE format definition */
    
        if (!par->block_align)
    
            par->block_align = (av_get_bits_per_sample(par->codec_id) * par->channels) >> 3;
    
        if (aiff->block_duration) {
    
            par->bit_rate = par->sample_rate * (par->block_align << 3) /
                            aiff->block_duration;
    
    
        return num_frames;
    }
    
    static int aiff_probe(AVProbeData *p)
    {
        /* check file header */
        if (p->buf[0] == 'F' && p->buf[1] == 'O' &&
            p->buf[2] == 'R' && p->buf[3] == 'M' &&
            p->buf[8] == 'A' && p->buf[9] == 'I' &&
            p->buf[10] == 'F' && (p->buf[11] == 'F' || p->buf[11] == 'C'))
            return AVPROBE_SCORE_MAX;
        else
            return 0;
    }
    
    /* aiff input */
    
    static int aiff_read_header(AVFormatContext *s)
    
        int ret, size, filesize;
    
        int64_t offset = 0, position;
    
        uint32_t tag;
        unsigned version = AIFF_C_VERSION1;
    
        AVIOContext *pb = s->pb;
    
        AIFFInputContext *aiff = s->priv_data;
    
        ID3v2ExtraMeta *id3v2_extra_meta = NULL;
    
    
        /* check FORM header */
        filesize = get_tag(pb, &tag);
        if (filesize < 0 || tag != MKTAG('F', 'O', 'R', 'M'))
            return AVERROR_INVALIDDATA;
    
        /* AIFF data type */
    
        tag = avio_rl32(pb);
    
        if (tag == MKTAG('A', 'I', 'F', 'F'))       /* Got an AIFF file */
            version = AIFF;
        else if (tag != MKTAG('A', 'I', 'F', 'C'))  /* An AIFF-C file then */
            return AVERROR_INVALIDDATA;
    
        filesize -= 4;
    
    
        st = avformat_new_stream(s, NULL);
    
    
        while (filesize > 0) {
            /* parse different chunks */
            size = get_tag(pb, &tag);
    
            if (size == AVERROR_EOF && offset > 0 && st->codecpar->block_align) {
    
                av_log(s, AV_LOG_WARNING, "header parser hit EOF\n");
                goto got_sound;
            }
    
    Baptiste Coudurier's avatar
    Baptiste Coudurier committed
            case MKTAG('C', 'O', 'M', 'M'):     /* Common chunk */
                /* Then for the complete header info */
    
                st->nb_frames = get_aiff_header(s, size, version);
    
    Baptiste Coudurier's avatar
    Baptiste Coudurier committed
                if (st->nb_frames < 0)
                    return st->nb_frames;
                if (offset > 0) // COMM is after SSND
                    goto got_sound;
                break;
    
            case MKTAG('I', 'D', '3', ' '):
    
                position = avio_tell(pb);
    
                ff_id3v2_read(s, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta, size);
    
                if (id3v2_extra_meta)
                    if ((ret = ff_id3v2_parse_apic(s, &id3v2_extra_meta)) < 0) {
                        ff_id3v2_free_extra_meta(&id3v2_extra_meta);
                        return ret;
                    }
    
                ff_id3v2_free_extra_meta(&id3v2_extra_meta);
    
                if (position + size > avio_tell(pb))
                    avio_skip(pb, position + size - avio_tell(pb));
    
    Baptiste Coudurier's avatar
    Baptiste Coudurier committed
            case MKTAG('F', 'V', 'E', 'R'):     /* Version chunk */
    
                version = avio_rb32(pb);
    
    Baptiste Coudurier's avatar
    Baptiste Coudurier committed
                break;
            case MKTAG('N', 'A', 'M', 'E'):     /* Sample name chunk */
    
                get_meta(s, "title"    , size);
    
    Baptiste Coudurier's avatar
    Baptiste Coudurier committed
                break;
            case MKTAG('A', 'U', 'T', 'H'):     /* Author chunk */
    
                get_meta(s, "author"   , size);
    
    Baptiste Coudurier's avatar
    Baptiste Coudurier committed
                break;
            case MKTAG('(', 'c', ')', ' '):     /* Copyright chunk */
    
                get_meta(s, "copyright", size);
    
    Baptiste Coudurier's avatar
    Baptiste Coudurier committed
                break;
            case MKTAG('A', 'N', 'N', 'O'):     /* Annotation chunk */
    
                get_meta(s, "comment"  , size);
    
    Baptiste Coudurier's avatar
    Baptiste Coudurier committed
                break;
            case MKTAG('S', 'S', 'N', 'D'):     /* Sampled sound chunk */
    
                aiff->data_end = avio_tell(pb) + size;
    
                offset = avio_rb32(pb);      /* Offset of sound data */
                avio_rb32(pb);               /* BlockSize... don't care */
    
                offset += avio_tell(pb);    /* Compute absolute data offset */
    
                if (st->codecpar->block_align && !pb->seekable)    /* Assume COMM already parsed */
    
    Baptiste Coudurier's avatar
    Baptiste Coudurier committed
                    goto got_sound;
    
                if (!pb->seekable) {
    
    Baptiste Coudurier's avatar
    Baptiste Coudurier committed
                    av_log(s, AV_LOG_ERROR, "file is not seekable\n");
                    return -1;
                }
    
    Baptiste Coudurier's avatar
    Baptiste Coudurier committed
                break;
    
            case MKTAG('w', 'a', 'v', 'e'):
    
                if ((uint64_t)size > (1<<30))
                    return -1;
    
                if (ff_get_extradata(s, st->codecpar, pb, size) < 0)
    
                    return AVERROR(ENOMEM);
    
                if (   (st->codecpar->codec_id == AV_CODEC_ID_QDMC || st->codecpar->codec_id == AV_CODEC_ID_QDM2)
                    && size>=12*4 && !st->codecpar->block_align) {
    
                    st->codecpar->block_align = AV_RB32(st->codecpar->extradata+11*4);
                    aiff->block_duration = AV_RB32(st->codecpar->extradata+9*4);
                } else if (st->codecpar->codec_id == AV_CODEC_ID_QCELP) {
    
                        rate = st->codecpar->extradata[24];
    
                    switch (rate) {
                    case 'H': // RATE_HALF
    
                        st->codecpar->block_align = 17;
    
                        break;
                    case 'F': // RATE_FULL
                    default:
    
                        st->codecpar->block_align = 35;
    
                    st->codecpar->bit_rate = st->codecpar->sample_rate * (st->codecpar->block_align << 3) /
                                             aiff->block_duration;
    
            case MKTAG('C','H','A','N'):
    
                if(ff_mov_read_chan(s, pb, st, size) < 0)
    
                    return AVERROR_INVALIDDATA;
                break;
    
                if (offset > 0 && st->codecpar->block_align) // COMM && SSND
    
    Baptiste Coudurier's avatar
    Baptiste Coudurier committed
            default: /* Jump */
                if (size & 1)   /* Always even aligned */
                    size++;
    
        if (!st->codecpar->block_align) {
    
            av_log(s, AV_LOG_ERROR, "could not find COMM tag or invalid block_align value\n");
    
    
        /* Now positioned, get the sound data start and end */
    
        avpriv_set_pts_info(st, 64, 1, st->codecpar->sample_rate);
    
        st->duration = st->nb_frames * aiff->block_duration;
    
        avio_seek(pb, offset, SEEK_SET);
    
    
        return 0;
    }
    
    #define MAX_SIZE 4096
    
    static int aiff_read_packet(AVFormatContext *s,
                                AVPacket *pkt)
    {
    
        AVStream *st = s->streams[0];
    
        AIFFInputContext *aiff = s->priv_data;
        int64_t max_size;
    
        /* calculate size of remaining data */
    
        max_size = aiff->data_end - avio_tell(s->pb);
    
        switch (st->codecpar->codec_id) {
    
        case AV_CODEC_ID_ADPCM_IMA_QT:
        case AV_CODEC_ID_GSM:
        case AV_CODEC_ID_QDM2:
        case AV_CODEC_ID_QCELP:
    
            size = st->codecpar->block_align;
    
            size = (MAX_SIZE / st->codecpar->block_align) * st->codecpar->block_align;
    
        size = FFMIN(max_size, size);
        res = av_get_packet(s->pb, pkt, size);
    
        if (size >= st->codecpar->block_align)
    
            pkt->flags &= ~AV_PKT_FLAG_CORRUPT;
    
        /* Only one stream in an AIFF file */
        pkt->stream_index = 0;
    
        pkt->duration     = (res / st->codecpar->block_align) * aiff->block_duration;
    
        .name           = "aiff",
        .long_name      = NULL_IF_CONFIG_SMALL("Audio IFF"),
        .priv_data_size = sizeof(AIFFInputContext),
        .read_probe     = aiff_probe,
        .read_header    = aiff_read_header,
        .read_packet    = aiff_read_packet,
    
        .read_seek      = ff_pcm_read_seek,
    
        .codec_tag      = (const AVCodecTag* const []){ ff_codec_aiff_tags, 0 },