X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=libavformat%2Fargo_brp.c;h=059418cd1d5ce6b74ab2e4988090ab91535a90c1;hb=bc70684e74a185d7b80c8b80bdedda659cb581b8;hp=bb7767479bcf6bb4e5d469763fa57ddc1395324b;hpb=e76102a2d8bce97885208657535f007b8cfd8217;p=ffmpeg diff --git a/libavformat/argo_brp.c b/libavformat/argo_brp.c index bb7767479bc..059418cd1d5 100644 --- a/libavformat/argo_brp.c +++ b/libavformat/argo_brp.c @@ -32,14 +32,18 @@ #define BRP_BLOCK_HEADER_SIZE 12 #define BRP_STREAM_HEADER_SIZE 20 #define BRP_MAX_STREAMS 32 /* Soft cap, but even this is overkill. */ +#define BRP_BASF_LOOKAHEAD 10 /* How many blocks to search for the first BASF one. */ #define BVID_HEADER_SIZE 16 -#define BRP_MIN_BUFFER_SIZE FFMAX(FFMAX3(BRP_FILE_HEADER_SIZE, \ - BRP_BLOCK_HEADER_SIZE, \ - BRP_STREAM_HEADER_SIZE), \ - BVID_HEADER_SIZE) +#define MASK_HEADER_SIZE 12 +#define BRP_MIN_BUFFER_SIZE FFMAX3(FFMAX3(BRP_FILE_HEADER_SIZE, \ + BRP_BLOCK_HEADER_SIZE, \ + BRP_STREAM_HEADER_SIZE), \ + BVID_HEADER_SIZE, \ + MASK_HEADER_SIZE) #define BRP_CODEC_ID_BVID MKTAG('B', 'V', 'I', 'D') #define BRP_CODEC_ID_BASF MKTAG('B', 'A', 'S', 'F') +#define BRP_CODEC_ID_MASK MKTAG('M', 'A', 'S', 'K') typedef struct ArgoBRPFileHeader { uint32_t magic; @@ -60,6 +64,12 @@ typedef struct ArgoBVIDHeader { uint32_t depth; } ArgoBVIDHeader; +typedef struct ArgoMASKHeader { + uint32_t num_frames; + uint32_t width; + uint32_t height; +} ArgoMASKHeader; + typedef struct ArgoBRPStreamHeader { uint32_t codec_id; uint32_t id; @@ -72,24 +82,18 @@ typedef struct ArgoBRPStreamHeader { ArgoBVIDHeader bvid; /* If codec_id == BRP_CODEC_ID_BASF */ ArgoASFFileHeader basf; + /* If codec_id == BRP_CODEC_ID_MASK */ + ArgoMASKHeader mask; } extradata; } ArgoBRPStreamHeader; typedef struct ArgoBRPDemuxContext { ArgoBRPFileHeader fhdr; ArgoBRPStreamHeader streams[BRP_MAX_STREAMS]; - /* To know how much of a BASF to give. */ - int64_t lastpts; - int hit_eof; - /* BASF-specific fields. */ struct { int index; ArgoASFChunkHeader ckhdr; - int64_t blocks_read; - int64_t offset; - /* ms, not samples. */ - int64_t lastpts; } basf; } ArgoBRPDemuxContext; @@ -114,6 +118,9 @@ static int read_extradata(AVFormatContext *s, const ArgoBRPStreamHeader *hdr, } else if (hdr->codec_id == BRP_CODEC_ID_BASF) { name = "BASF"; size = ASF_FILE_HEADER_SIZE; + } else if (hdr->codec_id == BRP_CODEC_ID_MASK) { + name = "MASK"; + size = MASK_HEADER_SIZE; } else { avpriv_request_sample(s, "BRP codec id 0x%x", hdr->codec_id); @@ -184,6 +191,10 @@ static int argo_brp_read_header(AVFormatContext *s) hdr->byte_rate = AV_RL32(buf + 12); hdr->extradata_size = AV_RL32(buf + 16); + /* This should always be the case. */ + if (hdr->id != i) + return AVERROR_INVALIDDATA; + /* Timestamps are in milliseconds. */ avpriv_set_pts_info(st, 64, 1, 1000); st->duration = hdr->duration_ms; @@ -200,15 +211,16 @@ static int argo_brp_read_header(AVFormatContext *s) ArgoBVIDHeader *bvid = &hdr->extradata.bvid; st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; - - /* No decoder for this yet. */ - st->codecpar->codec_id = AV_CODEC_ID_NONE; + st->codecpar->codec_id = AV_CODEC_ID_ARGO; bvid->num_frames = AV_RL32(buf + 0); bvid->width = AV_RL32(buf + 4); bvid->height = AV_RL32(buf + 8); bvid->depth = AV_RL32(buf + 12); + if (bvid->num_frames == 0) + return AVERROR_INVALIDDATA; + /* These are from 1990's games, sanity check this. */ if (bvid->width >= 65536 || bvid->height >= 65536 || bvid->depth > 24 || bvid->depth % 8 != 0) { @@ -217,15 +229,8 @@ static int argo_brp_read_header(AVFormatContext *s) st->codecpar->width = bvid->width; st->codecpar->height = bvid->height; - - if (bvid->depth == 8) { - st->codecpar->format = AV_PIX_FMT_PAL8; - } else if (bvid->depth == 24) { - st->codecpar->format = AV_PIX_FMT_RGB24; - } else { - avpriv_request_sample(s, "depth == %u", bvid->depth); - return AVERROR_PATCHWELCOME; - } + st->nb_frames = bvid->num_frames; + st->codecpar->bits_per_raw_sample = bvid->depth; } else if (hdr->codec_id == BRP_CODEC_ID_BASF) { /* * It would make the demuxer significantly more complicated @@ -245,38 +250,65 @@ static int argo_brp_read_header(AVFormatContext *s) if ((ret = ff_argo_asf_validate_file_header(s, &hdr->extradata.basf)) < 0) return ret; + st->nb_frames = hdr->extradata.basf.num_chunks; + } else if (hdr->codec_id == BRP_CODEC_ID_MASK) { + ArgoMASKHeader *mask = &hdr->extradata.mask; + + st->codecpar->codec_type = AVMEDIA_TYPE_DATA; + + mask->num_frames = AV_RL32(buf + 0); + mask->width = AV_RL32(buf + 4); + mask->height = AV_RL32(buf + 8); + + st->nb_frames = mask->num_frames; } else { av_assert0(0); /* Caught above, should never happen. */ } } - /* - * This is nasty. BASF streams only have one (huge) block. - * It should be the first one. It contains the chunk header, so - * it needs to be read here. - */ + /* Try to find the first BASF chunk. */ if (brp->basf.index >= 0) { AVStream *st = s->streams[brp->basf.index]; ArgoBRPStreamHeader *hdr = brp->streams + brp->basf.index; ArgoBRPBlockHeader blk; + int64_t offset; + int i; av_assert0(st->codecpar->codec_id == AV_CODEC_ID_ADPCM_ARGO); av_assert0(brp->streams[brp->basf.index].extradata_size == ASF_FILE_HEADER_SIZE); - if ((ret = avio_read(pb, buf, BRP_BLOCK_HEADER_SIZE)) < 0) + if ((ret = avio_tell(s->pb)) < 0) return ret; - else if (ret != BRP_BLOCK_HEADER_SIZE) - return AVERROR(EIO); - blk.stream_id = AV_RL32(buf + 0); - blk.start_ms = AV_RL32(buf + 4); - blk.size = AV_RL32(buf + 8); + offset = ret; + + av_log(s, AV_LOG_TRACE, "Searching %d blocks for BASF...", BRP_BASF_LOOKAHEAD); + + for (i = 0; i < BRP_BASF_LOOKAHEAD; i++) { + if ((ret = avio_read(pb, buf, BRP_BLOCK_HEADER_SIZE)) < 0) + return ret; + else if (ret != BRP_BLOCK_HEADER_SIZE) + return AVERROR(EIO); - if (blk.stream_id != brp->basf.index) { - avpriv_request_sample(st, "first block not BASF"); - return AVERROR_PATCHWELCOME; + blk.stream_id = AV_RL32(buf + 0); + blk.start_ms = AV_RL32(buf + 4); + blk.size = AV_RL32(buf + 8); + + if (blk.stream_id == brp->basf.index || blk.stream_id == -1) + break; + + if ((ret = avio_skip(pb, blk.size)) < 0) + return ret; + } + + if (i == BRP_BASF_LOOKAHEAD || blk.stream_id == -1) { + /* Don't error here, as there may still be a valid video stream. */ + av_log(s, AV_LOG_TRACE, "not found\n"); + goto done; } + av_log(s, AV_LOG_TRACE, "found at index %d\n", i); + if (blk.size < ASF_CHUNK_HEADER_SIZE) return AVERROR_INVALIDDATA; @@ -287,90 +319,45 @@ static int argo_brp_read_header(AVFormatContext *s) ff_argo_asf_parse_chunk_header(&brp->basf.ckhdr, buf); - if ((ret = ff_argo_asf_fill_stream(st, &hdr->extradata.basf, &brp->basf.ckhdr)) < 0) + /* + * Special Case Hack. It seems that in files where the BASF block isn't first, + * v1.1 streams are allowed to be non-22050... + * Bump the version to 1.2 so ff_argo_asf_fill_stream() doesn't "correct" it. + * + * Found in Alien Odyssey games files in: + * ./GRAPHICS/COMMBUNK/{{COMADD1,COMM2_{1,2,3E},COMM3_{2,3,4,5,6}},FADE{1,2}}.BRP + * + * Either this format really inconsistent, or FX Fighter and Croc just ignored the + * sample rate field... + */ + if (i != 0 && hdr->extradata.basf.version_major == 1 && hdr->extradata.basf.version_minor == 1) + hdr->extradata.basf.version_minor = 2; + + if ((ret = ff_argo_asf_fill_stream(s, st, &hdr->extradata.basf, &brp->basf.ckhdr)) < 0) return ret; /* Convert ms to samples. */ - st->start_time = (blk.start_ms * st->codecpar->sample_rate) / 1000; - - if ((ret = avio_tell(s->pb)) < 0) - return ret; - - brp->basf.offset = ret; + st->start_time = av_rescale_rnd(blk.start_ms, st->codecpar->sample_rate, 1000, AV_ROUND_UP); + st->duration = av_rescale_rnd(hdr->duration_ms, st->codecpar->sample_rate, 1000, AV_ROUND_UP); - if ((ret = avio_skip(s->pb, blk.size - ASF_CHUNK_HEADER_SIZE)) < 0) +done: + if ((ret = avio_seek(s->pb, offset, SEEK_SET)) < 0) return ret; } return 0; } -static int argo_brp_read_basf(AVFormatContext *s, AVPacket *pkt, - ArgoBRPDemuxContext *brp, int ignorepts) -{ - ArgoASFChunkHeader *ckhdr = &brp->basf.ckhdr; - AVCodecParameters *par; - int64_t ret, old; - - if (brp->basf.index < 0) - return 0; - - par = s->streams[brp->basf.index]->codecpar; - - if (brp->basf.blocks_read >= ckhdr->num_blocks) - return 0; - - if (!ignorepts && brp->lastpts < brp->basf.lastpts) - return 0; - - if ((ret = avio_tell(s->pb)) < 0) - return ret; - - old = ret; - - if ((ret = avio_seek(s->pb, brp->basf.offset, SEEK_SET)) < 0) - return ret; - else if (ret != brp->basf.offset) - return AVERROR(EIO); - - if ((ret = av_get_packet(s->pb, pkt, par->block_align)) < 0) - return ret; - - if ((ret = avio_seek(s->pb, old, SEEK_SET)) < 0) - return ret; - else if (ret != old) - return AVERROR(EIO); - - pkt->stream_index = brp->basf.index; - pkt->duration = ckhdr->num_samples; - - brp->basf.offset += pkt->size; - brp->basf.blocks_read += 1; - /* Need the ceil() because ((32 * 1000) / 44100) < 1 */ - brp->basf.lastpts += ceilf((ckhdr->num_samples * 1000.0f) / ckhdr->sample_rate); - return 1; -} - static int argo_brp_read_packet(AVFormatContext *s, AVPacket *pkt) { ArgoBRPDemuxContext *brp = s->priv_data; ArgoBRPBlockHeader blk; - AVIOContext *pb = s->pb; - uint8_t buf[BRP_BLOCK_HEADER_SIZE]; + const ArgoBRPStreamHeader *shdr; + AVStream *st; + uint8_t buf[BRP_MIN_BUFFER_SIZE]; + ArgoASFChunkHeader ckhdr; int ret; - /* - * Special-case: send some more BASF content if we're running behind. - * Grr, why couldn't they just interleave it. - */ - if ((ret = argo_brp_read_basf(s, pkt, brp, brp->hit_eof)) < 0) - return ret; - else if (ret > 0) - return 0; - - if (brp->hit_eof) - return AVERROR_EOF; - - if ((ret = avio_read(pb, buf, BRP_BLOCK_HEADER_SIZE)) < 0) + if ((ret = avio_read(s->pb, buf, BRP_BLOCK_HEADER_SIZE)) < 0) return ret; else if (ret != BRP_BLOCK_HEADER_SIZE) return AVERROR(EIO); @@ -379,34 +366,54 @@ static int argo_brp_read_packet(AVFormatContext *s, AVPacket *pkt) blk.start_ms = AV_RL32(buf + 4); blk.size = AV_RL32(buf + 8); - /* This is meant to be EOF, but there might be more BASF packets. */ - if (blk.stream_id == -1) { - brp->hit_eof = 1; - /* This is nasty, but safe. */ - return argo_brp_read_packet(s, pkt); - } + if (blk.stream_id == -1) + return AVERROR_EOF; if (blk.stream_id < -1 || blk.stream_id >= s->nb_streams) return AVERROR_INVALIDDATA; - /* Skip BASF blocks. */ + st = s->streams[blk.stream_id]; + shdr = brp->streams + blk.stream_id; + if (blk.stream_id == brp->basf.index) { - if ((ret = avio_skip(s->pb, blk.size)) < 0) - return ret; - } else { - if ((ret = av_get_packet(s->pb, pkt, blk.size)) < 0) + if (blk.size < ASF_CHUNK_HEADER_SIZE) + return AVERROR_INVALIDDATA; + + if ((ret = avio_read(s->pb, buf, ASF_CHUNK_HEADER_SIZE)) < 0) return ret; - else if (ret != blk.size) + + ff_argo_asf_parse_chunk_header(&ckhdr, buf); + + /* Ensure the chunk attributes are the same. */ + if (ckhdr.sample_rate != brp->basf.ckhdr.sample_rate || + ckhdr.flags != brp->basf.ckhdr.flags || + ckhdr.unk1 != brp->basf.ckhdr.unk1 || + ckhdr.unk2 != brp->basf.ckhdr.unk2) return AVERROR_INVALIDDATA; + + blk.size -= ASF_CHUNK_HEADER_SIZE; + } + + if ((ret = av_get_packet(s->pb, pkt, blk.size)) < 0) + return ret; + else if (ret != blk.size) + return AVERROR_INVALIDDATA; + + if (blk.stream_id == brp->basf.index) { + pkt->duration = ckhdr.num_samples * ckhdr.num_blocks; + pkt->pts = av_rescale_rnd(blk.start_ms, ckhdr.sample_rate, 1000, AV_ROUND_UP); + } else if (shdr->codec_id == BRP_CODEC_ID_BVID) { + pkt->duration = av_rescale_rnd(1, st->duration, shdr->extradata.bvid.num_frames, AV_ROUND_UP); + pkt->pts = blk.start_ms; + } else { + pkt->pts = blk.start_ms; } pkt->stream_index = blk.stream_id; - pkt->pts = blk.start_ms; - brp->lastpts = FFMAX(brp->lastpts, blk.start_ms); return 0; } -AVInputFormat ff_argo_brp_demuxer = { +const AVInputFormat ff_argo_brp_demuxer = { .name = "argo_brp", .long_name = NULL_IF_CONFIG_SMALL("Argonaut Games BRP"), .priv_data_size = sizeof(ArgoBRPDemuxContext),