[FFmpeg-devel] [PATCH 2/3] avformat/qmagedec: Quram Qmage demuxer
Peter Ross
pross at xvid.org
Thu Nov 21 07:45:41 EET 2024
---
libavformat/Makefile | 1 +
libavformat/allformats.c | 1 +
libavformat/qmagedec.c | 244 +++++++++++++++++++++++++++++++++++++++
3 files changed, 246 insertions(+)
create mode 100644 libavformat/qmagedec.c
diff --git a/libavformat/Makefile b/libavformat/Makefile
index 7ca68a7036..c0ba699985 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -492,6 +492,7 @@ OBJS-$(CONFIG_PP_BNK_DEMUXER) += pp_bnk.o
OBJS-$(CONFIG_PVA_DEMUXER) += pva.o
OBJS-$(CONFIG_PVF_DEMUXER) += pvfdec.o pcm.o
OBJS-$(CONFIG_QCP_DEMUXER) += qcp.o
+OBJS-$(CONFIG_QMAGE_DEMUXER) += qmagedec.o
OBJS-$(CONFIG_QOA_DEMUXER) += qoadec.o
OBJS-$(CONFIG_R3D_DEMUXER) += r3d.o
OBJS-$(CONFIG_RAWVIDEO_DEMUXER) += rawvideodec.o
diff --git a/libavformat/allformats.c b/libavformat/allformats.c
index 445f13f42a..2210d8ea75 100644
--- a/libavformat/allformats.c
+++ b/libavformat/allformats.c
@@ -390,6 +390,7 @@ extern const FFOutputFormat ff_psp_muxer;
extern const FFInputFormat ff_pva_demuxer;
extern const FFInputFormat ff_pvf_demuxer;
extern const FFInputFormat ff_qcp_demuxer;
+extern const FFInputFormat ff_qmage_demuxer;
extern const FFInputFormat ff_qoa_demuxer;
extern const FFInputFormat ff_r3d_demuxer;
extern const FFInputFormat ff_rawvideo_demuxer;
diff --git a/libavformat/qmagedec.c b/libavformat/qmagedec.c
new file mode 100644
index 0000000000..abe055f539
--- /dev/null
+++ b/libavformat/qmagedec.c
@@ -0,0 +1,244 @@
+/*
+ * Quram Qmage image format demuxer
+ * Copyright (c) 2024 Peter Ross
+ *
+ * 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 "avformat.h"
+#include "demux.h"
+#include "internal.h"
+#include "libavcodec/get_bits.h"
+#include "libavutil/intreadwrite.h"
+#include "libavutil/mem.h"
+
+#define QMAGE_MAGIC 0x514d
+#define QVERSION_1_43_LESS 0xb
+
+static int qmage_probe(const AVProbeData *p)
+{
+ if (AV_RB16(p->buf) != QMAGE_MAGIC || !AV_RL16(p->buf + 6) || !AV_RL16(p->buf + 8))
+ return 0;
+
+ return AVPROBE_SCORE_EXTENSION / 4;
+}
+
+/* keyframe alpha size is not stored in the bitstream, so we must parse the
+ * bitstream to determine size
+ */
+static int parse_a9ll_alpha_size(AVFormatContext * s, int width, int height)
+{
+ AVIOContext *pb = s->pb;
+ int len1, len2, ret;
+ uint8_t * data;
+ GetBitContext gb1, gb2;
+ int64_t start = avio_tell(pb);
+
+ if ((width & 7) || (height & 3)) {
+ avpriv_request_sample(s, "unaligned alpha");
+ return AVERROR_PATCHWELCOME;
+ }
+
+ len1 = avio_rl32(pb);
+ len2 = avio_rl32(pb);
+ if (len1 < 8 || len2 < 8 || len1 > len2)
+ return AVERROR_INVALIDDATA;
+
+ len1 -= 8;
+ len2 -= 8;
+ data = av_malloc(len2 + AV_INPUT_BUFFER_PADDING_SIZE);
+ if (!data)
+ return AVERROR(ENOMEM);
+ avio_read(pb, data, len2);
+ if ((ret = init_get_bits8(&gb1, data, len1)) < 0)
+ return ret;
+ if ((ret = init_get_bits8(&gb2, data + len1, len2 - len1)) < 0)
+ return ret;
+
+ for (int y = 0; y < height; y += 4) {
+ for (int x = 0; x < width; x += 8) {
+ int mode = get_bits(&gb1, 2);
+ if (mode < 3) {
+ int cbp = avio_rl16(pb);
+ for (int k = 0; k < 16; k++) {
+ if (!(cbp & (1 << k))) {
+ int nb_bits = get_bits(&gb2, 3);
+ if (nb_bits == 7) {
+ avio_skip(pb, 2);
+ } else {
+ skip_bits(&gb1, nb_bits + 1);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ free(data);
+ return ((avio_tell(pb) + 3) & ~3) - start;
+}
+
+typedef struct {
+ int qversion;
+ int raw_type;
+ int transparency;
+ int mode;
+ int width;
+ int height;
+ int alpha_position;
+ int total_frame_number;
+ int current_frame_number;
+ int animation_delay_time;
+ int animation_no_repeat;
+ int header_size;
+} Header;
+
+static int read_header(AVFormatContext * s, Header * h)
+{
+ AVIOContext *pb = s->pb;
+
+ int magic = avio_rb16(pb);
+ if (avio_feof(pb))
+ return AVERROR_EOF;
+ if (magic != QMAGE_MAGIC) {
+ av_log(s, AV_LOG_ERROR, "unexpected magic 0x%x at 0x%" PRIx64 "\n", magic, avio_tell(pb) - 4);
+ return AVERROR_INVALIDDATA;
+ }
+
+ h->qversion = avio_r8(pb);
+ h->raw_type = avio_r8(pb);
+ switch (h->raw_type) {
+ case 0: //RGB565
+ h->transparency = 0;
+ break;
+ case 3: //RGBA5658
+ case 6: //RGBA
+ h->transparency = 1;
+ break;
+ default:
+ avpriv_request_sample(s, "raw_type=%d\n", h->raw_type);
+ return AVERROR_INVALIDDATA;
+ }
+
+ h->mode = !!(avio_r8(pb) & 0x80);
+ avio_skip(pb, 1);
+ h->width = avio_rl16(pb);
+ h->height = avio_rl16(pb);
+ avio_skip(pb, 2);
+
+ if (h->qversion == QVERSION_1_43_LESS) {
+ if (h->transparency || h->mode)
+ h->alpha_position = avio_rl32(pb);
+ else
+ h->alpha_position = -1;
+
+ } else if (h->qversion > QVERSION_1_43_LESS) {
+ h->alpha_position = avio_rl16(pb);
+ avio_skip(pb, 2);
+ } else {
+ avpriv_request_sample(s, "qversion=0x%x", h->qversion);
+ return AVERROR_INVALIDDATA;
+ }
+
+ if (h->mode) {
+ h->total_frame_number = avio_rl16(pb);
+ h->current_frame_number = avio_rl16(pb);
+ h->animation_delay_time = avio_rl16(pb);
+ h->animation_no_repeat = avio_r8(pb);
+ avio_skip(pb, 1);
+ h->header_size = 24;
+ } else {
+ h->total_frame_number = h->current_frame_number = 1;
+ h->header_size = h->transparency ? 16 : 12;
+ }
+
+ if (h->qversion > QVERSION_1_43_LESS) {
+ if (!h->mode || h->current_frame_number <= 1)
+ h->alpha_position *= 4;
+ }
+
+ if (h->mode) {
+ if (h->alpha_position <= h->header_size)
+ return AVERROR_INVALIDDATA;
+
+ if (h->transparency) {
+ int alpha_size;
+ avio_seek(pb, h->alpha_position - h->header_size, SEEK_CUR);
+ if (h->current_frame_number == 1) {
+ alpha_size = parse_a9ll_alpha_size(s, h->width, h->height);
+ } else {
+ alpha_size = avio_rl32(pb);
+ if (alpha_size < 4)
+ return AVERROR_INVALIDDATA;
+ }
+ return h->alpha_position + alpha_size;
+ } else {
+ return h->alpha_position;
+ }
+ }
+
+ return avio_size(pb);
+}
+
+static int qmage_read_header(AVFormatContext *s)
+{
+ Header h;
+ int ret;
+ AVStream * st;
+
+ ret = read_header(s, &h);
+ if (ret < 0)
+ return ret;
+
+ st = avformat_new_stream(s, NULL);
+ if (!st)
+ return AVERROR(ENOMEM);
+ st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
+ st->codecpar->codec_id = AV_CODEC_ID_QMAGE;
+ st->codecpar->width = h.width;
+ st->codecpar->height = h.height;
+ st->nb_frames = h.total_frame_number;
+ avpriv_set_pts_info(st, 64, 1, 15);
+
+ avio_seek(s->pb, 0, SEEK_SET);
+ return 0;
+}
+
+static int qmage_read_packet(AVFormatContext *s, AVPacket *pkt)
+{
+ AVIOContext *pb = s->pb;
+ Header h;
+ uint64_t pos;
+ int size;
+
+ pos = avio_tell(pb);
+ size = read_header(s, &h);
+ if (size < 0)
+ return size;
+
+ avio_seek(pb, pos, SEEK_SET);
+ return av_get_packet(pb, pkt, size);
+}
+
+const FFInputFormat ff_qmage_demuxer = {
+ .p.name = "qmage",
+ .p.long_name = NULL_IF_CONFIG_SMALL("Quram Qmage"),
+ .p.flags = AVFMT_GENERIC_INDEX,
+ .read_probe = qmage_probe,
+ .read_header = qmage_read_header,
+ .read_packet = qmage_read_packet,
+};
--
2.45.2
-- Peter
(A907 E02F A6E5 0CD2 34CD 20D2 6760 79C5 AC40 DD6B)
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 195 bytes
Desc: not available
URL: <https://ffmpeg.org/pipermail/ffmpeg-devel/attachments/20241121/3f24b750/attachment.sig>
More information about the ffmpeg-devel
mailing list