/* * WavPack demuxer * Copyright (c) 2006,2011 Konstantin Shishkov * * 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/audioconvert.h" #include "libavutil/intreadwrite.h" #include "libavutil/dict.h" #include "avformat.h" #include "internal.h" #include "apetag.h" #include "id3v1.h" // specs say that maximum block size is 1Mb #define WV_BLOCK_LIMIT 1047576 #define WV_EXTRA_SIZE 12 #define WV_START_BLOCK 0x0800 #define WV_END_BLOCK 0x1000 #define WV_SINGLE_BLOCK (WV_START_BLOCK | WV_END_BLOCK) enum WV_FLAGS { WV_MONO = 0x0004, WV_HYBRID = 0x0008, WV_JOINT = 0x0010, WV_CROSSD = 0x0020, WV_HSHAPE = 0x0040, WV_FLOAT = 0x0080, WV_INT32 = 0x0100, WV_HBR = 0x0200, WV_HBAL = 0x0400, WV_MCINIT = 0x0800, WV_MCEND = 0x1000, }; static const int wv_rates[16] = { 6000, 8000, 9600, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000, 192000, -1 }; typedef struct { uint32_t blksize, flags; int rate, chan, bpp; uint32_t chmask; uint32_t samples, soff; int multichannel; int block_parsed; uint8_t extra[WV_EXTRA_SIZE]; int64_t pos; int64_t apetag_start; } WVContext; static int wv_probe(AVProbeData *p) { /* check file header */ if (p->buf_size <= 32) return 0; if (p->buf[0] == 'w' && p->buf[1] == 'v' && p->buf[2] == 'p' && p->buf[3] == 'k') return AVPROBE_SCORE_MAX; else return 0; } static int wv_read_block_header(AVFormatContext *ctx, AVIOContext *pb, int append) { WVContext *wc = ctx->priv_data; uint32_t tag, ver; int size; int rate, bpp, chan; uint32_t chmask; wc->pos = avio_tell(pb); /* don't return bogus packets with the ape tag data */ if (wc->apetag_start && wc->pos >= wc->apetag_start) return AVERROR_EOF; if (!append) { tag = avio_rl32(pb); if (tag != MKTAG('w', 'v', 'p', 'k')) return AVERROR_INVALIDDATA; size = avio_rl32(pb); if (size < 24 || size > WV_BLOCK_LIMIT) { av_log(ctx, AV_LOG_ERROR, "Incorrect block size %i\n", size); return AVERROR_INVALIDDATA; } wc->blksize = size; ver = avio_rl16(pb); if (ver < 0x402 || ver > 0x410) { av_log(ctx, AV_LOG_ERROR, "Unsupported version %03X\n", ver); return AVERROR_PATCHWELCOME; } avio_r8(pb); // track no avio_r8(pb); // track sub index wc->samples = avio_rl32(pb); // total samples in file wc->soff = avio_rl32(pb); // offset in samples of current block avio_read(pb, wc->extra, WV_EXTRA_SIZE); } else { size = wc->blksize; } wc->flags = AV_RL32(wc->extra + 4); /* Blocks with zero samples don't contain actual audio information * and should be ignored */ if (!AV_RN32(wc->extra)) return 0; // parse flags bpp = ((wc->flags & 3) + 1) << 3; chan = 1 + !(wc->flags & WV_MONO); chmask = wc->flags & WV_MONO ? AV_CH_LAYOUT_MONO : AV_CH_LAYOUT_STEREO; rate = wv_rates[(wc->flags >> 23) & 0xF]; wc->multichannel = !!((wc->flags & WV_SINGLE_BLOCK) != WV_SINGLE_BLOCK); if (wc->multichannel) { chan = wc->chan; chmask = wc->chmask; } if ((rate == -1 || !chan) && !wc->block_parsed) { int64_t block_end = avio_tell(pb) + wc->blksize - 24; if (!pb->seekable) { av_log(ctx, AV_LOG_ERROR, "Cannot determine additional parameters\n"); return AVERROR_INVALIDDATA; } while (avio_tell(pb) < block_end) { int id, size; id = avio_r8(pb); size = (id & 0x80) ? avio_rl24(pb) : avio_r8(pb); size <<= 1; if (id & 0x40) size--; switch (id & 0x3F) { case 0xD: if (size <= 1) { av_log(ctx, AV_LOG_ERROR, "Insufficient channel information\n"); return AVERROR_INVALIDDATA; } chan = avio_r8(pb); switch (size - 2) { case 0: chmask = avio_r8(pb); break; case 1: chmask = avio_rl16(pb); break; case 2: chmask = avio_rl24(pb); break; case 3: chmask = avio_rl32(pb); break; case 5: avio_skip(pb, 1); chan |= (avio_r8(pb) & 0xF) << 8; chmask = avio_rl24(pb); break; default: av_log(ctx, AV_LOG_ERROR, "Invalid channel info size %d\n", size); return AVERROR_INVALIDDATA; } break; case 0x27: rate = avio_rl24(pb); break; default: avio_skip(pb, size); } if (id & 0x40) avio_skip(pb, 1); } if (rate == -1) { av_log(ctx, AV_LOG_ERROR, "Cannot determine custom sampling rate\n"); return AVERROR_INVALIDDATA; } avio_seek(pb, block_end - wc->blksize + 24, SEEK_SET); } if (!wc->bpp) wc->bpp = bpp; if (!wc->chan) wc->chan = chan; if (!wc->chmask) wc->chmask = chmask; if (!wc->rate) wc->rate = rate; if (wc->flags && bpp != wc->bpp) { av_log(ctx, AV_LOG_ERROR, "Bits per sample differ, this block: %i, header block: %i\n", bpp, wc->bpp); return AVERROR_INVALIDDATA; } if (wc->flags && !wc->multichannel && chan != wc->chan) { av_log(ctx, AV_LOG_ERROR, "Channels differ, this block: %i, header block: %i\n", chan, wc->chan); return AVERROR_INVALIDDATA; } if (wc->flags && rate != -1 && rate != wc->rate) { av_log(ctx, AV_LOG_ERROR, "Sampling rate differ, this block: %i, header block: %i\n", rate, wc->rate); return AVERROR_INVALIDDATA; } wc->blksize = size - 24; return 0; } static int wv_read_header(AVFormatContext *s) { AVIOContext *pb = s->pb; WVContext *wc = s->priv_data; AVStream *st; int ret; wc->block_parsed = 0; for (;;) { if ((ret = wv_read_block_header(s, pb, 0)) < 0) return ret; if (!AV_RN32(wc->extra)) avio_skip(pb, wc->blksize - 24); else break; } /* now we are ready: build format streams */ st = avformat_new_stream(s, NULL); if (!st) return AVERROR(ENOMEM); st->codec->codec_type = AVMEDIA_TYPE_AUDIO; st->codec->codec_id = AV_CODEC_ID_WAVPACK; st->codec->channels = wc->chan; st->codec->channel_layout = wc->chmask; st->codec->sample_rate = wc->rate; st->codec->bits_per_coded_sample = wc->bpp; avpriv_set_pts_info(st, 64, 1, wc->rate); st->start_time = 0; if (wc->samples != 0xFFFFFFFFu) st->duration = wc->samples; if (s->pb->seekable) { int64_t cur = avio_tell(s->pb); wc->apetag_start = ff_ape_parse_tag(s); if (!av_dict_get(s->metadata, "", NULL, AV_DICT_IGNORE_SUFFIX)) ff_id3v1_read(s); avio_seek(s->pb, cur, SEEK_SET); } return 0; } static int wv_read_packet(AVFormatContext *s, AVPacket *pkt) { WVContext *wc = s->priv_data; int ret; int size, ver, off; int64_t pos; uint32_t block_samples; if (url_feof(s->pb)) return AVERROR_EOF; if (wc->block_parsed) { if ((ret = wv_read_block_header(s, s->pb, 0)) < 0) return ret; } pos = wc->pos; off = wc->multichannel ? 4 : 0; if (av_new_packet(pkt, wc->blksize + WV_EXTRA_SIZE + off) < 0) return AVERROR(ENOMEM); if (wc->multichannel) AV_WL32(pkt->data, wc->blksize + WV_EXTRA_SIZE + 12); memcpy(pkt->data + off, wc->extra, WV_EXTRA_SIZE); ret = avio_read(s->pb, pkt->data + WV_EXTRA_SIZE + off, wc->blksize); if (ret != wc->blksize) { av_free_packet(pkt); return AVERROR(EIO); } while (!(wc->flags & WV_END_BLOCK)) { if (avio_rl32(s->pb) != MKTAG('w', 'v', 'p', 'k')) { av_free_packet(pkt); return AVERROR_INVALIDDATA; } if ((ret = av_append_packet(s->pb, pkt, 4)) < 0) { av_free_packet(pkt); return ret; } size = AV_RL32(pkt->data + pkt->size - 4); if (size < 24 || size > WV_BLOCK_LIMIT) { av_free_packet(pkt); av_log(s, AV_LOG_ERROR, "Incorrect block size %d\n", size); return AVERROR_INVALIDDATA; } wc->blksize = size; ver = avio_rl16(s->pb); if (ver < 0x402 || ver > 0x410) { av_free_packet(pkt); av_log(s, AV_LOG_ERROR, "Unsupported version %03X\n", ver); return AVERROR_PATCHWELCOME; } avio_r8(s->pb); // track no avio_r8(s->pb); // track sub index wc->samples = avio_rl32(s->pb); // total samples in file wc->soff = avio_rl32(s->pb); // offset in samples of current block if ((ret = av_append_packet(s->pb, pkt, WV_EXTRA_SIZE)) < 0) { av_free_packet(pkt); return ret; } memcpy(wc->extra, pkt->data + pkt->size - WV_EXTRA_SIZE, WV_EXTRA_SIZE); if ((ret = wv_read_block_header(s, s->pb, 1)) < 0) { av_free_packet(pkt); return ret; } ret = av_append_packet(s->pb, pkt, wc->blksize); if (ret < 0) { av_free_packet(pkt); return ret; } } pkt->stream_index = 0; wc->block_parsed = 1; pkt->pts = wc->soff; block_samples = AV_RN32(wc->extra); if (block_samples > INT32_MAX) av_log(s, AV_LOG_WARNING, "Too many samples in block: %"PRIu32"\n", block_samples); else pkt->duration = block_samples; av_add_index_entry(s->streams[0], pos, pkt->pts, 0, 0, AVINDEX_KEYFRAME); return 0; } static int wv_read_seek(AVFormatContext *s, int stream_index, int64_t timestamp, int flags) { AVStream *st = s->streams[stream_index]; WVContext *wc = s->priv_data; AVPacket pkt1, *pkt = &pkt1; int ret; int index = av_index_search_timestamp(st, timestamp, flags); int64_t pos, pts; /* if found, seek there */ if (index >= 0 && timestamp <= st->index_entries[st->nb_index_entries - 1].timestamp) { wc->block_parsed = 1; avio_seek(s->pb, st->index_entries[index].pos, SEEK_SET); return 0; } /* if timestamp is out of bounds, return error */ if (timestamp < 0 || timestamp >= s->duration) return AVERROR(EINVAL); pos = avio_tell(s->pb); do { ret = av_read_frame(s, pkt); if (ret < 0) { avio_seek(s->pb, pos, SEEK_SET); return ret; } pts = pkt->pts; av_free_packet(pkt); } while(pts < timestamp); return 0; } AVInputFormat ff_wv_demuxer = { .name = "wv", .long_name = NULL_IF_CONFIG_SMALL("WavPack"), .priv_data_size = sizeof(WVContext), .read_probe = wv_probe, .read_header = wv_read_header, .read_packet = wv_read_packet, .read_seek = wv_read_seek, };