@@ -2,6 +2,9 @@ The last version increases of all libraries were on 2026-06-23.
API changes, most recent first:
+2026-10-xx - xxxxxxxxxx - lavc 63.17.100 - codec_id.h
+ Add AV_CODEC_ID_RW2.
+
2026-09-xx - xxxxxxxxxx - lavfi 12.5.100 - avfilter.h buffersrc.h buffersink.h
Add AVFilterLink.chroma_location, AVBufferSrcParameters.chroma_location and
av_buffersink_get_chroma_location().
@@ -2001,6 +2001,14 @@ static const AVCodecDescriptor codec_descriptors[] = {
.profiles = NULL_IF_CONFIG_SMALL(ff_astc_profiles),
.mime_types= MT("image/astc"),
},
+ {
+ .id = AV_CODEC_ID_RW2,
+ .type = AVMEDIA_TYPE_VIDEO,
+ .name = "rw2",
+ .long_name = NULL_IF_CONFIG_SMALL("Panasonic RW2"),
+ .props = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSLESS,
+ .mime_types= MT("image/x-panasonic-rw2"),
+ },
/* various PCM "codecs" */
{
@@ -325,6 +325,7 @@ enum AVCodecID {
AV_CODEC_ID_JPEGXS,
AV_CODEC_ID_WEBP_ANIM,
AV_CODEC_ID_ASTC,
+ AV_CODEC_ID_RW2,
/* various PCM "codecs" */
AV_CODEC_ID_FIRST_AUDIO = 0x10000, ///< A dummy id pointing at the start of audio codecs
@@ -29,7 +29,7 @@
#include "version_major.h"
-#define LIBAVCODEC_VERSION_MINOR 16
+#define LIBAVCODEC_VERSION_MINOR 17
#define LIBAVCODEC_VERSION_MICRO 100
#define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
--
2.52.0
>From 4aa9894fab7f107f02df29392455269f6d685c22 Mon Sep 17 00:00:00 2001
From: Lynne <dev@lynne.ee>
Date: Fri, 2 Oct 2026 13:45:34 +0900
Subject: [PATCH 2/8] lavf: add a Panasonic RW2 demuxer
Exports IFD0 as extradata and reads only the raw stripes into the packet,
skipping the embedded previews.
---
libavformat/Makefile | 1 +
libavformat/allformats.c | 1 +
libavformat/rw2dec.c | 187 +++++++++++++++++++++++++++++++++++++++
libavformat/version.h | 2 +-
4 files changed, 190 insertions(+), 1 deletion(-)
create mode 100644 libavformat/rw2dec.c
@@ -556,6 +556,7 @@ OBJS-$(CONFIG_RTP_MUXER) += rtp.o \
rtpenc_xiph.o
OBJS-$(CONFIG_RTSP_DEMUXER) += rtsp.o rtspdec.o httpauth.o
OBJS-$(CONFIG_RTSP_MUXER) += rtsp.o rtspenc.o httpauth.o
+OBJS-$(CONFIG_RW2_DEMUXER) += rw2dec.o
OBJS-$(CONFIG_S337M_DEMUXER) += s337m.o spdif.o
OBJS-$(CONFIG_SAMI_DEMUXER) += samidec.o subtitles.o
OBJS-$(CONFIG_SAP_DEMUXER) += sapdec.o
@@ -424,6 +424,7 @@ extern const FFOutputFormat ff_rtp_muxer;
extern const FFOutputFormat ff_rtp_mpegts_muxer;
extern const FFInputFormat ff_rtsp_demuxer;
extern const FFOutputFormat ff_rtsp_muxer;
+extern const FFInputFormat ff_rw2_demuxer;
extern const FFInputFormat ff_s337m_demuxer;
extern const FFInputFormat ff_sami_demuxer;
extern const FFInputFormat ff_sap_demuxer;
new file mode 100644
@@ -0,0 +1,187 @@
+/*
+ * Panasonic RW2 demuxer
+ * Copyright (c) 2026 Lynne <dev@lynne.ee>
+ *
+ * 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
+ */
+
+/*
+ * Extradata is the start of the file, up to IFD0 and the raw tag values.
+ * The packet spans the raw data stripes, skipping the embedded previews.
+ */
+
+#include "libavutil/intreadwrite.h"
+#include "libavutil/pixfmt.h"
+
+#include "avformat.h"
+#include "avio_internal.h"
+#include "demux.h"
+#include "internal.h"
+
+#define RW2_MAX_STRIPES 5
+
+typedef struct RW2DemuxContext {
+ int64_t data_start, data_end;
+ int done;
+} RW2DemuxContext;
+
+static int rw2_probe(const AVProbeData *p)
+{
+ if (AV_RL32(p->buf) == MKTAG('I', 'I', 'U', '\0') &&
+ AV_RL32(p->buf + 4) >= 8)
+ return AVPROBE_SCORE_MAX;
+ return 0;
+}
+
+static int rw2_read_header(AVFormatContext *s)
+{
+ RW2DemuxContext *rw2 = s->priv_data;
+ AVIOContext *pb = s->pb;
+ int64_t offsets_pos = 0, sizes_pos = 0, header_end, size;
+ int width = 0, height = 0, cfa = 0, bpp = 0, nb_stripes = 0;
+ int nb_entries, ret;
+ AVStream *st;
+
+ static const enum AVPixelFormat cfa_fmts[] = {
+ AV_PIX_FMT_BAYER_RGGB16, AV_PIX_FMT_BAYER_GRBG16,
+ AV_PIX_FMT_BAYER_GBRG16, AV_PIX_FMT_BAYER_BGGR16,
+ };
+
+ avio_skip(pb, 4);
+ int64_t ifd = avio_rl32(pb);
+ if ((ret = avio_seek(pb, ifd, SEEK_SET)) < 0)
+ return ret;
+
+ nb_entries = avio_rl16(pb);
+ if (nb_entries > 512)
+ return AVERROR_INVALIDDATA;
+ header_end = ifd + 2 + 12*nb_entries + 4;
+
+ for (int i = 0; i < nb_entries; i++) {
+ int tag = avio_rl16(pb);
+ int type = avio_rl16(pb);
+ uint32_t cnt = avio_rl32(pb);
+ uint32_t val = avio_rl32(pb);
+ uint32_t v = type == 3 ? val & 0xFFFF : val;
+
+ switch (tag) {
+ case 0x02: width = v; break;
+ case 0x03: height = v; break;
+ case 0x09: cfa = v; break;
+ case 0x0A: bpp = v; break;
+ case 0x42: nb_stripes = FFMIN(v, RW2_MAX_STRIPES); break;
+ case 0x44: offsets_pos = val; break;
+ case 0x46: sizes_pos = val; break;
+ }
+
+ /* Out-of-line values of the RawFormat 8 tags */
+ if (tag >= 0x39 && tag <= 0x48 && type == 7 && cnt > 4)
+ header_end = FFMAX(header_end, (int64_t)val + cnt);
+ }
+
+ if (avio_feof(pb) || !nb_stripes || !offsets_pos || !sizes_pos ||
+ header_end > (1 << 20))
+ return AVERROR_INVALIDDATA;
+
+ rw2->data_start = INT64_MAX;
+ rw2->data_end = 0;
+ for (int i = 0; i < nb_stripes; i++) {
+ int64_t offset, bits;
+ if ((ret = avio_seek(pb, offsets_pos + 2 + 4*i, SEEK_SET)) < 0)
+ return ret;
+ offset = avio_rl32(pb);
+ if ((ret = avio_seek(pb, sizes_pos + 2 + 4*i, SEEK_SET)) < 0)
+ return ret;
+ bits = avio_rl32(pb);
+ rw2->data_start = FFMIN(rw2->data_start, offset);
+ rw2->data_end = FFMAX(rw2->data_end, offset + ((bits + 7) >> 3));
+ }
+
+ size = avio_size(pb);
+ if (rw2->data_end <= rw2->data_start ||
+ rw2->data_end - rw2->data_start > INT_MAX - AV_INPUT_BUFFER_PADDING_SIZE ||
+ (size > 0 && rw2->data_end > size))
+ return AVERROR_INVALIDDATA;
+
+ st = avformat_new_stream(s, NULL);
+ if (!st)
+ return AVERROR(ENOMEM);
+
+ if ((ret = avio_seek(pb, 0, SEEK_SET)) < 0)
+ return ret;
+ if ((ret = ff_get_extradata(s, st->codecpar, pb, header_end)) < 0)
+ return ret;
+
+ st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
+ st->codecpar->codec_id = AV_CODEC_ID_RW2;
+ st->codecpar->width = width;
+ st->codecpar->height = height;
+ st->codecpar->bits_per_raw_sample = bpp;
+ st->codecpar->color_trc = AVCOL_TRC_LINEAR;
+ if (cfa >= 1 && cfa <= 4)
+ st->codecpar->format = cfa_fmts[cfa - 1];
+
+ /* A known rate lets probing finish without reading the payload */
+ st->avg_frame_rate = st->r_frame_rate = (AVRational){ 1, 1 };
+ st->duration = 1;
+ avpriv_set_pts_info(st, 64, 1, 1);
+
+ return 0;
+}
+
+static int rw2_read_packet(AVFormatContext *s, AVPacket *pkt)
+{
+ RW2DemuxContext *rw2 = s->priv_data;
+ int64_t ret;
+
+ if (rw2->done)
+ return AVERROR_EOF;
+
+ if ((ret = avio_seek(s->pb, rw2->data_start, SEEK_SET)) < 0)
+ return ret;
+
+ ret = av_get_packet(s->pb, pkt, rw2->data_end - rw2->data_start);
+ if (ret < 0)
+ return ret;
+
+ rw2->done = 1;
+ pkt->stream_index = 0;
+ pkt->flags |= AV_PKT_FLAG_KEY;
+
+ return 0;
+}
+
+static int rw2_read_seek(AVFormatContext *s, int stream_index,
+ int64_t timestamp, int flags)
+{
+ RW2DemuxContext *rw2 = s->priv_data;
+ rw2->done = 0;
+ return 0;
+}
+
+const FFInputFormat ff_rw2_demuxer = {
+ .p.name = "rw2",
+ .p.long_name = NULL_IF_CONFIG_SMALL("Panasonic RW2"),
+ .p.mime_type = "image/x-panasonic-rw2",
+ .p.extensions = "rw2",
+ .p.flags = AVFMT_NOTIMESTAMPS,
+ .priv_data_size = sizeof(RW2DemuxContext),
+ .read_probe = rw2_probe,
+ .read_header = rw2_read_header,
+ .read_packet = rw2_read_packet,
+ .read_seek = rw2_read_seek,
+};
@@ -31,7 +31,7 @@
#include "version_major.h"
-#define LIBAVFORMAT_VERSION_MINOR 7
+#define LIBAVFORMAT_VERSION_MINOR 8
#define LIBAVFORMAT_VERSION_MICRO 100
#define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
--
2.52.0
>From e1dd3dd510ac2054c965eea27ce94ef4608dbe73 Mon Sep 17 00:00:00 2001
From: Lynne <dev@lynne.ee>
Date: Fri, 2 Oct 2026 13:45:34 +0900
Subject: [PATCH 3/8] lavu/frame: allow left cropping of formats with
multi-byte pixels
av_frame_apply_cropping() returned AVERROR_BUG when the plane offset was
more aligned than crop_left, which is always the case for single-plane
formats with more than a byte per pixel (gray16, rgb48, 16-bit Bayer,
p010). The rounding that follows already handles it.
---
libavutil/frame.c | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
@@ -803,11 +803,8 @@ int av_frame_apply_cropping(AVFrame *frame, int flags)
min_log2_align = FFMIN(log2_align, min_log2_align);
}
- /* we assume, and it should always be true, that the data alignment is
- * related to the cropping alignment by a constant power-of-2 factor */
- if (log2_crop_align < min_log2_align)
- return AVERROR_BUG;
-
+ /* The data alignment is the cropping alignment scaled by a power of 2,
+ * up for multi-byte pixels and down for subsampled planes */
if (min_log2_align < 5 && log2_crop_align != INT_MAX) {
frame->crop_left &= ~((1 << (5 + log2_crop_align - min_log2_align)) - 1);
ret = calc_cropping_offsets(offsets, frame, desc);
--
2.52.0
>From 6b49660e45fee879c037f9b3ca3b45b501e0ce8e Mon Sep 17 00:00:00 2001
From: Lynne <dev@lynne.ee>
Date: Fri, 2 Oct 2026 13:45:34 +0900
Subject: [PATCH 4/8] lavc: add a Panasonic RW2 decoder
Supports RawFormat 8, used by the S1II, S5II and GH6. Outputs the Bayer
mosaic, with the sensor borders as cropping and the black and white
levels as raw color params side data.
Bit-exact with LibRaw on 801 S1II files, at 3x its speed per thread.
---
Changelog | 1 +
doc/general_contents.texi | 2 +
libavcodec/Makefile | 1 +
libavcodec/allcodecs.c | 1 +
libavcodec/rw2.h | 94 ++++++
libavcodec/rw2dec.c | 580 ++++++++++++++++++++++++++++++++++++++
6 files changed, 679 insertions(+)
create mode 100644 libavcodec/rw2.h
create mode 100644 libavcodec/rw2dec.c
@@ -15,6 +15,7 @@ version <next>:
- H.264 data partitioning support
- DSD (dsd_msbf) encoder
- Rhetorex ADPCM decoder
+- Panasonic RW2 decoder and demuxer
version 9.0:
@@ -947,6 +947,8 @@ following image formats are supported:
@tab V.Flash PTX format
@item QOI @tab X @tab X
@tab Quite OK Image format
+@item RW2 @tab @tab X
+ @tab Panasonic raw image, RawFormat 8 only
@item SGI @tab X @tab X
@tab SGI RGB image format
@item Sun Rasterfile @tab X @tab X
@@ -697,6 +697,7 @@ OBJS-$(CONFIG_RV20_ENCODER) += rv20enc.o
OBJS-$(CONFIG_RV30_DECODER) += rv30.o rv34.o rv30dsp.o
OBJS-$(CONFIG_RV40_DECODER) += rv40.o rv34.o rv40dsp.o
OBJS-$(CONFIG_RV60_DECODER) += rv60dec.o rv60dsp.o
+OBJS-$(CONFIG_RW2_DECODER) += rw2dec.o
OBJS-$(CONFIG_SAMI_DECODER) += samidec.o ass.o htmlsubtitles.o
OBJS-$(CONFIG_S302M_DECODER) += s302m.o
OBJS-$(CONFIG_S302M_ENCODER) += s302menc.o
@@ -304,6 +304,7 @@ extern const FFCodec ff_rv20_decoder;
extern const FFCodec ff_rv30_decoder;
extern const FFCodec ff_rv40_decoder;
extern const FFCodec ff_rv60_decoder;
+extern const FFCodec ff_rw2_decoder;
extern const FFCodec ff_s302m_encoder;
extern const FFCodec ff_s302m_decoder;
extern const FFCodec ff_sanm_decoder;
new file mode 100644
@@ -0,0 +1,94 @@
+/*
+ * Panasonic RW2 raw image decoder
+ * Copyright (c) 2026 Lynne <dev@lynne.ee>
+ *
+ * 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
+ */
+
+#ifndef AVCODEC_RW2_H
+#define AVCODEC_RW2_H
+
+#include <stdint.h>
+
+#include "libavutil/frame.h"
+#include "libavutil/pixfmt.h"
+
+#include "vlc.h"
+
+#define RW2_MAX_STRIPES 5
+#define RW2_JOINT_CATS 13 /* categories with a code per magnitude, 2^k each */
+#define RW2_MAX_CODES ((1 << RW2_JOINT_CATS) + 17)
+
+typedef struct RW2Stripe {
+ uint32_t offset; /* bytes, from the start of the file */
+ uint32_t size_bits;
+ uint32_t left;
+ uint32_t width;
+ uint32_t height;
+} RW2Stripe;
+
+/* Coding parameters, tags 0x39 - 0x41 */
+typedef struct RW2Coding {
+ uint32_t gamma_shift[6]; /* 0x39 */
+ uint16_t gamma_point[6]; /* 0x3A */
+ int datamax; /* 0x3B */
+ int initial[4]; /* 0x3C - 0x3F */
+ uint8_t code_len[17]; /* 0x40, Huffman code per magnitude category */
+ uint16_t code_val[17];
+ uint8_t code_shift[17]; /* 0x41, magnitude bits left out */
+} RW2Coding;
+
+/* IFD0 contents, RawFormat 8 */
+typedef struct RW2Header {
+ int raw_width, raw_height;
+ int border[4]; /* top, left, bottom, right */
+ int cfa;
+ int bpp;
+ int raw_format;
+ int orientation;
+ int black[3];
+ int linearity[3];
+
+ RW2Coding c;
+
+ int nb_stripes;
+ RW2Stripe stripes[RW2_MAX_STRIPES];
+} RW2Header;
+
+typedef struct RW2Code {
+ uint8_t len;
+ uint16_t code;
+ int16_t sym;
+} RW2Code;
+
+typedef struct RW2DecContext {
+ AVFrame *frame;
+ void *hwaccel_picture_private;
+ enum AVPixelFormat pix_fmt;
+
+ RW2Header h;
+
+ const uint8_t *payload;
+ uint32_t payload_offset; /* file offset of payload[0] */
+
+ VLC vlc;
+ RW2Code codes[RW2_MAX_CODES];
+ uint16_t gamma[1 << 16];
+ int use_gamma;
+} RW2DecContext;
+
+#endif /* AVCODEC_RW2_H */
new file mode 100644
@@ -0,0 +1,580 @@
+/*
+ * Panasonic RW2 raw image decoder
+ * Copyright (c) 2026 Lynne <dev@lynne.ee>
+ *
+ * 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
+ * Panasonic RW2 decoder, RawFormat 8 only (S1II, S5II, GH6).
+ * Bit-exact with LibRaw's pana8.cpp, quirks included.
+ *
+ * Input is either the whole file as one packet, or the file's IFD0 prefix
+ * as extradata with a packet starting at the first stripe (the demuxer).
+ */
+
+#include "libavutil/raw_color_params.h"
+#include "libavutil/reverse.h"
+
+#define BITSTREAM_LE
+#include "avcodec.h"
+#include "bitstream.h"
+#include "bytestream.h"
+#include "codec_internal.h"
+#include "decode.h"
+#include "exif.h"
+#include "hwaccel_internal.h"
+#include "thread.h"
+#include "tiff_common.h"
+
+#include "rw2.h"
+
+#define RW2_VLC_BITS 11
+#define RW2_JOINT_BITS 16 /* longest code with its magnitude folded in */
+#define RW2_SYM_DELTA 0x2000 /* joint symbols: delta + RW2_SYM_DELTA */
+#define RW2_SYM_ESC 0x4000 /* other categories: RW2_SYM_ESC + category */
+
+static av_always_inline int get_delta(const RW2DecContext *s,
+ BitstreamContext *bc, int *delta)
+{
+ int sym = bits_read_vlc(bc, s->vlc.table, RW2_VLC_BITS, 2);
+ if (sym < RW2_SYM_ESC) {
+ if (sym < 0)
+ return AVERROR_INVALIDDATA;
+ *delta = sym - RW2_SYM_DELTA;
+ return 0;
+ }
+
+ /* Escapes read k - shift magnitude bits, MSB-first, the sign leading */
+ int k = sym - RW2_SYM_ESC;
+ int shift = s->h.c.code_shift[k], extra = k - shift;
+ int d = 0;
+ if (extra > 0) {
+ unsigned v = bits_read(bc, extra);
+ unsigned m = (ff_reverse[v & 0xFF] << 8 | ff_reverse[v >> 8]) >> (16 - extra);
+ d = (m << shift) & 0xFFFF;
+ if (!(v & 1))
+ d += !shift - (1 << k);
+ } else if (k && !bits_peek(bc, 1)) {
+ /* No magnitude, but LibRaw still tests the next bit for the sign */
+ d = !shift - (1 << k);
+ }
+
+ *delta = d + (shift ? 1 << (shift - 1) : 0);
+ return 0;
+}
+
+static int gamma_curve(const RW2Coding *c, uint32_t v)
+{
+ int seg = 0;
+ if (v >= c->gamma_point[1]) {
+ seg = 1;
+ if (v >= c->gamma_point[2]) {
+ seg = 2;
+ if (v >= c->gamma_point[3]) {
+ seg = 3;
+ if (v >= c->gamma_point[4])
+ seg = v >= c->gamma_point[5] ? 5 : 4;
+ }
+ }
+ }
+
+ /* LibRaw only keeps the second half of each 0x3A pair, which makes the
+ * output offset of every segment 0 */
+ uint32_t sh = c->gamma_shift[seg];
+ uint32_t r = v - c->gamma_point[seg];
+ if ((sh & 0x1F) == 31)
+ r = seg == 5 ? 0xFFFF : 0;
+ else if (sh & 0x10)
+ r <<= sh & 0xF;
+ else if ((sh & 0x1F) == 15)
+ r = 0;
+ else if (sh & 0x1F)
+ r = (r + (1 << ((sh & 0x1F) - 1))) >> (sh & 0x1F);
+
+ return FFMIN(r, c->datamax);
+}
+
+static int build_tables(RW2DecContext *s)
+{
+ const RW2Coding *c = &s->h.c;
+ int nb = 0, ret;
+
+ /* A code per magnitude where both fit and nothing is shifted, the rest escape */
+ for (int k = 0; k < 17; k++) {
+ int len = c->code_len[k], code = c->code_val[k];
+ if (!len)
+ continue;
+ if (c->code_shift[k] || k >= RW2_JOINT_CATS || len + k > RW2_JOINT_BITS) {
+ s->codes[nb++] = (RW2Code){ len, code, RW2_SYM_ESC + k };
+ continue;
+ }
+ for (int m = 0; m < (1 << k); m++) {
+ int d = k && !(m >> (k - 1)) ? m + 1 - (1 << k) : m;
+ s->codes[nb++] = (RW2Code){ len + k, code << k | m, RW2_SYM_DELTA + d };
+ }
+ }
+
+ ff_vlc_free(&s->vlc);
+ ret = ff_vlc_init_sparse(&s->vlc, RW2_VLC_BITS, nb,
+ &s->codes[0].len, sizeof(*s->codes), 1,
+ &s->codes[0].code, sizeof(*s->codes), 2,
+ &s->codes[0].sym, sizeof(*s->codes), 2,
+ VLC_INIT_OUTPUT_LE);
+ if (ret < 0)
+ return ret;
+
+ /* The gamma LUT, an identity on every file seen so far */
+ s->use_gamma = 0;
+ for (int i = 0; i <= c->datamax; i++) {
+ s->gamma[i] = gamma_curve(c, i);
+ s->use_gamma |= s->gamma[i] != i;
+ }
+
+ return 0;
+}
+
+static int decode_stripe(AVCodecContext *avctx, void *tdata,
+ int jobnr, int threadnr)
+{
+ RW2DecContext *s = avctx->priv_data;
+ AVFrame *frame = tdata;
+ const RW2Stripe *st = &s->h.stripes[jobnr];
+ const ptrdiff_t stride = frame->linesize[0];
+ const uint16_t *gamma = s->use_gamma ? s->gamma : NULL;
+ const int datamax = s->h.c.datamax;
+ int line_base[4], cur[4];
+ BitstreamContext bc;
+ int ret;
+
+ ret = bits_init(&bc, s->payload + (st->offset - s->payload_offset),
+ st->size_bits);
+ if (ret < 0)
+ return ret;
+
+ for (int i = 0; i < 4; i++)
+ line_base[i] = s->h.c.initial[i];
+
+ for (int y = 0; y < st->height; y += 2) {
+ uint16_t *dst0 = (uint16_t *)(frame->data[0] + y*stride) + st->left;
+ uint16_t *dst1 = (uint16_t *)((uint8_t *)dst0 + stride);
+
+ /* Each sample predicts from the same position in the previous 2x2 */
+ memcpy(cur, line_base, sizeof(cur));
+ for (int x = 0; x < st->width; x += 2) {
+ /* Coding order: top-left, bottom-left, top-right, bottom-right */
+ static const uint8_t slot[4] = { 0, 2, 1, 3 };
+ for (int i = 0; i < 4; i++) {
+ int delta, j = slot[i];
+ if ((ret = get_delta(s, &bc, &delta)) < 0)
+ return ret;
+ cur[j] = av_clip(cur[j] + delta, 0, datamax);
+ }
+
+ if (gamma) {
+ dst0[x] = gamma[cur[0]]; dst0[x + 1] = gamma[cur[1]];
+ dst1[x] = gamma[cur[2]]; dst1[x + 1] = gamma[cur[3]];
+ } else {
+ dst0[x] = cur[0]; dst0[x + 1] = cur[1];
+ dst1[x] = cur[2]; dst1[x + 1] = cur[3];
+ }
+
+ if (!x)
+ memcpy(line_base, cur, sizeof(line_base));
+ }
+
+ if (bits_left(&bc) < 0)
+ return AVERROR_INVALIDDATA;
+ }
+
+ return 0;
+}
+
+static int read_array_count(GetByteContext *gb, unsigned type, unsigned count,
+ unsigned expected, int max)
+{
+ int n;
+ if (type != AV_TIFF_UNDEFINED || count != expected)
+ return 0;
+ n = bytestream2_get_le16(gb);
+ return FFMIN(n, max);
+}
+
+/* Tag layouts and clamps follow LibRaw */
+static int parse_header(RW2Header *h, const uint8_t *data, int size)
+{
+ RW2Coding *c = &h->c;
+ GetByteContext gb;
+ int nb_entries;
+
+ memset(h, 0, sizeof(*h));
+
+ bytestream2_init(&gb, data, size);
+ if (bytestream2_get_le32(&gb) != MKTAG('I', 'I', 'U', '\0'))
+ return AVERROR_INVALIDDATA;
+ bytestream2_seek(&gb, bytestream2_get_le32(&gb), SEEK_SET);
+
+ nb_entries = bytestream2_get_le16(&gb);
+ if (nb_entries > 512 || bytestream2_get_bytes_left(&gb) < nb_entries*12)
+ return AVERROR_INVALIDDATA;
+
+ for (int i = 0; i < nb_entries; i++) {
+ unsigned tag, type, count, n, v;
+ int next;
+
+ if (ff_tread_tag(&gb, 1, &tag, &type, &count, &next) < 0)
+ goto next;
+
+ switch (tag) {
+ case 0x02: h->raw_width = ff_tget(&gb, type, 1); break;
+ case 0x03: h->raw_height = ff_tget(&gb, type, 1); break;
+ case 0x04: case 0x05: case 0x06: case 0x07:
+ h->border[tag - 0x04] = ff_tget(&gb, type, 1);
+ break;
+ case 0x09: h->cfa = ff_tget(&gb, type, 1); break;
+ case 0x0A: h->bpp = ff_tget(&gb, type, 1); break;
+ case 0x0E: case 0x0F: case 0x10:
+ h->linearity[tag - 0x0E] = ff_tget(&gb, type, 1);
+ break;
+ case 0x1C: case 0x1D: case 0x1E:
+ h->black[tag - 0x1C] = ff_tget(&gb, type, 1);
+ break;
+ case 0x2D: h->raw_format = ff_tget(&gb, type, 1); break;
+ case 0x39:
+ n = read_array_count(&gb, type, count, 26, 6);
+ for (int j = 0; j < n; j++)
+ c->gamma_shift[j] = bytestream2_get_le32(&gb);
+ break;
+ case 0x3A:
+ n = read_array_count(&gb, type, count, 26, 6);
+ for (int j = 0; j < n; j++) {
+ bytestream2_skip(&gb, 2);
+ c->gamma_point[j] = bytestream2_get_le16(&gb);
+ }
+ break;
+ case 0x3B: c->datamax = ff_tget(&gb, type, 1) & 0xFFFF; break;
+ case 0x3C: case 0x3D: case 0x3E: case 0x3F:
+ c->initial[tag - 0x3C] = ff_tget(&gb, type, 1) & 0xFFFF;
+ break;
+ case 0x40:
+ n = read_array_count(&gb, type, count, 70, 17);
+ for (int k = 0; k < n; k++) {
+ v = bytestream2_get_le16(&gb);
+ int len = FFMIN(v, 16);
+ v = bytestream2_get_le16(&gb);
+ int code = FFMIN(v, 0xFFF) & ((1 << len) - 1);
+
+ /* LibRaw takes the first match, drop the codes it never reaches */
+ for (int j = 0; j < k && len; j++)
+ if (c->code_len[j] && c->code_len[j] <= len &&
+ code >> (len - c->code_len[j]) == c->code_val[j])
+ len = 0;
+ c->code_len[k] = len;
+ c->code_val[k] = code;
+ }
+ break;
+ case 0x41:
+ n = read_array_count(&gb, type, count, 36, 17);
+ for (int j = 0; j < n; j++) {
+ v = bytestream2_get_le16(&gb);
+ c->code_shift[j] = FFMIN(v, 64) & 0x1F;
+ }
+ break;
+ case 0x42:
+ v = ff_tget(&gb, type, 1);
+ h->nb_stripes = FFMIN(v, RW2_MAX_STRIPES);
+ break;
+ case 0x44: case 0x45: case 0x46:
+ n = read_array_count(&gb, type, count, 50, RW2_MAX_STRIPES);
+ for (int j = 0; j < n; j++) {
+ v = bytestream2_get_le32(&gb);
+ if (tag == 0x44)
+ h->stripes[j].offset = v;
+ else if (tag == 0x45)
+ h->stripes[j].left = v;
+ else
+ h->stripes[j].size_bits = v;
+ }
+ break;
+ case 0x47: case 0x48:
+ n = read_array_count(&gb, type, count, 26, RW2_MAX_STRIPES);
+ for (int j = 0; j < n; j++) {
+ v = bytestream2_get_le16(&gb);
+ if (tag == 0x47)
+ h->stripes[j].width = v;
+ else
+ h->stripes[j].height = v;
+ }
+ break;
+ case 0x112: h->orientation = ff_tget(&gb, type, 1); break;
+ }
+
+next:
+ bytestream2_seek(&gb, next, SEEK_SET);
+ }
+
+ return 0;
+}
+
+static int check_stripes(AVCodecContext *avctx, const RW2Header *h,
+ uint32_t payload_offset, int payload_size)
+{
+ uint32_t left = 0;
+
+ if (h->nb_stripes < 1)
+ return AVERROR_INVALIDDATA;
+
+ /* Stripes must tile the image exactly */
+ for (int i = 0; i < h->nb_stripes; i++) {
+ const RW2Stripe *st = &h->stripes[i];
+ if (st->left != left || st->height != h->raw_height ||
+ !st->width || (st->width & 1) || st->width > h->raw_width - left ||
+ !st->size_bits || st->size_bits > INT_MAX - 7 ||
+ st->offset < payload_offset ||
+ st->offset - payload_offset > payload_size ||
+ (st->size_bits + 7) >> 3 > payload_size - (st->offset - payload_offset)) {
+ av_log(avctx, AV_LOG_ERROR, "Invalid stripe %d\n", i);
+ return AVERROR_INVALIDDATA;
+ }
+ left += st->width;
+ }
+
+ if (left != h->raw_width)
+ return AVERROR_INVALIDDATA;
+
+ return 0;
+}
+
+static enum AVPixelFormat get_pixel_format(AVCodecContext *avctx,
+ enum AVPixelFormat pix_fmt)
+{
+ enum AVPixelFormat pix_fmts[] = {
+ pix_fmt,
+ AV_PIX_FMT_NONE,
+ };
+
+ return ff_get_format(avctx, pix_fmts);
+}
+
+static int export_metadata(AVCodecContext *avctx, AVFrame *frame)
+{
+ RW2DecContext *s = avctx->priv_data;
+ const RW2Header *h = &s->h;
+ int ret;
+
+ /* Even crops keep the CFA phase */
+ if (h->border[0] < h->border[2] && h->border[2] <= h->raw_height &&
+ h->border[1] < h->border[3] && h->border[3] <= h->raw_width) {
+ frame->crop_top = h->border[0] & ~1;
+ frame->crop_left = h->border[1] & ~1;
+ frame->crop_bottom = (h->raw_height - h->border[2]) & ~1;
+ frame->crop_right = (h->raw_width - h->border[3]) & ~1;
+ }
+
+ if (h->orientation > 1 && h->orientation <= 8) {
+ AVFrameSideData *sd;
+ ret = ff_frame_new_side_data(avctx, frame, AV_FRAME_DATA_DISPLAYMATRIX,
+ sizeof(int32_t) * 9, &sd);
+ if (ret < 0)
+ return ret;
+ if (sd)
+ av_exif_orientation_to_matrix((int32_t *)sd->data, h->orientation);
+ }
+
+ /* Per-channel black levels are not representable, use green's */
+ int white = FFMAX3(h->linearity[0], h->linearity[1], h->linearity[2]);
+ if (!white)
+ white = (1 << h->bpp) - 1;
+
+ AVRawColorParams *rcp = av_raw_color_params_create_side_data(frame);
+ if (!rcp)
+ return AVERROR(ENOMEM);
+ rcp->black_level = av_make_q(h->black[1], 65535);
+ rcp->white_level = av_make_q(white, 65535);
+
+ return 0;
+}
+
+static int decode_frame(AVCodecContext *avctx, AVFrame *frame,
+ int *got_frame, AVPacket *avpkt)
+{
+ RW2DecContext *s = avctx->priv_data;
+ RW2Header *h = &s->h;
+ int ret;
+
+ static const enum AVPixelFormat cfa_fmts[] = {
+ AV_PIX_FMT_BAYER_RGGB16, AV_PIX_FMT_BAYER_GRBG16,
+ AV_PIX_FMT_BAYER_GBRG16, AV_PIX_FMT_BAYER_BGGR16,
+ };
+
+ if (avctx->extradata_size)
+ ret = parse_header(h, avctx->extradata, avctx->extradata_size);
+ else
+ ret = parse_header(h, avpkt->data, avpkt->size);
+ if (ret < 0)
+ return ret;
+
+ if (h->raw_format != 8) {
+ if (!h->raw_format)
+ return AVERROR_INVALIDDATA;
+ avpriv_request_sample(avctx, "RawFormat %d", h->raw_format);
+ return AVERROR_PATCHWELCOME;
+ }
+
+ if (h->cfa < 1 || h->cfa > 4 || h->bpp < 8 || h->bpp > 16 ||
+ (h->raw_width & 1) || (h->raw_height & 1))
+ return AVERROR_INVALIDDATA;
+
+ ret = ff_set_dimensions(avctx, h->raw_width, h->raw_height);
+ if (ret < 0)
+ return ret;
+
+ /* With extradata, the packet starts at the first stripe */
+ s->payload = avpkt->data;
+ s->payload_offset = 0;
+ if (avctx->extradata_size) {
+ s->payload_offset = UINT32_MAX;
+ for (int i = 0; i < h->nb_stripes; i++)
+ s->payload_offset = FFMIN(s->payload_offset, h->stripes[i].offset);
+ }
+
+ ret = check_stripes(avctx, h, s->payload_offset, avpkt->size);
+ if (ret < 0)
+ return ret;
+
+ if (cfa_fmts[h->cfa - 1] != s->pix_fmt) {
+ s->pix_fmt = cfa_fmts[h->cfa - 1];
+ ret = get_pixel_format(avctx, s->pix_fmt);
+ if (ret < 0)
+ return ret;
+ avctx->pix_fmt = ret;
+ }
+ avctx->bits_per_raw_sample = h->bpp;
+
+ if (avctx->skip_frame >= AVDISCARD_ALL)
+ return avpkt->size;
+
+ ret = ff_thread_get_buffer(avctx, frame, 0);
+ if (ret < 0)
+ return ret;
+
+ s->frame = frame;
+
+ ret = ff_hwaccel_frame_priv_alloc(avctx, &s->hwaccel_picture_private);
+ if (ret < 0)
+ return ret;
+
+ ff_thread_finish_setup(avctx);
+
+ if (avctx->hwaccel) {
+ const FFHWAccel *hwaccel = ffhwaccel(avctx->hwaccel);
+
+ ret = hwaccel->start_frame(avctx, avpkt->buf, avpkt->data, avpkt->size);
+ if (ret < 0)
+ return ret;
+
+ for (int i = 0; i < h->nb_stripes; i++) {
+ const RW2Stripe *st = &h->stripes[i];
+ ret = hwaccel->decode_slice(avctx, s->payload + (st->offset - s->payload_offset),
+ (st->size_bits + 7) >> 3);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = hwaccel->end_frame(avctx);
+ if (ret < 0)
+ return ret;
+
+ av_refstruct_unref(&s->hwaccel_picture_private);
+ } else {
+ int rets[RW2_MAX_STRIPES];
+
+ ret = build_tables(s);
+ if (ret < 0)
+ return ret;
+
+ avctx->execute2(avctx, decode_stripe, frame, rets, h->nb_stripes);
+ for (int i = 0; i < h->nb_stripes; i++) {
+ if (rets[i] < 0) {
+ av_log(avctx, AV_LOG_ERROR, "Error decoding stripe %d\n", i);
+ return rets[i];
+ }
+ }
+ }
+
+ frame->pict_type = AV_PICTURE_TYPE_I;
+ frame->flags |= AV_FRAME_FLAG_KEY;
+
+ ret = export_metadata(avctx, frame);
+ if (ret < 0)
+ return ret;
+
+ *got_frame = 1;
+
+ return avpkt->size;
+}
+
+#if HAVE_THREADS
+static int update_thread_context(AVCodecContext *dst, const AVCodecContext *src)
+{
+ RW2DecContext *ssrc = src->priv_data;
+ RW2DecContext *sdst = dst->priv_data;
+
+ sdst->pix_fmt = ssrc->pix_fmt;
+
+ return 0;
+}
+#endif
+
+static av_cold int decode_init(AVCodecContext *avctx)
+{
+ RW2DecContext *s = avctx->priv_data;
+
+ avctx->color_trc = AVCOL_TRC_LINEAR;
+ avctx->color_primaries = AVCOL_PRI_UNSPECIFIED;
+ avctx->colorspace = AVCOL_SPC_UNSPECIFIED;
+
+ s->pix_fmt = AV_PIX_FMT_NONE;
+
+ return 0;
+}
+
+static av_cold int decode_end(AVCodecContext *avctx)
+{
+ RW2DecContext *s = avctx->priv_data;
+ av_refstruct_unref(&s->hwaccel_picture_private);
+ ff_vlc_free(&s->vlc);
+ return 0;
+}
+
+const FFCodec ff_rw2_decoder = {
+ .p.name = "rw2",
+ CODEC_LONG_NAME("Panasonic RW2"),
+ .p.type = AVMEDIA_TYPE_VIDEO,
+ .p.id = AV_CODEC_ID_RW2,
+ .priv_data_size = sizeof(RW2DecContext),
+ .init = decode_init,
+ .close = decode_end,
+ FF_CODEC_DECODE_CB(decode_frame),
+ UPDATE_THREAD_CONTEXT(update_thread_context),
+ .p.capabilities = AV_CODEC_CAP_DR1 |
+ AV_CODEC_CAP_FRAME_THREADS |
+ AV_CODEC_CAP_SLICE_THREADS,
+ .caps_internal = FF_CODEC_CAP_INIT_CLEANUP |
+ FF_CODEC_CAP_SKIP_FRAME_FILL_PARAM,
+};
--
2.52.0
>From ca1f0b115d9c3375151e3b623db55abd14bc01b0 Mon Sep 17 00:00:00 2001
From: Lynne <dev@lynne.ee>
Date: Fri, 2 Oct 2026 17:46:29 +0900
Subject: [PATCH 5/8] lavu/raw_color_params: add Panasonic RW2 parameters
The as-shot white balance of RW2 files, as red and blue multipliers
relative to green. The files carry no camera matrix.
---
doc/APIchanges | 3 +++
libavutil/raw_color_params.h | 30 ++++++++++++++++++++++++++++++
libavutil/version.h | 2 +-
3 files changed, 34 insertions(+), 1 deletion(-)
@@ -2,6 +2,9 @@ The last version increases of all libraries were on 2026-06-23.
API changes, most recent first:
+2026-10-xx - xxxxxxxxxx - lavu 61.11.100 - raw_color_params.h
+ Add AV_RAW_COLOR_PARAMS_RW2 and AVRW2RawColorParams.
+
2026-10-xx - xxxxxxxxxx - lavc 63.17.100 - codec_id.h
Add AV_CODEC_ID_RW2.
@@ -35,6 +35,12 @@ enum AVRawColorParamsType {
* (codec.prores_raw).
*/
AV_RAW_COLOR_PARAMS_PRORES_RAW,
+
+ /**
+ * The union is valid when interpreted as AVRW2RawColorParams
+ * (codec.rw2).
+ */
+ AV_RAW_COLOR_PARAMS_RW2,
};
/**
@@ -80,6 +86,29 @@ typedef struct AVProResRawColorParams {
AVRational gain;
} AVProResRawColorParams;
+/**
+ * Panasonic RW2 as-shot white balance, from the file's WB level tags.
+ *
+ * The file carries no camera matrix. After white balance and debayering,
+ * camera RGB needs a profile for the camera model, which the EXIF side data
+ * names.
+ *
+ * @note The struct must be allocated as part of AVRawColorParams using
+ * av_raw_color_params_alloc(). Its size is not a part of the public ABI.
+ */
+typedef struct AVRW2RawColorParams {
+ /**
+ * White balance multiplier for the red channel, applied pre-debayer.
+ */
+ AVRational wb_red;
+
+ /**
+ * White balance multiplier for the blue channel, applied pre-debayer.
+ * The green channel is implicitly 1.0.
+ */
+ AVRational wb_blue;
+} AVRW2RawColorParams;
+
/**
* Per-frame color information for a RAW camera codec. Carried as side data of
* type AV_FRAME_DATA_RAW_COLOR_PARAMS.
@@ -125,6 +154,7 @@ typedef struct AVRawColorParams {
*/
union {
AVProResRawColorParams prores_raw;
+ AVRW2RawColorParams rw2;
} codec;
} AVRawColorParams;
@@ -79,7 +79,7 @@
*/
#define LIBAVUTIL_VERSION_MAJOR 61
-#define LIBAVUTIL_VERSION_MINOR 10
+#define LIBAVUTIL_VERSION_MINOR 11
#define LIBAVUTIL_VERSION_MICRO 100
#define LIBAVUTIL_VERSION_INT AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \
--
2.52.0
>From ab1d7858ad4e8552c696c04aa343ee9af88ac4aa Mon Sep 17 00:00:00 2001
From: Lynne <dev@lynne.ee>
Date: Fri, 2 Oct 2026 17:47:06 +0900
Subject: [PATCH 6/8] lavc/rw2dec: export the as-shot white balance
Tags 0x24 - 0x26, as AVRW2RawColorParams.
---
libavcodec/rw2.h | 1 +
libavcodec/rw2dec.c | 9 +++++++++
2 files changed, 10 insertions(+)
@@ -62,6 +62,7 @@ typedef struct RW2Header {
int orientation;
int black[3];
int linearity[3];
+ int wb[3]; /* as-shot white balance levels */
RW2Coding c;
@@ -254,6 +254,9 @@ static int parse_header(RW2Header *h, const uint8_t *data, int size)
case 0x1C: case 0x1D: case 0x1E:
h->black[tag - 0x1C] = ff_tget(&gb, type, 1);
break;
+ case 0x24: case 0x25: case 0x26:
+ h->wb[tag - 0x24] = ff_tget(&gb, type, 1);
+ break;
case 0x2D: h->raw_format = ff_tget(&gb, type, 1); break;
case 0x39:
n = read_array_count(&gb, type, count, 26, 6);
@@ -407,6 +410,12 @@ static int export_metadata(AVCodecContext *avctx, AVFrame *frame)
rcp->black_level = av_make_q(h->black[1], 65535);
rcp->white_level = av_make_q(white, 65535);
+ if (h->wb[1]) {
+ rcp->type = AV_RAW_COLOR_PARAMS_RW2;
+ rcp->codec.rw2.wb_red = av_make_q(h->wb[0], h->wb[1]);
+ rcp->codec.rw2.wb_blue = av_make_q(h->wb[2], h->wb[1]);
+ }
+
return 0;
}
--
2.52.0
>From cc8e470ad04589f3f9bbf03bfb1b819a1db7b441 Mon Sep 17 00:00:00 2001
From: Lynne <dev@lynne.ee>
Date: Fri, 2 Oct 2026 17:47:06 +0900
Subject: [PATCH 7/8] lavf/rw2dec: cover the EXIF IFDs with the extradata
The extradata ended at the raw coding tags, leaving the Make and Model
strings, the XMP and the EXIF IFD out. Follow every pointer in IFD0 and
its EXIF sub-IFDs instead, except the embedded previews, as long as it
all stays under 1 MiB.
---
libavformat/rw2dec.c | 44 +++++++++++++++++++++++++++++++++++++++++---
1 file changed, 41 insertions(+), 3 deletions(-)
@@ -20,8 +20,9 @@
*/
/*
- * Extradata is the start of the file, up to IFD0 and the raw tag values.
- * The packet spans the raw data stripes, skipping the embedded previews.
+ * Extradata is the start of the file, up to the last byte that IFD0 and the
+ * EXIF IFDs point to, the embedded previews aside. The packet spans the raw
+ * data stripes.
*/
#include "libavutil/intreadwrite.h"
@@ -47,11 +48,43 @@ static int rw2_probe(const AVProbeData *p)
return 0;
}
+/* End of an IFD and the data it points to, following the EXIF sub-IFDs */
+static int64_t ifd_end(AVIOContext *pb, int64_t ifd, int depth)
+{
+ static const uint8_t type_size[] = { 0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8, 4 };
+ int64_t end, pos;
+ int n;
+
+ if (!ifd || depth > 2 || avio_seek(pb, ifd, SEEK_SET) < 0)
+ return 0;
+
+ n = avio_rl16(pb);
+ end = ifd + 2 + 12*n + 4;
+ for (int i = 0; i < n && !avio_feof(pb); i++) {
+ unsigned tag = avio_rl16(pb);
+ unsigned type = avio_rl16(pb);
+ uint32_t cnt = avio_rl32(pb);
+ uint32_t val = avio_rl32(pb);
+ int64_t size = type < FF_ARRAY_ELEMS(type_size) ? (int64_t)type_size[type] * cnt : 0;
+
+ if (tag == 0x8769 || tag == 0x8825 || tag == 0xA005) {
+ pos = avio_tell(pb);
+ end = FFMAX(end, ifd_end(pb, val, depth + 1));
+ avio_seek(pb, pos, SEEK_SET);
+ } else if (size > 4 && tag != 0x2E && tag != 0x127) {
+ /* Out-of-line values, the previews excepted */
+ end = FFMAX(end, val + size);
+ }
+ }
+
+ return end;
+}
+
static int rw2_read_header(AVFormatContext *s)
{
RW2DemuxContext *rw2 = s->priv_data;
AVIOContext *pb = s->pb;
- int64_t offsets_pos = 0, sizes_pos = 0, header_end, size;
+ int64_t offsets_pos = 0, sizes_pos = 0, header_end, exif_end, size;
int width = 0, height = 0, cfa = 0, bpp = 0, nb_stripes = 0;
int nb_entries, ret;
AVStream *st;
@@ -97,6 +130,11 @@ static int rw2_read_header(AVFormatContext *s)
header_end > (1 << 20))
return AVERROR_INVALIDDATA;
+ /* The rest of the metadata too, unless something points way off */
+ exif_end = ifd_end(pb, ifd, 0);
+ if (exif_end <= (1 << 20))
+ header_end = FFMAX(header_end, exif_end);
+
rw2->data_start = INT64_MAX;
rw2->data_end = 0;
for (int i = 0; i < nb_stripes; i++) {
--
2.52.0
>From aa4f204fa6afbd61061f9348e2939d0ece59ec38 Mon Sep 17 00:00:00 2001
From: Lynne <dev@lynne.ee>
Date: Fri, 2 Oct 2026 17:47:06 +0900
Subject: [PATCH 8/8] lavc/rw2dec: export the EXIF metadata
Only the TIFF and EXIF tags of IFD0 and its sub-IFDs. Panasonic's raw
parameters are side data already, and the previews and strip pointers
are useless outside the file. The orientation stays a display matrix.
---
libavcodec/rw2dec.c | 92 ++++++++++++++++++++++++++++++++++++++-------
1 file changed, 78 insertions(+), 14 deletions(-)
@@ -28,6 +28,7 @@
* as extradata with a packet starting at the first stripe (the demuxer).
*/
+#include "libavutil/mem.h"
#include "libavutil/raw_color_params.h"
#include "libavutil/reverse.h"
@@ -38,6 +39,7 @@
#include "codec_internal.h"
#include "decode.h"
#include "exif.h"
+#include "exif_internal.h"
#include "hwaccel_internal.h"
#include "thread.h"
#include "tiff_common.h"
@@ -374,7 +376,70 @@ static enum AVPixelFormat get_pixel_format(AVCodecContext *avctx,
return ff_get_format(avctx, pix_fmts);
}
-static int export_metadata(AVCodecContext *avctx, AVFrame *frame)
+/* The TIFF and EXIF tags of the file. Panasonic's own tags, below 0x100 and
+ * at 0x118 - 0x127, hold the raw parameters exported as side data and the
+ * previews; the strips point into the file. */
+static int export_exif(AVCodecContext *avctx, AVFrame *frame,
+ const uint8_t *data, int size)
+{
+ AVExifMetadata ifd = { 0 };
+ unsigned off, n, kept = 0;
+ int64_t avail;
+ uint8_t *buf, *e;
+ int ret;
+
+ if (size < 8)
+ return 0;
+ off = AV_RL32(data + 4);
+ avail = (int64_t)size - off;
+ if (off < 8 || avail < 6 || (n = AV_RL16(data + off)) > (avail - 6) / 12)
+ return 0;
+
+ buf = av_memdup(data, size);
+ if (!buf)
+ return AVERROR(ENOMEM);
+
+ /* "IIU\0" to a TIFF header */
+ AV_WL16(buf + 2, 42);
+
+ e = buf + off + 2;
+ for (unsigned i = 0; i < n; i++) {
+ const uint8_t *src = buf + off + 2 + 12*i;
+ unsigned tag = AV_RL16(src), type = AV_RL16(src + 2);
+ uint32_t cnt = AV_RL32(src + 4), val = AV_RL32(src + 8);
+ int64_t vsize;
+
+ if (!type || type > AV_TIFF_IFD)
+ continue;
+ vsize = (int64_t)type_sizes[type] * cnt;
+ if (tag < 0x100 || (tag >= 0x118 && tag <= 0x127) ||
+ tag == 0x111 || tag == 0x117 ||
+ (vsize > 4 && val + vsize > size) ||
+ ((type == AV_TIFF_IFD || tag == 0x8769 || tag == 0x8825) && val >= size))
+ continue;
+
+ memmove(e, src, 12);
+ e += 12;
+ kept++;
+ }
+ AV_WL16(buf + off, kept);
+ AV_WL32(e, 0);
+
+ ret = av_exif_parse_buffer(avctx, buf, size, &ifd, AV_EXIF_TIFF_HEADER);
+ av_free(buf);
+ if (ret < 0)
+ return 0;
+
+ /* The orientation is exported as a display matrix instead */
+ av_exif_remove_entry(avctx, &ifd, 0x112, 0);
+
+ ret = ff_decode_exif_attach_ifd(avctx, frame, &ifd);
+ av_exif_free(&ifd);
+ return ret;
+}
+
+static int export_metadata(AVCodecContext *avctx, AVFrame *frame,
+ const uint8_t *hdr, int hdr_size)
{
RW2DecContext *s = avctx->priv_data;
const RW2Header *h = &s->h;
@@ -416,7 +481,7 @@ static int export_metadata(AVCodecContext *avctx, AVFrame *frame)
rcp->codec.rw2.wb_blue = av_make_q(h->wb[2], h->wb[1]);
}
- return 0;
+ return export_exif(avctx, frame, hdr, hdr_size);
}
static int decode_frame(AVCodecContext *avctx, AVFrame *frame,
@@ -431,10 +496,10 @@ static int decode_frame(AVCodecContext *avctx, AVFrame *frame,
AV_PIX_FMT_BAYER_GBRG16, AV_PIX_FMT_BAYER_BGGR16,
};
- if (avctx->extradata_size)
- ret = parse_header(h, avctx->extradata, avctx->extradata_size);
- else
- ret = parse_header(h, avpkt->data, avpkt->size);
+ const uint8_t *hdr = avctx->extradata_size ? avctx->extradata : avpkt->data;
+ int hdr_size = avctx->extradata_size ? avctx->extradata_size : avpkt->size;
+
+ ret = parse_header(h, hdr, hdr_size);
if (ret < 0)
return ret;
@@ -454,13 +519,12 @@ static int decode_frame(AVCodecContext *avctx, AVFrame *frame,
return ret;
/* With extradata, the packet starts at the first stripe */
- s->payload = avpkt->data;
- s->payload_offset = 0;
- if (avctx->extradata_size) {
- s->payload_offset = UINT32_MAX;
- for (int i = 0; i < h->nb_stripes; i++)
- s->payload_offset = FFMIN(s->payload_offset, h->stripes[i].offset);
- }
+ uint32_t first = UINT32_MAX;
+ for (int i = 0; i < h->nb_stripes; i++)
+ first = FFMIN(first, h->stripes[i].offset);
+ s->payload = avpkt->data;
+ s->payload_offset = avctx->extradata_size ? first : 0;
+ hdr_size = FFMIN(hdr_size, first);
ret = check_stripes(avctx, h, s->payload_offset, avpkt->size);
if (ret < 0)
@@ -529,7 +593,7 @@ static int decode_frame(AVCodecContext *avctx, AVFrame *frame,
frame->pict_type = AV_PICTURE_TYPE_I;
frame->flags |= AV_FRAME_FLAG_KEY;
- ret = export_metadata(avctx, frame);
+ ret = export_metadata(avctx, frame, hdr, hdr_size);
if (ret < 0)
return ret;