From patchwork Fri Aug 28 18:45:27 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Anamitra Ghorui X-Patchwork-Id: 21967 Return-Path: X-Original-To: patchwork@ffaux-bg.ffmpeg.org Delivered-To: patchwork@ffaux-bg.ffmpeg.org Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org [79.124.17.100]) by ffaux.localdomain (Postfix) with ESMTP id C5F9C44B46C for ; Fri, 28 Aug 2020 21:45:58 +0300 (EEST) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id B158868AE44; Fri, 28 Aug 2020 21:45:58 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail.teknik.io (mail.teknik.io [5.79.72.163]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 5D864689767 for ; Fri, 28 Aug 2020 21:45:49 +0300 (EEST) dkim-signature: v=1; a=rsa-sha256; d=teknik.io; s=dkim; c=relaxed/relaxed; q=dns/txt; h=From:Subject:Date:Message-ID:To:MIME-Version:Content-Transfer-Encoding:In-Reply-To:References; bh=xfSbUtBpDQMUVzX7ZkkFisBo78x9mFqAOeLD4eNgvkc=; b=bDi3HeArfD+KANjAZyEkTDK2nFIP+BwIxffL6gnwiANjj6Xu3zyTY5603qqh95FwokGQ6JcKzBYhIgfqFFYWEAFUNkrZiwqbx3LNbLKN8yOjfCEh8C0NaNvZem5UL6QT9QZkIdXj/yxdnzCq4FI+/sjf+tf9NeRb0bYaiBJykVNd3heXsMRC8SWVv68et0gZD2zOgDAyOvPpSufGaq08HUpfnx/C6uRvYaxrAzJW4X9EFGT7g/S/NG0QBv UX+5jqni7PPIl/QyLdOWoiZCgmy7p4gJSznYkiDtntj6/B0B/Am3FS9Ro7iS8nmR+9jyWxVKYFt2s6RRBKJfOerOCljA== Received: from localhost.localdomain (Unknown [103.82.156.1]) by mail.teknik.io with ESMTPSA (version=TLSv1.2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128) ; Fri, 28 Aug 2020 11:45:48 -0700 From: Anamitra Ghorui To: ffmpeg-devel@ffmpeg.org Date: Sat, 29 Aug 2020 00:15:27 +0530 Message-Id: <20200828184531.29647-3-aghorui@teknik.io> X-Mailer: git-send-email 2.28.0 In-Reply-To: <20200828184531.29647-1-aghorui@teknik.io> References: <20200828184531.29647-1-aghorui@teknik.io> MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH v7 2/6] avformat: add FLIF demuxing support X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" Signed-off-by: Anamitra Ghorui --- configure | 1 + libavformat/Makefile | 1 + libavformat/allformats.c | 1 + libavformat/flifdec.c | 445 +++++++++++++++++++++++++++++++++++++++ libavformat/version.h | 4 +- 5 files changed, 450 insertions(+), 2 deletions(-) create mode 100644 libavformat/flifdec.c diff --git a/configure b/configure index 564072053b..b99f8ed935 100755 --- a/configure +++ b/configure @@ -3307,6 +3307,7 @@ fifo_muxer_deps="threads" flac_demuxer_select="flac_parser" flv_muxer_select="aac_adtstoasc_bsf" gxf_muxer_select="pcm_rechunk_bsf" +flif_demuxer_select="zlib exif" hds_muxer_select="flv_muxer" hls_muxer_select="mpegts_muxer" hls_muxer_suggest="gcrypt openssl" diff --git a/libavformat/Makefile b/libavformat/Makefile index cbb33fe37c..a105122526 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -192,6 +192,7 @@ OBJS-$(CONFIG_FLAC_DEMUXER) += flacdec.o rawdec.o \ OBJS-$(CONFIG_FLAC_MUXER) += flacenc.o flacenc_header.o \ vorbiscomment.o OBJS-$(CONFIG_FLIC_DEMUXER) += flic.o +OBJS-$(CONFIG_FLIF_DEMUXER) += flifdec.o OBJS-$(CONFIG_FLV_DEMUXER) += flvdec.o OBJS-$(CONFIG_LIVE_FLV_DEMUXER) += flvdec.o OBJS-$(CONFIG_FLV_MUXER) += flvenc.o avc.o diff --git a/libavformat/allformats.c b/libavformat/allformats.c index 0aa9dd7198..d003889b6f 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -150,6 +150,7 @@ extern AVOutputFormat ff_fits_muxer; extern AVInputFormat ff_flac_demuxer; extern AVOutputFormat ff_flac_muxer; extern AVInputFormat ff_flic_demuxer; +extern AVInputFormat ff_flif_demuxer; extern AVInputFormat ff_flv_demuxer; extern AVOutputFormat ff_flv_muxer; extern AVInputFormat ff_live_flv_demuxer; diff --git a/libavformat/flifdec.c b/libavformat/flifdec.c new file mode 100644 index 0000000000..a7272075fd --- /dev/null +++ b/libavformat/flifdec.c @@ -0,0 +1,445 @@ +/* + * FLIF demuxer + * Copyright (c) 2020 Anamitra Ghorui + * + * 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 + * FLIF demuxer. + */ + +#include "avformat.h" +#include "libavutil/common.h" +#include "libavutil/bprint.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/opt.h" +#include "internal.h" +#include "libavcodec/exif.h" + +#include "libavcodec/flif16.h" +#include "libavcodec/flif16_rangecoder.h" + +#include "config.h" + +#if CONFIG_ZLIB +# include +#endif + +/* + * FLIF's reference encoder currently encodes metadata as a raw DEFLATE stream + * (RFC 1951). In order to decode a raw deflate stream using Zlib, inflateInit2 + * must be used with windowBits being between -8 .. -15. + */ +#define ZLIB_WINDOW_BITS -15 +#define BUF_SIZE 4096 + +typedef struct FLIFDemuxContext { +#if CONFIG_ZLIB + z_stream stream; + uint8_t active; +#endif +} FLIFDemuxContext; + + +#if CONFIG_ZLIB +static int flif_inflate(FLIFDemuxContext *s, uint8_t *buf, int buf_size, + uint8_t **out_buf, int *out_buf_size) +{ + int ret; + z_stream *stream = &s->stream; + + if (!s->active) { + s->active = 1; + stream->zalloc = Z_NULL; + stream->zfree = Z_NULL; + stream->opaque = Z_NULL; + stream->avail_in = 0; + stream->next_in = Z_NULL; + ret = inflateInit2(stream, ZLIB_WINDOW_BITS); + + if (ret != Z_OK) + return ret; + + *out_buf_size = buf_size; + *out_buf = av_realloc_f(*out_buf, *out_buf_size, 1); + if (!*out_buf) + return AVERROR(ENOMEM); + } + + stream->next_in = buf; + stream->avail_in = buf_size; + + do { + while (stream->total_out >= (*out_buf_size - 1)) { + *out_buf = av_realloc_f(*out_buf, (*out_buf_size) * 2, 1); + if (!*out_buf) + return AVERROR(ENOMEM); + *out_buf_size *= 2; + } + + stream->next_out = *out_buf + stream->total_out; + stream->avail_out = *out_buf_size - stream->total_out - 1; + + ret = inflate(stream, Z_PARTIAL_FLUSH); + + switch (ret) { + case Z_NEED_DICT: + case Z_DATA_ERROR: + (void)inflateEnd(stream); + return AVERROR_INVALIDDATA; + case Z_MEM_ERROR: + (void)inflateEnd(stream); + return AVERROR(ENOMEM); + } + } while (stream->avail_in > 0); + + if (ret == Z_STREAM_END) { + s->active = 0; + (*out_buf)[stream->total_out] = '\0'; + (void) inflateEnd(stream); + } else + ret = AVERROR(EAGAIN); + + return ret; // Return Z_BUF_ERROR/EAGAIN as long as input is incomplete. +} +#endif + +#if CONFIG_ZLIB && CONFIG_EXIF +static int flif_read_exif(void *logctx, uint8_t *buf, int buf_size, AVDictionary **d) +{ + uint8_t le; + uint32_t temp; + int ret; + GetByteContext gb; + + // Read exif header + if (memcmp("Exif", buf, 4)) + return AVERROR_INVALIDDATA; + + buf += 6; + + // Figure out endianness + if (buf[0] == 'M' && buf[1] == 'M') + le = 0; + else if (buf[0] == 'I' && buf[1] == 'I') + le = 1; + else + return AVERROR_INVALIDDATA; + + buf += 2; + + bytestream2_init(&gb, buf, buf_size - 8); + temp = ff_tget_short(&gb, le); + + // Check TIFF marker + if (temp != 0x002A) + return AVERROR_INVALIDDATA; + + buf += 2; + + if (le) + temp = bytestream2_get_le32(&gb); + else + temp = bytestream2_get_be32(&gb); + + // Subtract read bytes, then skip + bytestream2_skip(&gb, temp - 8); + + ret = ff_exif_decode_ifd(logctx, &gb, le, 0, d); + + return ret; +} +#endif + +static int flif16_probe(const AVProbeData *p) +{ + uint32_t vlist[3] = {0}; + unsigned int count = 0, pos = 0; + + // Magic Number + if (memcmp(p->buf, flif16_header, 4)) { + return 0; + } + + for (int i = 0; i < 2 + (((p->buf[4] >> 4) > 4) ? 1 : 0); i++) { + while (p->buf[5 + pos] > 127) { + if (!(count--)) { + return 0; + } + VARINT_APPEND(vlist[i], p->buf[5 + pos]); + ++pos; + } + VARINT_APPEND(vlist[i], p->buf[5 + pos]); + count = 0; + } + + if (!((vlist[0] + 1) && (vlist[1] + 1))) + return 0; + + if (((p->buf[4] >> 4) > 4) && !(vlist[2] + 2)) + return 0; + + return AVPROBE_SCORE_MAX; +} + +static int flif16_read_header(AVFormatContext *s) +{ +#if CONFIG_ZLIB + FLIFDemuxContext *dc = s->priv_data; +#endif + GetByteContext gb; + FLIF16RangeCoder rc = (FLIF16RangeCoder) {0}; + AVIOContext *pb = s->pb; + AVStream *st; + + int64_t duration = 0; + uint32_t vlist[3] = {0}; + uint32_t flag, animated, temp; + uint32_t bpc = 0; + uint32_t metadata_size = 0; +#if CONFIG_ZLIB + int out_buf_size = 0; + int buf_size = 0; +#endif + unsigned int count = 4; + int ret; + int format; + int segment = 0, i = 0; + uint32_t num_frames; + uint8_t num_planes; + + // Suppress unused variable compiler warning if Zlib is not present. +#if !CONFIG_ZLIB + av_unused uint8_t tag[5] = {0}; +#else + uint8_t tag[5] = {0}; +#endif + + uint8_t buf[BUF_SIZE]; + uint8_t *out_buf = NULL; + uint8_t loops = 0; + +#if !CONFIG_ZLIB + av_log(s, AV_LOG_WARNING, "ffmpeg has not been compiled with Zlib. Metadata may not be decoded.\n"); +#endif + + // Magic Number + if (avio_rl32(pb) != (*((uint32_t *) flif16_header))) { + av_log(s, AV_LOG_ERROR, "bad magic number\n"); + return AVERROR_INVALIDDATA; + } + + st = avformat_new_stream(s, NULL); + if (!st) + return AVERROR(ENOMEM); + flag = avio_r8(pb); + animated = (flag >> 4) > 4; + duration = !animated; + bpc = avio_r8(pb); // Bytes per channel + + num_planes = flag & 0x0F; + + for (int i = 0; i < (2 + animated); i++) { + while ((temp = avio_r8(pb)) > 127) { + if (!(count--)) + return AVERROR_INVALIDDATA; + VARINT_APPEND(vlist[i], temp); + } + VARINT_APPEND(vlist[i], temp); + count = 4; + } + + vlist[0]++; + vlist[1]++; + + if (animated) + vlist[2] += 2; + else + vlist[2] = 1; + + num_frames = vlist[2]; + + while ((temp = avio_r8(pb))) { + // Get metadata identifier + tag[0] = temp; + for(int i = 1; i <= 3; i++) + tag[i] = avio_r8(pb); + + // Read varint + while ((temp = avio_r8(pb)) > 127) { + if (!(count--)) + return AVERROR_INVALIDDATA; + VARINT_APPEND(metadata_size, temp); + } + VARINT_APPEND(metadata_size, temp); + count = 4; + +#if CONFIG_ZLIB + + /* + * Decompression Routines + * There are 3 supported metadata chunks currently in FLIF: eXmp, eXif, + * and iCCp. Currently, iCCp color profiles are not handled. + */ + + if (*((uint32_t *) tag) == MKTAG('i','C','C','P')) { + goto metadata_skip; + } + + while (metadata_size > 0) { + if ((buf_size = avio_read_partial(pb, buf, FFMIN(BUF_SIZE, metadata_size))) < 0) + return buf_size; + metadata_size -= buf_size; + if((ret = flif_inflate(dc, buf, buf_size, &out_buf, &out_buf_size)) < 0 && + ret != AVERROR(EAGAIN)) { + if (ret == AVERROR(ENOMEM)) + return ret; + av_log(s, AV_LOG_ERROR, "could not decode metadata segment: %s\n", tag); + goto metadata_skip; + } + } + + switch (*((uint32_t *) tag)) { + case MKTAG('e', 'X', 'i','f'): +#if CONFIG_EXIF + ret = flif_read_exif(s, out_buf, out_buf_size, &s->metadata); + if (ret < 0) + av_log(s, AV_LOG_WARNING, "metadata may be corrupted\n"); +#endif + break; + + default: + av_dict_set(&s->metadata, tag, out_buf, 0); + break; + } +#else + avio_skip(pb, metadata_size); +#endif + + continue; + +#if CONFIG_ZLIB +metadata_skip: + avio_skip(pb, metadata_size); +#endif + } + + av_freep(&out_buf); + + do { + if ((ret = avio_read_partial(pb, buf, BUF_SIZE)) < 0) + return ret; + bytestream2_init(&gb, buf, ret); + } while (ff_flif16_rac_init(&rc, &gb) < 0); + + while (1) { + switch (segment) { + case 0: + if (bpc == '0') { + bpc = 0; + for (; i < num_planes; i++) { + RAC_GET(&rc, NULL, 1, 15, &temp, FLIF16_RAC_UNI_INT8); + bpc = FFMAX(bpc, (1 << temp) - 1); + } + i = 0; + } else + bpc = (bpc == '1') ? 255 : 65535; + if (num_frames < 2) + goto end; + segment++; + + case 1: + if (num_planes > 3) { + RAC_GET(&rc, NULL, 0, 1, &temp, FLIF16_RAC_UNI_INT8); + } + segment++; + + case 2: + if (num_frames > 1) { + RAC_GET(&rc, NULL, 0, 100, &loops, FLIF16_RAC_UNI_INT8); + } + if (!loops) + loops = 1; + segment++; + + case 3: + if (num_frames > 1) { + for (; i < num_frames; i++) { + temp = 0; + RAC_GET(&rc, NULL, 0, 60000, &(temp), FLIF16_RAC_UNI_INT16); + duration += temp; + } + i = 0; + } else + duration = 1; + goto end; + } + +need_more_data: + if ((ret = avio_read_partial(pb, buf, BUF_SIZE)) < 0) + return ret; + bytestream2_init(&gb, buf, ret); + } + +end: + if (bpc > 65535) { + av_log(s, AV_LOG_ERROR, "depth per channel greater than 16 bits not supported\n"); + return AVERROR_PATCHWELCOME; + } + + format = flif16_out_frame_type[FFMIN(num_planes, 4)][bpc > 255]; + + // The minimum possible delay in a FLIF16 image is 1 millisecond. + // Therefore time base is 10^-3, i.e. 1/1000 + avpriv_set_pts_info(st, 64, 1, 1000); + st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; + st->codecpar->codec_id = AV_CODEC_ID_FLIF16; + st->codecpar->width = vlist[0]; + st->codecpar->height = vlist[1]; + st->codecpar->format = format; + st->duration = duration * loops; + st->start_time = 0; + st->nb_frames = vlist[2]; + st->need_parsing = 1; + + // Jump to start because flif16 decoder needs header data too + if (avio_seek(pb, 0, SEEK_SET) != 0) + return AVERROR(EIO); + return 0; +} + + +static int flif16_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + AVIOContext *pb = s->pb; + int ret; + ret = av_get_packet(pb, pkt, avio_size(pb)); + return ret; +} + +AVInputFormat ff_flif_demuxer = { + .name = "flif", + .long_name = NULL_IF_CONFIG_SMALL("Free Lossless Image Format (FLIF)"), + .priv_data_size = sizeof(FLIFDemuxContext), + .extensions = "flif", + .flags = AVFMT_GENERIC_INDEX|AVFMT_NOTIMESTAMPS, + .read_probe = flif16_probe, + .read_header = flif16_read_header, + .read_packet = flif16_read_packet, +}; diff --git a/libavformat/version.h b/libavformat/version.h index 88876aec79..146db09d1b 100644 --- a/libavformat/version.h +++ b/libavformat/version.h @@ -32,8 +32,8 @@ // Major bumping may affect Ticket5467, 5421, 5451(compatibility with Chromium) // Also please add any ticket numbers that you believe might be affected here #define LIBAVFORMAT_VERSION_MAJOR 58 -#define LIBAVFORMAT_VERSION_MINOR 51 -#define LIBAVFORMAT_VERSION_MICRO 101 +#define LIBAVFORMAT_VERSION_MINOR 52 +#define LIBAVFORMAT_VERSION_MICRO 100 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \ LIBAVFORMAT_VERSION_MINOR, \