986 lines · 37.5 KB · C
RawDownload
/* *  Copyright (c) 2012 The WebM project authors. All Rights Reserved. * *  Use of this source code is governed by a BSD-style license *  that can be found in the LICENSE file in the root of the source *  tree. An additional intellectual property rights grant can be found *  in the file PATENTS.  All contributing project authors may *  be found in the AUTHORS file in the root of the source tree. */ //  This is an example demonstrating how to implement a multi-layer VPx//  encoding scheme based on temporal scalability for video applications//  that benefit from a scalable bitstream. #include <assert.h>#include <math.h>#include <stdio.h>#include <stdlib.h>#include <string.h> #include "./vpx_config.h"#include "./y4minput.h"#include "../vpx_ports/vpx_timer.h"#include "vpx/vp8cx.h"#include "vpx/vpx_encoder.h"#include "vpx_ports/bitops.h" #include "../tools_common.h"#include "../video_writer.h" #define ROI_MAP 0 #define zero(Dest) memset(&(Dest), 0, sizeof(Dest)); static const char *exec_name; void usage_exit(void) { exit(EXIT_FAILURE); } // Denoiser states for vp8, for temporal denoising.enum denoiserStateVp8 {  kVp8DenoiserOff,  kVp8DenoiserOnYOnly,  kVp8DenoiserOnYUV,  kVp8DenoiserOnYUVAggressive,  kVp8DenoiserOnAdaptive}; // Denoiser states for vp9, for temporal denoising.enum denoiserStateVp9 {  kVp9DenoiserOff,  kVp9DenoiserOnYOnly,  // For SVC: denoise the top two spatial layers.  kVp9DenoiserOnYTwoSpatialLayers}; static int mode_to_num_layers[13] = { 1, 2, 2, 3, 3, 3, 3, 5, 2, 3, 3, 3, 3 }; // For rate control encoding stats.struct RateControlMetrics {  // Number of input frames per layer.  int layer_input_frames[VPX_TS_MAX_LAYERS];  // Total (cumulative) number of encoded frames per layer.  int layer_tot_enc_frames[VPX_TS_MAX_LAYERS];  // Number of encoded non-key frames per layer.  int layer_enc_frames[VPX_TS_MAX_LAYERS];  // Framerate per layer layer (cumulative).  double layer_framerate[VPX_TS_MAX_LAYERS];  // Target average frame size per layer (per-frame-bandwidth per layer).  double layer_pfb[VPX_TS_MAX_LAYERS];  // Actual average frame size per layer.  double layer_avg_frame_size[VPX_TS_MAX_LAYERS];  // Average rate mismatch per layer (|target - actual| / target).  double layer_avg_rate_mismatch[VPX_TS_MAX_LAYERS];  // Actual encoding bitrate per layer (cumulative).  double layer_encoding_bitrate[VPX_TS_MAX_LAYERS];  // Average of the short-time encoder actual bitrate.  // TODO(marpan): Should we add these short-time stats for each layer?  double avg_st_encoding_bitrate;  // Variance of the short-time encoder actual bitrate.  double variance_st_encoding_bitrate;  // Window (number of frames) for computing short-timee encoding bitrate.  int window_size;  // Number of window measurements.  int window_count;  int layer_target_bitrate[VPX_MAX_LAYERS];}; // Note: these rate control metrics assume only 1 key frame in the// sequence (i.e., first frame only). So for temporal pattern# 7// (which has key frame for every frame on base layer), the metrics// computation will be off/wrong.// TODO(marpan): Update these metrics to account for multiple key frames// in the stream.static void set_rate_control_metrics(struct RateControlMetrics *rc,                                     vpx_codec_enc_cfg_t *cfg) {  int i = 0;  // Set the layer (cumulative) framerate and the target layer (non-cumulative)  // per-frame-bandwidth, for the rate control encoding stats below.  const double framerate = cfg->g_timebase.den / cfg->g_timebase.num;  const int ts_number_layers = cfg->ts_number_layers;  rc->layer_framerate[0] = framerate / cfg->ts_rate_decimator[0];  rc->layer_pfb[0] =      1000.0 * rc->layer_target_bitrate[0] / rc->layer_framerate[0];  for (i = 0; i < ts_number_layers; ++i) {    if (i > 0) {      rc->layer_framerate[i] = framerate / cfg->ts_rate_decimator[i];      rc->layer_pfb[i] =          1000.0 *          (rc->layer_target_bitrate[i] - rc->layer_target_bitrate[i - 1]) /          (rc->layer_framerate[i] - rc->layer_framerate[i - 1]);    }    rc->layer_input_frames[i] = 0;    rc->layer_enc_frames[i] = 0;    rc->layer_tot_enc_frames[i] = 0;    rc->layer_encoding_bitrate[i] = 0.0;    rc->layer_avg_frame_size[i] = 0.0;    rc->layer_avg_rate_mismatch[i] = 0.0;  }  rc->window_count = 0;  rc->window_size = 15;  rc->avg_st_encoding_bitrate = 0.0;  rc->variance_st_encoding_bitrate = 0.0;  // Target bandwidth for the whole stream.  // Set to layer_target_bitrate for highest layer (total bitrate).  cfg->rc_target_bitrate = rc->layer_target_bitrate[ts_number_layers - 1];} static void printout_rate_control_summary(struct RateControlMetrics *rc,                                          vpx_codec_enc_cfg_t *cfg,                                          int frame_cnt) {  unsigned int i = 0;  int tot_num_frames = 0;  double perc_fluctuation = 0.0;  printf("Total number of processed frames: %d\n\n", frame_cnt - 1);  printf("Rate control layer stats for %d layer(s):\n\n",         cfg->ts_number_layers);  for (i = 0; i < cfg->ts_number_layers; ++i) {    const int num_dropped =        (i > 0) ? (rc->layer_input_frames[i] - rc->layer_enc_frames[i])                : (rc->layer_input_frames[i] - rc->layer_enc_frames[i] - 1);    tot_num_frames += rc->layer_input_frames[i];    rc->layer_encoding_bitrate[i] = 0.001 * rc->layer_framerate[i] *                                    rc->layer_encoding_bitrate[i] /                                    tot_num_frames;    rc->layer_avg_frame_size[i] =        rc->layer_avg_frame_size[i] / rc->layer_enc_frames[i];    rc->layer_avg_rate_mismatch[i] =        100.0 * rc->layer_avg_rate_mismatch[i] / rc->layer_enc_frames[i];    printf("For layer#: %d \n", i);    printf("Bitrate (target vs actual): %d %f \n", rc->layer_target_bitrate[i],           rc->layer_encoding_bitrate[i]);    printf("Average frame size (target vs actual): %f %f \n", rc->layer_pfb[i],           rc->layer_avg_frame_size[i]);    printf("Average rate_mismatch: %f \n", rc->layer_avg_rate_mismatch[i]);    printf(        "Number of input frames, encoded (non-key) frames, "        "and perc dropped frames: %d %d %f \n",        rc->layer_input_frames[i], rc->layer_enc_frames[i],        100.0 * num_dropped / rc->layer_input_frames[i]);    printf("\n");  }  rc->avg_st_encoding_bitrate = rc->avg_st_encoding_bitrate / rc->window_count;  rc->variance_st_encoding_bitrate =      rc->variance_st_encoding_bitrate / rc->window_count -      (rc->avg_st_encoding_bitrate * rc->avg_st_encoding_bitrate);  perc_fluctuation = 100.0 * sqrt(rc->variance_st_encoding_bitrate) /                     rc->avg_st_encoding_bitrate;  printf("Short-time stats, for window of %d frames: \n", rc->window_size);  printf("Average, rms-variance, and percent-fluct: %f %f %f \n",         rc->avg_st_encoding_bitrate, sqrt(rc->variance_st_encoding_bitrate),         perc_fluctuation);  if ((frame_cnt - 1) != tot_num_frames)    die("Error: Number of input frames not equal to output! \n");} #if ROI_MAPstatic void set_roi_map(const char *enc_name, vpx_codec_enc_cfg_t *cfg,                        vpx_roi_map_t *roi) {  unsigned int i, j;  int block_size = 0;  uint8_t is_vp8 = strncmp(enc_name, "vp8", 3) == 0 ? 1 : 0;  uint8_t is_vp9 = strncmp(enc_name, "vp9", 3) == 0 ? 1 : 0;  if (!is_vp8 && !is_vp9) {    die("unsupported codec.");  }  zero(*roi);   block_size = is_vp9 && !is_vp8 ? 8 : 16;   // ROI is based on the segments (4 for vp8, 8 for vp9), smallest unit for  // segment is 16x16 for vp8, 8x8 for vp9.  roi->rows = (cfg->g_h + block_size - 1) / block_size;  roi->cols = (cfg->g_w + block_size - 1) / block_size;   // Applies delta QP on the segment blocks, varies from -63 to 63.  // Setting to negative means lower QP (better quality).  // Below we set delta_q to the extreme (-63) to show strong effect.  // VP8 uses the first 4 segments. VP9 uses all 8 segments.  zero(roi->delta_q);  roi->delta_q[1] = -63;   // Applies delta loopfilter strength on the segment blocks, varies from -63 to  // 63. Setting to positive means stronger loopfilter. VP8 uses the first 4  // segments. VP9 uses all 8 segments.  zero(roi->delta_lf);   if (is_vp8) {    // Applies skip encoding threshold on the segment blocks, varies from 0 to    // UINT_MAX. Larger value means more skipping of encoding is possible.    // This skip threshold only applies on delta frames.    zero(roi->static_threshold);  }   if (is_vp9) {    // Apply skip segment. Setting to 1 means this block will be copied from    // previous frame.    zero(roi->skip);  }   if (is_vp9) {    // Apply ref frame segment.    // -1 : Do not apply this segment.    //  0 : Froce using intra.    //  1 : Force using last.    //  2 : Force using golden.    //  3 : Force using alfref but not used in non-rd pickmode for 0 lag.    memset(roi->ref_frame, -1, sizeof(roi->ref_frame));    roi->ref_frame[1] = 1;  }   // Use 2 states: 1 is center square, 0 is the rest.  roi->roi_map =      (uint8_t *)calloc(roi->rows * roi->cols, sizeof(*roi->roi_map));  for (i = 0; i < roi->rows; ++i) {    for (j = 0; j < roi->cols; ++j) {      if (i > (roi->rows >> 2) && i < ((roi->rows * 3) >> 2) &&          j > (roi->cols >> 2) && j < ((roi->cols * 3) >> 2)) {        roi->roi_map[i * roi->cols + j] = 1;      }    }  }}#endif // Temporal scaling parameters:// NOTE: The 3 prediction frames cannot be used interchangeably due to// differences in the way they are handled throughout the code. The// frames should be allocated to layers in the order LAST, GF, ARF.// Other combinations work, but may produce slightly inferior results.static void set_temporal_layer_pattern(int layering_mode,                                       vpx_codec_enc_cfg_t *cfg,                                       int *layer_flags,                                       int *flag_periodicity) {  switch (layering_mode) {    case 0: {      // 1-layer.      int ids[1] = { 0 };      cfg->ts_periodicity = 1;      *flag_periodicity = 1;      cfg->ts_number_layers = 1;      cfg->ts_rate_decimator[0] = 1;      memcpy(cfg->ts_layer_id, ids, sizeof(ids));      // Update L only.      layer_flags[0] =          VPX_EFLAG_FORCE_KF | VP8_EFLAG_NO_UPD_GF | VP8_EFLAG_NO_UPD_ARF;      break;    }    case 1: {      // 2-layers, 2-frame period.      int ids[2] = { 0, 1 };      cfg->ts_periodicity = 2;      *flag_periodicity = 2;      cfg->ts_number_layers = 2;      cfg->ts_rate_decimator[0] = 2;      cfg->ts_rate_decimator[1] = 1;      memcpy(cfg->ts_layer_id, ids, sizeof(ids));#if 1      // 0=L, 1=GF, Intra-layer prediction enabled.      layer_flags[0] = VPX_EFLAG_FORCE_KF | VP8_EFLAG_NO_UPD_GF |                       VP8_EFLAG_NO_UPD_ARF | VP8_EFLAG_NO_REF_GF |                       VP8_EFLAG_NO_REF_ARF;      layer_flags[1] =          VP8_EFLAG_NO_UPD_ARF | VP8_EFLAG_NO_UPD_LAST | VP8_EFLAG_NO_REF_ARF;#else      // 0=L, 1=GF, Intra-layer prediction disabled.      layer_flags[0] = VPX_EFLAG_FORCE_KF | VP8_EFLAG_NO_UPD_GF |                       VP8_EFLAG_NO_UPD_ARF | VP8_EFLAG_NO_REF_GF |                       VP8_EFLAG_NO_REF_ARF;      layer_flags[1] = VP8_EFLAG_NO_UPD_ARF | VP8_EFLAG_NO_UPD_LAST |                       VP8_EFLAG_NO_REF_ARF | VP8_EFLAG_NO_REF_LAST;#endif      break;    }    case 2: {      // 2-layers, 3-frame period.      int ids[3] = { 0, 1, 1 };      cfg->ts_periodicity = 3;      *flag_periodicity = 3;      cfg->ts_number_layers = 2;      cfg->ts_rate_decimator[0] = 3;      cfg->ts_rate_decimator[1] = 1;      memcpy(cfg->ts_layer_id, ids, sizeof(ids));      // 0=L, 1=GF, Intra-layer prediction enabled.      layer_flags[0] = VPX_EFLAG_FORCE_KF | VP8_EFLAG_NO_REF_GF |                       VP8_EFLAG_NO_REF_ARF | VP8_EFLAG_NO_UPD_GF |                       VP8_EFLAG_NO_UPD_ARF;      layer_flags[1] = layer_flags[2] =          VP8_EFLAG_NO_REF_GF | VP8_EFLAG_NO_REF_ARF | VP8_EFLAG_NO_UPD_ARF |          VP8_EFLAG_NO_UPD_LAST;      break;    }    case 3: {      // 3-layers, 6-frame period.      int ids[6] = { 0, 2, 2, 1, 2, 2 };      cfg->ts_periodicity = 6;      *flag_periodicity = 6;      cfg->ts_number_layers = 3;      cfg->ts_rate_decimator[0] = 6;      cfg->ts_rate_decimator[1] = 3;      cfg->ts_rate_decimator[2] = 1;      memcpy(cfg->ts_layer_id, ids, sizeof(ids));      // 0=L, 1=GF, 2=ARF, Intra-layer prediction enabled.      layer_flags[0] = VPX_EFLAG_FORCE_KF | VP8_EFLAG_NO_REF_GF |                       VP8_EFLAG_NO_REF_ARF | VP8_EFLAG_NO_UPD_GF |                       VP8_EFLAG_NO_UPD_ARF;      layer_flags[3] =          VP8_EFLAG_NO_REF_ARF | VP8_EFLAG_NO_UPD_ARF | VP8_EFLAG_NO_UPD_LAST;      layer_flags[1] = layer_flags[2] = layer_flags[4] = layer_flags[5] =          VP8_EFLAG_NO_UPD_GF | VP8_EFLAG_NO_UPD_LAST;      break;    }    case 4: {      // 3-layers, 4-frame period.      int ids[4] = { 0, 2, 1, 2 };      cfg->ts_periodicity = 4;      *flag_periodicity = 4;      cfg->ts_number_layers = 3;      cfg->ts_rate_decimator[0] = 4;      cfg->ts_rate_decimator[1] = 2;      cfg->ts_rate_decimator[2] = 1;      memcpy(cfg->ts_layer_id, ids, sizeof(ids));      // 0=L, 1=GF, 2=ARF, Intra-layer prediction disabled.      layer_flags[0] = VPX_EFLAG_FORCE_KF | VP8_EFLAG_NO_REF_GF |                       VP8_EFLAG_NO_REF_ARF | VP8_EFLAG_NO_UPD_GF |                       VP8_EFLAG_NO_UPD_ARF;      layer_flags[2] = VP8_EFLAG_NO_REF_GF | VP8_EFLAG_NO_REF_ARF |                       VP8_EFLAG_NO_UPD_ARF | VP8_EFLAG_NO_UPD_LAST;      layer_flags[1] = layer_flags[3] =          VP8_EFLAG_NO_REF_ARF | VP8_EFLAG_NO_UPD_LAST | VP8_EFLAG_NO_UPD_GF |          VP8_EFLAG_NO_UPD_ARF;      break;    }    case 5: {      // 3-layers, 4-frame period.      int ids[4] = { 0, 2, 1, 2 };      cfg->ts_periodicity = 4;      *flag_periodicity = 4;      cfg->ts_number_layers = 3;      cfg->ts_rate_decimator[0] = 4;      cfg->ts_rate_decimator[1] = 2;      cfg->ts_rate_decimator[2] = 1;      memcpy(cfg->ts_layer_id, ids, sizeof(ids));      // 0=L, 1=GF, 2=ARF, Intra-layer prediction enabled in layer 1, disabled      // in layer 2.      layer_flags[0] = VPX_EFLAG_FORCE_KF | VP8_EFLAG_NO_REF_GF |                       VP8_EFLAG_NO_REF_ARF | VP8_EFLAG_NO_UPD_GF |                       VP8_EFLAG_NO_UPD_ARF;      layer_flags[2] =          VP8_EFLAG_NO_REF_ARF | VP8_EFLAG_NO_UPD_LAST | VP8_EFLAG_NO_UPD_ARF;      layer_flags[1] = layer_flags[3] =          VP8_EFLAG_NO_REF_ARF | VP8_EFLAG_NO_UPD_LAST | VP8_EFLAG_NO_UPD_GF |          VP8_EFLAG_NO_UPD_ARF;      break;    }    case 6: {      // 3-layers, 4-frame period.      int ids[4] = { 0, 2, 1, 2 };      cfg->ts_periodicity = 4;      *flag_periodicity = 4;      cfg->ts_number_layers = 3;      cfg->ts_rate_decimator[0] = 4;      cfg->ts_rate_decimator[1] = 2;      cfg->ts_rate_decimator[2] = 1;      memcpy(cfg->ts_layer_id, ids, sizeof(ids));      // 0=L, 1=GF, 2=ARF, Intra-layer prediction enabled.      layer_flags[0] = VPX_EFLAG_FORCE_KF | VP8_EFLAG_NO_REF_GF |                       VP8_EFLAG_NO_REF_ARF | VP8_EFLAG_NO_UPD_GF |                       VP8_EFLAG_NO_UPD_ARF;      layer_flags[2] =          VP8_EFLAG_NO_REF_ARF | VP8_EFLAG_NO_UPD_LAST | VP8_EFLAG_NO_UPD_ARF;      layer_flags[1] = layer_flags[3] =          VP8_EFLAG_NO_UPD_LAST | VP8_EFLAG_NO_UPD_GF;      break;    }    case 7: {      // NOTE: Probably of academic interest only.      // 5-layers, 16-frame period.      int ids[16] = { 0, 4, 3, 4, 2, 4, 3, 4, 1, 4, 3, 4, 2, 4, 3, 4 };      cfg->ts_periodicity = 16;      *flag_periodicity = 16;      cfg->ts_number_layers = 5;      cfg->ts_rate_decimator[0] = 16;      cfg->ts_rate_decimator[1] = 8;      cfg->ts_rate_decimator[2] = 4;      cfg->ts_rate_decimator[3] = 2;      cfg->ts_rate_decimator[4] = 1;      memcpy(cfg->ts_layer_id, ids, sizeof(ids));      layer_flags[0] = VPX_EFLAG_FORCE_KF;      layer_flags[1] = layer_flags[3] = layer_flags[5] = layer_flags[7] =          layer_flags[9] = layer_flags[11] = layer_flags[13] = layer_flags[15] =              VP8_EFLAG_NO_UPD_LAST | VP8_EFLAG_NO_UPD_GF |              VP8_EFLAG_NO_UPD_ARF;      layer_flags[2] = layer_flags[6] = layer_flags[10] = layer_flags[14] =          VP8_EFLAG_NO_UPD_ARF | VP8_EFLAG_NO_UPD_GF;      layer_flags[4] = layer_flags[12] =          VP8_EFLAG_NO_REF_LAST | VP8_EFLAG_NO_UPD_ARF;      layer_flags[8] = VP8_EFLAG_NO_REF_LAST | VP8_EFLAG_NO_REF_GF;      break;    }    case 8: {      // 2-layers, with sync point at first frame of layer 1.      int ids[2] = { 0, 1 };      cfg->ts_periodicity = 2;      *flag_periodicity = 8;      cfg->ts_number_layers = 2;      cfg->ts_rate_decimator[0] = 2;      cfg->ts_rate_decimator[1] = 1;      memcpy(cfg->ts_layer_id, ids, sizeof(ids));      // 0=L, 1=GF.      // ARF is used as predictor for all frames, and is only updated on      // key frame. Sync point every 8 frames.       // Layer 0: predict from L and ARF, update L and G.      layer_flags[0] =          VPX_EFLAG_FORCE_KF | VP8_EFLAG_NO_REF_GF | VP8_EFLAG_NO_UPD_ARF;      // Layer 1: sync point: predict from L and ARF, and update G.      layer_flags[1] =          VP8_EFLAG_NO_REF_GF | VP8_EFLAG_NO_UPD_LAST | VP8_EFLAG_NO_UPD_ARF;      // Layer 0, predict from L and ARF, update L.      layer_flags[2] =          VP8_EFLAG_NO_REF_GF | VP8_EFLAG_NO_UPD_GF | VP8_EFLAG_NO_UPD_ARF;      // Layer 1: predict from L, G and ARF, and update G.      layer_flags[3] = VP8_EFLAG_NO_UPD_ARF | VP8_EFLAG_NO_UPD_LAST |                       VP8_EFLAG_NO_UPD_ENTROPY;      // Layer 0.      layer_flags[4] = layer_flags[2];      // Layer 1.      layer_flags[5] = layer_flags[3];      // Layer 0.      layer_flags[6] = layer_flags[4];      // Layer 1.      layer_flags[7] = layer_flags[5];      break;    }    case 9: {      // 3-layers: Sync points for layer 1 and 2 every 8 frames.      int ids[4] = { 0, 2, 1, 2 };      cfg->ts_periodicity = 4;      *flag_periodicity = 8;      cfg->ts_number_layers = 3;      cfg->ts_rate_decimator[0] = 4;      cfg->ts_rate_decimator[1] = 2;      cfg->ts_rate_decimator[2] = 1;      memcpy(cfg->ts_layer_id, ids, sizeof(ids));      // 0=L, 1=GF, 2=ARF.      layer_flags[0] = VPX_EFLAG_FORCE_KF | VP8_EFLAG_NO_REF_GF |                       VP8_EFLAG_NO_REF_ARF | VP8_EFLAG_NO_UPD_GF |                       VP8_EFLAG_NO_UPD_ARF;      layer_flags[1] = VP8_EFLAG_NO_REF_GF | VP8_EFLAG_NO_REF_ARF |                       VP8_EFLAG_NO_UPD_LAST | VP8_EFLAG_NO_UPD_GF;      layer_flags[2] = VP8_EFLAG_NO_REF_GF | VP8_EFLAG_NO_REF_ARF |                       VP8_EFLAG_NO_UPD_LAST | VP8_EFLAG_NO_UPD_ARF;      layer_flags[3] = layer_flags[5] =          VP8_EFLAG_NO_UPD_LAST | VP8_EFLAG_NO_UPD_GF;      layer_flags[4] = VP8_EFLAG_NO_REF_GF | VP8_EFLAG_NO_REF_ARF |                       VP8_EFLAG_NO_UPD_GF | VP8_EFLAG_NO_UPD_ARF;      layer_flags[6] =          VP8_EFLAG_NO_REF_ARF | VP8_EFLAG_NO_UPD_LAST | VP8_EFLAG_NO_UPD_ARF;      layer_flags[7] = VP8_EFLAG_NO_UPD_LAST | VP8_EFLAG_NO_UPD_GF |                       VP8_EFLAG_NO_UPD_ARF | VP8_EFLAG_NO_UPD_ENTROPY;      break;    }    case 10: {      // 3-layers structure where ARF is used as predictor for all frames,      // and is only updated on key frame.      // Sync points for layer 1 and 2 every 8 frames.       int ids[4] = { 0, 2, 1, 2 };      cfg->ts_periodicity = 4;      *flag_periodicity = 8;      cfg->ts_number_layers = 3;      cfg->ts_rate_decimator[0] = 4;      cfg->ts_rate_decimator[1] = 2;      cfg->ts_rate_decimator[2] = 1;      memcpy(cfg->ts_layer_id, ids, sizeof(ids));      // 0=L, 1=GF, 2=ARF.      // Layer 0: predict from L and ARF; update L and G.      layer_flags[0] =          VPX_EFLAG_FORCE_KF | VP8_EFLAG_NO_UPD_ARF | VP8_EFLAG_NO_REF_GF;      // Layer 2: sync point: predict from L and ARF; update none.

Lines 1–500 of 986.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t