| /* | |
| * Phantom Cine demuxer | |
| * Copyright (c) 2010-2011 Peter Ross <pross@xvid.org> | |
| * | |
| * 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 | |
| */ | |
| /** | |
| * @file | |
| * Phantom Cine demuxer | |
| * @author Peter Ross <pross@xvid.org> | |
| */ | |
| #include "libavutil/intreadwrite.h" | |
| #include "libavcodec/bmp.h" | |
| #include "libavutil/intfloat.h" | |
| #include "avformat.h" | |
| #include "demux.h" | |
| #include "internal.h" | |
| typedef struct { | |
| uint64_t pts; | |
| uint64_t maxsize; | |
| } CineDemuxContext; | |
| /** Compression */ | |
| enum { | |
| CC_RGB = 0, /**< Gray */ | |
| CC_LEAD = 1, /**< LEAD (M)JPEG */ | |
| CC_UNINT = 2 /**< Uninterpolated color image (CFA field indicates color ordering) */ | |
| }; | |
| /** Color Filter Array */ | |
| enum { | |
| CFA_NONE = 0, /**< GRAY */ | |
| CFA_VRI = 1, /**< GBRG/RGGB */ | |
| CFA_VRIV6 = 2, /**< BGGR/GRBG */ | |
| CFA_BAYER = 3, /**< GB/RG */ | |
| CFA_BAYERFLIP = 4, /**< RG/GB */ | |
| }; | |
| #define CFA_TLGRAY 0x80000000U | |
| #define CFA_TRGRAY 0x40000000U | |
| #define CFA_BLGRAY 0x20000000U | |
| #define CFA_BRGRAY 0x10000000U | |
| static int cine_read_probe(const AVProbeData *p) | |
| { | |
| int HeaderSize; | |
| if (p->buf[0] == 'C' && p->buf[1] == 'I' && // Type | |
| (HeaderSize = AV_RL16(p->buf + 2)) >= 0x2C && // HeaderSize | |
| AV_RL16(p->buf + 4) <= CC_UNINT && // Compression | |
| AV_RL16(p->buf + 6) <= 1 && // Version | |
| AV_RL32(p->buf + 20) && // ImageCount | |
| AV_RL32(p->buf + 24) >= HeaderSize && // OffImageHeader | |
| AV_RL32(p->buf + 28) >= HeaderSize && // OffSetup | |
| AV_RL32(p->buf + 32) >= HeaderSize) // OffImageOffsets | |
| return AVPROBE_SCORE_MAX; | |
| return 0; | |
| } | |
| static int set_metadata_int(AVDictionary **dict, const char *key, int value, int allow_zero) | |
| { | |
| if (value || allow_zero) { | |
| return av_dict_set_int(dict, key, value, 0); | |
| } | |
| return 0; | |
| } | |
| static int set_metadata_float(AVDictionary **dict, const char *key, float value, int allow_zero) | |
| { | |
| if (value != 0 || allow_zero) { | |
| char tmp[64]; | |
| snprintf(tmp, sizeof(tmp), "%f", value); | |
| return av_dict_set(dict, key, tmp, 0); | |
| } | |
| return 0; | |
| } | |
| static int cine_read_header(AVFormatContext *avctx) | |
| { | |
| AVIOContext *pb = avctx->pb; | |
| AVStream *st; | |
| unsigned int version, compression, offImageHeader, offSetup, offImageOffsets, biBitCount, length, CFA; | |
| int vflip; | |
| char *description; | |
| uint64_t i; | |
| st = avformat_new_stream(avctx, NULL); | |
| if (!st) | |
| return AVERROR(ENOMEM); | |
| st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; | |
| st->codecpar->codec_id = AV_CODEC_ID_RAWVIDEO; | |
| st->codecpar->codec_tag = 0; | |
| /* CINEFILEHEADER structure */ | |
| avio_skip(pb, 4); // Type, Headersize | |
| compression = avio_rl16(pb); | |
| version = avio_rl16(pb); | |
| if (version != 1) { | |
| avpriv_request_sample(avctx, "unknown version %i", version); | |
| return AVERROR_INVALIDDATA; | |
| } | |
| avio_skip(pb, 12); // FirstMovieImage, TotalImageCount, FirstImageNumber | |
| st->duration = avio_rl32(pb); | |
| offImageHeader = avio_rl32(pb); | |
| offSetup = avio_rl32(pb); | |
| offImageOffsets = avio_rl32(pb); | |
| avio_skip(pb, 8); // TriggerTime | |
| /* BITMAPINFOHEADER structure */ | |
| avio_seek(pb, offImageHeader, SEEK_SET); | |
| avio_skip(pb, 4); //biSize | |
| st->codecpar->width = avio_rl32(pb); | |
| st->codecpar->height = avio_rl32(pb); | |
| if (avio_rl16(pb) != 1) // biPlanes | |
| return AVERROR_INVALIDDATA; | |
| biBitCount = avio_rl16(pb); | |
| if (biBitCount != 8 && biBitCount != 16 && biBitCount != 24 && biBitCount != 48) { | |
| avpriv_request_sample(avctx, "unsupported biBitCount %i", biBitCount); | |
| return AVERROR_INVALIDDATA; | |
| } | |
| switch (avio_rl32(pb)) { | |
| case BMP_RGB: | |
| vflip = 0; | |
| break; | |
| case 0x100: /* BI_PACKED */ | |
| st->codecpar->codec_tag = MKTAG('B', 'I', 'T', 0); | |
| vflip = 1; | |
| break; | |
| default: | |
| avpriv_request_sample(avctx, "unknown bitmap compression"); | |
| return AVERROR_INVALIDDATA; | |
| } | |
| avio_skip(pb, 4); // biSizeImage | |
| /* parse SETUP structure */ | |
| avio_seek(pb, offSetup, SEEK_SET); | |
| avio_skip(pb, 140); // FrameRatae16 .. descriptionOld | |
| if (avio_rl16(pb) != 0x5453) | |
| return AVERROR_INVALIDDATA; | |
| length = avio_rl16(pb); | |
| if (length < 0x163C) { | |
| avpriv_request_sample(avctx, "short SETUP header"); | |
| return AVERROR_INVALIDDATA; | |
| } | |
| avio_skip(pb, 616); // Binning .. bFlipH | |
| if (!avio_rl32(pb) ^ vflip) { | |
| st->codecpar->extradata = av_strdup("BottomUp"); | |
| if (!st->codecpar->extradata) { | |
| st->codecpar->extradata_size = 0; | |
| return AVERROR(ENOMEM); | |
| } | |
| st->codecpar->extradata_size = 9; | |
| } | |
| avio_skip(pb, 4); // Grid | |
| avpriv_set_pts_info(st, 64, 1, avio_rl32(pb)); | |
| avio_skip(pb, 20); // Shutter .. bEnableColor | |
| set_metadata_int(&st->metadata, "camera_version", avio_rl32(pb), 0); | |
| set_metadata_int(&st->metadata, "firmware_version", avio_rl32(pb), 0); | |
| set_metadata_int(&st->metadata, "software_version", avio_rl32(pb), 0); | |
| set_metadata_int(&st->metadata, "recording_timezone", avio_rl32(pb), 0); | |
| CFA = avio_rl32(pb); | |
| set_metadata_int(&st->metadata, "brightness", avio_rl32(pb), 1); | |
| set_metadata_int(&st->metadata, "contrast", avio_rl32(pb), 1); | |
| set_metadata_int(&st->metadata, "gamma", avio_rl32(pb), 1); | |
| avio_skip(pb, 12 + 16); // Reserved1 .. AutoExpRect | |
| set_metadata_float(&st->metadata, "wbgain[0].r", av_int2float(avio_rl32(pb)), 1); | |
| set_metadata_float(&st->metadata, "wbgain[0].b", av_int2float(avio_rl32(pb)), 1); | |
| avio_skip(pb, 36); // WBGain[1].. WBView | |
| st->codecpar->bits_per_coded_sample = avio_rl32(pb); | |
| if (compression == CC_RGB) { | |
| if (biBitCount == 8) { | |
| st->codecpar->format = AV_PIX_FMT_GRAY8; | |
| } else if (biBitCount == 16) { | |
| st->codecpar->format = AV_PIX_FMT_GRAY16LE; | |
| } else if (biBitCount == 24) { | |
| st->codecpar->format = AV_PIX_FMT_BGR24; | |
| } else if (biBitCount == 48) { | |
| st->codecpar->format = AV_PIX_FMT_BGR48LE; | |
| } else { | |
| avpriv_request_sample(avctx, "unsupported biBitCount %i", biBitCount); | |
| return AVERROR_INVALIDDATA; | |
| } | |
| } else if (compression == CC_UNINT) { | |
| switch (CFA & 0xFFFFFF) { | |
| case CFA_BAYER: | |
| if (biBitCount == 8) { | |
| st->codecpar->format = AV_PIX_FMT_BAYER_GBRG8; | |
| } else if (biBitCount == 16) { | |
| st->codecpar->format = AV_PIX_FMT_BAYER_GBRG16LE; | |
| } else { | |
| avpriv_request_sample(avctx, "unsupported biBitCount %i", biBitCount); | |
| return AVERROR_INVALIDDATA; | |
| } | |
| break; | |
| case CFA_BAYERFLIP: | |
| if (biBitCount == 8) { | |
| st->codecpar->format = AV_PIX_FMT_BAYER_RGGB8; | |
| } else if (biBitCount == 16) { | |
| st->codecpar->format = AV_PIX_FMT_BAYER_RGGB16LE; | |
| } else { | |
| avpriv_request_sample(avctx, "unsupported biBitCount %i", biBitCount); | |
| return AVERROR_INVALIDDATA; | |
| } | |
| break; | |
| default: | |
| avpriv_request_sample(avctx, "unsupported Color Field Array (CFA) %i", CFA & 0xFFFFFF); | |
| return AVERROR_INVALIDDATA; | |
| } | |
| } else { //CC_LEAD | |
| avpriv_request_sample(avctx, "unsupported compression %i", compression); | |
| return AVERROR_INVALIDDATA; | |
| } | |
| avio_skip(pb, 668); // Conv8Min ... Sensor | |
| set_metadata_int(&st->metadata, "shutter_ns", avio_rl32(pb), 0); | |
| avio_skip(pb, 24); // EDRShutterNs ... ImHeightAcq | |
| #define DESCRIPTION_SIZE 4096 | |
| description = av_malloc(DESCRIPTION_SIZE + 1); | |
| if (!description) | |
| return AVERROR(ENOMEM); | |
| i = avio_get_str(pb, DESCRIPTION_SIZE, description, DESCRIPTION_SIZE + 1); | |
| if (i < DESCRIPTION_SIZE) | |
| avio_skip(pb, DESCRIPTION_SIZE - i); | |
| if (description[0]) | |
| av_dict_set(&st->metadata, "description", description, AV_DICT_DONT_STRDUP_VAL); | |
| else | |
| av_free(description); | |
| avio_skip(pb, 1176); // RisingEdge ... cmUser | |
| set_metadata_int(&st->metadata, "enable_crop", avio_rl32(pb), 1); | |
| set_metadata_int(&st->metadata, "crop_left", avio_rl32(pb), 1); | |
| set_metadata_int(&st->metadata, "crop_top", avio_rl32(pb), 1); | |
| set_metadata_int(&st->metadata, "crop_right", avio_rl32(pb), 1); | |
| set_metadata_int(&st->metadata, "crop_bottom", avio_rl32(pb), 1); | |
| /* parse image offsets */ | |
| avio_seek(pb, offImageOffsets, SEEK_SET); | |
| for (i = 0; i < st->duration; i++) { | |
| int64_t pos = avio_rl64(pb); | |
| if (avio_feof(pb) || pos < 0) | |
| return AVERROR_INVALIDDATA; | |
| av_add_index_entry(st, pos, i, 0, 0, AVINDEX_KEYFRAME); | |
| } | |
| return 0; | |
| } | |
| static int cine_read_packet(AVFormatContext *avctx, AVPacket *pkt) | |
| { | |
| CineDemuxContext *cine = avctx->priv_data; | |
| AVStream *st = avctx->streams[0]; | |
| FFStream *const sti = ffstream(st); | |
| AVIOContext *pb = avctx->pb; | |
| int n, size, ret; | |
| int64_t ret64; | |
| if (cine->pts >= sti->nb_index_entries) | |
| return AVERROR_EOF; | |
| ret64 = avio_seek(pb, sti->index_entries[cine->pts].pos, SEEK_SET); | |
| if (ret64 < 0) | |
| return ret64; | |
| n = avio_rl32(pb); | |
| if (n < 8) | |
| return AVERROR_INVALIDDATA; | |
| avio_skip(pb, n - 8); | |
| size = avio_rl32(pb); | |
| if (avio_feof(pb) || size < 0) | |
| return AVERROR_INVALIDDATA; | |
| if (cine->maxsize && (uint64_t)sti->index_entries[cine->pts].pos + size + n > cine->maxsize) | |
| size = cine->maxsize - sti->index_entries[cine->pts].pos - n; | |
| ret = av_get_packet(pb, pkt, size); | |
| if (ret < 0) | |
| return ret; | |
| if (ret != size) | |
| cine->maxsize = (uint64_t)sti->index_entries[cine->pts].pos + n + ret; | |
| pkt->pts = cine->pts++; | |
| pkt->stream_index = 0; | |
| pkt->flags |= AV_PKT_FLAG_KEY; | |
| return 0; | |
| } | |
| static int cine_read_seek(AVFormatContext *avctx, int stream_index, int64_t timestamp, int flags) | |
| { | |
| CineDemuxContext *cine = avctx->priv_data; | |
| if ((flags & AVSEEK_FLAG_FRAME) || (flags & AVSEEK_FLAG_BYTE)) | |
| return AVERROR(ENOSYS); | |
| if (!(avctx->pb->seekable & AVIO_SEEKABLE_NORMAL)) | |
| return AVERROR(EIO); | |
| cine->pts = timestamp; | |
| return 0; | |
| } | |
| const FFInputFormat ff_cine_demuxer = { | |
| .p.name = "cine", | |
| .p.long_name = NULL_IF_CONFIG_SMALL("Phantom Cine"), | |
| .priv_data_size = sizeof(CineDemuxContext), | |
| .read_probe = cine_read_probe, | |
| .read_header = cine_read_header, | |
| .read_packet = cine_read_packet, | |
| .read_seek = cine_read_seek, | |
| }; |