/* * WavPack demuxer * Copyright (c) 2006,2011 Konstantin Shishkov * * This file is part of Libav. * * Libav 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, * 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 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "libavutil/channel_layout.h" #include "libavutil/intreadwrite.h" #include "libavutil/dict.h" #include "avformat.h" #include "internal.h" #include "apetag.h" #include "id3v1.h" #include "wv.h" 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 WVContext { uint8_t block_header[WV_HEADER_SIZE]; WvHeader header; int rate, chan, bpp; uint32_t chmask; int multichannel; int block_parsed; 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) { WVContext *wc = ctx->priv_data; int ret; int rate, bpp, chan; uint32_t chmask, flags; 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; ret = avio_read(pb, wc->block_header, WV_HEADER_SIZE); if (ret != WV_HEADER_SIZE) return (ret < 0) ? ret : AVERROR_EOF; ret = ff_wv_parse_header(&wc->header, wc->block_header); if (ret < 0) { av_log(ctx, AV_LOG_ERROR, "Invalid block header.\n"); return ret; } if (wc->header.version < 0x402 || wc->header.version > 0x410) { av_log(ctx, AV_LOG_ERROR, "Unsupported version %03X\n", wc->header.version); return AVERROR_PATCHWELCOME; } /* Blocks with zero samples don't contain actual audio information * and should be ignored */ if (!wc->header.samples) return 0; // parse flags flags = wc->header.flags; bpp = ((flags & 3) + 1) << 3; chan = 1 + !(flags & WV_MONO); chmask = flags & WV_MONO ? AV_CH_LAYOUT_MONO : AV_CH_LAYOUT_STEREO; rate = wv_rates[(flags >> 23) & 0xF]; wc->multichannel = !(wc->header.initial && wc->header.final); if (wc->multichannel) { chan = wc->chan; chmask = wc->chmask; } if ((rate == -1 || !chan) && !wc->block_parsed) { int64_t block_end = avio_tell(pb) + wc->header.blocksize; 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->header.blocksize, 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 (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 (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 (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; } 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) return ret; if (!wc->header.samples) avio_skip(pb, wc->header.blocksize); else break; } /* now we are ready: build format streams */ st = avformat_new_stream(s, NULL); if (!st) return AVERROR(ENOMEM); st->codecpar->codec_type = AVMEDIA_TYPE_AUDIO; st->codecpar->codec_id = AV_CODEC_ID_WAVPACK; st->codecpar->channels = wc->chan; st->codecpar->channel_layout = wc->chmask; st->codecpar->sample_rate = wc->rate; st->codecpar->bits_per_coded_sample = wc->bpp; avpriv_set_pts_info(st, 64, 1, wc->rate); st->start_time = 0; st->duration = wc->header.total_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 off; int64_t pos; uint32_t block_samples; if (s->pb->eof_reached) return AVERROR_EOF; if (wc->block_parsed) { if ((ret = wv_read_block_header(s, s->pb)) < 0) return ret; } pos = wc->pos; if (av_new_packet(pkt, wc->header.blocksize + WV_HEADER_SIZE) < 0) return AVERROR(ENOMEM); memcpy(pkt->data, wc->block_header, WV_HEADER_SIZE); ret = avio_read(s->pb, pkt->data + WV_HEADER_SIZE, wc->header.blocksize); if (ret != wc->header.blocksize) { av_packet_unref(pkt); return AVERROR(EIO); } while (!(wc->header.flags & WV_FLAG_FINAL_BLOCK)) { if ((ret = wv_read_block_header(s, s->pb)) < 0) { av_packet_unref(pkt); return ret; } off = pkt->size; if ((ret = av_grow_packet(pkt, WV_HEADER_SIZE + wc->header.blocksize)) < 0) { av_packet_unref(pkt); return ret; } memcpy(pkt->data + off, wc->block_header, WV_HEADER_SIZE); ret = avio_read(s->pb, pkt->data + off + WV_HEADER_SIZE, wc->header.blocksize); if (ret != wc->header.blocksize) { av_packet_unref(pkt); return (ret < 0) ? ret : AVERROR_EOF; } } pkt->stream_index = 0; wc->block_parsed = 1; pkt->pts = wc->header.block_idx; block_samples = wc->header.samples; 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_packet_unref(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, };