libdcp
verify_j2k.cc
Go to the documentation of this file.
1 /*
2  Copyright (C) 2021 Carl Hetherington <cth@carlh.net>
3 
4  This file is part of libdcp.
5 
6  libdcp is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10 
11  libdcp is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with libdcp. If not, see <http://www.gnu.org/licenses/>.
18 
19  In addition, as a special exception, the copyright holders give
20  permission to link the code of portions of this program with the
21  OpenSSL library under certain conditions as described in each
22  individual source file, and distribute linked combinations
23  including the two.
24 
25  You must obey the GNU General Public License in all respects
26  for all of the code used other than OpenSSL. If you modify
27  file(s) with this exception, you may extend this exception to your
28  version of the file(s), but you are not obligated to do so. If you
29  do not wish to do so, delete this exception statement from your
30  version. If you delete this exception statement from all source
31  files in the program, then also delete it here.
32 */
33 
34 
40 #include "compose.hpp"
41 #include "data.h"
42 #include "raw_convert.h"
43 #include "verify.h"
44 #include "verify_j2k.h"
45 #include <memory>
46 #include <vector>
47 
48 
49 using std::shared_ptr;
50 using std::map;
51 using std::runtime_error;
52 using std::string;
53 using std::vector;
54 using boost::optional;
55 using dcp::raw_convert;
56 
57 
58 class InvalidCodestream : public runtime_error
59 {
60 public:
61  InvalidCodestream (string note)
62  : runtime_error(note)
63  {}
64 };
65 
66 
67 void
68 dcp::verify_j2k(shared_ptr<const Data> j2k, int start_index, int frame_index, int frame_rate, vector<VerificationNote>& notes)
69 {
70  /* See ITU-T T800 (visible on https://github.com/Ymagis/ClairMeta/issues/130) */
71  unsigned int const max_tile_part_size = std::floor(200e6 / (8 * frame_rate));
72 
73  try {
74  auto ptr = j2k->data();
75  auto end = ptr + j2k->size();
76 
77  map<string, uint8_t> markers = {
78  { "SOC", 0x4f },
79  { "SIZ", 0x51 },
80  { "COD", 0x52 },
81  { "COC", 0x53 },
82  { "TLM", 0x55 },
83  { "QCD", 0x5c },
84  { "QCC", 0x5d },
85  { "POC", 0x5f },
86  { "COM", 0x64 },
87  { "SOT", 0x90 },
88  { "SOD", 0x93 },
89  { "EOC", 0xd9 },
90  };
91 
92  auto marker_name_from_id = [&markers](uint8_t b) -> optional<string> {
93  for (auto const& i: markers) {
94  if (i.second == b) {
95  return i.first;
96  }
97  }
98  return {};
99  };
100 
101  auto require_marker = [&](string name) {
102  if (ptr == end || *ptr != 0xff) {
103  throw InvalidCodestream ("missing marker start byte");
104  }
105  ++ptr;
106  if (ptr == end || *ptr != markers[name]) {
107  throw InvalidCodestream ("missing_marker " + name);
108  }
109  ++ptr;
110  };
111 
112  auto get_8 = [&]() {
113  if (ptr >= end) {
114  throw InvalidCodestream ("unexpected end of file");
115  }
116  return *ptr++;
117  };
118 
119  auto get_16 = [&]() {
120  if (ptr >= (end - 1)) {
121  throw InvalidCodestream ("unexpected end of file");
122  }
123  auto const a = *ptr++;
124  auto const b = *ptr++;
125  return b | (a << 8);
126  };
127 
128  auto get_32 = [&]() -> uint32_t {
129  if (ptr >= (end - 3)) {
130  throw InvalidCodestream ("unexpected end of file");
131  }
132  auto const a = *ptr++;
133  auto const b = *ptr++;
134  auto const c = *ptr++;
135  auto const d = *ptr++;
136  return d | (c << 8) | (b << 16) | (a << 24);
137  };
138 
139  auto require_8 = [&](uint8_t value, string note) {
140  auto v = get_8 ();
141  if (v != value) {
142  throw InvalidCodestream (String::compose(note, v));
143  }
144  };
145 
146  auto require_16 = [&](uint16_t value, string note) {
147  auto v = get_16 ();
148  if (v != value) {
149  throw InvalidCodestream (String::compose(note, v));
150  }
151  };
152 
153  auto require_32 = [&](uint32_t value, string note) {
154  auto v = get_32 ();
155  if (v != value) {
156  throw InvalidCodestream (String::compose(note, v));
157  }
158  };
159 
160  require_marker ("SOC");
161  require_marker ("SIZ");
162  auto L_siz = get_16();
163  if (L_siz != 47) {
164  throw InvalidCodestream("unexpected SIZ size " + raw_convert<string>(L_siz));
165  }
166 
167  get_16(); // CA: codestream capabilities
168  auto const image_width = get_32();
169  auto const image_height = get_32();
170  auto const fourk = image_width > 2048;
171  require_32 (0, "invalid top-left image x coordinate %1");
172  require_32 (0, "invalid top-left image y coordinate %1");
173  auto const tile_width = get_32();
174  auto const tile_height = get_32();
175  if (tile_width != image_width || tile_height != image_height) {
176  notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_TILE_SIZE });
177  }
178  require_32 (0, "invalid tile anchor x coordinate %1");
179  require_32 (0, "invalid tile anchor y coordinate %1");
180  require_16 (3, "invalid component count %1");
181  for (auto i = 0; i < 3; ++i) {
182  require_8 (12 - 1, "invalid bit depth %1");
183  require_8 (1, "invalid horizontal subsampling factor %1");
184  require_8 (1, "invalid vertical subsampling factor %1");
185  }
186 
187  auto num_COD = 0;
188  auto num_QCD = 0;
190  auto num_POC_in_main = 0;
192  auto num_POC_after_main = 0;
193  bool main_header_finished = false;
194  bool tlm = false;
195 
196  while (ptr < end)
197  {
198  require_8(0xff, "missing marker start byte");
199  auto marker_id = get_8();
200  auto marker_name = marker_name_from_id (marker_id);
201  if (!marker_name) {
202  char buffer[16];
203  snprintf (buffer, 16, "%2x", marker_id);
204  throw InvalidCodestream(String::compose("unknown marker %1", buffer));
205  } else if (*marker_name == "SOT") {
206  require_16(10, "invalid SOT size %1");
207  get_16(); // tile index
208  auto const tile_part_length = get_32();
209  auto const tile_part_index = get_8();
210  auto tile_parts = get_8();
211  if (!fourk && tile_parts != 3) {
212  notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_TILE_PARTS_FOR_2K, raw_convert<string>(tile_parts) });
213  }
214  if (fourk && tile_parts != 6) {
215  notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_TILE_PARTS_FOR_4K, raw_convert<string>(tile_parts) });
216  }
217  if (tile_part_length > max_tile_part_size) {
218  VerificationNote note{VerificationNote::Type::ERROR, VerificationNote::Code::INVALID_JPEG2000_TILE_PART_SIZE};
219  note.set_frame(frame_index);
220  note.set_frame_rate(frame_rate);
221  note.set_component(tile_part_index);
222  note.set_size(tile_part_length);
223  notes.push_back(note);
224  }
225  main_header_finished = true;
226  } else if (*marker_name == "SOD") {
227  while (ptr < (end - 1) && (ptr[0] != 0xff || ptr[1] < 0x90)) {
228  ++ptr;
229  }
230  } else if (*marker_name == "SIZ") {
231  throw InvalidCodestream ("duplicate SIZ marker");
232  } else if (*marker_name == "COD") {
233  num_COD++;
234  get_16(); // length
235  require_8(1, "invalid coding style %1");
236  require_8(4, "invalid progression order %1"); // CPRL
237  require_16(1, "invalid quality layers count %1");
238  require_8(1, "invalid multi-component transform flag %1");
239  require_8(fourk ? 6 : 5, "invalid number of transform levels %1");
240  auto log_code_block_width = get_8();
241  if (log_code_block_width != 3) {
242  notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_CODE_BLOCK_WIDTH, raw_convert<string>(4 * (2 << log_code_block_width)) });
243  }
244  auto log_code_block_height = get_8();
245  if (log_code_block_height != 3) {
246  notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_CODE_BLOCK_HEIGHT, raw_convert<string>(4 * (2 << log_code_block_height)) });
247  }
248  require_8(0, "invalid mode variations");
249  require_8(0, "invalid wavelet transform type %1"); // 9/7 irreversible
250  require_8(0x77, "invalid precinct size %1");
251  require_8(0x88, "invalid precinct size %1");
252  require_8(0x88, "invalid precinct size %1");
253  require_8(0x88, "invalid precinct size %1");
254  require_8(0x88, "invalid precinct size %1");
255  require_8(0x88, "invalid precinct size %1");
256  if (fourk) {
257  require_8(0x88, "invalid precinct size %1");
258  }
259  } else if (*marker_name == "QCD") {
260  num_QCD++;
261  auto const L_qcd = get_16();
262  auto quantization_style = get_8();
263  int guard_bits = (quantization_style >> 5) & 7;
264  if (fourk && guard_bits != 2) {
265  notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_4K, raw_convert<string>(guard_bits) });
266  }
267  if (!fourk && guard_bits != 1) {
268  notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_GUARD_BITS_FOR_2K, raw_convert<string>(guard_bits) });
269  }
270  ptr += L_qcd - 3;
271  } else if (*marker_name == "COC") {
272  get_16(); // length
273  auto const coc_component_number = get_8();
274  /* I don't know if this is really a requirement, but it seems to make sense that there should only
275  * be components 0, 1 and 2. DoM bug #2395 is about a DCP with COC component number 1 which seems
276  * like it should be OK.
277  */
278  if (coc_component_number > 2) {
279  throw InvalidCodestream(String::compose("invalid COC component number %1", coc_component_number));
280  }
281  require_8(1, "invalid coding style %1");
282  require_8(5, "invalid number of transform levels %1");
283  require_8(3, "invalid code block width exponent %1");
284  require_8(3, "invalid code block height exponent %1");
285  require_8(0, "invalid mode variations");
286  require_8(0x77, "invalid precinct size %1");
287  require_8(0x88, "invalid precinct size %1");
288  require_8(0x88, "invalid precinct size %1");
289  require_8(0x88, "invalid precinct size %1");
290  require_8(0x88, "invalid precinct size %1");
291  require_8(0x88, "invalid precinct size %1");
292  } else if (*marker_name == "TLM") {
293  auto const len = get_16();
294  ptr += len - 2;
295  tlm = true;
296  } else if (*marker_name == "QCC" || *marker_name == "COM") {
297  auto const len = get_16();
298  ptr += len - 2;
299  } else if (*marker_name == "POC") {
300  if (main_header_finished) {
301  num_POC_after_main++;
302  } else {
303  num_POC_in_main++;
304  }
305 
306  auto require_8_poc = [&](uint16_t value, string note) {
307  if (get_8() != value) {
308  notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INCORRECT_JPEG2000_POC_MARKER, String::compose(note, value) });
309  }
310  };
311 
312  auto require_16_poc = [&](uint16_t value, string note) {
313  if (get_16() != value) {
314  notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INCORRECT_JPEG2000_POC_MARKER, String::compose(note, value) });
315  }
316  };
317 
318  require_16_poc(16, "invalid length %1");
319  require_8_poc(0, "invalid RSpoc %1");
320  require_8_poc(0, "invalid CSpoc %1");
321  require_16_poc(1, "invalid LYEpoc %1");
322  require_8_poc(6, "invalid REpoc %1");
323  require_8_poc(3, "invalid CEpoc %1");
324  require_8_poc(4, "invalid Ppoc %1");
325  require_8_poc(6, "invalid RSpoc %1");
326  require_8_poc(0, "invalid CSpoc %1");
327  require_16_poc(1, "invalid LYEpoc %1");
328  require_8_poc(7, "invalid REpoc %1");
329  require_8_poc(3, "invalid CEpoc %1");
330  require_8_poc(4, "invalid Ppoc %1");
331  }
332  }
333 
334  if (num_COD == 0) {
335  throw InvalidCodestream("no COD marker found");
336  }
337  if (num_COD > 1) {
338  throw InvalidCodestream("more than one COD marker found");
339  }
340  if (num_QCD == 0) {
341  throw InvalidCodestream("no QCD marker found");
342  }
343  if (num_QCD > 1) {
344  throw InvalidCodestream("more than one QCD marker found");
345  }
346  if (num_POC_in_main != 0 && !fourk) {
347  notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INCORRECT_JPEG2000_POC_MARKER_COUNT_FOR_2K, raw_convert<string>(num_POC_in_main) });
348  }
349  if (num_POC_in_main != 1 && fourk) {
350  notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INCORRECT_JPEG2000_POC_MARKER_COUNT_FOR_4K, raw_convert<string>(num_POC_in_main) });
351  }
352  if (num_POC_after_main != 0) {
353  notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::INVALID_JPEG2000_POC_MARKER_LOCATION });
354  }
355  if (!tlm) {
356  notes.push_back ({ VerificationNote::Type::BV21_ERROR, VerificationNote::Code::MISSING_JPEG200_TLM_MARKER });
357  }
358  }
359  catch (InvalidCodestream const& e)
360  {
361  VerificationNote note({VerificationNote::Type::ERROR, VerificationNote::Code::INVALID_JPEG2000_CODESTREAM, string(e.what())});
362  note.set_frame(start_index + frame_index);
363  note.set_frame_rate(frame_rate);
364  notes.push_back(note);
365  }
366 }
367 
Data class.
void verify_j2k(std::shared_ptr< const Data > data, int start_index, int frame_index, int frame_rate, std::vector< VerificationNote > &notes)
Definition: verify_j2k.cc:68
P raw_convert(Q, int precision=16, bool fixed=false)
Definition: raw_convert.h:57
Methods for conversion to/from string.
dcp::verify() method and associated code
Verification that JPEG2000 files meet requirements.