Coverage Report

Created: 2026-04-01 07:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/vvenc/source/Lib/apputils/YuvFileIO.h
Line
Count
Source
1
/* -----------------------------------------------------------------------------
2
The copyright in this software is being made available under the Clear BSD
3
License, included below. No patent rights, trademark rights and/or 
4
other Intellectual Property Rights other than the copyrights concerning 
5
the Software are granted under this license.
6
7
The Clear BSD License
8
9
Copyright (c) 2019-2026, Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. & The VVenC Authors.
10
All rights reserved.
11
12
Redistribution and use in source and binary forms, with or without modification,
13
are permitted (subject to the limitations in the disclaimer below) provided that
14
the following conditions are met:
15
16
     * Redistributions of source code must retain the above copyright notice,
17
     this list of conditions and the following disclaimer.
18
19
     * Redistributions in binary form must reproduce the above copyright
20
     notice, this list of conditions and the following disclaimer in the
21
     documentation and/or other materials provided with the distribution.
22
23
     * Neither the name of the copyright holder nor the names of its
24
     contributors may be used to endorse or promote products derived from this
25
     software without specific prior written permission.
26
27
NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
28
THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
29
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
30
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
31
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
32
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
33
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
34
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
35
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
36
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
37
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
38
POSSIBILITY OF SUCH DAMAGE.
39
40
41
------------------------------------------------------------------------------------------- */
42
/** \file     YuvFileIO.h
43
    \brief    yuv file I/O class (header)
44
*/
45
46
#pragma once
47
48
#include <iostream>
49
#include <fstream>
50
#include <sstream>
51
#include <string>
52
#include <vector>
53
#include <algorithm>
54
#include <regex>
55
56
#include "vvenc/vvencCfg.h"
57
#include "vvenc/vvenc.h"
58
59
#include "FileIOHelper.h"
60
#include "LogoRenderer.h"
61
62
#if defined (_WIN32) || defined (WIN32) || defined (_WIN64) || defined (WIN64)
63
#include <io.h>
64
#include <fcntl.h>
65
#endif
66
67
//! \ingroup Interface
68
//! \{
69
70
struct vvencYUVBuffer;
71
72
namespace apputils {
73
74
typedef int16_t LPel;
75
76
// ====================================================================================================================
77
78
class YuvFileIO
79
{
80
private:
81
  std::string         m_lastError;                              ///< temporal storage for last occured error 
82
  std::fstream        m_cHandle;                                ///< file handle
83
  int                 m_fileBitdepth        = 0;                ///< bitdepth of input/output video file
84
  int                 m_MSBExtendedBitDepth = 0;                ///< bitdepth after addition of MSBs (with value 0)
85
  int                 m_bitdepthShift       = 0;                ///< number of bits to increase or decrease image by before/after write/read
86
  vvencChromaFormat   m_fileChrFmt          = VVENC_CHROMA_420; ///< chroma format of the file
87
  vvencChromaFormat   m_bufferChrFmt        = VVENC_CHROMA_420; ///< chroma format of the buffer
88
  bool                m_clipToRec709        = false;            ///< clip data according to Recom.709
89
  bool                m_packedYUVMode       = false;            ///< used packed buffer file format
90
  bool                m_readStdin           = false;            ///< read input from stdin
91
  bool                m_y4mMode             = false;            ///< use/force y4m file format
92
  size_t              m_packetCount         = 0;
93
  LogoRenderer        m_cLogoRenderer;
94
95
public:
96
97
  int open( const std::string &fileName, bool bWriteMode, int fileBitDepth, int MSBExtendedBitDepth, int internalBitDepth, 
98
            vvencChromaFormat fileChrFmt, vvencChromaFormat bufferChrFmt, bool clipToRec709, bool packedYUVMode, bool y4mMode,
99
            std::string cLogoFilename = "" )
100
0
  {
101
0
    //NOTE: files cannot have bit depth greater than 16
102
0
    m_fileBitdepth        = std::min<unsigned>( fileBitDepth, 16 );
103
0
    m_MSBExtendedBitDepth = MSBExtendedBitDepth;
104
0
    m_bitdepthShift       = internalBitDepth - m_MSBExtendedBitDepth;
105
0
    if( internalBitDepth == 8 && fileBitDepth == 10 && MSBExtendedBitDepth == fileBitDepth )
106
0
    {
107
0
      m_bitdepthShift     = 0;
108
0
    }
109
0
    m_fileChrFmt          = fileChrFmt;
110
0
    m_bufferChrFmt        = bufferChrFmt;
111
0
    m_clipToRec709        = clipToRec709;
112
0
    m_packedYUVMode       = packedYUVMode;
113
0
    m_readStdin           = false;
114
0
    m_y4mMode             = y4mMode;
115
0
    m_packetCount         = 0;
116
0
117
0
    if( m_packedYUVMode && !bWriteMode && m_fileBitdepth != 10 )
118
0
    {
119
0
      m_lastError = "\nERROR: file bitdepth for packed yuv input must be 10";
120
0
      return -1;
121
0
    }
122
0
123
0
    if ( m_fileBitdepth > 16 )
124
0
    {
125
0
      m_lastError =  "\nERROR: Cannot handle a yuv file of bit depth greater than 16";
126
0
      return -1;
127
0
    }
128
0
129
0
    if ( m_y4mMode && bWriteMode )
130
0
    {
131
0
      m_lastError =  "\nERROR: Cannot handle y4m yuv output (only support for y4m input)";
132
0
      return -1;
133
0
    }
134
0
135
0
    if( bWriteMode )
136
0
    {
137
0
      m_cHandle.open( fileName.c_str(), std::ios::binary | std::ios::out );
138
0
139
0
      if( m_cHandle.fail() )
140
0
      {
141
0
        m_lastError =  "\nFailed to open output YUV file:  " + fileName;
142
0
        return -1;
143
0
      }
144
0
    }
145
0
    else
146
0
    {
147
0
      if( !cLogoFilename.empty() )
148
0
      {
149
0
        std::stringstream strstr;
150
0
        if ( 0 != m_cLogoRenderer.init( cLogoFilename, m_bufferChrFmt, strstr ) )
151
0
        {
152
0
          if( !strstr.str().empty() )
153
0
            m_lastError = strstr.str();
154
0
          else
155
0
            m_lastError = "failed to open Logo overlay renderer";
156
0
          return -1;
157
0
        }
158
0
      }
159
0
160
0
      if( !strcmp( fileName.c_str(), "-" ) )
161
0
      {
162
0
        m_readStdin = true;
163
0
  #if defined (_WIN32) || defined (WIN32) || defined (_WIN64) || defined (WIN64)
164
0
        if( _setmode( _fileno( stdin ), _O_BINARY ) == -1 )
165
0
        {
166
0
          m_lastError =  "\nError: Failed to set stdin to binary mode";
167
0
          return -1;
168
0
        }
169
0
  #endif
170
0
        return 0;
171
0
      }
172
0
173
0
      m_cHandle.open( fileName.c_str(), std::ios::binary | std::ios::in );
174
0
175
0
      if( m_cHandle.fail() )
176
0
      {
177
0
        m_lastError =  "\nFailed to open input YUV file:  " + fileName;
178
0
        return -1;
179
0
      }
180
0
181
0
      if ( m_y4mMode || FileIOHelper::isY4mInputFilename( fileName ) )
182
0
      {
183
0
        std::istream& inStream = m_cHandle;
184
0
        std::string headerline;
185
0
        getline(inStream, headerline);  // jump over y4m header
186
0
        m_y4mMode   = true;
187
0
      }
188
0
    }
189
0
    return 0;
190
0
  }
191
192
  void close()
193
0
  {
194
0
    if( !m_readStdin )
195
0
      m_cHandle.close();
196
0
197
0
    if( m_cLogoRenderer.isInitialized() )
198
0
    {
199
0
      m_cLogoRenderer.uninit();
200
0
    }
201
0
  }
202
203
0
  bool  isOpen()  { return m_cHandle.is_open(); }
204
0
  bool  isEof()   { return m_cHandle.eof();     }
205
0
  bool  isFail()  { return m_cHandle.fail();    }
206
0
  std::string getLastError() const { return m_lastError; }
207
208
  int   skipYuvFrames ( int numFrames, int width, int height )
209
0
  {
210
0
    if ( numFrames <= 0 )
211
0
    {
212
0
      return -1;
213
0
    }
214
0
215
0
    //set the frame size according to the chroma format
216
0
    std::streamoff frameSize      = 0;
217
0
    const int numComp = (m_fileChrFmt==VVENC_CHROMA_400) ? 1 : 3;
218
0
219
0
    if( m_packedYUVMode)
220
0
    {
221
0
      for ( int i = 0; i < numComp; i++ )
222
0
      {
223
0
        const int csx_file = ( (i == 0) || (m_fileChrFmt==VVENC_CHROMA_444) ) ? 0 : 1;
224
0
        const int csy_file = ( (i == 0) || (m_fileChrFmt!=VVENC_CHROMA_420) ) ? 0 : 1;
225
0
        frameSize += (( ( width * 5 / 4 ) >> csx_file) * (height >> csy_file));
226
0
      }
227
0
    }
228
0
    else
229
0
    {
230
0
      unsigned wordsize             = ( m_fileBitdepth > 8 ) ? 2 : 1;
231
0
      for ( int i = 0; i < numComp; i++ )
232
0
      {
233
0
        const int csx_file = ( (i == 0) || (m_fileChrFmt==VVENC_CHROMA_444) ) ? 0 : 1;
234
0
        const int csy_file = ( (i == 0) || (m_fileChrFmt!=VVENC_CHROMA_420) ) ? 0 : 1;
235
0
        frameSize += ( width >> csx_file ) * ( height >> csy_file );
236
0
      }
237
0
      frameSize *= wordsize;
238
0
    }
239
0
240
0
    if( m_y4mMode )
241
0
    {
242
0
      const char Y4MHeader[] = {'F','R','A','M','E'};
243
0
      frameSize += (sizeof(Y4MHeader) + 1);  /* assume basic FRAME\n headers */;
244
0
    }
245
0
246
0
    const std::streamoff offset = frameSize * numFrames;
247
0
248
0
    std::istream& inStream = m_readStdin ? std::cin : m_cHandle;
249
0
250
0
    // check for file size
251
0
    if( !m_readStdin )
252
0
    {
253
0
      std::streamoff fsize = m_cHandle.tellg();
254
0
      m_cHandle.seekg( 0, std::ios::end );
255
0
      std::streamoff filelength = m_cHandle.tellg() - fsize;
256
0
      m_cHandle.seekg( fsize, std::ios::beg );
257
0
      if( offset >= filelength )
258
0
      {
259
0
        return -1;
260
0
      }
261
0
    }
262
0
263
0
    // attempt to seek
264
0
    if ( !! inStream.seekg( offset, std::ios::cur ) )
265
0
    {
266
0
      return 0; /* success */
267
0
    }
268
0
269
0
    inStream.clear();
270
0
271
0
    // fall back to consuming the input
272
0
    char buf[ 512 ];
273
0
    const std::streamsize bufsize            = static_cast<std::streamsize>( sizeof( buf ) );
274
0
    const std::streamsize offset_mod_bufsize = static_cast<std::streamsize>( ( offset % sizeof( buf ) ) );
275
0
    for ( std::streamoff i = 0; i < offset - offset_mod_bufsize; i += bufsize )
276
0
    {
277
0
      inStream.read( buf, bufsize );
278
0
    }
279
0
    inStream.read( buf, offset_mod_bufsize );
280
0
281
0
    return 0;
282
0
  }
283
284
285
  int readYuvBuf ( vvencYUVBuffer& yuvInBuf, bool& eof )
286
0
  {
287
0
    eof = false;
288
0
    // check end-of-file
289
0
    if ( isEof() )
290
0
    {
291
0
      m_lastError = "end of file";
292
0
      eof = true;
293
0
      return 0;
294
0
    }
295
0
296
0
    if ( m_packedYUVMode &&  ( 0 != (yuvInBuf.planes[0].width >> 1) % 4 ) )
297
0
    {
298
0
      m_lastError = "unsupported file width for packed input";
299
0
      return -1;
300
0
    }
301
0
302
0
    const bool monochromFix    = ( m_bufferChrFmt==VVENC_CHROMA_400 && m_fileChrFmt!=VVENC_CHROMA_400 ); 
303
0
    const bool is16bit         = m_fileBitdepth > 8;
304
0
    const int desired_bitdepth = m_MSBExtendedBitDepth + m_bitdepthShift;
305
0
    const bool b709Compliance  = ( m_clipToRec709 ) && ( m_bitdepthShift < 0 && desired_bitdepth >= 8 );     /* ITU-R BT.709 compliant clipping for converting say 10b to 8b */
306
0
    const LPel minVal           = b709Compliance ? ( (    1 << ( desired_bitdepth - 8 ) )    ) : 0;
307
0
    const LPel maxVal           = b709Compliance ? ( ( 0xff << ( desired_bitdepth - 8 ) ) -1 ) : ( 1 << desired_bitdepth ) - 1;
308
0
    const int numComp                = (m_fileChrFmt==VVENC_CHROMA_400) ? 1 : 3;
309
0
310
0
    for( int comp = 0; comp < numComp; comp++ )
311
0
    {
312
0
      vvencYUVPlane yuvPlane = yuvInBuf.planes[ comp ];
313
0
314
0
      if( monochromFix && comp )
315
0
      {
316
0
        yuvPlane.width  = yuvInBuf.planes[0].width  >> (m_fileChrFmt == VVENC_CHROMA_444 ? 0 : 1);
317
0
        yuvPlane.height = 2*yuvInBuf.planes[0].height >> (m_fileChrFmt != VVENC_CHROMA_420 ? 0 : 1);
318
0
        yuvPlane.stride = yuvPlane.width;
319
0
      }
320
0
321
0
      std::istream& inStream = m_readStdin ? std::cin : m_cHandle;
322
0
323
0
      if( m_y4mMode && comp == 0 )
324
0
      {
325
0
        std::string y4mPrefix;
326
0
        getline(inStream, y4mPrefix);   /* assume basic FRAME\n headers */
327
0
        if( y4mPrefix != "FRAME")
328
0
        {
329
0
          m_lastError = "Source image does not contain valid y4m header (FRAME) - end of stream";
330
0
          eof = true;
331
0
          return ( m_packetCount ? 0 : -1); // return error if no frames has been proceeded, otherwise expect eof
332
0
        }
333
0
      }
334
0
335
0
      if ( ! FileIOHelper::readYuvPlane( inStream, yuvPlane, is16bit, m_fileBitdepth, m_packedYUVMode, comp, m_fileChrFmt, m_bufferChrFmt ) )
336
0
      {
337
0
        eof = true;
338
0
        return 0;
339
0
      }
340
0
341
0
      if ( m_bufferChrFmt == VVENC_CHROMA_400 && comp)
342
0
        continue;
343
0
344
0
      FileIOHelper::scaleYuvPlane( yuvPlane, yuvPlane, m_bitdepthShift, minVal, maxVal );
345
0
    }
346
0
347
0
    if( m_cLogoRenderer.isInitialized() )
348
0
    {
349
0
      LogoInputOptions cLogo = m_cLogoRenderer.getLogoInputOptions();
350
0
      if ( cLogo.sourceWidth > yuvInBuf.planes[0].width || cLogo.sourceHeight > yuvInBuf.planes[0].height )
351
0
      {
352
0
        std::stringstream css;
353
0
        css << "input picture size (" << yuvInBuf.planes[0].width << "x" << yuvInBuf.planes[0].height << ") < logo size (" <<
354
0
                  cLogo.sourceWidth  << "x" << cLogo.sourceHeight << ") cannot render logo" << std::endl;
355
0
        m_lastError = css.str();
356
0
        return -1;
357
0
      }
358
0
359
0
      if( 0 != m_cLogoRenderer.renderLogo( yuvInBuf ) )
360
0
      {
361
0
        m_lastError = "failed to render Logo";
362
0
        return -1;
363
0
      }
364
0
    }
365
0
366
0
    m_packetCount++;
367
0
368
0
    return 0;
369
0
  }
370
371
372
  bool writeYuvBuf ( const vvencYUVBuffer& yuvOutBuf )
373
0
  {
374
0
    // compute actual YUV frame size excluding padding size
375
0
    bool is16bit              = m_fileBitdepth > 8;
376
0
    bool nonZeroBitDepthShift = m_bitdepthShift != 0;
377
0
378
0
    vvencYUVBuffer yuvScaled;
379
0
    vvenc_YUVBuffer_default( &yuvScaled );
380
0
    vvenc_YUVBuffer_alloc_buffer( &yuvScaled, m_bufferChrFmt, yuvOutBuf.planes[ 0 ].width, yuvOutBuf.planes[ 0 ].height );
381
0
382
0
    if ( nonZeroBitDepthShift )
383
0
    {
384
0
      const bool b709Compliance = m_clipToRec709 && ( -m_bitdepthShift < 0 && m_MSBExtendedBitDepth >= 8 );     /* ITU-R BT.709 compliant clipping for converting say 10b to 8b */
385
0
      const LPel minVal          = b709Compliance? ( (    1 << ( m_MSBExtendedBitDepth - 8 ) )    ) : 0;
386
0
      const LPel maxVal          = b709Compliance? ( ( 0xff << ( m_MSBExtendedBitDepth - 8 ) ) -1 ) : ( 1 << m_MSBExtendedBitDepth ) - 1;
387
0
      const int numComp          = (m_bufferChrFmt==VVENC_CHROMA_400) ? 1 : 3;
388
0
389
0
      for( int comp = 0; comp < numComp; comp++ )
390
0
      {
391
0
        FileIOHelper::scaleYuvPlane( yuvScaled.planes[ comp ], yuvOutBuf.planes[ comp ],-m_bitdepthShift, minVal, maxVal );
392
0
      }
393
0
    }
394
0
395
0
    const vvencYUVBuffer& yuvWriteBuf = nonZeroBitDepthShift ? yuvScaled : yuvOutBuf;
396
0
397
0
    const int numComp = (m_fileChrFmt==VVENC_CHROMA_400) ? 1 : 3;
398
0
    for( int comp = 0; comp < numComp; comp++ )
399
0
    {
400
0
      if ( ! FileIOHelper::writeYuvPlane( m_cHandle, yuvWriteBuf.planes[ comp ], is16bit, m_fileBitdepth, m_packedYUVMode, comp, m_bufferChrFmt, m_fileChrFmt ) )
401
0
      {
402
0
        vvenc_YUVBuffer_free_buffer( &yuvScaled );
403
0
        return false;
404
0
      }
405
0
    }
406
0
407
0
    m_packetCount++;
408
0
    vvenc_YUVBuffer_free_buffer( &yuvScaled );
409
0
410
0
    return true;
411
0
  }
412
413
  int countYuvFrames( int width, int height, bool countFromStart = true )
414
0
  {
415
0
    if( m_readStdin ) return -1;
416
0
417
0
    //set the frame size according to the chroma format
418
0
    std::streamoff frameSize      = 0;
419
0
    const int numComp = (m_fileChrFmt==VVENC_CHROMA_400) ? 1 : 3;
420
0
421
0
    if( m_packedYUVMode)
422
0
    {
423
0
      for ( int i = 0; i < numComp; i++ )
424
0
      {
425
0
        const int csx_file = ( (i == 0) || (m_fileChrFmt==VVENC_CHROMA_444) ) ? 0 : 1;
426
0
        const int csy_file = ( (i == 0) || (m_fileChrFmt!=VVENC_CHROMA_420) ) ? 0 : 1;
427
0
        frameSize += (( ( width * 5 / 4 ) >> csx_file) * (height >> csy_file));
428
0
      }
429
0
    }
430
0
    else
431
0
    {
432
0
      unsigned wordsize             = ( m_fileBitdepth > 8 ) ? 2 : 1;
433
0
      for ( int i = 0; i < numComp; i++ )
434
0
      {
435
0
        const int csx_file = ( (i == 0) || (m_fileChrFmt==VVENC_CHROMA_444) ) ? 0 : 1;
436
0
        const int csy_file = ( (i == 0) || (m_fileChrFmt!=VVENC_CHROMA_420) ) ? 0 : 1;
437
0
        frameSize += ( width >> csx_file ) * ( height >> csy_file );
438
0
      }
439
0
      frameSize *= wordsize;
440
0
    }
441
0
442
0
    if( m_y4mMode )
443
0
    {
444
0
      const char Y4MHeader[] = {'F','R','A','M','E'};
445
0
      frameSize += (sizeof(Y4MHeader) + 1);  /* assume basic FRAME\n headers */;
446
0
    }
447
0
448
0
    std::streamoff lastPos = m_cHandle.tellg();  // backup last position
449
0
450
0
    if( countFromStart )
451
0
    {
452
0
      m_cHandle.seekg( 0, std::ios::beg );
453
0
    }
454
0
    std::streamoff curPos = m_cHandle.tellg();
455
0
456
0
    m_cHandle.seekg( 0, std::ios::end );
457
0
    std::streamoff filelength = m_cHandle.tellg() - curPos;
458
0
459
0
    m_cHandle.seekg( lastPos, std::ios::beg ); // rewind to last pos
460
0
461
0
    return (int)(filelength / frameSize);
462
0
  }
463
};
464
465
} // namespace apputils
466
467
//! \}
468