1 /*****************************************************************************
2  * This file is part of Kvazaar HEVC encoder.
3  *
4  * Copyright (c) 2021, Tampere University, ITU/ISO/IEC, project contributors
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without modification,
8  * are permitted provided that the following conditions are met:
9  *
10  * * Redistributions of source code must retain the above copyright notice, this
11  *   list of conditions and the following disclaimer.
12  *
13  * * Redistributions in binary form must reproduce the above copyright notice, this
14  *   list of conditions and the following disclaimer in the documentation and/or
15  *   other materials provided with the distribution.
16  *
17  * * Neither the name of the Tampere University or ITU/ISO/IEC nor the names of its
18  *   contributors may be used to endorse or promote products derived from
19  *   this software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
25  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26  * INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION HOWEVER CAUSED AND ON
28  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  * INCLUDING NEGLIGENCE OR OTHERWISE ARISING IN ANY WAY OUT OF THE USE OF THIS
31  ****************************************************************************/
32 
33 /*
34  * \file
35  */
36 
37 #include <string.h>
38 #include <stdio.h>
39 
40 #include "yuv_io.h"
41 
fill_after_frame(unsigned height,unsigned array_width,unsigned array_height,kvz_pixel * data)42 static void fill_after_frame(unsigned height, unsigned array_width,
43                              unsigned array_height, kvz_pixel *data)
44 {
45   kvz_pixel* p = data + height * array_width;
46   kvz_pixel* end = data + array_width * array_height;
47 
48   while (p < end) {
49     // Fill the line by copying the line above.
50     memcpy(p, p - array_width, array_width);
51     p += array_width;
52   }
53 }
54 
55 
read_and_fill_frame_data(FILE * file,unsigned width,unsigned height,unsigned bytes_per_sample,unsigned array_width,kvz_pixel * data)56 static int read_and_fill_frame_data(FILE *file,
57                                     unsigned width, unsigned height, unsigned bytes_per_sample,
58                                     unsigned array_width, kvz_pixel *data)
59 {
60   kvz_pixel* p = data;
61   kvz_pixel* end = data + array_width * height;
62   kvz_pixel fill_char;
63   unsigned i;
64 
65   while (p < end) {
66     // Read the beginning of the line from input.
67     if (width != fread(p, bytes_per_sample, width, file))
68       return 0;
69 
70     // Fill the rest with the last pixel value.
71     fill_char = p[width - 1];
72 
73     for (i = width; i < array_width; ++i) {
74       p[i] = fill_char;
75     }
76 
77     p += array_width;
78   }
79   return 1;
80 }
81 
82 
swap_16b_buffer_bytes(kvz_pixel * input,int size)83 static void swap_16b_buffer_bytes(kvz_pixel* input, int size)
84 {
85   for (int i = 0; i < size; ++i) {
86     input[i] = ((input[i] & 0xff) << 8) + ((input[i] & 0xff00) >> 8);
87   }
88 }
89 
90 
shift_to_bitdepth(kvz_pixel * input,int size,int from_bitdepth,int to_bitdepth)91 static void shift_to_bitdepth(kvz_pixel* input, int size, int from_bitdepth, int to_bitdepth)
92 {
93   int shift = to_bitdepth - from_bitdepth;
94   kvz_pixel bitdepth_mask = (1 << from_bitdepth) - 1;
95 
96   for (int i = 0; i < size; ++i) {
97     // Shifting by a negative number is undefined.
98     if (shift > 0) {
99       input[i] = (input[i] & bitdepth_mask) << shift;
100     } else {
101       input[i] = (input[i] & bitdepth_mask) >> shift;
102     }
103   }
104 }
105 
106 
107 // Shift and copy 1-byte aligned samples to 2-byte aligned array
shift_to_bitdepth_and_spread(kvz_pixel * input,int size,int from_bitdepth,int to_bitdepth)108 static void shift_to_bitdepth_and_spread(kvz_pixel *input,
109                                          int size,
110                                          int from_bitdepth,
111                                          int to_bitdepth)
112 {
113   assert(sizeof(kvz_pixel) > 1);
114   int shift = to_bitdepth - from_bitdepth;
115   unsigned char *byte_buf = (unsigned char *)input;
116   kvz_pixel bitdepth_mask = (1 << from_bitdepth) - 1;
117 
118   // Starting from the back of the 1-byte samples, copy each sample to it's
119   // place in the 2-byte per sample array, overwriting the bytes that have
120   // already been copied in the process.
121   // Even though the two pointers are aliased, this should work because the
122   // future values read through byte_buf poiner never change as a result of
123   // writing through input pointer.
124   for (int i = size - 1; i >= 0; --i) {
125     // Shifting by a negative number is undefined.
126     if (shift > 0) {
127       input[i] = (byte_buf[i] & bitdepth_mask) << shift;
128     } else {
129       input[i] = (byte_buf[i] & bitdepth_mask) >> shift;
130     }
131   }
132 }
133 
134 
machine_is_big_endian()135 static bool machine_is_big_endian()
136 {
137   // Big and little endianess refers to which end of the egg you prefer to eat
138   // first. Therefore in big endian system, the most significant bits are in
139   // the first address.
140 
141   uint16_t number = 1;
142   char first_byte = *(char*)&number;
143 
144   return (first_byte == 0);
145 }
146 
147 
mask_to_bitdepth(kvz_pixel * buf,unsigned length,unsigned bitdepth)148 static void mask_to_bitdepth(kvz_pixel *buf, unsigned length, unsigned bitdepth)
149 {
150   kvz_pixel bitdepth_mask = (1 << bitdepth) - 1;
151   for (int i = 0; i < length; ++i) {
152     buf[i] = buf[i] & bitdepth_mask;
153   }
154 }
155 
156 
yuv_io_read_plane(FILE * file,unsigned in_width,unsigned in_height,unsigned in_bitdepth,unsigned out_width,unsigned out_height,unsigned out_bitdepth,kvz_pixel * out_buf)157 static int yuv_io_read_plane(
158     FILE* file,
159     unsigned in_width, unsigned in_height, unsigned in_bitdepth,
160     unsigned out_width, unsigned out_height, unsigned out_bitdepth,
161     kvz_pixel *out_buf)
162 {
163   unsigned bytes_per_sample = in_bitdepth > 8 ? 2 : 1;
164   unsigned buf_bytes = in_width * in_height * bytes_per_sample;
165   unsigned out_length = out_width * out_height;
166 
167   if (in_width == out_width) {
168     // No need to extend pixels.
169     const size_t pixel_size = sizeof(unsigned char);
170     if (fread(out_buf, pixel_size, buf_bytes, file) != buf_bytes)  return 0;
171   } else {
172     // Need to copy pixels to fill the image in horizontal direction.
173     if (!read_and_fill_frame_data(file, in_width, in_height, bytes_per_sample, out_width, out_buf)) return 0;
174   }
175 
176   if (in_height != out_height) {
177     // Need to copy pixels to fill the image in vertical direction.
178     fill_after_frame(in_height, out_width, out_height, out_buf);
179   }
180 
181   if (in_bitdepth > 8) {
182     // Assume little endian input.
183     if (machine_is_big_endian()) {
184       swap_16b_buffer_bytes(out_buf, out_length);
185     }
186   }
187 
188   // Shift the data to the correct bitdepth.
189   // Ignore any bits larger than in_bitdepth to guarantee ouput data will be
190   // in the correct range.
191   if (in_bitdepth <= 8 && out_bitdepth > 8) {
192     shift_to_bitdepth_and_spread(out_buf, out_length, in_bitdepth, out_bitdepth);
193   } else if (in_bitdepth != out_bitdepth) {
194     shift_to_bitdepth(out_buf, out_length, in_bitdepth, out_bitdepth);
195   } else if (in_bitdepth % 8 != 0) {
196     mask_to_bitdepth(out_buf, out_length, out_bitdepth);
197   }
198 
199   return 1;
200 }
201 
202 
read_frame_header(FILE * input)203 static int read_frame_header(FILE* input) {
204   char buffer[256];
205   bool frame_start = false;
206 
207   while (!frame_start) {
208     for (int i = 0; i < 256; i++) {
209       buffer[i] = getc(input);
210       if (buffer[i] == EOF) return 0;
211       // ToDo: frame headers can have some information structured same as start headers
212       // This info is just skipped for now, since it's not clear what it could be.
213       if (buffer[i] == 0x0A) {
214         frame_start = true;
215         break;
216       }
217     }
218   }
219   return 1;
220 }
221 
222 /**
223  * \brief Read a single frame from a file.
224  *
225  * Read luma and chroma values from file. Extend pixels if the image buffer
226  * is larger than the input image.
227  *
228  * \param file          input file
229  * \param input_width   width of the input video in pixels
230  * \param input_height  height of the input video in pixels
231  * \param img_out       image buffer
232  *
233  * \return              1 on success, 0 on failure
234  */
yuv_io_read(FILE * file,unsigned in_width,unsigned out_width,unsigned in_bitdepth,unsigned out_bitdepth,kvz_picture * img_out,unsigned file_format)235 int yuv_io_read(FILE* file,
236                 unsigned in_width, unsigned out_width,
237                 unsigned in_bitdepth, unsigned out_bitdepth,
238                 kvz_picture *img_out, unsigned file_format)
239 {
240   assert(in_width % 2 == 0);
241   assert(out_width % 2 == 0);
242 
243   int ok;
244 
245   if (file_format == KVZ_FORMAT_Y4M) {
246     ok = read_frame_header(file);
247     if (!ok) return 0;
248   }
249 
250 
251 
252   ok = yuv_io_read_plane(
253       file,
254       in_width, out_width, in_bitdepth,
255       img_out->width, img_out->height, out_bitdepth,
256       img_out->y);
257   if (!ok) return 0;
258 
259   if (img_out->chroma_format != KVZ_CSP_400) {
260     unsigned uv_width_in = in_width / 2;
261     unsigned uv_height_in = out_width / 2;
262     unsigned uv_width_out = img_out->width / 2;
263     unsigned uv_height_out = img_out->height / 2;
264 
265     ok = yuv_io_read_plane(
266         file,
267         uv_width_in, uv_height_in, in_bitdepth,
268         uv_width_out, uv_height_out, out_bitdepth,
269         img_out->u);
270     if (!ok) return 0;
271 
272     ok = yuv_io_read_plane(
273         file,
274         uv_width_in, uv_height_in, in_bitdepth,
275         uv_width_out, uv_height_out, out_bitdepth,
276         img_out->v);
277     if (!ok) return 0;
278   }
279 
280   return 1;
281 }
282 
283 
284 /**
285  * \brief Seek forward in a YUV file.
286  *
287  * \param file          the input file
288  * \param frames        number of frames to seek
289  * \param input_width   width of the input video in pixels
290  * \param input_height  height of the input video in pixels
291  *
292  * \return              1 on success, 0 on failure
293  */
yuv_io_seek(FILE * file,unsigned frames,unsigned input_width,unsigned input_height,unsigned file_format)294 int yuv_io_seek(FILE* file, unsigned frames,
295                 unsigned input_width, unsigned input_height,
296                 unsigned file_format)
297 {
298     const size_t frame_bytes = input_width * input_height * 3 / 2;
299 
300     if (file_format == KVZ_FORMAT_Y4M) {
301       for (unsigned i = 0; i < frames; i++) {
302         if (!read_frame_header(file)) return 0;
303         if (fseek(file, frame_bytes, SEEK_CUR)) return 0;
304       }
305       return 1;
306     }
307 
308     const int64_t skip_bytes = (int64_t)(frames * frame_bytes);
309 
310     // Attempt to seek normally.
311     size_t error = fseek(file, skip_bytes, SEEK_CUR);
312     if (!error) return 1;
313 
314     // Seek failed. Skip data by reading.
315     error = 0;
316     unsigned char* tmp[4096];
317     size_t bytes_left = skip_bytes;
318     while (bytes_left > 0 && !error) {
319       const size_t skip = MIN(4096, bytes_left);
320       error = fread(tmp, sizeof(unsigned char), skip, file) != skip;
321       bytes_left -= skip;
322     }
323 
324     return !error || feof(file);
325 }
326 
327 
328 /**
329  * \brief Write a single frame to a file.
330  *
331  * \param file           output file
332  * \param img            image to output
333  * \param output_width   width of the output in pixels
334  * \param output_height  height of the output in pixels
335  *
336  * \return              1 on success, 0 on failure
337  */
yuv_io_write(FILE * file,const kvz_picture * img,unsigned output_width,unsigned output_height)338 int yuv_io_write(FILE* file,
339                 const kvz_picture *img,
340                 unsigned output_width, unsigned output_height)
341 {
342   const int width = img->width;
343   for (int y = 0; y < output_height; ++y) {
344     fwrite(&img->y[y * width], sizeof(*img->y), output_width, file);
345     // TODO: Check that fwrite succeeded.
346   }
347 
348   if (img->chroma_format != KVZ_CSP_400) {
349     for (int y = 0; y < output_height / 2; ++y) {
350       fwrite(&img->u[y * width / 2], sizeof(*img->u), output_width / 2, file);
351     }
352     for (int y = 0; y < output_height / 2; ++y) {
353       fwrite(&img->v[y * width / 2], sizeof(*img->v), output_width / 2, file);
354     }
355   }
356 
357   return 1;
358 }
359