1 /**
2 az @brief Read RAW Yuv files, commonly used for video compression
3 *
4 * This file is a part of PFSTOOLS package.
5 * ----------------------------------------------------------------------
6 * Copyright (C) 2017 Rafal Mantiuk
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 * ----------------------------------------------------------------------
22 *
23 * @author Rafal Mantiuk, <mantiuk@mpi-sb.mpg.de>
24 *
25 * $Id: pfsinrgbe.cpp,v 1.5 2014/06/17 21:57:08 rafm Exp $
26 */
27
28 #include <config.h>
29 #include <string.h>
30
31 #include <cstdlib>
32
33 #include <algorithm>
34 #include <iostream>
35 #include <vector>
36
37 #include <getopt.h>
38 #include <pfs.h>
39 #include <pfsutils.cpp>
40 #include <climits>
41
42 #define PROG_NAME "pfsinyuv"
43
44 char charsToRemove[7] = {'b', 'i', 't', 's', 'f', 'p', 's'};
45
46 template<typename T>
clamp(T val,T min,T max)47 inline T clamp( T val, T min, T max )
48 {
49 if( val < min )
50 return min;
51 if( val > max )
52 return max;
53 return val;
54 }
55
56
57 template<typename T>
read_yuv_channel(FILE * fh,int width,int height,int stride,pfs::Array2D * dest,float gain,float offset,float min,float max)58 bool read_yuv_channel( FILE *fh, int width, int height, int stride, pfs::Array2D *dest, float gain, float offset, float min, float max )
59 {
60 T *line_buf = new T[width];
61
62 for( int y = 0; y < height; y++ ) { size_t read = fread( line_buf, sizeof( T ), width, fh );
63 if( y == 0 && read == 0 ) { // End of file reached
64 delete [] line_buf;
65 return false;
66 }
67
68 if( read != width ) {
69 delete [] line_buf;
70 throw pfs::Exception( "Error when reading Yuv file" );
71 }
72 for( int x = 0; x < width; x++ ) {
73 float v_float = (float)line_buf[x]*gain - offset;
74 (*dest)(x<<stride, y<<stride) = clamp( v_float, min, max );
75 }
76
77 }
78 delete [] line_buf;
79 return true;
80 }
81
removeCharsFromString(std::string & str,char * charsToRemove)82 void removeCharsFromString(std::string &str, char* charsToRemove ) {
83 for ( unsigned int i = 0; i < strlen(charsToRemove); ++i ) {
84 str.erase( remove(str.begin(), str.end(), charsToRemove[i]), str.end() );
85 }
86 }
87
parseIntField(std::string i)88 int parseIntField(std::string i){
89 //parses an integer field. Removes pesky characters like the b in 10b or the p in 1080p
90 removeCharsFromString(i, charsToRemove);
91 return std::atoi(i.data());
92 }
93
94 //more readable helper method for strings, case insensitive
contains(std::string s1,std::string s2)95 bool contains(std::string s1, std::string s2){
96
97 std::transform(s1.begin(), s1.end(), s1.begin(), ::tolower);
98 std::transform(s2.begin(), s2.end(), s2.begin(), ::tolower);
99
100 return s1.find(s2) != std::string::npos || s1 == s2;
101 }
102
103
104 //returns a vector of strings tokenized
split(std::string s,std::string delim)105 std::vector<std::string> split(std::string s, std::string delim){
106 return std::vector<std::string>();
107 }
108
parseFileName(const char * fileNameIn,int * width,int * height,int * bitdepth,pfs::ColorSpace * colorspace,bool * upscale_420,int * fps)109 bool parseFileName(const char * fileNameIn, int * width, int * height, int * bitdepth, pfs::ColorSpace * colorspace, bool * upscale_420, int * fps){
110 try{
111 /* Parses the filename, puts the result into fields pointed to by the arguments
112 returns true if all went well, false if there's a malformed filename
113 string should be <prefix>_<width>x<height(p)>_<fps>_<bitdepth(b)>_<colorspace>_<chroma_format>
114 Regex would be nice*/
115 std::string fileName(fileNameIn);
116 const std::string delimiter = "_";
117 size_t pos = 0;
118 std::string token;
119 std::vector<std::string> tokens;
120
121 while ((pos = fileName.find(delimiter)) != std::string::npos) {
122 token = fileName.substr(0, pos);
123 tokens.push_back(token);
124 fileName.erase(0, pos + delimiter.length());
125 }
126 tokens.push_back(fileName);
127
128 for (std::vector<string>::iterator tok = tokens.begin() + 1; tok != tokens.end(); ++tok) {
129
130 if (contains(*tok, "x")){
131 sscanf(tok->c_str(), "%dx%d", width, height);
132 }
133 else if(*tok == "10" || *tok == "8" || *tok == "12" || tok->at(tok->length() - 1) == 'b' || contains(*tok, "bit")){ //last character is a b
134 *bitdepth = parseIntField(*tok);
135 }
136 else if(*tok == "24" || *tok == "25" || *tok == "50" || *tok == "60" || contains(*tok, "fps")){
137 *fps = parseIntField(*tok);
138 }
139 else if (contains(*tok, "pq") || (contains(*tok, "2020") && !contains(*tok, "hlg"))){
140 *colorspace = pfs::CS_PQYCbCr2020;
141 }
142 else if (contains(*tok, "bt") || contains(*tok, "709")){
143 *colorspace = pfs::CS_YCbCr709;
144 }
145 else if (contains(*tok, "hlg")){
146 *colorspace = pfs::CS_HLGYCbCr2020;
147 }
148 else if(contains(*tok, "444") || contains(*tok, "420")){
149 *upscale_420 = contains(*tok, "420");
150 }
151 }
152 return true;
153 }
154 catch(...){
155 return false;
156 }
157 }
158
upscale_420to444(pfs::Array2D * ch)159 void upscale_420to444( pfs::Array2D *ch )
160 {
161 const int height = ch->getRows();
162 const int width = ch->getCols();
163
164 // For even rows
165 for( int y = 0; y < height; y += 2 ) {
166 float v_l = (*ch)(0,y);
167 float v_r;
168 int x;
169 for( x = 1; x < (width-1); x += 2 ) {
170 v_r = (*ch)(x+1,y);
171 // Interpolate between pixels on the left and right
172 (*ch)(x,y) = (v_l + v_r)/2.f;
173 v_l = v_r;
174 }
175 if( x < width )
176 (*ch)(x,y) = v_r;
177 }
178
179 // For odd rows
180 int y;
181 for( y = 1; y < (height-1); y += 2 ) {
182 for( int x = 0; x < width; x++ ) {
183 (*ch)(x,y) = ((*ch)(x,y-1) + (*ch)(x,y+1))/2.f;
184 }
185 }
186 // Last row
187 if( y < height ) {
188 for( int x = 0; x < width; x++ ) {
189 (*ch)(x,y) = (*ch)(x,y-1);
190 }
191 }
192 }
193
194
195 class YUVReader
196 {
197 FILE *fh;
198 int width, height;
199 int bit_depth;
200 bool subsampling_420;
201 unsigned long int fileSize;
202
203 enum ColorSpace { REC709, REC2020 };
204 ColorSpace color_space;
205
206 public:
YUVReader(FILE * fh,int width,int height,int bit_depth,bool subsampling_420)207 YUVReader( FILE *fh, int width, int height, int bit_depth, bool subsampling_420 ) : fh(fh), width( width ), height( height ), bit_depth( bit_depth ), subsampling_420( subsampling_420 )
208 {
209 //initialize filesize
210 fseek(fh, 0L, SEEK_END);
211 fileSize = ftell(fh);
212 //go back from where we came
213 fseek(fh, 0L, SEEK_SET);
214 }
215
getWidth() const216 int getWidth() const
217 {
218 return width;
219 }
220
getHeight() const221 int getHeight() const
222 {
223 return height;
224 }
225
getFileSize() const226 unsigned long int getFileSize() const
227 {
228 return fileSize;
229 }
230
231 /*
232 Advances a number of frames through the file without reading them */
233 // bool advanceFrames(int frameNo){
234 // int divisor = subsampling_420 ? 4 : 1; //this is the integer divisor of the green and blue channels (they are 4 times smaller if 4:2:0 subsampling is enabled)
235 // long int offset = frameNo * (/*Red:*/ width * height * sizeof(STORE_FORMAT) + /*Green, Blue:*/ 2*width*height*sizeof(STORE_FORMAT)/divisor);
236 // return fseek(fh, offset, SEEK_CUR);
237 // }
238
239 /* See to a given frame */
seekToFrame(int frameNo)240 bool seekToFrame(int frameNo){
241 unsigned long int offset = getFileOffsetFromFrame(frameNo);
242 if(offset > fileSize){
243 throw pfs::Exception("Seeking past EOF, is your given frame range within the range of the input?");
244 }
245 else{
246 return fseek(fh, offset, SEEK_SET);
247 }
248 }
249
getFileOffsetFromFrame(int frameNo)250 unsigned long int getFileOffsetFromFrame(int frameNo){
251 unsigned long int divisor = subsampling_420 ? 4 : 1; //this is the integer divisor of the green and blue channels (they are 4 times smaller if 4:2:0 subsampling is enabled)
252
253 if( frameNo <= 0 )
254 throw pfs::Exception( "Invalid frame index. Frame are indexed from 1" );
255
256 unsigned long int storeFormatSize = bit_depth <= 8 ? sizeof(unsigned char) : sizeof(unsigned short);
257 unsigned long int offset = (frameNo-1) * (/*Luma:*/ width * height * storeFormatSize + /*CrCb:*/ 2*width*height*storeFormatSize/divisor);
258 return offset;
259 }
260
261 /**
262 * Read a single frame from Yuv video file and store in 3 RGB channels of type pfs::Array.
263 * Integer values are converted into floating point values.
264 *
265 * @return TRUE if the frame has been loaded successfully, FALSE if end-of-file.
266 * Exception is thrown if there is an issue reading a full frame.
267 */
268
readImage(pfs::Array2D * R,pfs::Array2D * G,pfs::Array2D * B)269 bool readImage( pfs::Array2D *R, pfs::Array2D *G, pfs::Array2D *B ){
270 if(bit_depth <= 8){
271 return readImageTyped<unsigned char>(R, G, B);
272 }
273 else{
274 return readImageTyped<unsigned short>(R, G, B);
275 }
276 }
277
278 private:
279 template<typename T>
readImageTyped(pfs::Array2D * R,pfs::Array2D * G,pfs::Array2D * B)280 bool readImageTyped( pfs::Array2D *R, pfs::Array2D *G, pfs::Array2D *B)
281 {
282
283 { // Read Y
284 const float offset = 16.f/219.f;
285 const float gain = 1.f/( (float)(1<<(bit_depth-8))*219.f);
286 if( !read_yuv_channel<T>( fh, width, height, 0, R, gain, offset, 0.f, 1.f ) ) {
287 return false;
288 }
289 }
290
291 { // Read Cb, Cr
292 const float offset = 128.f/224.f;
293 const float gain = 1.f/( (float)(1<<(bit_depth-8))*224.f);
294 unsigned int chroma_width = width;
295 unsigned int chroma_height = height;
296 unsigned int chroma_stride = 0;
297 if (subsampling_420){
298 chroma_width = width/2;
299 chroma_height = height/2;
300 chroma_stride = 1;
301 }
302
303 if( !read_yuv_channel<T>( fh, chroma_width, chroma_height, chroma_stride, G, gain, offset, -0.5f, 0.5f ) )
304 throw pfs::Exception( "EOF reached when reading the Cb portion of a frame" );
305
306 if( !read_yuv_channel<T>( fh, chroma_width, chroma_height, chroma_stride, B, gain, offset, -0.5f, 0.5f ) )
307 throw pfs::Exception( "EOF reached when reading the Cr portion of a frame" );
308 if(subsampling_420){
309 upscale_420to444( B );
310 upscale_420to444( G );
311 }
312 }
313 return true;
314 }
315 };
316
317
318 class QuietException
319 {
320 };
321
printHelp()322 void printHelp()
323 {
324 std::cerr << PROG_NAME " [--verbose] [--quiet] [--width] [--height] [--colorspace] [--no-guess] [--chroma-format] [--bit-depth] [--frames] [--help]" << std::endl
325 << "See man page for more information." << std::endl;
326 }
327
328
readFrames(int argc,char * argv[])329 void readFrames( int argc, char* argv[] )
330 {
331 pfs::DOMIO pfsio;
332
333 bool verbose = false;
334 bool quiet = false;
335 bool opt_noguess = false;
336 int opt_width = -1;
337 int opt_height = -1;
338 int opt_bitdepth = -1;
339 const char * opt_chroma_subsampling = NULL;
340 int opt_frame_min = -1;
341 int opt_frame_max = -1;
342 int opt_frame_stride = 0;
343 int opt_fps = -1;
344 char * filename;
345 pfs::ColorSpace opt_colorspace = pfs::CS_INVALID;
346
347 // Parse command line parameters
348 static struct option cmdLineOptions[] = {
349 { "help", no_argument, NULL, 'p' },
350 { "verbose", no_argument, NULL, 'v' },
351 { "width", required_argument, NULL, 'w' },
352 { "height", required_argument, NULL, 'h' },
353 { "bit-depth", required_argument, NULL, 'b' },
354 { "colorspace", required_argument, NULL, 'c' },
355 { "quiet", no_argument, NULL, 'q' },
356 { "no-guess", no_argument, NULL, 'n'},
357 { "fps", required_argument, NULL, 'f'},
358 { "chroma-format", required_argument, NULL, 's'},
359 { "frames", required_argument, NULL, 'r'},
360 { NULL, 0, NULL, 0 }
361 };
362 static const char optstring[] = "-pw:h:vqb:c:f:nr:s:";
363
364 int optionIndex = 0;
365 while( 1 ) {
366 int c = getopt_long (argc, argv, optstring, cmdLineOptions, &optionIndex);
367 if(c == -1){
368 break;
369 }
370 switch( c ) {
371 case 'p':
372 printHelp();
373 throw QuietException();
374 case 'v':
375 verbose = true;
376 break;
377 case 'w':
378 opt_width = strtol( optarg, NULL, 10 );
379 break;
380 case 'h':
381 opt_height = strtol( optarg, NULL, 10 );
382 break;
383 case 'b':
384 opt_bitdepth = strtol( optarg, NULL, 10 );
385 break;
386 case 's':
387 opt_chroma_subsampling = optarg;
388 break;
389 case 'n':
390 opt_noguess = true;
391 break;
392 case 'f':
393 opt_fps = strtol(optarg, NULL, 10);
394 break;
395 case 'r': {
396 pfs::parseFrameRange(optarg, opt_frame_min, opt_frame_max, opt_frame_stride);
397 VERBOSE_STR << "Reading frames " << opt_frame_min << ":" << opt_frame_stride << ":"
398 << opt_frame_max << std::endl;
399 break;
400 }
401 case 'c':
402 if( !strcasecmp( optarg, "pq2020" ) ) {
403 opt_colorspace = pfs::CS_PQYCbCr2020;
404 }
405 else if( !strcasecmp( optarg, "bt709" )) {
406 opt_colorspace = pfs::CS_YCbCr709;
407 }
408 else if( !strcasecmp( optarg, "hlg2020")){
409 opt_colorspace = pfs::CS_HLGYCbCr2020;
410 }
411 else {
412 throw pfs::Exception( "Unrecognized colorspace name" );
413 }
414 break;
415 case 'q':
416 quiet = true;
417 break;
418 case '?':
419 throw QuietException();
420 case ':':
421 throw QuietException();
422 case '\1':
423 filename = optarg;
424 }
425 }
426
427 FILE * fh;
428 if(filename != NULL){
429 fh = fopen (filename, "rb");
430 }
431
432 if( fh == NULL ) {
433 throw pfs::Exception("Couldn't find a filename to open, did you provide one");
434 }; // No more frames
435
436 int width = -1, height = -1, bitdepth = -1, frame_min = 1, frame_max = INT_MAX, frame_stride = 1, fps = -1;
437 bool upscale_420 = true;
438 pfs::ColorSpace colorspace = pfs::CS_INVALID;
439
440 VERBOSE_STR << "reading file '" << filename << "'" << std::endl;
441 //we infer the metadata from the filename unless noguess is specified
442 if(!opt_noguess){
443 std::string rawName(filename); //these three lines extract the filename proper, without the containing directory prefix
444 long unsigned int substring = rawName.find_last_of("\\/");
445 rawName = rawName.substr(substring == std::string::npos ? 0 : substring);
446 bool goodFileName = parseFileName(rawName.c_str(), &width, &height, &bitdepth, &colorspace, &upscale_420, &fps);
447 if(!goodFileName){
448 VERBOSE_STR << "Unable to parse filename: " << filename << "\n";
449 }
450 }
451 // Over-ride the auto-recognized params with the ones specified as arguments
452 if( opt_width > -1 )
453 width = opt_width;
454 if( opt_height > -1 )
455 height = opt_height;
456 if( opt_bitdepth > -1 )
457 bitdepth = opt_bitdepth;
458 if( opt_frame_min > -1 )
459 frame_min = opt_frame_min;
460 if( opt_frame_max > -1 )
461 frame_max = opt_frame_max;
462 if( opt_frame_stride != 0)
463 frame_stride = opt_frame_stride;
464 if( opt_colorspace != pfs::CS_INVALID )
465 colorspace = opt_colorspace;
466 if( opt_chroma_subsampling != NULL ){
467 // bad reading, essentially just force upscaling if they input the subsampling as 420
468 upscale_420 = strcmp(opt_chroma_subsampling, "420") == 0;
469 }
470 if( opt_fps > 0 ){
471 fps = opt_fps;
472 }
473
474 VERBOSE_STR << "Yuv file " << width << "x" << height << " " << bitdepth << "bits" << std::endl;
475 switch( colorspace ) {
476 case pfs::CS_PQYCbCr2020:
477 VERBOSE_STR << "colorspace: HDR PQ BT2020" << std::endl;
478 break;
479 case pfs::CS_YCbCr709:
480 VERBOSE_STR << "colorspace: SDR BT709" << std::endl;
481 break;
482 case pfs::CS_HLGYCbCr2020:
483 VERBOSE_STR << "colorspace: HDR HLG BT2020" << std::endl;
484 break;
485 }
486
487 if( width <= 0 || height <= 0 )
488 throw pfs::Exception( "Unspecified or incorrect resolution of the Yuv file" );
489
490 if( bitdepth < 8 || bitdepth > 16 )
491 throw pfs::Exception( "Unspecified or incorrect bit-depth of the Yuv file" );
492
493 if( frame_min < 0 || frame_max < 0){
494 throw pfs::Exception( "Frame range is invalid ");
495 }
496 if( colorspace == pfs::CS_INVALID ){
497 throw pfs::Exception( "Unspecified colorspace of the Yuv file" );
498 }
499 if( fps > 0 ){
500 VERBOSE_STR << "FPS: " << fps << std::endl;
501 }
502
503 YUVReader reader( fh, width, height, bitdepth, upscale_420);
504
505
506 int currentFrame = frame_min;
507
508 while( (currentFrame <= frame_max && frame_stride > 0)
509 || (currentFrame >= frame_max && frame_stride < 0) ) { //inclusive
510
511 pfs::Frame *frame = pfsio.createFrame( reader.getWidth(),
512 reader.getHeight() );
513 pfs::Channel *X, *Y, *Z;
514 frame->createXYZChannels( X, Y, Z );
515
516 if(reader.seekToFrame(currentFrame)) {
517 //some error occured in reading :(
518 pfsio.freeFrame( frame );
519 break;
520 }
521
522 //Store RGB data temporarily in XYZ channels
523 bool success = reader.readImage( X, Y, Z );
524 if( !success ) { // EOF reached
525 pfsio.freeFrame( frame );
526 break;
527 }
528
529 VERBOSE_STR << "Reading frame " << currentFrame << std::endl;
530
531 if( colorspace == pfs::CS_YCbCr709 ) {
532 // The trick to get LDR data in the pfs stream
533 pfs::transformColorSpace( colorspace, X, Y, Z, pfs::CS_SRGB, X, Y, Z );
534 pfs::transformColorSpace( pfs::CS_RGB, X, Y, Z, pfs::CS_XYZ, X, Y, Z );
535 } else {
536 pfs::transformColorSpace( colorspace, X, Y, Z, pfs::CS_XYZ, X, Y, Z );
537 }
538
539
540 switch( colorspace ) {
541 case pfs::CS_PQYCbCr2020:
542 frame->getTags()->setString("LUMINANCE", "ABSOLUTE");
543 break;
544 case pfs::CS_YCbCr709:
545 frame->getTags()->setString("LUMINANCE", "DISPLAY");
546 break;
547 case pfs::CS_HLGYCbCr2020:
548 frame->getTags()->setString("LUMINANCE", "ABSOLUTE");
549 break;
550 }
551
552 if( fps > 0 ){
553 frame->getTags()->setString("FPS", pfs::intToString(fps).c_str());
554 }
555
556 const char *fileNameTag = strcmp( "-", filename )==0 ? "stdin" : filename;
557 frame->getTags()->setString( "FILE_NAME", fileNameTag );
558
559 pfsio.writeFrame( frame, stdout );
560 pfsio.freeFrame( frame );
561
562 currentFrame += frame_stride;
563 }
564 fclose(fh);
565 }
566
567
main(int argc,char * argv[])568 int main( int argc, char* argv[] )
569 {
570 try {
571 readFrames( argc, argv );
572 }
573 catch( pfs::Exception ex ) {
574 fprintf( stderr, PROG_NAME " error: %s\n", ex.getMessage() );
575 return EXIT_FAILURE;
576 }
577 catch( QuietException ) {
578 return EXIT_FAILURE;
579 }
580 return EXIT_SUCCESS;
581 }
582