TMC3.cpp 18.5 KB
Newer Older
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
1
/* The copyright in this software is being made available under the BSD
David Flynn's avatar
David Flynn committed
2
3
4
 * Licence, included below.  This software may be subject to other third
 * party and contributor rights, including patent rights, and no such
 * rights are granted under this licence.
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
5
 *
David Flynn's avatar
David Flynn committed
6
 * Copyright (c) 2017-2018, ISO/IEC
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
7
8
9
10
11
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
David Flynn's avatar
David Flynn committed
12
13
14
15
16
17
18
19
20
21
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of the ISO/IEC nor the names of its contributors
 *   may be used to endorse or promote products derived from this
 *   software without specific prior written permission.
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
22
23
24
25
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
David Flynn's avatar
David Flynn committed
26
27
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
28
29
30
31
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
David Flynn's avatar
David Flynn committed
32
33
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
34
35
36
 */

#include "TMC3.h"
37
#include "program_options_lite.h"
38
#include "io_tlv.h"
39
#include "version.h"
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
40
41
42
43

using namespace std;
using namespace pcc;

44
45
46
47
48
49
50
51
52
//============================================================================

struct Parameters {
  bool isDecoder;

  std::string uncompressedDataPath;
  std::string compressedStreamPath;
  std::string reconstructedDataPath;

53
54
55
  // Filename for saving pre inverse scaled point cloud.
  std::string preInvScalePath;

56
57
58
59
60
61
62
63
64
  pcc::EncoderParams encoder;
  pcc::DecoderParams decoder;

  // todo(df): this should be per-attribute
  ColorTransform colorTransform;
};

//============================================================================

65
66
67
int
main(int argc, char* argv[])
{
68
  cout << "MPEG PCC tmc3 version " << ::pcc::version << endl;
69

Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
70
71
72
73
  Parameters params;
  if (!ParseParameters(argc, argv, params)) {
    return -1;
  }
74
75
76
77
78
79
80

  // Timers to count elapsed wall/user time
  pcc::chrono::Stopwatch<std::chrono::steady_clock> clock_wall;
  pcc::chrono::Stopwatch<pcc::chrono::utime_inc_children_clock> clock_user;

  clock_wall.start();

Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
81
  int ret = 0;
82
  if (params.isDecoder) {
83
    ret = Decompress(params, clock_user);
84
85
  } else {
    ret = Compress(params, clock_user);
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
86
87
  }

88
89
90
91
92
93
94
95
  clock_wall.stop();

  using namespace std::chrono;
  auto total_wall = duration_cast<milliseconds>(clock_wall.count()).count();
  auto total_user = duration_cast<milliseconds>(clock_user.count()).count();
  std::cout << "Processing time (wall): " << total_wall / 1000.0 << " s\n";
  std::cout << "Processing time (user): " << total_user / 1000.0 << " s\n";

Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
96
97
98
  return ret;
}

99
100
101
//---------------------------------------------------------------------------
// :: Command line / config parsing helpers

102
103
104
105
template<typename T>
static std::istream&
readUInt(std::istream& in, T& val)
{
106
107
108
109
110
111
  unsigned int tmp;
  in >> tmp;
  val = T(tmp);
  return in;
}

112
113
114
static std::istream&
operator>>(std::istream& in, ColorTransform& val)
{
115
116
117
  return readUInt(in, val);
}

118
namespace pcc {
119
static std::istream&
120
operator>>(std::istream& in, AttributeEncoding& val)
121
{
122
  return readUInt(in, val);
123
124
}
}  // namespace pcc
125

126
namespace pcc {
127
128
129
static std::istream&
operator>>(std::istream& in, GeometryCodecType& val)
{
130
  return readUInt(in, val);
131
132
}
}  // namespace pcc
133

134
namespace pcc {
135
static std::ostream&
136
operator<<(std::ostream& out, const AttributeEncoding& val)
137
{
138
  switch (val) {
139
140
141
  case AttributeEncoding::kPredictingTransform: out << "0 (Pred)"; break;
  case AttributeEncoding::kRAHTransform: out << "1 (RAHT)"; break;
  case AttributeEncoding::kLiftingTransform: out << "2 (Lift)"; break;
142
143
  }
  return out;
144
145
}
}  // namespace pcc
146

147
namespace pcc {
148
149
150
static std::ostream&
operator<<(std::ostream& out, const GeometryCodecType& val)
{
151
  switch (val) {
152
  case GeometryCodecType::kOctree: out << "1 (TMC1 Octree)"; break;
153
154
155
  case GeometryCodecType::kTriSoup: out << "2 (TMC3 TriSoup)"; break;
  }
  return out;
156
157
}
}  // namespace pcc
158

159
160
161
//---------------------------------------------------------------------------
// :: Command line / config parsing

162
163
164
bool
ParseParameters(int argc, char* argv[], Parameters& params)
{
165
166
  namespace po = df::program_options_lite;

167
168
169
170
171
  struct {
    AttributeDescription desc;
    AttributeParameterSet aps;
  } params_attr;

172
173
174
175
176
177
178
179
180
181
182
183
184
  bool print_help = false;

  // a helper to set the attribute
  std::function<po::OptionFunc::Func> attribute_setter =
    [&](po::Options&, const std::string& name, po::ErrorReporter) {
      // copy the current state of parsed attribute parameters
      //
      // NB: this does not cause the default values of attr to be restored
      // for the next attribute block.  A side-effect of this is that the
      // following is allowed leading to attribute foo having both X=1 and
      // Y=2:
      //   "--attr.X=1 --attribute foo --attr.Y=2 --attribute foo"
      //
185
186
187
188
189
190
191
192
193
194
195
196
197
198

      // NB: insert returns any existing element
      const auto& it = params.encoder.attributeIdxMap.insert(
        {name, int(params.encoder.attributeIdxMap.size())});

      if (it.second) {
        params.encoder.sps.attributeSets.push_back(params_attr.desc);
        params.encoder.aps.push_back(params_attr.aps);
        return;
      }

      // update existing entry
      params.encoder.sps.attributeSets[it.first->second] = params_attr.desc;
      params.encoder.aps[it.first->second] = params_attr.aps;
199
200
    };

201
  /* clang-format off */
202
203
204
205
206
207
208
209
210
211
212
  // The definition of the program/config options, along with default values.
  //
  // NB: when updating the following tables:
  //      (a) please keep to 80-columns for easier reading at a glance,
  //      (b) do not vertically align values -- it breaks quickly
  //
  po::Options opts;
  opts.addOptions()
  ("help", print_help, false, "this help text")
  ("config,c", po::parseConfigFile, "configuration file name")

213
214
  (po::Section("General"))

215
  ("mode", params.isDecoder, false,
216
217
    "The encoding/decoding mode:\n"
    "  0: encode\n"
218
    "  1: decode")
219
220
221

  // i/o parameters
  ("reconstructedDataPath",
222
223
    params.reconstructedDataPath, {},
    "The ouput reconstructed pointcloud file path (decoder only)")
224
225

  ("uncompressedDataPath",
226
227
    params.uncompressedDataPath, {},
    "The input pointcloud file path")
228
229

  ("compressedStreamPath",
230
231
    params.compressedStreamPath, {},
    "The compressed bitstream path (encoder=output, decoder=input)")
232

233
  ("postRecolorPath",
234
    params.encoder.postRecolorPath, {},
235
    "Recolored pointcloud file path (encoder only)")
236
237

  ("preInvScalePath",
238
    params.preInvScalePath, {},
239
    "Pre inverse scaled pointcloud file path (decoder only)")
240

241
  // general
242
  // todo(df): this should be per-attribute
243
  ("colorTransform",
244
245
246
247
    params.colorTransform, COLOR_TRANSFORM_RGB_TO_YCBCR,
    "The colour transform to be applied:\n"
    "  0: none\n"
    "  1: RGB to YCbCr (Rec.709)")
248

249
250
251
  (po::Section("Decoder"))

  ("roundOutputPositions",
252
    params.decoder.roundOutputPositions, false,
253
254
255
256
    "todo(kmammou)")

  (po::Section("Encoder"))

257
  ("positionQuantizationScale",
258
    params.encoder.sps.seq_source_geom_scale_factor, 1.f,
259
    "Scale factor to be applied to point positions during quantization process")
260
261

  ("mergeDuplicatedPoints",
262
    params.encoder.gps.geom_unique_points_flag, true,
263
    "Enables removal of duplicated points")
264

265
  (po::Section("Geometry"))
266

267
  // tools
268
  ("geometryCodec",
269
    params.encoder.gps.geom_codec_type, GeometryCodecType::kOctree,
270
    "Controls the method used to encode geometry:\n"
271
272
    "  1: octree (TMC3)\n"
    "  2: trisoup (TMC1)")
273

274
  ("neighbourContextRestriction",
275
    params.encoder.gps.neighbour_context_restriction_flag, false,
276
    "Limit geometry octree occupancy contextualisation to sibling nodes")
277

278
  ("neighbourAvailBoundaryLog2",
279
    params.encoder.gps.neighbour_avail_boundary_log2, 0,
280
281
282
    "Defines the avaliability volume for neighbour occupancy lookups."
    " 0: unconstrained")

283
  ("inferredDirectCodingMode",
284
    params.encoder.gps.inferred_direct_coding_mode_enabled_flag, true,
285
    "Permits early termination of the geometry octree for isolated points")
286

287
288
  // (trisoup) geometry parameters
  ("triSoupDepth",  // log2(maxBB+1), where maxBB+1 is analogous to image width
289
    params.encoder.gps.trisoup_depth, 10,
290
    "Depth of voxels (reconstructed points) in trisoup geometry")
291
292

  ("triSoupLevel",
293
    params.encoder.gps.trisoup_triangle_level, 7,
294
    "Level of triangles (reconstructed surface) in trisoup geometry")
295
296

  ("triSoupIntToOrigScale",  // reciprocal of positionQuantizationScale
297
298
    params.encoder.sps.donotuse_trisoup_int_to_orig_scale, 1.f,
    "orig_coords = integer_coords * intToOrigScale")
299

300
301
  (po::Section("Attributes"))

302
303
304
  // attribute processing
  //   NB: Attribute options are special in the way they are applied (see above)
  ("attribute",
305
306
307
    attribute_setter,
    "Encode the given attribute (NB, must appear after the"
    "following attribute parameters)")
308

309
310
311
312
  ("bitdepth",
    params_attr.desc.attr_bitdepth, 8,
    "Attribute bitdepth")

313
  ("transformType",
314
    params_attr.aps.attr_encoding, AttributeEncoding::kPredictingTransform,
315
    "Coding method to use for attribute:\n"
316
    "  0: Hierarchical neighbourhood prediction\n"
317
    "  1: Region Adaptive Hierarchical Transform (RAHT)\n"
318
    "  2: Hierarichical neighbourhood prediction as lifting transform")
319

320
  ("rahtLeafDecimationDepth",
321
    params_attr.aps.raht_binary_level_threshold, 3,
322
323
    "Sets coefficients to zero in the bottom n levels of RAHT tree. "
    "Used for chroma-subsampling in attribute=color only.")
324

325
  ("rahtQuantizationStep",
326
327
    params_attr.aps.quant_step_size_luma, {},
    "deprecated -- use quantizationStepsLuma")
328
329

  ("rahtDepth",
330
    params_attr.aps.raht_depth, 21,
331
332
    "Number of bits for morton representation of an RAHT co-ordinate"
    "component")
333

334
  ("numberOfNearestNeighborsInPrediction",
335
    params_attr.aps.num_pred_nearest_neighbours, 4,
336
    "Attribute's maximum number of nearest neighbors to be used for prediction")
337
338

  ("levelOfDetailCount",
339
    params_attr.aps.numDetailLevels, 1,
340
    "Attribute's number of levels of detail")
341
342

  ("quantizationSteps",
343
344
    params_attr.aps.quant_step_size_luma, {},
    "deprecated -- use quantizationStepsLuma")
345
346

  ("quantizationStepsLuma",
347
    params_attr.aps.quant_step_size_luma, {},
348
349
350
    "Attribute's luma quantization step sizes (one for each LoD)")

  ("quantizationStepsChroma",
351
    params_attr.aps.quant_step_size_chroma, {},
352
    "Attribute's chroma quantization step sizes (one for each LoD)")
353

354
355
  ("dist2",
    params_attr.aps.dist2, {},
356
    "Attribute's list of squared distances (one for each LoD)")
357
  ;
358
  /* clang-format on */
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373

  po::setDefaults(opts);
  po::ErrorReporter err;
  const list<const char*>& argv_unhandled =
    po::scanArgv(opts, argc, (const char**)argv, err);

  for (const auto arg : argv_unhandled) {
    err.warn() << "Unhandled argument ignored: " << arg << "\n";
  }

  if (argc == 1 || print_help) {
    po::doHelp(std::cout, opts, 78);
    return false;
  }

374
375
376
377
  if (int(params.encoder.gps.geom_codec_type) == 0) {
    err.error() << "Bypassed geometry coding is no longer supported\n";
  }

378
  // For trisoup, ensure that positionQuantizationScale is the exact inverse of intToOrigScale.
379
380
381
  if (params.encoder.gps.geom_codec_type == GeometryCodecType::kTriSoup) {
    params.encoder.sps.seq_source_geom_scale_factor =
      1.0f / params.encoder.sps.donotuse_trisoup_int_to_orig_scale;
382
383
  }

384
  // For RAHT, ensure that the unused lod count = 0 (prevents mishaps)
385
386
387
388
389
  for (const auto& it : params.encoder.attributeIdxMap) {
    auto& attr_aps = params.encoder.aps[it.second];

    if (attr_aps.attr_encoding == AttributeEncoding::kRAHTransform) {
      attr_aps.numDetailLevels = 0;
390
391
392
    }
  }

393
  // sanity checks
394
  //  - validate that quantizationStepsLuma/Chroma, dist2
395
  //    of each attribute contain levelOfDetailCount elements.
396
397
398
399
400
401
402
403
  for (const auto& it : params.encoder.attributeIdxMap) {
    const auto& attr_sps = params.encoder.sps.attributeSets[it.second];
    const auto& attr_aps = params.encoder.aps[it.second];

    bool isLifting =
      attr_aps.attr_encoding == AttributeEncoding::kPredictingTransform
      || attr_aps.attr_encoding == AttributeEncoding::kLiftingTransform;

404
405
406
407
408
409
410
411
412
413
414
    if (it.first == "color") {
      // todo(??): permit relaxing of the following constraint
      if (attr_sps.attr_bitdepth > 8)
        err.error() << it.first << ".bitdepth must be less than 9\n";
    }

    if (it.first == "reflectance") {
      if (attr_sps.attr_bitdepth > 16)
        err.error() << it.first << ".bitdepth must be less than 17\n";
    }

415
416
    if (isLifting) {
      int lod = attr_aps.numDetailLevels;
417

418
      if (lod > 255) {
419
        err.error() << it.first
420
                    << ".levelOfDetailCount must be less than 256\n";
421
      }
422
      // todo(df): the following two checks are removed in m42640/2
423
424
      if (attr_aps.dist2.size() != lod) {
        err.error() << it.first << ".dist2 does not have " << lod
425
                    << " entries\n";
426
      }
427
428
      if (attr_aps.quant_step_size_luma.size() != lod) {
        err.error() << it.first << ".quantizationStepsLuma does not have "
429
430
                    << lod << " entries\n";
      }
431
432
433
434
      if (it.first == "color") {
        if (attr_aps.quant_step_size_chroma.size() != lod) {
          err.error() << it.first << ".quantizationStepsChroma does not have "
                      << lod << " entries\n";
435
        }
436
      }
437

438
      if (
439
        attr_aps.num_pred_nearest_neighbours
440
441
        > PCCTMC3MaxPredictionNearestNeighborCount) {
        err.error()
442
          << it.first
443
444
445
          << ".numberOfNearestNeighborsInPrediction must be less than "
          << PCCTMC3MaxPredictionNearestNeighborCount << "\n";
      }
446
    }
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
447
448
  }

449
450
  // check required arguments are specified

451
  if (!params.isDecoder && params.uncompressedDataPath.empty())
452
453
    err.error() << "uncompressedDataPath not set\n";

454
  if (params.isDecoder && params.reconstructedDataPath.empty())
455
456
457
458
459
460
461
462
463
464
    err.error() << "reconstructedDataPath not set\n";

  if (params.compressedStreamPath.empty())
    err.error() << "compressedStreamPath not set\n";

  // report the current configuration (only in the absence of errors so
  // that errors/warnings are more obvious and in the same place).
  if (err.is_errored)
    return false;

465
466
  // Dump the complete derived configuration
  cout << "+ Effective configuration parameters\n";
467

468
  po::dumpCfg(cout, opts, "General", 4);
469
  if (params.isDecoder) {
470
    po::dumpCfg(cout, opts, "Decoder", 4);
471
  } else {
472
473
474
    po::dumpCfg(cout, opts, "Encoder", 4);
    po::dumpCfg(cout, opts, "Geometry", 4);

475
    for (const auto& it : params.encoder.attributeIdxMap) {
476
      // NB: when dumping the config, opts references params_attr
477
478
      params_attr.desc = params.encoder.sps.attributeSets[it.second];
      params_attr.aps = params.encoder.aps[it.second];
479
480
481
      cout << "    " << it.first << "\n";
      po::dumpCfg(cout, opts, "Attributes", 8);
    }
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
482
483
  }

484
485
  cout << endl;

Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
486
487
  return true;
}
488

489
int
490
Compress(Parameters& params, Stopwatch& clock)
491
{
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
492
  PCCPointSet3 pointCloud;
493
494
495
  if (
    !pointCloud.read(params.uncompressedDataPath)
    || pointCloud.getPointCount() == 0) {
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
496
497
498
499
    cout << "Error: can't open input file!" << endl;
    return -1;
  }

500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
  // Sanitise the input point cloud
  // todo(df): remove the following with generic handling of properties
  bool codeColour = params.encoder.attributeIdxMap.count("color");
  if (!codeColour)
    pointCloud.removeColors();
  assert(codeColour == pointCloud.hasColors());

  bool codeReflectance = params.encoder.attributeIdxMap.count("reflectance");
  if (!codeReflectance)
    pointCloud.removeReflectances();
  assert(codeReflectance == pointCloud.hasReflectances());

  ofstream fout(params.compressedStreamPath, ios::binary);
  if (!fout.is_open()) {
    return -1;
  }

517
518
  clock.start();

Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
519
520
521
522
523
524
525
526
527
528
  if (params.colorTransform == COLOR_TRANSFORM_RGB_TO_YCBCR) {
    pointCloud.convertRGBToYUV();
  }
  PCCTMC3Encoder3 encoder;

  std::unique_ptr<PCCPointSet3> reconstructedPointCloud;
  if (!params.reconstructedDataPath.empty()) {
    reconstructedPointCloud.reset(new PCCPointSet3);
  }

529
  int ret = encoder.compress(
530
531
    pointCloud, &params.encoder,
    [&](const PayloadBuffer& buf) { writeTlv(buf, fout); },
532
    reconstructedPointCloud.get());
533
  if (ret) {
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
534
535
536
    cout << "Error: can't compress point cloud!" << endl;
    return -1;
  }
537

538
  std::cout << "Total bitstream size " << fout.tellp() << " B" << std::endl;
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
539
540
  fout.close();

541
542
  clock.stop();

Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
543
544
545
546
547
548
549
550
551
  if (!params.reconstructedDataPath.empty()) {
    if (params.colorTransform == COLOR_TRANSFORM_RGB_TO_YCBCR) {
      reconstructedPointCloud->convertYUVToRGB();
    }
    reconstructedPointCloud->write(params.reconstructedDataPath, true);
  }

  return 0;
}
552
int
553
Decompress(Parameters& params, Stopwatch& clock)
554
{
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
555
556
557
558
559
  ifstream fin(params.compressedStreamPath, ios::binary);
  if (!fin.is_open()) {
    return -1;
  }

560
561
  clock.start();

562
  PayloadBuffer buf;
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
563
  PCCTMC3Decoder3 decoder;
564

565
566
567
  while (true) {
    PayloadBuffer* buf_ptr = &buf;
    readTlv(fin, &buf);
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
568

569
570
571
    // at end of file (or other error), flush decoder
    if (!fin)
      buf_ptr = nullptr;
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
572

573
574
575
    int ret = decoder.decompress(
      params.decoder, buf_ptr, [&](const PCCPointSet3& decodedPointCloud) {
        PCCPointSet3 pointCloud(decodedPointCloud);
576

577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
        if (params.colorTransform == COLOR_TRANSFORM_RGB_TO_YCBCR) {
          pointCloud.convertYUVToRGB();
        }

        // Dump the decoded colour using the pre inverse scaled geometry
        if (!params.preInvScalePath.empty()) {
          pointCloud.write(params.preInvScalePath);
        }

        decoder.inverseQuantization(
          pointCloud, params.decoder.roundOutputPositions);

        clock.stop();

        if (!pointCloud.write(params.reconstructedDataPath, true)) {
          cout << "Error: can't open output file!" << endl;
        }

        clock.start();
      });

    if (ret) {
      cout << "Error: can't decompress point cloud!" << endl;
      return -1;
    }

    if (!buf_ptr)
      break;
Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
605
  }
606

607
608
609
610
611
612
  fin.clear();
  fin.seekg(0, ios_base::end);
  std::cout << "Total bitstream size " << fin.tellg() << " B" << std::endl;

  clock.stop();

Khaled Mammou's avatar
TMC3v0  
Khaled Mammou committed
613
614
  return 0;
}