[PATCH v8 3/8] media: v4l2-jpeg: Add a baseline JPEG header writer
From: Atanas Filipov
Date: Tue Nov 10 2026 - 12:10:38 EST
Several V4L2 JPEG encoder drivers build the JPEG header in the driver
from a fixed template and patch the image size and quantization tables
for each frame. Add v4l2_jpeg_write_header() for the baseline case: it
writes SOI, an optional JFIF APP0, one DQT segment per table, SOF0, the
ITU-T T.81 Annex K reference Huffman tables in one DHT segment and SOS,
and returns the offsets of the fields set per frame. Add
v4l2_jpeg_hdr_set_size() to set the image size in such a header.
The field values come from media/jpeg.h, so the driver keeps only the
hardware specifics.
Suggested-by: Dmitry Baryshkov <dmitry.baryshkov@xxxxxxxxxxxxxxxx>
Signed-off-by: Atanas Filipov <atanas.filipov@xxxxxxxxxxxxxxxx>
---
drivers/media/v4l2-core/v4l2-jpeg.c | 165 ++++++++++++++++++++++++++++
include/media/v4l2-jpeg.h | 31 ++++++
2 files changed, 196 insertions(+)
diff --git a/drivers/media/v4l2-core/v4l2-jpeg.c b/drivers/media/v4l2-core/v4l2-jpeg.c
index 36a0f1a1b0d97..f466dc6a457de 100644
--- a/drivers/media/v4l2-core/v4l2-jpeg.c
+++ b/drivers/media/v4l2-core/v4l2-jpeg.c
@@ -13,7 +13,10 @@
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/module.h>
+#include <linux/stddef.h>
+#include <linux/string.h>
#include <linux/types.h>
+#include <media/jpeg.h>
#include <media/v4l2-jpeg.h>
MODULE_DESCRIPTION("V4L2 JPEG header parser helpers");
@@ -711,3 +714,165 @@ int v4l2_jpeg_parse_header(void *buf, size_t len, struct v4l2_jpeg_header *out)
return marker;
}
EXPORT_SYMBOL_GPL(v4l2_jpeg_parse_header);
+
+/* baseline JPEG header writer */
+
+/* Segment length of a header struct: the marker is not counted */
+#define JPEG_SEG_LEN(hdr) (sizeof(struct hdr) - offsetof(struct jpeg_seg_hdr, length))
+
+struct jpeg_hdr_buf {
+ u8 *buf;
+ size_t len;
+ size_t pos;
+};
+
+/* Writes past the end only advance pos; v4l2_jpeg_write_header() checks it */
+static void jpeg_put_bytes(struct jpeg_hdr_buf *b, const void *src, size_t n)
+{
+ if (b->pos + n <= b->len) {
+ if (src)
+ memcpy(b->buf + b->pos, src, n);
+ else
+ memset(b->buf + b->pos, 0, n);
+ }
+ b->pos += n;
+}
+
+static void jpeg_put_u8(struct jpeg_hdr_buf *b, u8 val)
+{
+ jpeg_put_bytes(b, &val, 1);
+}
+
+static void jpeg_put_be16(struct jpeg_hdr_buf *b, u16 val)
+{
+ u8 data[2] = { val >> 8, val & 0xff };
+
+ jpeg_put_bytes(b, data, sizeof(data));
+}
+
+static void jpeg_put_dht(struct jpeg_hdr_buf *b, u8 class_id, const u8 *table, size_t len)
+{
+ jpeg_put_u8(b, class_id);
+ jpeg_put_bytes(b, table, len);
+}
+
+/**
+ * v4l2_jpeg_write_header - write a baseline JPEG header
+ * @buf: destination buffer
+ * @len: length of the destination buffer
+ * @params: header parameters
+ * @layout: returns the header size and the offsets of the fields set per frame
+ *
+ * Writes SOI, an optional JFIF APP0, one DQT segment per quantization table,
+ * SOF0, one DHT segment with the reference Huffman tables of ITU-T.81 Annex K
+ * and SOS. Width, height and the quantization table values are written as
+ * zero; set them with v4l2_jpeg_hdr_set_size() and through the
+ * @layout->dqt_offset offsets.
+ *
+ * Returns 0 on success, -EINVAL for an unsupported number of components or
+ * -ENOSPC if @buf is too small.
+ */
+int v4l2_jpeg_write_header(void *buf, size_t len, const struct v4l2_jpeg_hdr_params *params,
+ struct v4l2_jpeg_hdr_layout *layout)
+{
+ static const u8 jfif[] = {
+ 'J', 'F', 'I', 'F', JFIF_IDENT_TERM,
+ JFIF_VERSION_MAJOR, JFIF_VERSION_MINOR,
+ JFIF_UNITS_NONE,
+ JFIF_DENSITY_HI, JFIF_DENSITY_LO, /* X density */
+ JFIF_DENSITY_HI, JFIF_DENSITY_LO, /* Y density */
+ JFIF_THUMBNAIL_SIZE, JFIF_THUMBNAIL_SIZE,
+ };
+ struct jpeg_hdr_buf b = { .buf = buf, .len = len };
+ unsigned int nc = params->num_components;
+ unsigned int ntables = nc == JPEG_COMP_MONO ? 1 : 2;
+ unsigned int i;
+
+ if (nc != JPEG_COMP_MONO && nc != JPEG_COMP_COLOR)
+ return -EINVAL;
+
+ memset(layout, 0, sizeof(*layout));
+
+ jpeg_put_be16(&b, SOI);
+
+ if (params->jfif) {
+ jpeg_put_be16(&b, APP0);
+ jpeg_put_be16(&b, sizeof(__be16) + sizeof(jfif));
+ jpeg_put_bytes(&b, jfif, sizeof(jfif));
+ }
+
+ /* one 8-bit table per segment, luma Tq 0, chroma Tq 1 */
+ for (i = 0; i < ntables; i++) {
+ jpeg_put_be16(&b, DQT);
+ /* length field, Pq/Tq byte, table */
+ jpeg_put_be16(&b, sizeof(__be16) + sizeof(u8) + V4L2_JPEG_PIXELS_IN_BLOCK);
+ jpeg_put_u8(&b, i ? JPEG_QTABLE_CHROMA : JPEG_QTABLE_LUMA);
+ layout->dqt_offset[i] = b.pos;
+ jpeg_put_bytes(&b, NULL, V4L2_JPEG_PIXELS_IN_BLOCK);
+ }
+
+ layout->sof_offset = b.pos;
+ jpeg_put_be16(&b, SOF0);
+ jpeg_put_be16(&b, JPEG_SEG_LEN(jpeg_sof_hdr) + nc * sizeof(struct jpeg_sof_comp));
+ jpeg_put_u8(&b, JPEG_SAMPLE_PRECISION_BITS);
+ jpeg_put_be16(&b, 0); /* height */
+ jpeg_put_be16(&b, 0); /* width */
+ jpeg_put_u8(&b, nc);
+ for (i = 0; i < nc; i++) {
+ jpeg_put_u8(&b, i + 1);
+ jpeg_put_u8(&b, i ? JPEG_SAMPLING_H1V1 : params->luma_sampling);
+ jpeg_put_u8(&b, i ? JPEG_QTABLE_CHROMA : JPEG_QTABLE_LUMA);
+ }
+
+ jpeg_put_be16(&b, DHT);
+ /* length field, then per table class a Tc/Th byte and the DC and AC tables */
+ jpeg_put_be16(&b, sizeof(__be16) + ntables * (2 * sizeof(u8) + V4L2_JPEG_REF_HT_DC_LEN +
+ V4L2_JPEG_REF_HT_AC_LEN));
+ jpeg_put_dht(&b, V4L2_JPEG_LUM_HT | V4L2_JPEG_DC_HT, v4l2_jpeg_ref_table_luma_dc_ht,
+ V4L2_JPEG_REF_HT_DC_LEN);
+ jpeg_put_dht(&b, V4L2_JPEG_LUM_HT | V4L2_JPEG_AC_HT, v4l2_jpeg_ref_table_luma_ac_ht,
+ V4L2_JPEG_REF_HT_AC_LEN);
+ if (nc == JPEG_COMP_COLOR) {
+ jpeg_put_dht(&b, V4L2_JPEG_CHR_HT | V4L2_JPEG_DC_HT,
+ v4l2_jpeg_ref_table_chroma_dc_ht, V4L2_JPEG_REF_HT_DC_LEN);
+ jpeg_put_dht(&b, V4L2_JPEG_CHR_HT | V4L2_JPEG_AC_HT,
+ v4l2_jpeg_ref_table_chroma_ac_ht, V4L2_JPEG_REF_HT_AC_LEN);
+ }
+
+ jpeg_put_be16(&b, SOS);
+ jpeg_put_be16(&b, JPEG_SEG_LEN(jpeg_sos_hdr) + nc * sizeof(struct jpeg_sos_comp) +
+ sizeof(struct jpeg_sos_tail));
+ jpeg_put_u8(&b, nc);
+ for (i = 0; i < nc; i++) {
+ jpeg_put_u8(&b, i + 1);
+ jpeg_put_u8(&b, i ? JPEG_TABLE_SEL_CHROMA : JPEG_TABLE_SEL_LUMA);
+ }
+ jpeg_put_u8(&b, JPEG_SPECTRAL_START);
+ jpeg_put_u8(&b, JPEG_SPECTRAL_END);
+ jpeg_put_u8(&b, JPEG_APPROX_HIGH_LOW);
+
+ if (b.pos > len)
+ return -ENOSPC;
+
+ layout->size = b.pos;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(v4l2_jpeg_write_header);
+
+/**
+ * v4l2_jpeg_hdr_set_size - set the image size in a header from v4l2_jpeg_write_header()
+ * @buf: header buffer
+ * @layout: layout returned by v4l2_jpeg_write_header()
+ * @width: image width
+ * @height: image height
+ */
+void v4l2_jpeg_hdr_set_size(void *buf, const struct v4l2_jpeg_hdr_layout *layout, u16 width,
+ u16 height)
+{
+ u8 *sof = (u8 *)buf + layout->sof_offset;
+
+ put_unaligned_be16(height, sof + offsetof(struct jpeg_sof_hdr, height));
+ put_unaligned_be16(width, sof + offsetof(struct jpeg_sof_hdr, width));
+}
+EXPORT_SYMBOL_GPL(v4l2_jpeg_hdr_set_size);
diff --git a/include/media/v4l2-jpeg.h b/include/media/v4l2-jpeg.h
index 62dda1560275a..d789ccfeba734 100644
--- a/include/media/v4l2-jpeg.h
+++ b/include/media/v4l2-jpeg.h
@@ -167,8 +167,39 @@ struct v4l2_jpeg_header {
enum v4l2_jpeg_app14_tf app14_tf;
};
+/**
+ * struct v4l2_jpeg_hdr_params - baseline JPEG header parameters
+ * @num_components: number of color components, 1 (grey) or 3 (YCbCr)
+ * @luma_sampling: luma horizontal and vertical sampling factors, as in the
+ * SOF0 component specification (0x11, 0x21 or 0x22); chroma uses 0x11
+ * @jfif: emit a JFIF APP0 segment after SOI
+ */
+struct v4l2_jpeg_hdr_params {
+ u8 num_components;
+ u8 luma_sampling;
+ bool jfif;
+};
+
+/**
+ * struct v4l2_jpeg_hdr_layout - offsets of the fields set per frame
+ * @size: header size in bytes
+ * @sof_offset: offset of the SOF0 marker
+ * @dqt_offset: offset of the luma and chroma quantization table values
+ * (V4L2_JPEG_PIXELS_IN_BLOCK bytes in zigzag order), 0 if not present
+ */
+struct v4l2_jpeg_hdr_layout {
+ u32 size;
+ u32 sof_offset;
+ u32 dqt_offset[2];
+};
+
int v4l2_jpeg_parse_header(void *buf, size_t len, struct v4l2_jpeg_header *out);
+int v4l2_jpeg_write_header(void *buf, size_t len, const struct v4l2_jpeg_hdr_params *params,
+ struct v4l2_jpeg_hdr_layout *layout);
+void v4l2_jpeg_hdr_set_size(void *buf, const struct v4l2_jpeg_hdr_layout *layout, u16 width,
+ u16 height);
+
extern const u8 v4l2_jpeg_zigzag_scan_index[V4L2_JPEG_PIXELS_IN_BLOCK];
extern const u8 v4l2_jpeg_ref_table_luma_qt[V4L2_JPEG_PIXELS_IN_BLOCK];
extern const u8 v4l2_jpeg_ref_table_chroma_qt[V4L2_JPEG_PIXELS_IN_BLOCK];
--
2.34.1