1/*
2 *  Copyright (c) 2013 The WebM project authors. All Rights Reserved.
3 *
4 *  Use of this source code is governed by a BSD-style license
5 *  that can be found in the LICENSE file in the root of the source
6 *  tree. An additional intellectual property rights grant can be found
7 *  in the file PATENTS.  All contributing project authors may
8 *  be found in the AUTHORS file in the root of the source tree.
9 */
10
11/**
12 * @file
13 * VP9 SVC encoding support via libvpx
14 */
15
16#include <assert.h>
17#include <math.h>
18#include <limits.h>
19#include <stdarg.h>
20#include <stdio.h>
21#include <stdlib.h>
22#include <string.h>
23#define VPX_DISABLE_CTRL_TYPECHECKS 1
24#include "./vpx_config.h"
25#include "vpx/svc_context.h"
26#include "vpx/vp8cx.h"
27#include "vpx/vpx_encoder.h"
28#include "vpx_mem/vpx_mem.h"
29#include "vp9/common/vp9_onyxc_int.h"
30
31#ifdef __MINGW32__
32#define strtok_r strtok_s
33#ifndef MINGW_HAS_SECURE_API
34// proto from /usr/x86_64-w64-mingw32/include/sec_api/string_s.h
35_CRTIMP char *__cdecl strtok_s(char *str, const char *delim, char **context);
36#endif  /* MINGW_HAS_SECURE_API */
37#endif  /* __MINGW32__ */
38
39#ifdef _MSC_VER
40#define strdup _strdup
41#define strtok_r strtok_s
42#endif
43
44#define SVC_REFERENCE_FRAMES 8
45#define SUPERFRAME_SLOTS (8)
46#define SUPERFRAME_BUFFER_SIZE (SUPERFRAME_SLOTS * sizeof(uint32_t) + 2)
47#define OPTION_BUFFER_SIZE 1024
48#define COMPONENTS 4  // psnr & sse statistics maintained for total, y, u, v
49
50#define MAX_QUANTIZER 63
51
52static const int DEFAULT_SCALE_FACTORS_NUM[VPX_SS_MAX_LAYERS] = {
53  4, 5, 7, 11, 16
54};
55
56static const int DEFAULT_SCALE_FACTORS_DEN[VPX_SS_MAX_LAYERS] = {
57  16, 16, 16, 16, 16
58};
59
60typedef enum {
61  QUANTIZER = 0,
62  BITRATE,
63  SCALE_FACTOR,
64  AUTO_ALT_REF,
65  ALL_OPTION_TYPES
66} LAYER_OPTION_TYPE;
67
68static const int option_max_values[ALL_OPTION_TYPES] = {
69  63, INT_MAX, INT_MAX, 1
70};
71
72static const int option_min_values[ALL_OPTION_TYPES] = {
73  0, 0, 1, 0
74};
75
76// One encoded frame
77typedef struct FrameData {
78  void                     *buf;    // compressed data buffer
79  size_t                    size;  // length of compressed data
80  vpx_codec_frame_flags_t   flags;    /**< flags for this frame */
81  struct FrameData         *next;
82} FrameData;
83
84typedef struct SvcInternal {
85  char options[OPTION_BUFFER_SIZE];        // set by vpx_svc_set_options
86
87  // values extracted from option, quantizers
88  vpx_svc_extra_cfg_t svc_params;
89  int enable_auto_alt_ref[VPX_SS_MAX_LAYERS];
90  int bitrates[VPX_SS_MAX_LAYERS];
91
92  // accumulated statistics
93  double psnr_sum[VPX_SS_MAX_LAYERS][COMPONENTS];   // total/Y/U/V
94  uint64_t sse_sum[VPX_SS_MAX_LAYERS][COMPONENTS];
95  uint32_t bytes_sum[VPX_SS_MAX_LAYERS];
96
97  // codec encoding values
98  int width;    // width of highest layer
99  int height;   // height of highest layer
100  int kf_dist;  // distance between keyframes
101
102  // state variables
103  int psnr_pkt_received;
104  int layer;
105  int use_multiple_frame_contexts;
106
107  char message_buffer[2048];
108  vpx_codec_ctx_t *codec_ctx;
109} SvcInternal;
110
111static SvcInternal *get_svc_internal(SvcContext *svc_ctx) {
112  if (svc_ctx == NULL) return NULL;
113  if (svc_ctx->internal == NULL) {
114    SvcInternal *const si = (SvcInternal *)malloc(sizeof(*si));
115    if (si != NULL) {
116      memset(si, 0, sizeof(*si));
117    }
118    svc_ctx->internal = si;
119  }
120  return (SvcInternal *)svc_ctx->internal;
121}
122
123static const SvcInternal *get_const_svc_internal(const SvcContext *svc_ctx) {
124  if (svc_ctx == NULL) return NULL;
125  return (const SvcInternal *)svc_ctx->internal;
126}
127
128static void svc_log_reset(SvcContext *svc_ctx) {
129  SvcInternal *const si = (SvcInternal *)svc_ctx->internal;
130  si->message_buffer[0] = '\0';
131}
132
133static int svc_log(SvcContext *svc_ctx, SVC_LOG_LEVEL level,
134                   const char *fmt, ...) {
135  char buf[512];
136  int retval = 0;
137  va_list ap;
138  SvcInternal *const si = get_svc_internal(svc_ctx);
139
140  if (level > svc_ctx->log_level) {
141    return retval;
142  }
143
144  va_start(ap, fmt);
145  retval = vsnprintf(buf, sizeof(buf), fmt, ap);
146  va_end(ap);
147
148  if (svc_ctx->log_print) {
149    printf("%s", buf);
150  } else {
151    strncat(si->message_buffer, buf,
152            sizeof(si->message_buffer) - strlen(si->message_buffer) - 1);
153  }
154
155  if (level == SVC_LOG_ERROR) {
156    si->codec_ctx->err_detail = si->message_buffer;
157  }
158  return retval;
159}
160
161static vpx_codec_err_t extract_option(LAYER_OPTION_TYPE type,
162                                      char *input,
163                                      int *value0,
164                                      int *value1) {
165  if (type == SCALE_FACTOR) {
166    *value0 = strtol(input, &input, 10);
167    if (*input++ != '/')
168      return VPX_CODEC_INVALID_PARAM;
169    *value1 = strtol(input, &input, 10);
170
171    if (*value0 < option_min_values[SCALE_FACTOR] ||
172        *value1 < option_min_values[SCALE_FACTOR] ||
173        *value0 > option_max_values[SCALE_FACTOR] ||
174        *value1 > option_max_values[SCALE_FACTOR] ||
175        *value0 > *value1)  // num shouldn't be greater than den
176      return VPX_CODEC_INVALID_PARAM;
177  } else {
178    *value0 = atoi(input);
179    if (*value0 < option_min_values[type] ||
180        *value0 > option_max_values[type])
181      return VPX_CODEC_INVALID_PARAM;
182  }
183  return VPX_CODEC_OK;
184}
185
186static vpx_codec_err_t parse_layer_options_from_string(SvcContext *svc_ctx,
187                                                       LAYER_OPTION_TYPE type,
188                                                       const char *input,
189                                                       int *option0,
190                                                       int *option1) {
191  int i;
192  vpx_codec_err_t res = VPX_CODEC_OK;
193  char *input_string;
194  char *token;
195  const char *delim = ",";
196  char *save_ptr;
197
198  if (input == NULL || option0 == NULL ||
199      (option1 == NULL && type == SCALE_FACTOR))
200    return VPX_CODEC_INVALID_PARAM;
201
202  input_string = strdup(input);
203  token = strtok_r(input_string, delim, &save_ptr);
204  for (i = 0; i < svc_ctx->spatial_layers; ++i) {
205    if (token != NULL) {
206      res = extract_option(type, token, option0 + i, option1 + i);
207      if (res != VPX_CODEC_OK)
208        break;
209      token = strtok_r(NULL, delim, &save_ptr);
210    } else {
211      break;
212    }
213  }
214  if (res == VPX_CODEC_OK && i != svc_ctx->spatial_layers) {
215    svc_log(svc_ctx, SVC_LOG_ERROR,
216            "svc: layer params type: %d    %d values required, "
217            "but only %d specified\n", type, svc_ctx->spatial_layers, i);
218    res = VPX_CODEC_INVALID_PARAM;
219  }
220  free(input_string);
221  return res;
222}
223
224/**
225 * Parse SVC encoding options
226 * Format: encoding-mode=<svc_mode>,layers=<layer_count>
227 *         scale-factors=<n1>/<d1>,<n2>/<d2>,...
228 *         quantizers=<q1>,<q2>,...
229 * svc_mode = [i|ip|alt_ip|gf]
230 */
231static vpx_codec_err_t parse_options(SvcContext *svc_ctx, const char *options) {
232  char *input_string;
233  char *option_name;
234  char *option_value;
235  char *input_ptr;
236  SvcInternal *const si = get_svc_internal(svc_ctx);
237  vpx_codec_err_t res = VPX_CODEC_OK;
238  int i, alt_ref_enabled = 0;
239
240  if (options == NULL) return VPX_CODEC_OK;
241  input_string = strdup(options);
242
243  // parse option name
244  option_name = strtok_r(input_string, "=", &input_ptr);
245  while (option_name != NULL) {
246    // parse option value
247    option_value = strtok_r(NULL, " ", &input_ptr);
248    if (option_value == NULL) {
249      svc_log(svc_ctx, SVC_LOG_ERROR, "option missing value: %s\n",
250              option_name);
251      res = VPX_CODEC_INVALID_PARAM;
252      break;
253    }
254    if (strcmp("spatial-layers", option_name) == 0) {
255      svc_ctx->spatial_layers = atoi(option_value);
256    } else if (strcmp("temporal-layers", option_name) == 0) {
257      svc_ctx->temporal_layers = atoi(option_value);
258    } else if (strcmp("scale-factors", option_name) == 0) {
259      res = parse_layer_options_from_string(svc_ctx, SCALE_FACTOR, option_value,
260                                            si->svc_params.scaling_factor_num,
261                                            si->svc_params.scaling_factor_den);
262      if (res != VPX_CODEC_OK) break;
263    } else if (strcmp("max-quantizers", option_name) == 0) {
264      res = parse_layer_options_from_string(svc_ctx, QUANTIZER, option_value,
265                                            si->svc_params.max_quantizers,
266                                            NULL);
267      if (res != VPX_CODEC_OK) break;
268    } else if (strcmp("min-quantizers", option_name) == 0) {
269      res = parse_layer_options_from_string(svc_ctx, QUANTIZER, option_value,
270                                            si->svc_params.min_quantizers,
271                                            NULL);
272      if (res != VPX_CODEC_OK) break;
273    } else if (strcmp("auto-alt-refs", option_name) == 0) {
274      res = parse_layer_options_from_string(svc_ctx, AUTO_ALT_REF, option_value,
275                                            si->enable_auto_alt_ref, NULL);
276      if (res != VPX_CODEC_OK) break;
277    } else if (strcmp("bitrates", option_name) == 0) {
278      res = parse_layer_options_from_string(svc_ctx, BITRATE, option_value,
279                                            si->bitrates, NULL);
280      if (res != VPX_CODEC_OK) break;
281    } else if (strcmp("multi-frame-contexts", option_name) == 0) {
282      si->use_multiple_frame_contexts = atoi(option_value);
283    } else {
284      svc_log(svc_ctx, SVC_LOG_ERROR, "invalid option: %s\n", option_name);
285      res = VPX_CODEC_INVALID_PARAM;
286      break;
287    }
288    option_name = strtok_r(NULL, "=", &input_ptr);
289  }
290  free(input_string);
291
292  for (i = 0; i < svc_ctx->spatial_layers; ++i) {
293    if (si->svc_params.max_quantizers[i] > MAX_QUANTIZER ||
294        si->svc_params.max_quantizers[i] < 0 ||
295        si->svc_params.min_quantizers[i] > si->svc_params.max_quantizers[i] ||
296        si->svc_params.min_quantizers[i] < 0)
297      res = VPX_CODEC_INVALID_PARAM;
298  }
299
300  if (si->use_multiple_frame_contexts &&
301      (svc_ctx->spatial_layers > 3 ||
302       svc_ctx->spatial_layers * svc_ctx->temporal_layers > 4))
303    res = VPX_CODEC_INVALID_PARAM;
304
305  for (i = 0; i < svc_ctx->spatial_layers; ++i)
306    alt_ref_enabled += si->enable_auto_alt_ref[i];
307  if (alt_ref_enabled > REF_FRAMES - svc_ctx->spatial_layers) {
308    svc_log(svc_ctx, SVC_LOG_ERROR,
309            "svc: auto alt ref: Maxinum %d(REF_FRAMES - layers) layers could"
310            "enabled auto alt reference frame, but % layers are enabled\n",
311            REF_FRAMES - svc_ctx->spatial_layers, alt_ref_enabled);
312    res = VPX_CODEC_INVALID_PARAM;
313  }
314
315  return res;
316}
317
318vpx_codec_err_t vpx_svc_set_options(SvcContext *svc_ctx, const char *options) {
319  SvcInternal *const si = get_svc_internal(svc_ctx);
320  if (svc_ctx == NULL || options == NULL || si == NULL) {
321    return VPX_CODEC_INVALID_PARAM;
322  }
323  strncpy(si->options, options, sizeof(si->options));
324  si->options[sizeof(si->options) - 1] = '\0';
325  return VPX_CODEC_OK;
326}
327
328void assign_layer_bitrates(const SvcContext *svc_ctx,
329                           vpx_codec_enc_cfg_t *const enc_cfg) {
330  int i;
331  const SvcInternal *const si = get_const_svc_internal(svc_ctx);
332
333  if (si->bitrates[0] != 0) {
334    enc_cfg->rc_target_bitrate = 0;
335    for (i = 0; i < svc_ctx->spatial_layers; ++i) {
336      enc_cfg->ss_target_bitrate[i] = (unsigned int)si->bitrates[i];
337      enc_cfg->rc_target_bitrate += si->bitrates[i];
338    }
339  } else {
340    float total = 0;
341    float alloc_ratio[VPX_SS_MAX_LAYERS] = {0};
342
343    for (i = 0; i < svc_ctx->spatial_layers; ++i) {
344      if (si->svc_params.scaling_factor_den[i] > 0) {
345        alloc_ratio[i] = (float)(si->svc_params.scaling_factor_num[i] * 1.0 /
346                                 si->svc_params.scaling_factor_den[i]);
347
348        alloc_ratio[i] *= alloc_ratio[i];
349        total += alloc_ratio[i];
350      }
351    }
352
353    for (i = 0; i < svc_ctx->spatial_layers; ++i) {
354      if (total > 0) {
355        enc_cfg->ss_target_bitrate[i] = (unsigned int)
356            (enc_cfg->rc_target_bitrate * alloc_ratio[i] / total);
357      }
358    }
359  }
360}
361
362vpx_codec_err_t vpx_svc_init(SvcContext *svc_ctx, vpx_codec_ctx_t *codec_ctx,
363                             vpx_codec_iface_t *iface,
364                             vpx_codec_enc_cfg_t *enc_cfg) {
365  vpx_codec_err_t res;
366  int i;
367  SvcInternal *const si = get_svc_internal(svc_ctx);
368  if (svc_ctx == NULL || codec_ctx == NULL || iface == NULL ||
369      enc_cfg == NULL) {
370    return VPX_CODEC_INVALID_PARAM;
371  }
372  if (si == NULL) return VPX_CODEC_MEM_ERROR;
373
374  si->codec_ctx = codec_ctx;
375
376  si->width = enc_cfg->g_w;
377  si->height = enc_cfg->g_h;
378
379  if (enc_cfg->kf_max_dist < 2) {
380    svc_log(svc_ctx, SVC_LOG_ERROR, "key frame distance too small: %d\n",
381            enc_cfg->kf_max_dist);
382    return VPX_CODEC_INVALID_PARAM;
383  }
384  si->kf_dist = enc_cfg->kf_max_dist;
385
386  if (svc_ctx->spatial_layers == 0)
387    svc_ctx->spatial_layers = VPX_SS_DEFAULT_LAYERS;
388  if (svc_ctx->spatial_layers < 1 ||
389      svc_ctx->spatial_layers > VPX_SS_MAX_LAYERS) {
390    svc_log(svc_ctx, SVC_LOG_ERROR, "spatial layers: invalid value: %d\n",
391            svc_ctx->spatial_layers);
392    return VPX_CODEC_INVALID_PARAM;
393  }
394
395  for (i = 0; i < VPX_SS_MAX_LAYERS; ++i) {
396    si->svc_params.max_quantizers[i] = MAX_QUANTIZER;
397    si->svc_params.min_quantizers[i] = 0;
398    si->svc_params.scaling_factor_num[i] = DEFAULT_SCALE_FACTORS_NUM[i];
399    si->svc_params.scaling_factor_den[i] = DEFAULT_SCALE_FACTORS_DEN[i];
400  }
401
402  // Parse aggregate command line options. Options must start with
403  // "layers=xx" then followed by other options
404  res = parse_options(svc_ctx, si->options);
405  if (res != VPX_CODEC_OK) return res;
406
407  if (svc_ctx->spatial_layers < 1)
408    svc_ctx->spatial_layers = 1;
409  if (svc_ctx->spatial_layers > VPX_SS_MAX_LAYERS)
410    svc_ctx->spatial_layers = VPX_SS_MAX_LAYERS;
411
412  if (svc_ctx->temporal_layers < 1)
413    svc_ctx->temporal_layers = 1;
414  if (svc_ctx->temporal_layers > VPX_TS_MAX_LAYERS)
415    svc_ctx->temporal_layers = VPX_TS_MAX_LAYERS;
416
417  assign_layer_bitrates(svc_ctx, enc_cfg);
418
419#if CONFIG_SPATIAL_SVC
420  for (i = 0; i < svc_ctx->spatial_layers; ++i)
421    enc_cfg->ss_enable_auto_alt_ref[i] = si->enable_auto_alt_ref[i];
422#endif
423
424  if (svc_ctx->temporal_layers > 1) {
425    int i;
426    for (i = 0; i < svc_ctx->temporal_layers; ++i) {
427      enc_cfg->ts_target_bitrate[i] = enc_cfg->rc_target_bitrate /
428                                      svc_ctx->temporal_layers;
429      enc_cfg->ts_rate_decimator[i] = 1 << (svc_ctx->temporal_layers - 1 - i);
430    }
431  }
432
433  // modify encoder configuration
434  enc_cfg->ss_number_layers = svc_ctx->spatial_layers;
435  enc_cfg->ts_number_layers = svc_ctx->temporal_layers;
436
437  if (enc_cfg->g_error_resilient == 0 && si->use_multiple_frame_contexts == 0)
438    enc_cfg->g_error_resilient = 1;
439
440  // Initialize codec
441  res = vpx_codec_enc_init(codec_ctx, iface, enc_cfg, VPX_CODEC_USE_PSNR);
442  if (res != VPX_CODEC_OK) {
443    svc_log(svc_ctx, SVC_LOG_ERROR, "svc_enc_init error\n");
444    return res;
445  }
446
447  vpx_codec_control(codec_ctx, VP9E_SET_SVC, 1);
448  vpx_codec_control(codec_ctx, VP9E_SET_SVC_PARAMETERS, &si->svc_params);
449
450  return VPX_CODEC_OK;
451}
452
453/**
454 * Encode a frame into multiple layers
455 * Create a superframe containing the individual layers
456 */
457vpx_codec_err_t vpx_svc_encode(SvcContext *svc_ctx, vpx_codec_ctx_t *codec_ctx,
458                               struct vpx_image *rawimg, vpx_codec_pts_t pts,
459                               int64_t duration, int deadline) {
460  vpx_codec_err_t res;
461  vpx_codec_iter_t iter;
462  const vpx_codec_cx_pkt_t *cx_pkt;
463  SvcInternal *const si = get_svc_internal(svc_ctx);
464  if (svc_ctx == NULL || codec_ctx == NULL || si == NULL) {
465    return VPX_CODEC_INVALID_PARAM;
466  }
467
468  svc_log_reset(svc_ctx);
469
470  res = vpx_codec_encode(codec_ctx, rawimg, pts, (uint32_t)duration, 0,
471                         deadline);
472  if (res != VPX_CODEC_OK) {
473    return res;
474  }
475  // save compressed data
476  iter = NULL;
477  while ((cx_pkt = vpx_codec_get_cx_data(codec_ctx, &iter))) {
478    switch (cx_pkt->kind) {
479#if CONFIG_SPATIAL_SVC
480      case VPX_CODEC_SPATIAL_SVC_LAYER_PSNR: {
481        int i;
482        for (i = 0; i < svc_ctx->spatial_layers; ++i) {
483          int j;
484          svc_log(svc_ctx, SVC_LOG_DEBUG,
485                  "SVC frame: %d, layer: %d, PSNR(Total/Y/U/V): "
486                  "%2.3f  %2.3f  %2.3f  %2.3f \n",
487                  si->psnr_pkt_received, i,
488                  cx_pkt->data.layer_psnr[i].psnr[0],
489                  cx_pkt->data.layer_psnr[i].psnr[1],
490                  cx_pkt->data.layer_psnr[i].psnr[2],
491                  cx_pkt->data.layer_psnr[i].psnr[3]);
492          svc_log(svc_ctx, SVC_LOG_DEBUG,
493                  "SVC frame: %d, layer: %d, SSE(Total/Y/U/V): "
494                  "%2.3f  %2.3f  %2.3f  %2.3f \n",
495                  si->psnr_pkt_received, i,
496                  cx_pkt->data.layer_psnr[i].sse[0],
497                  cx_pkt->data.layer_psnr[i].sse[1],
498                  cx_pkt->data.layer_psnr[i].sse[2],
499                  cx_pkt->data.layer_psnr[i].sse[3]);
500
501          for (j = 0; j < COMPONENTS; ++j) {
502            si->psnr_sum[i][j] +=
503                cx_pkt->data.layer_psnr[i].psnr[j];
504            si->sse_sum[i][j] += cx_pkt->data.layer_psnr[i].sse[j];
505          }
506        }
507        ++si->psnr_pkt_received;
508        break;
509      }
510      case VPX_CODEC_SPATIAL_SVC_LAYER_SIZES: {
511        int i;
512        for (i = 0; i < svc_ctx->spatial_layers; ++i)
513          si->bytes_sum[i] += cx_pkt->data.layer_sizes[i];
514        break;
515      }
516#endif
517      default: {
518        break;
519      }
520    }
521  }
522
523  return VPX_CODEC_OK;
524}
525
526const char *vpx_svc_get_message(const SvcContext *svc_ctx) {
527  const SvcInternal *const si = get_const_svc_internal(svc_ctx);
528  if (svc_ctx == NULL || si == NULL) return NULL;
529  return si->message_buffer;
530}
531
532static double calc_psnr(double d) {
533  if (d == 0) return 100;
534  return -10.0 * log(d) / log(10.0);
535}
536
537// dump accumulated statistics and reset accumulated values
538const char *vpx_svc_dump_statistics(SvcContext *svc_ctx) {
539  int number_of_frames;
540  int i, j;
541  uint32_t bytes_total = 0;
542  double scale[COMPONENTS];
543  double psnr[COMPONENTS];
544  double mse[COMPONENTS];
545  double y_scale;
546
547  SvcInternal *const si = get_svc_internal(svc_ctx);
548  if (svc_ctx == NULL || si == NULL) return NULL;
549
550  svc_log_reset(svc_ctx);
551
552  number_of_frames = si->psnr_pkt_received;
553  if (number_of_frames <= 0) return vpx_svc_get_message(svc_ctx);
554
555  svc_log(svc_ctx, SVC_LOG_INFO, "\n");
556  for (i = 0; i < svc_ctx->spatial_layers; ++i) {
557
558    svc_log(svc_ctx, SVC_LOG_INFO,
559            "Layer %d Average PSNR=[%2.3f, %2.3f, %2.3f, %2.3f], Bytes=[%u]\n",
560            i, (double)si->psnr_sum[i][0] / number_of_frames,
561            (double)si->psnr_sum[i][1] / number_of_frames,
562            (double)si->psnr_sum[i][2] / number_of_frames,
563            (double)si->psnr_sum[i][3] / number_of_frames, si->bytes_sum[i]);
564    // the following psnr calculation is deduced from ffmpeg.c#print_report
565    y_scale = si->width * si->height * 255.0 * 255.0 * number_of_frames;
566    scale[1] = y_scale;
567    scale[2] = scale[3] = y_scale / 4;  // U or V
568    scale[0] = y_scale * 1.5;           // total
569
570    for (j = 0; j < COMPONENTS; j++) {
571      psnr[j] = calc_psnr(si->sse_sum[i][j] / scale[j]);
572      mse[j] = si->sse_sum[i][j] * 255.0 * 255.0 / scale[j];
573    }
574    svc_log(svc_ctx, SVC_LOG_INFO,
575            "Layer %d Overall PSNR=[%2.3f, %2.3f, %2.3f, %2.3f]\n", i, psnr[0],
576            psnr[1], psnr[2], psnr[3]);
577    svc_log(svc_ctx, SVC_LOG_INFO,
578            "Layer %d Overall MSE=[%2.3f, %2.3f, %2.3f, %2.3f]\n", i, mse[0],
579            mse[1], mse[2], mse[3]);
580
581    bytes_total += si->bytes_sum[i];
582    // clear sums for next time
583    si->bytes_sum[i] = 0;
584    for (j = 0; j < COMPONENTS; ++j) {
585      si->psnr_sum[i][j] = 0;
586      si->sse_sum[i][j] = 0;
587    }
588  }
589
590  // only display statistics once
591  si->psnr_pkt_received = 0;
592
593  svc_log(svc_ctx, SVC_LOG_INFO, "Total Bytes=[%u]\n", bytes_total);
594  return vpx_svc_get_message(svc_ctx);
595}
596
597void vpx_svc_release(SvcContext *svc_ctx) {
598  SvcInternal *si;
599  if (svc_ctx == NULL) return;
600  // do not use get_svc_internal as it will unnecessarily allocate an
601  // SvcInternal if it was not already allocated
602  si = (SvcInternal *)svc_ctx->internal;
603  if (si != NULL) {
604    free(si);
605    svc_ctx->internal = NULL;
606  }
607}
608
609