diff options
Diffstat (limited to 'libavformat/au.c')
-rw-r--r-- | libavformat/au.c | 229 |
1 files changed, 179 insertions, 50 deletions
diff --git a/libavformat/au.c b/libavformat/au.c index 20c9d41..520824f 100644 --- a/libavformat/au.c +++ b/libavformat/au.c @@ -4,20 +4,20 @@ * * first version by Francois Revol <revol@free.fr> * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * 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. * - * Libav is distributed in the hope that it will be useful, + * 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 Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -31,6 +31,12 @@ #include "internal.h" #include "avio_internal.h" #include "pcm.h" +#include "libavutil/avassert.h" + +/* if we don't know the size in advance */ +#define AU_UNKNOWN_SIZE ((uint32_t)(~0)) +/* the specification requires an annotation field of at least eight bytes */ +#define AU_DEFAULT_HEADER_SIZE (24+8) static const AVCodecTag codec_au_tags[] = { { AV_CODEC_ID_PCM_MULAW, 1 }, @@ -40,7 +46,12 @@ static const AVCodecTag codec_au_tags[] = { { AV_CODEC_ID_PCM_S32BE, 5 }, { AV_CODEC_ID_PCM_F32BE, 6 }, { AV_CODEC_ID_PCM_F64BE, 7 }, + { AV_CODEC_ID_ADPCM_G726LE, 23 }, + { AV_CODEC_ID_ADPCM_G722,24 }, + { AV_CODEC_ID_ADPCM_G726LE, 25 }, + { AV_CODEC_ID_ADPCM_G726LE, 26 }, { AV_CODEC_ID_PCM_ALAW, 27 }, + { AV_CODEC_ID_ADPCM_G726LE, MKBETAG('7','2','6','2') }, { AV_CODEC_ID_NONE, 0 }, }; @@ -55,11 +66,77 @@ static int au_probe(AVProbeData *p) return 0; } +static int au_read_annotation(AVFormatContext *s, int size) +{ + static const char * keys[] = { + "title", + "artist", + "album", + "track", + "genre", + NULL }; + AVIOContext *pb = s->pb; + enum { PARSE_KEY, PARSE_VALUE, PARSE_FINISHED } state = PARSE_KEY; + char c; + AVBPrint bprint; + char * key = NULL; + char * value = NULL; + int i; + + av_bprint_init(&bprint, 64, AV_BPRINT_SIZE_UNLIMITED); + + while (size-- > 0) { + c = avio_r8(pb); + switch(state) { + case PARSE_KEY: + if (c == '\0') { + state = PARSE_FINISHED; + } else if (c == '=') { + av_bprint_finalize(&bprint, &key); + av_bprint_init(&bprint, 64, AV_BPRINT_SIZE_UNLIMITED); + state = PARSE_VALUE; + } else { + av_bprint_chars(&bprint, c, 1); + } + break; + case PARSE_VALUE: + if (c == '\0' || c == '\n') { + if (av_bprint_finalize(&bprint, &value) != 0) { + av_log(s, AV_LOG_ERROR, "Memory error while parsing AU metadata.\n"); + } else { + av_bprint_init(&bprint, 64, AV_BPRINT_SIZE_UNLIMITED); + for (i = 0; keys[i] != NULL && key != NULL; i++) { + if (av_strcasecmp(keys[i], key) == 0) { + av_dict_set(&(s->metadata), keys[i], value, AV_DICT_DONT_STRDUP_VAL); + av_freep(&key); + value = NULL; + } + } + } + av_freep(&key); + av_freep(&value); + state = (c == '\0') ? PARSE_FINISHED : PARSE_KEY; + } else { + av_bprint_chars(&bprint, c, 1); + } + break; + case PARSE_FINISHED: + break; + default: + /* should never happen */ + av_assert0(0); + } + } + av_bprint_finalize(&bprint, NULL); + av_freep(&key); + return 0; +} + #define BLOCK_SIZE 1024 static int au_read_header(AVFormatContext *s) { - int size; + int size, data_size = 0; unsigned int tag; AVIOContext *pb = s->pb; unsigned int id, channels, rate; @@ -71,15 +148,20 @@ static int au_read_header(AVFormatContext *s) if (tag != MKTAG('.', 's', 'n', 'd')) return AVERROR_INVALIDDATA; size = avio_rb32(pb); /* header size */ - avio_rb32(pb); /* data size */ + data_size = avio_rb32(pb); /* data size in bytes */ + + if (data_size < 0 && data_size != AU_UNKNOWN_SIZE) { + av_log(s, AV_LOG_ERROR, "Invalid negative data size '%d' found\n", data_size); + return AVERROR_INVALIDDATA; + } id = avio_rb32(pb); rate = avio_rb32(pb); channels = avio_rb32(pb); if (size > 24) { - /* skip unused data */ - avio_skip(pb, size - 24); + /* parse annotation field to get metadata */ + au_read_annotation(s, size - 24); } codec = ff_codec_get_id(codec_au_tags, id); @@ -90,7 +172,15 @@ static int au_read_header(AVFormatContext *s) } bps = av_get_bits_per_sample(codec); - if (!bps) { + if (codec == AV_CODEC_ID_ADPCM_G726LE) { + if (id == MKBETAG('7','2','6','2')) { + bps = 2; + } else { + const uint8_t bpcss[] = {4, 0, 3, 5}; + av_assert0(id >= 23 && id < 23 + 4); + bps = bpcss[id - 23]; + } + } else if (!bps) { avpriv_request_sample(s, "Unknown bits per sample"); return AVERROR_PATCHWELCOME; } @@ -113,8 +203,11 @@ static int au_read_header(AVFormatContext *s) st->codecpar->codec_id = codec; st->codecpar->channels = channels; st->codecpar->sample_rate = rate; + st->codecpar->bits_per_coded_sample = bps; st->codecpar->bit_rate = channels * rate * bps; - st->codecpar->block_align = channels * bps >> 3; + st->codecpar->block_align = FFMAX(bps * st->codecpar->channels / 8, 1); + if (data_size != AU_UNKNOWN_SIZE) + st->duration = (((int64_t)data_size)<<3) / (st->codecpar->channels * (int64_t)bps); st->start_time = 0; avpriv_set_pts_info(st, 64, 1, rate); @@ -122,27 +215,12 @@ static int au_read_header(AVFormatContext *s) return 0; } -static int au_read_packet(AVFormatContext *s, AVPacket *pkt) -{ - int ret; - - ret = av_get_packet(s->pb, pkt, BLOCK_SIZE * - s->streams[0]->codecpar->block_align); - if (ret < 0) - return ret; - - pkt->stream_index = 0; - pkt->duration = ret / s->streams[0]->codecpar->block_align; - - return 0; -} - AVInputFormat ff_au_demuxer = { .name = "au", .long_name = NULL_IF_CONFIG_SMALL("Sun AU"), .read_probe = au_probe, .read_header = au_read_header, - .read_packet = au_read_packet, + .read_packet = ff_pcm_read_packet, .read_seek = ff_pcm_read_seek, .codec_tag = (const AVCodecTag* const []) { codec_au_tags, 0 }, }; @@ -151,37 +229,87 @@ AVInputFormat ff_au_demuxer = { #if CONFIG_AU_MUXER -#include "rawenc.h" +typedef struct AUContext { + uint32_t header_size; +} AUContext; -/* if we don't know the size in advance */ -#define AU_UNKNOWN_SIZE ((uint32_t)(~0)) +#include "rawenc.h" -/* AUDIO_FILE header */ -static int put_au_header(AVIOContext *pb, AVCodecParameters *par) +static int au_get_annotations(AVFormatContext *s, char **buffer) { - if (!par->codec_tag) - return AVERROR(EINVAL); - - ffio_wfourcc(pb, ".snd"); /* magic number */ - avio_wb32(pb, 24); /* header size */ - avio_wb32(pb, AU_UNKNOWN_SIZE); /* data size */ - avio_wb32(pb, par->codec_tag); /* codec ID */ - avio_wb32(pb, par->sample_rate); - avio_wb32(pb, par->channels); - - return 0; + static const char * keys[] = { + "Title", + "Artist", + "Album", + "Track", + "Genre", + NULL }; + int i; + int cnt = 0; + AVDictionary *m = s->metadata; + AVDictionaryEntry *t = NULL; + AVBPrint bprint; + + av_bprint_init(&bprint, 64, AV_BPRINT_SIZE_UNLIMITED); + + for (i = 0; keys[i] != NULL; i++) { + t = av_dict_get(m, keys[i], NULL, 0); + if (t != NULL) { + if (cnt++) + av_bprint_chars(&bprint, '\n', 1); + av_bprint_append_data(&bprint, keys[i], strlen(keys[i])); + av_bprint_chars(&bprint, '=', 1); + av_bprint_append_data(&bprint, t->value, strlen(t->value)); + } + } + /* pad with 0's */ + av_bprint_append_data(&bprint, "\0\0\0\0\0\0\0\0", 8); + return av_bprint_finalize(&bprint, buffer); } static int au_write_header(AVFormatContext *s) { - AVIOContext *pb = s->pb; int ret; + AUContext *au = s->priv_data; + AVIOContext *pb = s->pb; + AVCodecParameters *par = s->streams[0]->codecpar; + char *annotations = NULL; - s->priv_data = NULL; + au->header_size = AU_DEFAULT_HEADER_SIZE; - if ((ret = put_au_header(pb, s->streams[0]->codecpar)) < 0) - return ret; + if (s->nb_streams != 1) { + av_log(s, AV_LOG_ERROR, "only one stream is supported\n"); + return AVERROR(EINVAL); + } + + par->codec_tag = ff_codec_get_tag(codec_au_tags, par->codec_id); + if (!par->codec_tag) { + av_log(s, AV_LOG_ERROR, "unsupported codec\n"); + return AVERROR(EINVAL); + } + if (av_dict_count(s->metadata) > 0) { + ret = au_get_annotations(s, &annotations); + if (ret < 0) + return ret; + if (annotations != NULL) { + au->header_size = (24 + strlen(annotations) + 8) & ~7; + if (au->header_size < AU_DEFAULT_HEADER_SIZE) + au->header_size = AU_DEFAULT_HEADER_SIZE; + } + } + ffio_wfourcc(pb, ".snd"); /* magic number */ + avio_wb32(pb, au->header_size); /* header size */ + avio_wb32(pb, AU_UNKNOWN_SIZE); /* data size */ + avio_wb32(pb, par->codec_tag); /* codec ID */ + avio_wb32(pb, par->sample_rate); + avio_wb32(pb, par->channels); + if (annotations != NULL) { + avio_write(pb, annotations, au->header_size - 24); + av_freep(&annotations); + } else { + avio_wb64(pb, 0); /* annotation field */ + } avio_flush(pb); return 0; @@ -190,13 +318,13 @@ static int au_write_header(AVFormatContext *s) static int au_write_trailer(AVFormatContext *s) { AVIOContext *pb = s->pb; - int64_t file_size; + AUContext *au = s->priv_data; + int64_t file_size = avio_tell(pb); - if (s->pb->seekable & AVIO_SEEKABLE_NORMAL) { + if ((s->pb->seekable & AVIO_SEEKABLE_NORMAL) && file_size < INT32_MAX) { /* update file size */ - file_size = avio_tell(pb); avio_seek(pb, 8, SEEK_SET); - avio_wb32(pb, (uint32_t)(file_size - 24)); + avio_wb32(pb, (uint32_t)(file_size - au->header_size)); avio_seek(pb, file_size, SEEK_SET); avio_flush(pb); } @@ -209,6 +337,7 @@ AVOutputFormat ff_au_muxer = { .long_name = NULL_IF_CONFIG_SMALL("Sun AU"), .mime_type = "audio/basic", .extensions = "au", + .priv_data_size = sizeof(AUContext), .audio_codec = AV_CODEC_ID_PCM_S16BE, .video_codec = AV_CODEC_ID_NONE, .write_header = au_write_header, |