2020-03-18 21:35:32 +03:00
// SPDX-License-Identifier: GPL-2.0-only
/*
* V4L2 JPEG header parser helpers .
*
* Copyright ( C ) 2019 Pengutronix , Philipp Zabel < kernel @ pengutronix . de >
*
* For reference , see JPEG ITU - T .81 ( ISO / IEC 10918 - 1 ) [ 1 ]
*
* [ 1 ] https : //www.w3.org/Graphics/JPEG/itu-t81.pdf
*/
# include <asm/unaligned.h>
# include <linux/errno.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/types.h>
# include <media/v4l2-jpeg.h>
MODULE_DESCRIPTION ( " V4L2 JPEG header parser helpers " ) ;
MODULE_AUTHOR ( " Philipp Zabel <kernel@pengutronix.de> " ) ;
MODULE_LICENSE ( " GPL " ) ;
/* Table B.1 - Marker code assignments */
# define SOF0 0xffc0 /* start of frame */
# define SOF1 0xffc1
# define SOF2 0xffc2
# define SOF3 0xffc3
# define SOF5 0xffc5
# define SOF7 0xffc7
# define JPG 0xffc8 /* extensions */
# define SOF9 0xffc9
# define SOF11 0xffcb
# define SOF13 0xffcd
# define SOF15 0xffcf
# define DHT 0xffc4 /* huffman table */
# define DAC 0xffcc /* arithmetic coding conditioning */
# define RST0 0xffd0 /* restart */
# define RST7 0xffd7
# define SOI 0xffd8 /* start of image */
# define EOI 0xffd9 /* end of image */
# define SOS 0xffda /* start of stream */
# define DQT 0xffdb /* quantization table */
# define DNL 0xffdc /* number of lines */
# define DRI 0xffdd /* restart interval */
# define DHP 0xffde /* hierarchical progression */
# define EXP 0xffdf /* expand reference */
# define APP0 0xffe0 /* application data */
2021-03-11 03:28:49 +03:00
# define APP14 0xffee /* application data for colour encoding */
2020-03-18 21:35:32 +03:00
# define APP15 0xffef
# define JPG0 0xfff0 /* extensions */
# define JPG13 0xfffd
# define COM 0xfffe /* comment */
# define TEM 0xff01 /* temporary */
/**
* struct jpeg_stream - JPEG byte stream
* @ curr : current position in stream
* @ end : end position , after last byte
*/
struct jpeg_stream {
u8 * curr ;
u8 * end ;
} ;
/* returns a value that fits into u8, or negative error */
static int jpeg_get_byte ( struct jpeg_stream * stream )
{
if ( stream - > curr > = stream - > end )
return - EINVAL ;
return * stream - > curr + + ;
}
/* returns a value that fits into u16, or negative error */
static int jpeg_get_word_be ( struct jpeg_stream * stream )
{
u16 word ;
if ( stream - > curr + sizeof ( __be16 ) > stream - > end )
return - EINVAL ;
word = get_unaligned_be16 ( stream - > curr ) ;
stream - > curr + = sizeof ( __be16 ) ;
return word ;
}
static int jpeg_skip ( struct jpeg_stream * stream , size_t len )
{
if ( stream - > curr + len > stream - > end )
return - EINVAL ;
stream - > curr + = len ;
return 0 ;
}
static int jpeg_next_marker ( struct jpeg_stream * stream )
{
int byte ;
u16 marker = 0 ;
while ( ( byte = jpeg_get_byte ( stream ) ) > = 0 ) {
marker = ( marker < < 8 ) | byte ;
/* skip stuffing bytes and REServed markers */
if ( marker = = TEM | | ( marker > 0xffbf & & marker < 0xffff ) )
return marker ;
}
return byte ;
}
/* this does not advance the current position in the stream */
static int jpeg_reference_segment ( struct jpeg_stream * stream ,
struct v4l2_jpeg_reference * segment )
{
u16 len ;
if ( stream - > curr + sizeof ( __be16 ) > stream - > end )
return - EINVAL ;
len = get_unaligned_be16 ( stream - > curr ) ;
if ( stream - > curr + len > stream - > end )
return - EINVAL ;
segment - > start = stream - > curr ;
segment - > length = len ;
return 0 ;
}
static int v4l2_jpeg_decode_subsampling ( u8 nf , u8 h_v )
{
if ( nf = = 1 )
return V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY ;
/* no chroma subsampling for 4-component images */
if ( nf = = 4 & & h_v ! = 0x11 )
return - EINVAL ;
switch ( h_v ) {
case 0x11 :
return V4L2_JPEG_CHROMA_SUBSAMPLING_444 ;
case 0x21 :
return V4L2_JPEG_CHROMA_SUBSAMPLING_422 ;
case 0x22 :
return V4L2_JPEG_CHROMA_SUBSAMPLING_420 ;
case 0x41 :
return V4L2_JPEG_CHROMA_SUBSAMPLING_411 ;
default :
return - EINVAL ;
}
}
static int jpeg_parse_frame_header ( struct jpeg_stream * stream , u16 sof_marker ,
struct v4l2_jpeg_frame_header * frame_header )
{
int len = jpeg_get_word_be ( stream ) ;
if ( len < 0 )
return len ;
/* Lf = 8 + 3 * Nf, Nf >= 1 */
if ( len < 8 + 3 )
return - EINVAL ;
if ( frame_header ) {
/* Table B.2 - Frame header parameter sizes and values */
int p , y , x , nf ;
int i ;
p = jpeg_get_byte ( stream ) ;
if ( p < 0 )
return p ;
/*
* Baseline DCT only supports 8 - bit precision .
* Extended sequential DCT also supports 12 - bit precision .
*/
if ( p ! = 8 & & ( p ! = 12 | | sof_marker ! = SOF1 ) )
return - EINVAL ;
y = jpeg_get_word_be ( stream ) ;
if ( y < 0 )
return y ;
if ( y = = 0 )
return - EINVAL ;
x = jpeg_get_word_be ( stream ) ;
if ( x < 0 )
return x ;
if ( x = = 0 )
return - EINVAL ;
nf = jpeg_get_byte ( stream ) ;
if ( nf < 0 )
return nf ;
/*
* The spec allows 1 < = Nf < = 255 , but we only support up to 4
* components .
*/
if ( nf < 1 | | nf > V4L2_JPEG_MAX_COMPONENTS )
return - EINVAL ;
if ( len ! = 8 + 3 * nf )
return - EINVAL ;
frame_header - > precision = p ;
frame_header - > height = y ;
frame_header - > width = x ;
frame_header - > num_components = nf ;
for ( i = 0 ; i < nf ; i + + ) {
struct v4l2_jpeg_frame_component_spec * component ;
int c , h_v , tq ;
c = jpeg_get_byte ( stream ) ;
if ( c < 0 )
return c ;
h_v = jpeg_get_byte ( stream ) ;
if ( h_v < 0 )
return h_v ;
if ( i = = 0 ) {
int subs ;
subs = v4l2_jpeg_decode_subsampling ( nf , h_v ) ;
if ( subs < 0 )
return subs ;
frame_header - > subsampling = subs ;
} else if ( h_v ! = 0x11 ) {
/* all chroma sampling factors must be 1 */
return - EINVAL ;
}
tq = jpeg_get_byte ( stream ) ;
if ( tq < 0 )
return tq ;
component = & frame_header - > component [ i ] ;
component - > component_identifier = c ;
component - > horizontal_sampling_factor =
( h_v > > 4 ) & 0xf ;
component - > vertical_sampling_factor = h_v & 0xf ;
component - > quantization_table_selector = tq ;
}
} else {
return jpeg_skip ( stream , len - 2 ) ;
}
return 0 ;
}
static int jpeg_parse_scan_header ( struct jpeg_stream * stream ,
struct v4l2_jpeg_scan_header * scan_header )
{
size_t skip ;
int len = jpeg_get_word_be ( stream ) ;
if ( len < 0 )
return len ;
/* Ls = 8 + 3 * Ns, Ns >= 1 */
if ( len < 6 + 2 )
return - EINVAL ;
if ( scan_header ) {
int ns ;
int i ;
ns = jpeg_get_byte ( stream ) ;
if ( ns < 0 )
return ns ;
if ( ns < 1 | | ns > 4 | | len ! = 6 + 2 * ns )
return - EINVAL ;
scan_header - > num_components = ns ;
for ( i = 0 ; i < ns ; i + + ) {
struct v4l2_jpeg_scan_component_spec * component ;
int cs , td_ta ;
cs = jpeg_get_byte ( stream ) ;
if ( cs < 0 )
return cs ;
td_ta = jpeg_get_byte ( stream ) ;
if ( td_ta < 0 )
return td_ta ;
component = & scan_header - > component [ i ] ;
component - > component_selector = cs ;
component - > dc_entropy_coding_table_selector =
( td_ta > > 4 ) & 0xf ;
component - > ac_entropy_coding_table_selector =
td_ta & 0xf ;
}
skip = 3 ; /* skip Ss, Se, Ah, and Al */
} else {
skip = len - 2 ;
}
return jpeg_skip ( stream , skip ) ;
}
/* B.2.4.1 Quantization table-specification syntax */
static int jpeg_parse_quantization_tables ( struct jpeg_stream * stream ,
u8 precision ,
struct v4l2_jpeg_reference * tables )
{
int len = jpeg_get_word_be ( stream ) ;
if ( len < 0 )
return len ;
/* Lq = 2 + n * 65 (for baseline DCT), n >= 1 */
if ( len < 2 + 65 )
return - EINVAL ;
len - = 2 ;
while ( len > = 65 ) {
u8 pq , tq , * qk ;
int ret ;
int pq_tq = jpeg_get_byte ( stream ) ;
if ( pq_tq < 0 )
return pq_tq ;
/* quantization table element precision */
pq = ( pq_tq > > 4 ) & 0xf ;
/*
* Only 8 - bit Qk values for 8 - bit sample precision . Extended
* sequential DCT with 12 - bit sample precision also supports
* 16 - bit Qk values .
*/
if ( pq ! = 0 & & ( pq ! = 1 | | precision ! = 12 ) )
return - EINVAL ;
/* quantization table destination identifier */
tq = pq_tq & 0xf ;
if ( tq > 3 )
return - EINVAL ;
/* quantization table element */
qk = stream - > curr ;
ret = jpeg_skip ( stream , pq ? 128 : 64 ) ;
if ( ret < 0 )
return - EINVAL ;
if ( tables ) {
tables [ tq ] . start = qk ;
tables [ tq ] . length = pq ? 128 : 64 ;
}
len - = pq ? 129 : 65 ;
}
return 0 ;
}
/* B.2.4.2 Huffman table-specification syntax */
static int jpeg_parse_huffman_tables ( struct jpeg_stream * stream ,
struct v4l2_jpeg_reference * tables )
{
int mt ;
int len = jpeg_get_word_be ( stream ) ;
if ( len < 0 )
return len ;
/* Table B.5 - Huffman table specification parameter sizes and values */
if ( len < 2 + 17 )
return - EINVAL ;
for ( len - = 2 ; len > = 17 ; len - = 17 + mt ) {
u8 tc , th , * table ;
int tc_th = jpeg_get_byte ( stream ) ;
int i , ret ;
if ( tc_th < 0 )
return tc_th ;
/* table class - 0 = DC, 1 = AC */
tc = ( tc_th > > 4 ) & 0xf ;
if ( tc > 1 )
return - EINVAL ;
/* huffman table destination identifier */
th = tc_th & 0xf ;
/* only two Huffman tables for baseline DCT */
if ( th > 1 )
return - EINVAL ;
/* BITS - number of Huffman codes with length i */
table = stream - > curr ;
mt = 0 ;
for ( i = 0 ; i < 16 ; i + + ) {
int li ;
li = jpeg_get_byte ( stream ) ;
if ( li < 0 )
return li ;
mt + = li ;
}
/* HUFFVAL - values associated with each Huffman code */
ret = jpeg_skip ( stream , mt ) ;
if ( ret < 0 )
return ret ;
if ( tables ) {
tables [ ( tc < < 1 ) | th ] . start = table ;
tables [ ( tc < < 1 ) | th ] . length = stream - > curr - table ;
}
}
return jpeg_skip ( stream , len - 2 ) ;
}
/* B.2.4.4 Restart interval definition syntax */
static int jpeg_parse_restart_interval ( struct jpeg_stream * stream ,
u16 * restart_interval )
{
int len = jpeg_get_word_be ( stream ) ;
int ri ;
if ( len < 0 )
return len ;
if ( len ! = 4 )
return - EINVAL ;
ri = jpeg_get_word_be ( stream ) ;
if ( ri < 0 )
return ri ;
* restart_interval = ri ;
return 0 ;
}
static int jpeg_skip_segment ( struct jpeg_stream * stream )
{
int len = jpeg_get_word_be ( stream ) ;
if ( len < 0 )
return len ;
if ( len < 2 )
return - EINVAL ;
return jpeg_skip ( stream , len - 2 ) ;
}
2021-03-11 03:28:49 +03:00
/* Rec. ITU-T T.872 (06/2012) 6.5.3 */
static int jpeg_parse_app14_data ( struct jpeg_stream * stream ,
enum v4l2_jpeg_app14_tf * tf )
{
int ret ;
int lp ;
int skip ;
lp = jpeg_get_word_be ( stream ) ;
if ( lp < 0 )
return lp ;
/* Check for "Adobe\0" in Ap1..6 */
if ( stream - > curr + 6 > stream - > end | |
strncmp ( stream - > curr , " Adobe \0 " , 6 ) )
return - EINVAL ;
/* get to Ap12 */
ret = jpeg_skip ( stream , 11 ) ;
if ( ret < 0 )
return ret ;
ret = jpeg_get_byte ( stream ) ;
if ( ret < 0 )
return ret ;
* tf = ret ;
/* skip the rest of the segment, this ensures at least it is complete */
skip = lp - 2 - 11 ;
return jpeg_skip ( stream , skip ) ;
}
2020-03-18 21:35:32 +03:00
/**
2021-03-03 15:55:10 +03:00
* v4l2_jpeg_parse_header - locate marker segments and optionally parse headers
2020-03-18 21:35:32 +03:00
* @ buf : address of the JPEG buffer , should start with a SOI marker
* @ len : length of the JPEG buffer
* @ out : returns marker segment positions and optionally parsed headers
*
* The out - > scan_header pointer must be initialized to NULL or point to a valid
* v4l2_jpeg_scan_header structure . The out - > huffman_tables and
* out - > quantization_tables pointers must be initialized to NULL or point to a
* valid array of 4 v4l2_jpeg_reference structures each .
*
* Returns 0 or negative error if parsing failed .
*/
int v4l2_jpeg_parse_header ( void * buf , size_t len , struct v4l2_jpeg_header * out )
{
struct jpeg_stream stream ;
int marker ;
int ret = 0 ;
stream . curr = buf ;
stream . end = stream . curr + len ;
out - > num_dht = 0 ;
out - > num_dqt = 0 ;
2021-03-11 03:28:50 +03:00
/* the first bytes must be SOI, B.2.1 High-level syntax */
if ( jpeg_get_word_be ( & stream ) ! = SOI )
2020-03-18 21:35:32 +03:00
return - EINVAL ;
2021-03-11 03:28:49 +03:00
/* init value to signal if this marker is not present */
out - > app14_tf = V4L2_JPEG_APP14_TF_UNKNOWN ;
2020-03-18 21:35:32 +03:00
/* loop through marker segments */
while ( ( marker = jpeg_next_marker ( & stream ) ) > = 0 ) {
switch ( marker ) {
/* baseline DCT, extended sequential DCT */
case SOF0 . . . SOF1 :
ret = jpeg_reference_segment ( & stream , & out - > sof ) ;
if ( ret < 0 )
return ret ;
ret = jpeg_parse_frame_header ( & stream , marker ,
& out - > frame ) ;
break ;
/* progressive, lossless */
case SOF2 . . . SOF3 :
/* differential coding */
case SOF5 . . . SOF7 :
/* arithmetic coding */
case SOF9 . . . SOF11 :
case SOF13 . . . SOF15 :
case DAC :
case TEM :
return - EINVAL ;
case DHT :
ret = jpeg_reference_segment ( & stream ,
& out - > dht [ out - > num_dht + + % 4 ] ) ;
if ( ret < 0 )
return ret ;
2021-03-11 03:28:51 +03:00
if ( ! out - > huffman_tables ) {
ret = jpeg_skip_segment ( & stream ) ;
break ;
}
2020-03-18 21:35:32 +03:00
ret = jpeg_parse_huffman_tables ( & stream ,
out - > huffman_tables ) ;
break ;
case DQT :
ret = jpeg_reference_segment ( & stream ,
& out - > dqt [ out - > num_dqt + + % 4 ] ) ;
if ( ret < 0 )
return ret ;
2021-03-11 03:28:51 +03:00
if ( ! out - > quantization_tables ) {
ret = jpeg_skip_segment ( & stream ) ;
break ;
}
2020-03-18 21:35:32 +03:00
ret = jpeg_parse_quantization_tables ( & stream ,
out - > frame . precision ,
out - > quantization_tables ) ;
break ;
case DRI :
ret = jpeg_parse_restart_interval ( & stream ,
& out - > restart_interval ) ;
break ;
2021-03-11 03:28:49 +03:00
case APP14 :
ret = jpeg_parse_app14_data ( & stream ,
& out - > app14_tf ) ;
break ;
2020-03-18 21:35:32 +03:00
case SOS :
ret = jpeg_reference_segment ( & stream , & out - > sos ) ;
if ( ret < 0 )
return ret ;
ret = jpeg_parse_scan_header ( & stream , out - > scan ) ;
/*
* stop parsing , the scan header marks the beginning of
* the entropy coded segment
*/
out - > ecs_offset = stream . curr - ( u8 * ) buf ;
return ret ;
/* markers without parameters */
case RST0 . . . RST7 : /* restart */
case SOI : /* start of image */
case EOI : /* end of image */
break ;
/* skip unknown or unsupported marker segments */
default :
ret = jpeg_skip_segment ( & stream ) ;
break ;
}
if ( ret < 0 )
return ret ;
}
return marker ;
}
EXPORT_SYMBOL_GPL ( v4l2_jpeg_parse_header ) ;
/**
* v4l2_jpeg_parse_frame_header - parse frame header
* @ buf : address of the frame header , after the SOF0 marker
* @ len : length of the frame header
* @ frame_header : returns the parsed frame header
*
* Returns 0 or negative error if parsing failed .
*/
int v4l2_jpeg_parse_frame_header ( void * buf , size_t len ,
struct v4l2_jpeg_frame_header * frame_header )
{
struct jpeg_stream stream ;
stream . curr = buf ;
stream . end = stream . curr + len ;
return jpeg_parse_frame_header ( & stream , SOF0 , frame_header ) ;
}
EXPORT_SYMBOL_GPL ( v4l2_jpeg_parse_frame_header ) ;
/**
* v4l2_jpeg_parse_scan_header - parse scan header
* @ buf : address of the scan header , after the SOS marker
* @ len : length of the scan header
* @ scan_header : returns the parsed scan header
*
* Returns 0 or negative error if parsing failed .
*/
int v4l2_jpeg_parse_scan_header ( void * buf , size_t len ,
struct v4l2_jpeg_scan_header * scan_header )
{
struct jpeg_stream stream ;
stream . curr = buf ;
stream . end = stream . curr + len ;
return jpeg_parse_scan_header ( & stream , scan_header ) ;
}
EXPORT_SYMBOL_GPL ( v4l2_jpeg_parse_scan_header ) ;
/**
* v4l2_jpeg_parse_quantization_tables - parse quantization tables segment
* @ buf : address of the quantization table segment , after the DQT marker
* @ len : length of the quantization table segment
* @ precision : sample precision ( P ) in bits per component
* @ q_tables : returns four references into the buffer for the
* four possible quantization table destinations
*
* Returns 0 or negative error if parsing failed .
*/
int v4l2_jpeg_parse_quantization_tables ( void * buf , size_t len , u8 precision ,
struct v4l2_jpeg_reference * q_tables )
{
struct jpeg_stream stream ;
stream . curr = buf ;
stream . end = stream . curr + len ;
return jpeg_parse_quantization_tables ( & stream , precision , q_tables ) ;
}
EXPORT_SYMBOL_GPL ( v4l2_jpeg_parse_quantization_tables ) ;
/**
* v4l2_jpeg_parse_huffman_tables - parse huffman tables segment
* @ buf : address of the Huffman table segment , after the DHT marker
* @ len : length of the Huffman table segment
* @ huffman_tables : returns four references into the buffer for the
* four possible Huffman table destinations , in
* the order DC0 , DC1 , AC0 , AC1
*
* Returns 0 or negative error if parsing failed .
*/
int v4l2_jpeg_parse_huffman_tables ( void * buf , size_t len ,
struct v4l2_jpeg_reference * huffman_tables )
{
struct jpeg_stream stream ;
stream . curr = buf ;
stream . end = stream . curr + len ;
return jpeg_parse_huffman_tables ( & stream , huffman_tables ) ;
}
EXPORT_SYMBOL_GPL ( v4l2_jpeg_parse_huffman_tables ) ;