[FFmpeg-devel] [PATCHv2 1/3] avformat/mlvdec: demux LJ92 huffman comressed frames

Peter Ross pross at xvid.org
Sun Dec 22 09:40:23 EET 2024


A minimal DNG header is added to each LJ92 compressed frame, allowing
thme to be decoded by the TIFF decoder. The TIFF decoder is responsible
for setting up the MJPEG decoder, signalling the correct s->bayer flag,
and setting pix_fmt.

The LJ92 compressed frames can be muxed out to DNG files, and manipulated
in DNG software. Tested with darktable and rawtherapee.

Contributor: South East <8billion.people at gmail.com>
---
v2: also writes black/white level and color matrix to dng header

 libavformat/mlvdec.c | 131 +++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 127 insertions(+), 4 deletions(-)

diff --git a/libavformat/mlvdec.c b/libavformat/mlvdec.c
index 1a6d38f37c..22c94c6c58 100644
--- a/libavformat/mlvdec.c
+++ b/libavformat/mlvdec.c
@@ -26,6 +26,9 @@
 
 #include <time.h>
 
+#include "libavcodec/bytestream.h"
+#include "libavcodec/tiff.h"
+#include "libavcodec/tiff_common.h"
 #include "libavutil/imgutils.h"
 #include "libavutil/intreadwrite.h"
 #include "libavutil/mem.h"
@@ -44,6 +47,7 @@
 
 #define MLV_AUDIO_CLASS_WAV  1
 
+#define MLV_CLASS_FLAG_LJ92  0x20
 #define MLV_CLASS_FLAG_DELTA 0x40
 #define MLV_CLASS_FLAG_LZMA  0x80
 
@@ -52,6 +56,9 @@ typedef struct {
     int class[2];
     int stream_index;
     uint64_t pts;
+    int black_level;
+    int white_level;
+    int color_matrix1[9][2];
 } MlvContext;
 
 static int probe(const AVProbeData *p)
@@ -152,10 +159,17 @@ static int scan_file(AVFormatContext *avctx, AVStream *vst, AVStream *ast, int f
             vst->codecpar->width  = width;
             vst->codecpar->height = height;
             vst->codecpar->bits_per_coded_sample = bits_per_coded_sample;
-            avio_skip(pb, 8 + 16 + 24); // black_level, white_level, xywh, active_area, exposure_bias
+            mlv->black_level = avio_rl32(pb);
+            mlv->white_level = avio_rl32(pb);
+            avio_skip(pb, 16 + 24); // xywh, active_area, exposure_bias
             if (avio_rl32(pb) != 0x2010100) /* RGGB */
                 avpriv_request_sample(avctx, "cfa_pattern");
-            avio_skip(pb, 80); // calibration_illuminant1, color_matrix1, dynamic_range
+            avio_skip(pb, 4); // calibration_illuminant1,
+            for (int i = 0; i < 9; i++) {
+                mlv->color_matrix1[i][0] = avio_rl32(pb);
+                mlv->color_matrix1[i][1] = avio_rl32(pb);
+            }
+            avio_skip(pb, 4); // dynamic_range
             vst->codecpar->format    = AV_PIX_FMT_BAYER_RGGB16LE;
             vst->codecpar->codec_tag = MKTAG('B', 'I', 'T', 16);
             size -= 164;
@@ -307,6 +321,9 @@ static int read_header(AVFormatContext *avctx)
             vst->codecpar->codec_id = AV_CODEC_ID_RAWVIDEO;
             vst->codecpar->codec_tag = 0;
             break;
+        case MLV_CLASS_FLAG_LJ92|MLV_VIDEO_CLASS_RAW:
+            vst->codecpar->codec_id = AV_CODEC_ID_TIFF;
+            break;
         case MLV_VIDEO_CLASS_JPEG:
             vst->codecpar->codec_id = AV_CODEC_ID_MJPEG;
             vst->codecpar->codec_tag = 0;
@@ -400,6 +417,105 @@ static int read_header(AVFormatContext *avctx)
     return 0;
 }
 
+static void write_tiff_short(PutByteContext *pb, int tag, int value)
+{
+    bytestream2_put_le16(pb, tag);
+    bytestream2_put_le16(pb, TIFF_SHORT);
+    bytestream2_put_le32(pb, 1);
+    bytestream2_put_le16(pb, value);
+    bytestream2_put_le16(pb, 0);
+}
+
+static void write_tiff_short2(PutByteContext *pb, int tag, int v1, int v2)
+{
+    bytestream2_put_le16(pb, tag);
+    bytestream2_put_le16(pb, TIFF_SHORT);
+    bytestream2_put_le32(pb, 2);
+    bytestream2_put_le16(pb, v1);
+    bytestream2_put_le16(pb, v2);
+}
+
+static void write_tiff_long(PutByteContext *pb, int tag, int value)
+{
+    bytestream2_put_le16(pb, tag);
+    bytestream2_put_le16(pb, TIFF_LONG);
+    bytestream2_put_le32(pb, 1);
+    bytestream2_put_le32(pb, value);
+}
+
+static void write_tiff_byte4(PutByteContext *pb, int tag, int v1, int v2, int v3, int v4)
+{
+    bytestream2_put_le16(pb, tag);
+    bytestream2_put_le16(pb, TIFF_BYTE);
+    bytestream2_put_le32(pb, 4);
+    bytestream2_put_byte(pb, v1);
+    bytestream2_put_byte(pb, v2);
+    bytestream2_put_byte(pb, v3);
+    bytestream2_put_byte(pb, v4);
+}
+
+static int get_packet_lj92(AVFormatContext *avctx, AVStream *st, AVIOContext *pbio, AVPacket *pkt, int64_t size)
+{
+    MlvContext *mlv = avctx->priv_data;
+    PutByteContext pbctx, *pb = &pbctx;
+    int ret, header_size;
+    uint8_t *stripofs, *matrixofs;
+
+#define MAX_HEADER_SIZE 2048
+    if ((ret = av_new_packet(pkt, size + MAX_HEADER_SIZE)) < 0)
+        return ret;
+
+    bytestream2_init_writer(pb, pkt->data, MAX_HEADER_SIZE);
+
+    bytestream2_put_le16(pb, 0x4949);
+    bytestream2_put_le16(pb, 42);
+    bytestream2_put_le32(pb, 8);
+
+    bytestream2_put_le16(pb, 18); /* nb_entries */
+
+    write_tiff_long(pb, TIFF_SUBFILE, 0);
+    write_tiff_long(pb, TIFF_WIDTH, st->codecpar->width);
+    write_tiff_long(pb, TIFF_HEIGHT, st->codecpar->height);
+    write_tiff_long(pb, TIFF_BPP,  st->codecpar->bits_per_coded_sample);
+    write_tiff_short(pb, TIFF_COMPR, TIFF_NEWJPEG);
+    write_tiff_short(pb, TIFF_PHOTOMETRIC, TIFF_PHOTOMETRIC_CFA);
+    write_tiff_short(pb, TIFF_FILL_ORDER, 1);
+    write_tiff_long(pb, TIFF_STRIP_OFFS, 0); /* stripofs */
+    stripofs = pb->buffer - 4;
+
+    write_tiff_long(pb, TIFF_SAMPLES_PER_PIXEL, 1);
+    write_tiff_short(pb, TIFF_ROWSPERSTRIP, st->codecpar->height);
+    write_tiff_long(pb, TIFF_STRIP_SIZE, size);
+    write_tiff_short(pb, TIFF_PLANAR, 1);
+    write_tiff_short2(pb, TIFF_CFA_PATTERN_DIM, 2, 2);
+    write_tiff_byte4(pb, TIFF_CFA_PATTERN, 0, 1, 1, 2);
+    write_tiff_byte4(pb, DNG_VERSION, 1, 4, 0, 0);
+    write_tiff_long(pb, DNG_BLACK_LEVEL, mlv->black_level);
+    write_tiff_long(pb, DNG_WHITE_LEVEL, mlv->white_level);
+
+    bytestream2_put_le16(pb, DNG_COLOR_MATRIX1);
+    bytestream2_put_le16(pb, TIFF_SRATIONAL);
+    bytestream2_put_le32(pb, 9);
+    bytestream2_put_le32(pb, 0); /* matrixofs */
+    matrixofs = pb->buffer - 4;
+    bytestream2_put_le32(pb, 0);
+
+    AV_WL32(matrixofs, bytestream2_tell_p(pb));
+    for (int i = 0; i < 9; i++) {
+        bytestream2_put_le32(pb, mlv->color_matrix1[i][0]);
+        bytestream2_put_le32(pb, mlv->color_matrix1[i][1]);
+    }
+
+    header_size = bytestream2_tell_p(pb);
+
+    AV_WL32(stripofs, header_size);
+    ret = avio_read(pbio, pkt->data + header_size, size);
+    if (ret < 0)
+        return ret;
+
+    return 0;
+}
+
 static int read_packet(AVFormatContext *avctx, AVPacket *pkt)
 {
     MlvContext *mlv = avctx->priv_data;
@@ -435,15 +551,22 @@ static int read_packet(AVFormatContext *avctx, AVPacket *pkt)
     if (size < 16)
         return AVERROR_INVALIDDATA;
     avio_skip(pb, 12); //timestamp, frameNumber
-    if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
+    size -= 12;
+    if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
         avio_skip(pb, 8); // cropPosX, cropPosY, panPosX, panPosY
+        size -= 8;
+    }
     space = avio_rl32(pb);
     avio_skip(pb, space);
+    size -= space;
 
     if ((mlv->class[st->id] & (MLV_CLASS_FLAG_DELTA|MLV_CLASS_FLAG_LZMA))) {
         ret = AVERROR_PATCHWELCOME;
     } else if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
-        ret = av_get_packet(pb, pkt, (st->codecpar->width * st->codecpar->height * st->codecpar->bits_per_coded_sample + 7) >> 3);
+        if (st->codecpar->codec_id == AV_CODEC_ID_TIFF)
+            ret = get_packet_lj92(avctx, st, pb, pkt, size);
+        else
+            ret = av_get_packet(pb, pkt, (st->codecpar->width * st->codecpar->height * st->codecpar->bits_per_coded_sample + 7) >> 3);
     } else { // AVMEDIA_TYPE_AUDIO
         if (space > UINT_MAX - 24 || size < (24 + space))
             return AVERROR_INVALIDDATA;
-- 
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/20241222/eb96423e/attachment.sig>


More information about the ffmpeg-devel mailing list