| /* |
| * Raw FLAC demuxer |
| * Copyright (c) 2001 Fabrice Bellard |
| * |
| * 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 "libavcodec/flac.h" |
| #include "avformat.h" |
| #include "raw.h" |
| #include "id3v2.h" |
| #include "oggdec.h" |
| |
| typedef struct FlacSeekPoint { |
| uint64_t samplenum; // time in the stream (in samples) |
| uint64_t offset; // offset from first frame |
| uint16_t samplecount; // number of samples in that frame |
| } FlacSeekPoint; |
| |
| typedef struct FlacContext { |
| int hasseektable; |
| int firstpacket; // send a dts on first packets after seek |
| uint64_t firstdts; |
| int64_t frameoffset; // Offset of the first frame |
| uint32_t seekcount; |
| FlacSeekPoint *seekpoints; |
| } FlacContext; |
| |
| static int flac_read_header(AVFormatContext *s, |
| AVFormatParameters *ap) |
| { |
| FlacContext *flac = s->priv_data; |
| uint8_t buf[ID3v2_HEADER_SIZE]; |
| int ret, metadata_last=0, metadata_type, metadata_size, found_streaminfo=0; |
| uint8_t header[4]; |
| uint8_t *buffer=NULL; |
| AVStream *st = av_new_stream(s, 0); |
| if (!st) |
| return AVERROR(ENOMEM); |
| st->codec->codec_type = CODEC_TYPE_AUDIO; |
| st->codec->codec_id = CODEC_ID_FLAC; |
| st->need_parsing = AVSTREAM_PARSE_FULL; |
| /* the parameters will be extracted from the compressed bitstream */ |
| |
| flac->hasseektable=0; |
| flac->firstpacket=1; |
| flac->firstdts=0; |
| flac->seekcount=0; |
| |
| /* skip ID3v2 header if found */ |
| ret = get_buffer(s->pb, buf, ID3v2_HEADER_SIZE); |
| if (ret == ID3v2_HEADER_SIZE && ff_id3v2_match(buf)) { |
| int len = ff_id3v2_tag_len(buf); |
| url_fseek(s->pb, len - ID3v2_HEADER_SIZE, SEEK_CUR); |
| } else { |
| url_fseek(s->pb, 0, SEEK_SET); |
| } |
| |
| /* if fLaC marker is not found, assume there is no header */ |
| if (get_le32(s->pb) != MKTAG('f','L','a','C')) { |
| url_fseek(s->pb, -4, SEEK_CUR); |
| return 0; |
| } |
| |
| /* process metadata blocks */ |
| while (!url_feof(s->pb) && !metadata_last) { |
| get_buffer(s->pb, header, 4); |
| ff_flac_parse_block_header(header, &metadata_last, &metadata_type, |
| &metadata_size); |
| switch (metadata_type) { |
| /* allocate and read metadata block for supported types */ |
| case FLAC_METADATA_TYPE_STREAMINFO: |
| case FLAC_METADATA_TYPE_VORBIS_COMMENT: |
| buffer = av_mallocz(metadata_size + FF_INPUT_BUFFER_PADDING_SIZE); |
| if (!buffer) { |
| return AVERROR_NOMEM; |
| } |
| if (get_buffer(s->pb, buffer, metadata_size) != metadata_size) { |
| av_freep(&buffer); |
| return AVERROR_IO; |
| } |
| break; |
| // SageTV: report picture size and offset for external extraction |
| case FLAC_METADATA_TYPE_PICTURE: { |
| char value[64]; |
| uint32_t length; |
| get_le32(s->pb); // Skip picture type |
| length = get_be32(s->pb); |
| url_fskip(s->pb, length); // mime type |
| length = get_be32(s->pb); |
| url_fskip(s->pb, length); // description |
| url_fskip(s->pb, 16); // DWORDS: width, height, depth, palette size |
| length = get_be32(s->pb); |
| snprintf(value, sizeof(value), "%d", length); |
| av_metadata_set(&s->metadata, "ThumbnailSize", value); |
| snprintf(value, sizeof(value), "%"PRIi64, url_ftell(s->pb)); |
| av_metadata_set(&s->metadata, "ThumbnailOffset", value); |
| ret = url_fseek(s->pb, length, SEEK_CUR); |
| if (ret < 0) |
| return ret; |
| } |
| break; |
| case FLAC_METADATA_TYPE_SEEKTABLE: |
| // Each entry is 64 bits sample number, 64bits offset from first frame |
| // 16 bit number of samples |
| { |
| int32_t ind=0; |
| flac->seekcount=metadata_size/18; |
| flac->seekpoints = av_mallocz(flac->seekcount*sizeof(FlacSeekPoint)); |
| if (!flac->seekpoints) |
| return AVERROR(ENOMEM); |
| for(ind=0;ind<flac->seekcount;ind++) |
| { |
| flac->seekpoints[ind].samplenum=get_be64(s->pb); |
| flac->seekpoints[ind].offset=get_be64(s->pb); |
| flac->seekpoints[ind].samplecount=get_be16(s->pb); |
| metadata_size-=18; |
| } |
| flac->hasseektable=1; |
| } |
| break; |
| break; |
| /* skip metadata block for unsupported types */ |
| default: |
| ret = url_fseek(s->pb, metadata_size, SEEK_CUR); |
| if (ret < 0) |
| return ret; |
| } |
| |
| if (metadata_type == FLAC_METADATA_TYPE_STREAMINFO) { |
| FLACStreaminfo si; |
| /* STREAMINFO can only occur once */ |
| if (found_streaminfo) { |
| av_freep(&buffer); |
| return AVERROR_INVALIDDATA; |
| } |
| if (metadata_size != FLAC_STREAMINFO_SIZE) { |
| av_freep(&buffer); |
| return AVERROR_INVALIDDATA; |
| } |
| found_streaminfo = 1; |
| st->codec->extradata = buffer; |
| st->codec->extradata_size = metadata_size; |
| buffer = NULL; |
| |
| /* get codec params from STREAMINFO header */ |
| ff_flac_parse_streaminfo(st->codec, &si, st->codec->extradata); |
| |
| /* set time base and duration */ |
| if (si.samplerate > 0) { |
| av_set_pts_info(st, 64, 1, si.samplerate); |
| if (si.samples > 0) |
| st->duration = si.samples; |
| } |
| } else { |
| /* STREAMINFO must be the first block */ |
| if (!found_streaminfo) { |
| av_freep(&buffer); |
| return AVERROR_INVALIDDATA; |
| } |
| /* process supported blocks other than STREAMINFO */ |
| if (metadata_type == FLAC_METADATA_TYPE_VORBIS_COMMENT) { |
| if (vorbis_comment(s, buffer, metadata_size)) { |
| av_log(s, AV_LOG_WARNING, "error parsing VorbisComment metadata\n"); |
| } |
| } |
| av_freep(&buffer); |
| } |
| } |
| // The index we have built doesn't use the right base |
| flac->frameoffset=url_ftell(s->pb); |
| return 0; |
| } |
| |
| static int flac_probe(AVProbeData *p) |
| { |
| uint8_t *bufptr = p->buf; |
| uint8_t *end = p->buf + p->buf_size; |
| |
| if(ff_id3v2_match(bufptr)) |
| bufptr += ff_id3v2_tag_len(bufptr); |
| |
| if(bufptr > end-4 || memcmp(bufptr, "fLaC", 4)) return 0; |
| else return AVPROBE_SCORE_MAX/2; |
| } |
| |
| #define FLAC_PACKET_SIZE 1024 |
| |
| static int flac_read_partial_packet(AVFormatContext *s, AVPacket *pkt) |
| { |
| FlacContext *flac = s->priv_data; |
| int ret, size; |
| |
| size = FLAC_PACKET_SIZE; |
| |
| if (av_new_packet(pkt, size) < 0) |
| return AVERROR(EIO); |
| |
| if(flac->firstpacket) |
| { |
| pkt->dts=flac->firstdts; |
| flac->firstpacket=0; |
| } |
| pkt->pos= url_ftell(s->pb); |
| pkt->stream_index = 0; |
| ret = get_partial_buffer(s->pb, pkt->data, size); |
| if (ret <= 0) { |
| av_free_packet(pkt); |
| return AVERROR(EIO); |
| } |
| pkt->size = ret; |
| return ret; |
| } |
| |
| static int flac_read_seek(AVFormatContext *s, int stream_index, int64_t sample_time, int flags) |
| { |
| FlacContext *flac = s->priv_data; |
| AVStream *st; |
| int i, ret, ind; |
| uint64_t pos; |
| if (stream_index >= s->nb_streams) |
| return -1; |
| if (sample_time < 0) |
| sample_time = 0; |
| st = s->streams[stream_index]; |
| |
| for(ind=0;ind<flac->seekcount;ind++) |
| { |
| if(flac->seekpoints[ind].samplenum>=sample_time) |
| break; |
| } |
| ind-=1; |
| |
| if(ind<0) |
| { |
| pos=0; |
| flac->firstdts=0; |
| } |
| else |
| { |
| pos=flac->seekpoints[ind].offset; |
| flac->firstdts=flac->seekpoints[ind].samplenum; |
| } |
| pos+=flac->frameoffset; |
| |
| if ((ret = url_fseek(s->pb, pos, SEEK_SET)) < 0) |
| return ret; |
| |
| flac->firstpacket=1; |
| |
| /* for each stream, reset read state */ |
| for(i = 0; i < s->nb_streams; i++) { |
| st = s->streams[i]; |
| |
| if (st->parser) { |
| av_parser_close(st->parser); |
| st->parser = NULL; |
| av_free_packet(&st->cur_pkt); |
| } |
| st->last_IP_pts = AV_NOPTS_VALUE; |
| st->cur_dts = AV_NOPTS_VALUE; /* we set the current DTS to an unspecified origin */ |
| st->reference_dts = AV_NOPTS_VALUE; |
| /* fail safe */ |
| st->cur_ptr = NULL; |
| st->cur_len = 0; |
| } |
| return 0; |
| } |
| |
| static int flac_read_close(AVFormatContext *s) |
| { |
| FlacContext *flac = s->priv_data; |
| av_freep(&flac->seekpoints); |
| return 0; |
| } |
| |
| AVInputFormat flac_demuxer = { |
| "flac", |
| NULL_IF_CONFIG_SMALL("raw FLAC"), |
| sizeof(FlacContext), |
| flac_probe, |
| flac_read_header, |
| flac_read_partial_packet, |
| flac_read_close, |
| flac_read_seek, |
| .flags= AVFMT_GENERIC_INDEX, |
| .extensions = "flac", |
| .value = CODEC_ID_FLAC, |
| }; |