| /* | 
 |  * Copyright (c) 2021, Alliance for Open Media. All rights reserved | 
 |  * | 
 |  * This source code is subject to the terms of the BSD 3-Clause Clear License | 
 |  * and the Alliance for Open Media Patent License 1.0. If the BSD 3-Clause Clear | 
 |  * License was not distributed with this source code in the LICENSE file, you | 
 |  * can obtain it at aomedia.org/license/software-license/bsd-3-c-c/.  If the | 
 |  * Alliance for Open Media Patent License 1.0 was not distributed with this | 
 |  * source code in the PATENTS file, you can obtain it at | 
 |  * aomedia.org/license/patent-license/. | 
 |  */ | 
 |  | 
 | /*!\file | 
 |  * \brief This file has the implementation details of the grain table. | 
 |  * | 
 |  * The file format is an ascii representation for readability and | 
 |  * editability. Array parameters are separated from the non-array | 
 |  * parameters and prefixed with a few characters to make for easy | 
 |  * localization with a parameter set. Each entry is prefixed with "E" | 
 |  * and the other parameters are only specified if "update-parms" is | 
 |  * non-zero. | 
 |  * | 
 |  * filmgrn1 | 
 |  * E <start-time> <end-time> <apply-grain> <random-seed> <update-parms> | 
 |  *  p <ar_coeff_lag> <ar_coeff_shift> <grain_scale_shift> ... | 
 |  *  sY <num_y_points> <point_0_x> <point_0_y> ... | 
 |  *  sCb <num_cb_points> <point_0_x> <point_0_y> ... | 
 |  *  sCr <num_cr_points> <point_0_x> <point_0_y> ... | 
 |  *  cY <ar_coeff_y_0> .... | 
 |  *  cCb <ar_coeff_cb_0> .... | 
 |  *  cCr <ar_coeff_cr_0> .... | 
 |  * E <start-time> ... | 
 |  */ | 
 | #include <string.h> | 
 | #include <stdio.h> | 
 | #include "aom_dsp/aom_dsp_common.h" | 
 | #include "aom_dsp/grain_table.h" | 
 | #include "aom_mem/aom_mem.h" | 
 |  | 
 | static const char kFileMagic[8] = "filmgrn1"; | 
 |  | 
 | static void grain_table_entry_read(FILE *file, | 
 |                                    struct aom_internal_error_info *error_info, | 
 |                                    aom_film_grain_table_entry_t *entry) { | 
 |   aom_film_grain_t *pars = &entry->params; | 
 |   int num_read = | 
 |       fscanf(file, "E %" PRId64 " %" PRId64 " %d %hd %d\n", &entry->start_time, | 
 |              &entry->end_time, &pars->apply_grain, &pars->random_seed, | 
 |              &pars->update_parameters); | 
 |   if (num_read == 0 && feof(file)) return; | 
 |   if (num_read != 5) { | 
 |     aom_internal_error(error_info, AOM_CODEC_ERROR, | 
 |                        "Unable to read entry header. Read %d != 5", num_read); | 
 |     return; | 
 |   } | 
 |   if (pars->update_parameters) { | 
 |     num_read = fscanf(file, "p %d %d %d %d %d %d %d %d %d %d %d %d\n", | 
 |                       &pars->ar_coeff_lag, &pars->ar_coeff_shift, | 
 |                       &pars->grain_scale_shift, &pars->scaling_shift, | 
 |                       &pars->chroma_scaling_from_luma, &pars->overlap_flag, | 
 |                       &pars->cb_mult, &pars->cb_luma_mult, &pars->cb_offset, | 
 |                       &pars->cr_mult, &pars->cr_luma_mult, &pars->cr_offset); | 
 |     if (num_read != 12) { | 
 |       aom_internal_error(error_info, AOM_CODEC_ERROR, | 
 |                          "Unable to read entry params. Read %d != 12", | 
 |                          num_read); | 
 |       return; | 
 |     } | 
 |     if (!fscanf(file, "\tsY %d ", &pars->num_y_points)) { | 
 |       aom_internal_error(error_info, AOM_CODEC_ERROR, | 
 |                          "Unable to read num y points"); | 
 |       return; | 
 |     } | 
 |     for (int i = 0; i < pars->num_y_points; ++i) { | 
 |       if (2 != fscanf(file, "%d %d", &pars->scaling_points_y[i][0], | 
 |                       &pars->scaling_points_y[i][1])) { | 
 |         aom_internal_error(error_info, AOM_CODEC_ERROR, | 
 |                            "Unable to read y scaling points"); | 
 |         return; | 
 |       } | 
 |     } | 
 |     if (!fscanf(file, "\n\tsCb %d", &pars->num_cb_points)) { | 
 |       aom_internal_error(error_info, AOM_CODEC_ERROR, | 
 |                          "Unable to read num cb points"); | 
 |       return; | 
 |     } | 
 |     for (int i = 0; i < pars->num_cb_points; ++i) { | 
 |       if (2 != fscanf(file, "%d %d", &pars->scaling_points_cb[i][0], | 
 |                       &pars->scaling_points_cb[i][1])) { | 
 |         aom_internal_error(error_info, AOM_CODEC_ERROR, | 
 |                            "Unable to read cb scaling points"); | 
 |         return; | 
 |       } | 
 |     } | 
 |     if (!fscanf(file, "\n\tsCr %d", &pars->num_cr_points)) { | 
 |       aom_internal_error(error_info, AOM_CODEC_ERROR, | 
 |                          "Unable to read num cr points"); | 
 |       return; | 
 |     } | 
 |     for (int i = 0; i < pars->num_cr_points; ++i) { | 
 |       if (2 != fscanf(file, "%d %d", &pars->scaling_points_cr[i][0], | 
 |                       &pars->scaling_points_cr[i][1])) { | 
 |         aom_internal_error(error_info, AOM_CODEC_ERROR, | 
 |                            "Unable to read cr scaling points"); | 
 |         return; | 
 |       } | 
 |     } | 
 |  | 
 |     fscanf(file, "\n\tcY"); | 
 |     const int n = 2 * pars->ar_coeff_lag * (pars->ar_coeff_lag + 1); | 
 |     for (int i = 0; i < n; ++i) { | 
 |       if (1 != fscanf(file, "%d", &pars->ar_coeffs_y[i])) { | 
 |         aom_internal_error(error_info, AOM_CODEC_ERROR, | 
 |                            "Unable to read Y coeffs"); | 
 |         return; | 
 |       } | 
 |     } | 
 |     fscanf(file, "\n\tcCb"); | 
 |     for (int i = 0; i <= n; ++i) { | 
 |       if (1 != fscanf(file, "%d", &pars->ar_coeffs_cb[i])) { | 
 |         aom_internal_error(error_info, AOM_CODEC_ERROR, | 
 |                            "Unable to read Cb coeffs"); | 
 |         return; | 
 |       } | 
 |     } | 
 |     fscanf(file, "\n\tcCr"); | 
 |     for (int i = 0; i <= n; ++i) { | 
 |       if (1 != fscanf(file, "%d", &pars->ar_coeffs_cr[i])) { | 
 |         aom_internal_error(error_info, AOM_CODEC_ERROR, | 
 |                            "Unable to read Cr coeffs"); | 
 |         return; | 
 |       } | 
 |     } | 
 |     fscanf(file, "\n"); | 
 |   } | 
 | } | 
 |  | 
 | static void grain_table_entry_write(FILE *file, | 
 |                                     aom_film_grain_table_entry_t *entry) { | 
 |   const aom_film_grain_t *pars = &entry->params; | 
 |   fprintf(file, "E %" PRId64 " %" PRId64 " %d %d %d\n", entry->start_time, | 
 |           entry->end_time, pars->apply_grain, pars->random_seed, | 
 |           pars->update_parameters); | 
 |   if (pars->update_parameters) { | 
 |     fprintf(file, "\tp %d %d %d %d %d %d %d %d %d %d %d %d\n", | 
 |             pars->ar_coeff_lag, pars->ar_coeff_shift, pars->grain_scale_shift, | 
 |             pars->scaling_shift, pars->chroma_scaling_from_luma, | 
 |             pars->overlap_flag, pars->cb_mult, pars->cb_luma_mult, | 
 |             pars->cb_offset, pars->cr_mult, pars->cr_luma_mult, | 
 |             pars->cr_offset); | 
 |     fprintf(file, "\tsY %d ", pars->num_y_points); | 
 |     for (int i = 0; i < pars->num_y_points; ++i) { | 
 |       fprintf(file, " %d %d", pars->scaling_points_y[i][0], | 
 |               pars->scaling_points_y[i][1]); | 
 |     } | 
 |     fprintf(file, "\n\tsCb %d", pars->num_cb_points); | 
 |     for (int i = 0; i < pars->num_cb_points; ++i) { | 
 |       fprintf(file, " %d %d", pars->scaling_points_cb[i][0], | 
 |               pars->scaling_points_cb[i][1]); | 
 |     } | 
 |     fprintf(file, "\n\tsCr %d", pars->num_cr_points); | 
 |     for (int i = 0; i < pars->num_cr_points; ++i) { | 
 |       fprintf(file, " %d %d", pars->scaling_points_cr[i][0], | 
 |               pars->scaling_points_cr[i][1]); | 
 |     } | 
 |     fprintf(file, "\n\tcY"); | 
 |     const int n = 2 * pars->ar_coeff_lag * (pars->ar_coeff_lag + 1); | 
 |     for (int i = 0; i < n; ++i) { | 
 |       fprintf(file, " %d", pars->ar_coeffs_y[i]); | 
 |     } | 
 |     fprintf(file, "\n\tcCb"); | 
 |     for (int i = 0; i <= n; ++i) { | 
 |       fprintf(file, " %d", pars->ar_coeffs_cb[i]); | 
 |     } | 
 |     fprintf(file, "\n\tcCr"); | 
 |     for (int i = 0; i <= n; ++i) { | 
 |       fprintf(file, " %d", pars->ar_coeffs_cr[i]); | 
 |     } | 
 |     fprintf(file, "\n"); | 
 |   } | 
 | } | 
 |  | 
 | void aom_film_grain_table_append(aom_film_grain_table_t *t, int64_t time_stamp, | 
 |                                  int64_t end_time, | 
 |                                  const aom_film_grain_t *grain) { | 
 |   if (!t->tail || memcmp(grain, &t->tail->params, sizeof(*grain))) { | 
 |     aom_film_grain_table_entry_t *new_tail = aom_malloc(sizeof(*new_tail)); | 
 |     memset(new_tail, 0, sizeof(*new_tail)); | 
 |     if (t->tail) t->tail->next = new_tail; | 
 |     if (!t->head) t->head = new_tail; | 
 |     t->tail = new_tail; | 
 |  | 
 |     new_tail->start_time = time_stamp; | 
 |     new_tail->end_time = end_time; | 
 |     new_tail->params = *grain; | 
 |   } else { | 
 |     t->tail->end_time = AOMMAX(t->tail->end_time, end_time); | 
 |     t->tail->start_time = AOMMIN(t->tail->start_time, time_stamp); | 
 |   } | 
 | } | 
 |  | 
 | int aom_film_grain_table_lookup(aom_film_grain_table_t *t, int64_t time_stamp, | 
 |                                 int64_t end_time, int erase, | 
 |                                 aom_film_grain_t *grain) { | 
 |   aom_film_grain_table_entry_t *entry = t->head; | 
 |   aom_film_grain_table_entry_t *prev_entry = 0; | 
 |   uint16_t random_seed = grain ? grain->random_seed : 0; | 
 |   if (grain) memset(grain, 0, sizeof(*grain)); | 
 |  | 
 |   while (entry) { | 
 |     aom_film_grain_table_entry_t *next = entry->next; | 
 |     if (time_stamp >= entry->start_time && time_stamp < entry->end_time) { | 
 |       if (grain) { | 
 |         *grain = entry->params; | 
 |         if (time_stamp != 0) grain->random_seed = random_seed; | 
 |       } | 
 |       if (!erase) return 1; | 
 |  | 
 |       const int64_t entry_end_time = entry->end_time; | 
 |       if (time_stamp <= entry->start_time && end_time >= entry->end_time) { | 
 |         if (t->tail == entry) t->tail = prev_entry; | 
 |         if (prev_entry) { | 
 |           prev_entry->next = entry->next; | 
 |         } else { | 
 |           t->head = entry->next; | 
 |         } | 
 |         aom_free(entry); | 
 |       } else if (time_stamp <= entry->start_time && | 
 |                  end_time < entry->end_time) { | 
 |         entry->start_time = end_time; | 
 |       } else if (time_stamp > entry->start_time && | 
 |                  end_time >= entry->end_time) { | 
 |         entry->end_time = time_stamp; | 
 |       } else { | 
 |         aom_film_grain_table_entry_t *new_entry = | 
 |             aom_malloc(sizeof(*new_entry)); | 
 |         new_entry->next = entry->next; | 
 |         new_entry->start_time = end_time; | 
 |         new_entry->end_time = entry->end_time; | 
 |         new_entry->params = entry->params; | 
 |         entry->next = new_entry; | 
 |         entry->end_time = time_stamp; | 
 |         if (t->tail == entry) t->tail = new_entry; | 
 |       } | 
 |       // If segments aren't aligned, delete from the beggining of subsequent | 
 |       // segments | 
 |       if (end_time > entry_end_time) { | 
 |         aom_film_grain_table_lookup(t, entry->end_time, end_time, 1, 0); | 
 |       } | 
 |       return 1; | 
 |     } | 
 |     prev_entry = entry; | 
 |     entry = next; | 
 |   } | 
 |   return 0; | 
 | } | 
 |  | 
 | aom_codec_err_t aom_film_grain_table_read( | 
 |     aom_film_grain_table_t *t, const char *filename, | 
 |     struct aom_internal_error_info *error_info) { | 
 |   FILE *file = fopen(filename, "rb"); | 
 |   if (!file) { | 
 |     aom_internal_error(error_info, AOM_CODEC_ERROR, "Unable to open %s", | 
 |                        filename); | 
 |     return error_info->error_code; | 
 |   } | 
 |   error_info->error_code = AOM_CODEC_OK; | 
 |  | 
 |   // Read in one extra character as there should be white space after | 
 |   // the header. | 
 |   char magic[9]; | 
 |   if (!fread(magic, 9, 1, file) || memcmp(magic, kFileMagic, 8)) { | 
 |     aom_internal_error(error_info, AOM_CODEC_ERROR, | 
 |                        "Unable to read (or invalid) file magic"); | 
 |     fclose(file); | 
 |     return error_info->error_code; | 
 |   } | 
 |  | 
 |   aom_film_grain_table_entry_t *prev_entry = 0; | 
 |   while (!feof(file)) { | 
 |     aom_film_grain_table_entry_t *entry = aom_malloc(sizeof(*entry)); | 
 |     memset(entry, 0, sizeof(*entry)); | 
 |     grain_table_entry_read(file, error_info, entry); | 
 |     entry->next = 0; | 
 |  | 
 |     if (prev_entry) prev_entry->next = entry; | 
 |     if (!t->head) t->head = entry; | 
 |     t->tail = entry; | 
 |     prev_entry = entry; | 
 |  | 
 |     if (error_info->error_code != AOM_CODEC_OK) break; | 
 |   } | 
 |  | 
 |   fclose(file); | 
 |   return error_info->error_code; | 
 | } | 
 |  | 
 | aom_codec_err_t aom_film_grain_table_write( | 
 |     const aom_film_grain_table_t *t, const char *filename, | 
 |     struct aom_internal_error_info *error_info) { | 
 |   error_info->error_code = AOM_CODEC_OK; | 
 |  | 
 |   FILE *file = fopen(filename, "wb"); | 
 |   if (!file) { | 
 |     aom_internal_error(error_info, AOM_CODEC_ERROR, "Unable to open file %s", | 
 |                        filename); | 
 |     return error_info->error_code; | 
 |   } | 
 |  | 
 |   if (!fwrite(kFileMagic, 8, 1, file)) { | 
 |     aom_internal_error(error_info, AOM_CODEC_ERROR, | 
 |                        "Unable to write file magic"); | 
 |     fclose(file); | 
 |     return error_info->error_code; | 
 |   } | 
 |  | 
 |   fprintf(file, "\n"); | 
 |   aom_film_grain_table_entry_t *entry = t->head; | 
 |   while (entry) { | 
 |     grain_table_entry_write(file, entry); | 
 |     entry = entry->next; | 
 |   } | 
 |   fclose(file); | 
 |   return error_info->error_code; | 
 | } | 
 |  | 
 | void aom_film_grain_table_free(aom_film_grain_table_t *t) { | 
 |   aom_film_grain_table_entry_t *entry = t->head; | 
 |   while (entry) { | 
 |     aom_film_grain_table_entry_t *next = entry->next; | 
 |     aom_free(entry); | 
 |     entry = next; | 
 |   } | 
 |   memset(t, 0, sizeof(*t)); | 
 | } |