mirror of
https://github.com/ultravideo/uvg266.git
synced 2024-11-27 19:24:06 +00:00
Merge branch 'lowdelay_GOP'
Conflicts: README.md
This commit is contained in:
commit
cfe834bb53
19
README.md
19
README.md
|
@ -64,7 +64,10 @@ http://ultravideo.cs.tut.fi/#encoder for more information.
|
||||||
--pu-depth-intra <int>-<int> : Range for sizes of intra prediction units to try.
|
--pu-depth-intra <int>-<int> : Range for sizes of intra prediction units to try.
|
||||||
0: 64x64, 1: 32x32, 2: 16x16, 3: 8x8, 4: 4x4
|
0: 64x64, 1: 32x32, 2: 16x16, 3: 8x8, 4: 4x4
|
||||||
--no-info : Don't add information about the encoder to settings.
|
--no-info : Don't add information about the encoder to settings.
|
||||||
--gop <int> : Length of Group of Pictures, must be 8 or 0 [0]
|
--gop <string> : Definition for GOP [0]
|
||||||
|
- 0 disabled
|
||||||
|
- 8 B-frame pyramid of length 8
|
||||||
|
- lp-gop syntax, defined below (example: g8d4r3t2)
|
||||||
--bipred : Enable bi-prediction search
|
--bipred : Enable bi-prediction search
|
||||||
--bitrate <integer> : Target bitrate. [0]
|
--bitrate <integer> : Target bitrate. [0]
|
||||||
0: disable rate-control
|
0: disable rate-control
|
||||||
|
@ -126,13 +129,25 @@ http://ultravideo.cs.tut.fi/#encoder for more information.
|
||||||
-w, --width : Width of input in pixels
|
-w, --width : Width of input in pixels
|
||||||
-h, --height : Height of input in pixels
|
-h, --height : Height of input in pixels
|
||||||
|
|
||||||
For example:
|
|
||||||
|
###For example:
|
||||||
|
|
||||||
kvazaar -i BQMall_832x480_60.yuv --input-res 832x480 -o out.hevc -n 600 -q 32
|
kvazaar -i BQMall_832x480_60.yuv --input-res 832x480 -o out.hevc -n 600 -q 32
|
||||||
|
|
||||||
The only accepted input format so far is 8-bit YUV 4:2:0.
|
The only accepted input format so far is 8-bit YUV 4:2:0.
|
||||||
|
|
||||||
|
|
||||||
|
### LP-GOP syntax
|
||||||
|
The LP-GOP syntax is "lp-g(num)d(num)r(num)t(num)", where
|
||||||
|
- g = GOP length.
|
||||||
|
- d = Number of GOP layers.
|
||||||
|
- r = Number of references, where one reference is always the previous picture,
|
||||||
|
unless temporal scaling is used. The others are key-frames.
|
||||||
|
- t = How many references to skip for temporal scaling, where 4 means only
|
||||||
|
every fourth picture needs to be decoded.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
##Presets
|
##Presets
|
||||||
The names of the presets are the same as with x264: ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow and placebo. The effects of the presets are listed in the following table, where the names have been abreviated to fit the layout in GitHub.
|
The names of the presets are the same as with x264: ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow and placebo. The effects of the presets are listed in the following table, where the names have been abreviated to fit the layout in GitHub.
|
||||||
|
|
||||||
|
|
107
src/config.c
107
src/config.c
|
@ -565,8 +565,111 @@ int kvz_config_parse(kvz_config *cfg, const char *name, const char *value)
|
||||||
else if OPT("info")
|
else if OPT("info")
|
||||||
cfg->add_encoder_info = atobool(value);
|
cfg->add_encoder_info = atobool(value);
|
||||||
else if OPT("gop") {
|
else if OPT("gop") {
|
||||||
// TODO: Defining the whole GOP structure via parameters
|
if (!strncmp(value, "lp-", 3)) { // Handle GOPs starting with "lp-".
|
||||||
if(atoi(value) == 8) {
|
struct {
|
||||||
|
unsigned g; // length
|
||||||
|
unsigned d; // depth
|
||||||
|
unsigned r; // references
|
||||||
|
unsigned t; // temporal
|
||||||
|
} gop = { 0 };
|
||||||
|
|
||||||
|
if (sscanf(value, "lp-g%ud%ur%ut%u", &gop.g, &gop.d, &gop.r, &gop.t) != 4) {
|
||||||
|
fprintf(stderr, "Error in GOP syntax. Example: lp-g8d4r2t2\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gop.g < 1 || gop.g > 32) {
|
||||||
|
fprintf(stderr, "gop.g must be between 1 and 32.\n");
|
||||||
|
}
|
||||||
|
if (gop.d < 1 || gop.d > 8) {
|
||||||
|
fprintf(stderr, "gop.d must be between 1 and 8.\n");
|
||||||
|
}
|
||||||
|
if (gop.r < 1 || gop.r > 15) {
|
||||||
|
fprintf(stderr, "gop.d must be between 1 and 15.\n");
|
||||||
|
}
|
||||||
|
if (gop.t < 1 || gop.t > 15) {
|
||||||
|
fprintf(stderr, "gop.t must be between 1 and 32.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize modulos for testing depth.
|
||||||
|
// The picture belong to the lowest depth in which (poc % modulo) == 0.
|
||||||
|
unsigned depth_modulos[8] = { 0 };
|
||||||
|
for (int d = 0; d < gop.d; ++d) {
|
||||||
|
depth_modulos[gop.d - 1 - d] = 1 << d;
|
||||||
|
}
|
||||||
|
depth_modulos[0] = gop.g;
|
||||||
|
|
||||||
|
cfg->gop_lowdelay = 1;
|
||||||
|
cfg->gop_len = gop.g;
|
||||||
|
for (int g = 1; g <= gop.g; ++g) {
|
||||||
|
kvz_gop_config *gop_pic = &cfg->gop[g - 1];
|
||||||
|
|
||||||
|
// Find gop depth for picture.
|
||||||
|
int gop_layer = 0;
|
||||||
|
while (gop_layer < gop.d && (g % depth_modulos[gop_layer])) {
|
||||||
|
++gop_layer;
|
||||||
|
}
|
||||||
|
|
||||||
|
gop_pic->poc_offset = g;
|
||||||
|
gop_pic->layer = gop_layer + 1;
|
||||||
|
gop_pic->qp_offset = gop_layer + 1;
|
||||||
|
gop_pic->ref_pos_count = 0;
|
||||||
|
gop_pic->ref_neg_count = gop.r;
|
||||||
|
gop_pic->is_ref = 0;
|
||||||
|
|
||||||
|
// Set first ref to point to previous frame, and the rest to previous
|
||||||
|
// key-frames.
|
||||||
|
// If gop.t > 1, have (poc % gop.t) == 0 point gop.t frames away,
|
||||||
|
// instead of the previous frame. Set the frames in between to
|
||||||
|
// point to the nearest frame with a lower gop-depth.
|
||||||
|
if (gop.t > 1) {
|
||||||
|
if (gop_pic->poc_offset % gop.t == 0) {
|
||||||
|
gop_pic->ref_neg[0] = gop.t;
|
||||||
|
} else {
|
||||||
|
int r = gop_pic->poc_offset - 1;
|
||||||
|
while (r > 0) {
|
||||||
|
if (cfg->gop[r].layer < gop_pic->layer) break;
|
||||||
|
--r;
|
||||||
|
}
|
||||||
|
// Var r is now 0 or index of the pic with layer < depth.
|
||||||
|
if (cfg->gop[r].layer < gop_pic->layer) {
|
||||||
|
gop_pic->ref_neg[0] = gop_pic->poc_offset - cfg->gop[r].poc_offset;
|
||||||
|
cfg->gop[r].is_ref = 1;
|
||||||
|
} else {
|
||||||
|
// No ref was found, just refer to the previous key-frame.
|
||||||
|
gop_pic->ref_neg[0] = gop_pic->poc_offset % gop.g;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
gop_pic->ref_neg[0] = 1;
|
||||||
|
if (gop_pic->poc_offset >= 2) {
|
||||||
|
cfg->gop[gop_pic->poc_offset - 2].is_ref = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int keyframe = gop_pic->poc_offset % gop.g;
|
||||||
|
for (int i = 1; i < gop_pic->ref_neg_count; ++i) {
|
||||||
|
while (keyframe == gop_pic->ref_neg[i - 1]) {
|
||||||
|
keyframe += gop.g;
|
||||||
|
}
|
||||||
|
gop_pic->ref_neg[i] = keyframe;
|
||||||
|
}
|
||||||
|
|
||||||
|
gop_pic->qp_factor = 0.4624; // from HM
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int g = 0; g < gop.g; ++g) {
|
||||||
|
kvz_gop_config *gop_pic = &cfg->gop[g];
|
||||||
|
if (!gop_pic->is_ref) {
|
||||||
|
gop_pic->qp_factor = 0.68 * 1.31; // derived from HM
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key-frame is always a reference.
|
||||||
|
cfg->gop[gop.g - 1].is_ref = 1;
|
||||||
|
cfg->gop[gop.g - 1].qp_factor = 0.578; // from HM
|
||||||
|
} else if (atoi(value) == 8) {
|
||||||
|
cfg->gop_lowdelay = 0;
|
||||||
// GOP
|
// GOP
|
||||||
cfg->gop_len = 8;
|
cfg->gop_len = 8;
|
||||||
cfg->gop[0].poc_offset = 8; cfg->gop[0].qp_offset = 1; cfg->gop[0].layer = 1; cfg->gop[0].qp_factor = 0.442; cfg->gop[0].is_ref = 1;
|
cfg->gop[0].poc_offset = 8; cfg->gop[0].qp_offset = 1; cfg->gop[0].layer = 1; cfg->gop[0].qp_factor = 0.442; cfg->gop[0].is_ref = 1;
|
||||||
|
|
|
@ -547,6 +547,7 @@ static int encoder_control_init_gop_layer_weights(encoder_control_t * const enco
|
||||||
case 0:
|
case 0:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
case 4:
|
case 4:
|
||||||
// These weights were copied from http://doi.org/10.1109/TIP.2014.2336550
|
// These weights were copied from http://doi.org/10.1109/TIP.2014.2336550
|
||||||
if (encoder->target_avg_bpp <= 0.05) {
|
if (encoder->target_avg_bpp <= 0.05) {
|
||||||
|
|
|
@ -329,8 +329,13 @@ static void encoder_state_write_bitstream_seq_parameter_set(bitstream_t* stream,
|
||||||
WRITE_U(stream, 0, 1, "sps_sub_layer_ordering_info_present_flag");
|
WRITE_U(stream, 0, 1, "sps_sub_layer_ordering_info_present_flag");
|
||||||
|
|
||||||
//for each layer
|
//for each layer
|
||||||
|
if (encoder->cfg->gop_lowdelay) {
|
||||||
|
WRITE_UE(stream, encoder->cfg->ref_frames, "sps_max_dec_pic_buffering");
|
||||||
|
WRITE_UE(stream, 0, "sps_num_reorder_pics");
|
||||||
|
} else {
|
||||||
WRITE_UE(stream, encoder->cfg->ref_frames + encoder->cfg->gop_len, "sps_max_dec_pic_buffering");
|
WRITE_UE(stream, encoder->cfg->ref_frames + encoder->cfg->gop_len, "sps_max_dec_pic_buffering");
|
||||||
WRITE_UE(stream, encoder->cfg->gop_len, "sps_num_reorder_pics");
|
WRITE_UE(stream, encoder->cfg->gop_len, "sps_num_reorder_pics");
|
||||||
|
}
|
||||||
WRITE_UE(stream, 0, "sps_max_latency_increase");
|
WRITE_UE(stream, 0, "sps_max_latency_increase");
|
||||||
//end for
|
//end for
|
||||||
|
|
||||||
|
|
|
@ -788,6 +788,12 @@ static void encoder_state_new_frame(encoder_state_t * const state) {
|
||||||
state->global->pictype = KVZ_NAL_IDR_W_RADL;
|
state->global->pictype = KVZ_NAL_IDR_W_RADL;
|
||||||
} else {
|
} else {
|
||||||
state->global->slicetype = encoder->cfg->intra_period==1 ? KVZ_SLICE_I : (state->encoder_control->cfg->gop_len?KVZ_SLICE_B:KVZ_SLICE_P);
|
state->global->slicetype = encoder->cfg->intra_period==1 ? KVZ_SLICE_I : (state->encoder_control->cfg->gop_len?KVZ_SLICE_B:KVZ_SLICE_P);
|
||||||
|
|
||||||
|
// Use P-slice for lowdelay.
|
||||||
|
if (state->global->slicetype == KVZ_SLICE_B && encoder->cfg->gop_lowdelay) {
|
||||||
|
state->global->slicetype = KVZ_SLICE_P;
|
||||||
|
}
|
||||||
|
|
||||||
state->global->pictype = KVZ_NAL_TRAIL_R;
|
state->global->pictype = KVZ_NAL_TRAIL_R;
|
||||||
if (state->encoder_control->cfg->gop_len) {
|
if (state->encoder_control->cfg->gop_len) {
|
||||||
if (encoder->cfg->intra_period > 1 && (state->global->poc % encoder->cfg->intra_period) == 0) {
|
if (encoder->cfg->intra_period > 1 && (state->global->poc % encoder->cfg->intra_period) == 0) {
|
||||||
|
|
|
@ -64,7 +64,7 @@ int kvz_encoder_feed_frame(input_frame_buffer_t *buf,
|
||||||
assert(frame->source == NULL);
|
assert(frame->source == NULL);
|
||||||
assert(frame->rec != NULL);
|
assert(frame->rec != NULL);
|
||||||
|
|
||||||
if (cfg->gop_len == 0) {
|
if (cfg->gop_len == 0 || cfg->gop_lowdelay) {
|
||||||
// GOP disabled, just return the input frame.
|
// GOP disabled, just return the input frame.
|
||||||
|
|
||||||
if (img_in == NULL) return 0;
|
if (img_in == NULL) return 0;
|
||||||
|
@ -73,7 +73,7 @@ int kvz_encoder_feed_frame(input_frame_buffer_t *buf,
|
||||||
frame->source = kvz_image_copy_ref(img_in);
|
frame->source = kvz_image_copy_ref(img_in);
|
||||||
frame->rec->pts = img_in->pts;
|
frame->rec->pts = img_in->pts;
|
||||||
frame->rec->dts = img_in->dts;
|
frame->rec->dts = img_in->dts;
|
||||||
state->global->gop_offset = 0;
|
state->global->gop_offset = cfg->gop_lowdelay ? (state->global->frame-1) % cfg->gop_len : 0;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -172,6 +172,7 @@ typedef struct kvz_config
|
||||||
|
|
||||||
int32_t add_encoder_info;
|
int32_t add_encoder_info;
|
||||||
int8_t gop_len; /*!< \brief length of GOP for the video sequence */
|
int8_t gop_len; /*!< \brief length of GOP for the video sequence */
|
||||||
|
int8_t gop_lowdelay; /*!< \brief specifies that the GOP does not use future pictures */
|
||||||
kvz_gop_config gop[KVZ_MAX_GOP_LENGTH]; /*!< \brief Array of GOP settings */
|
kvz_gop_config gop[KVZ_MAX_GOP_LENGTH]; /*!< \brief Array of GOP settings */
|
||||||
|
|
||||||
int32_t target_bitrate;
|
int32_t target_bitrate;
|
||||||
|
|
|
@ -157,19 +157,30 @@ int8_t kvz_lambda_to_QP(const double lambda)
|
||||||
double kvz_select_picture_lambda_from_qp(encoder_state_t const * const state)
|
double kvz_select_picture_lambda_from_qp(encoder_state_t const * const state)
|
||||||
{
|
{
|
||||||
const int gop_len = state->encoder_control->cfg->gop_len;
|
const int gop_len = state->encoder_control->cfg->gop_len;
|
||||||
const double qp_temp = state->global->QP - 12;
|
const int intra_period = state->encoder_control->cfg->intra_period;
|
||||||
|
const int keyframe_period = gop_len > 0 ? gop_len : intra_period;
|
||||||
|
|
||||||
|
double lambda = pow(2.0, (state->global->QP - 12) / 3.0);
|
||||||
|
|
||||||
double qp_factor;
|
|
||||||
if (state->global->slicetype == KVZ_SLICE_I) {
|
if (state->global->slicetype == KVZ_SLICE_I) {
|
||||||
const double lambda_scale = 1.0 - CLIP(0.0, 0.5, 0.05 * gop_len);
|
lambda *= 0.57;
|
||||||
qp_factor = 0.57 * lambda_scale;
|
|
||||||
} else if (gop_len > 0) {
|
// Reduce lambda for I-frames according to the number of references.
|
||||||
qp_factor = 0.95 * state->global->QP_factor;
|
if (keyframe_period == 0) {
|
||||||
|
lambda *= 0.5;
|
||||||
} else {
|
} else {
|
||||||
// default QP factor from HM config
|
lambda *= 1.0 - CLIP(0.0, 0.5, 0.05 * (keyframe_period - 1));
|
||||||
qp_factor = 0.95 * 0.4624;
|
}
|
||||||
|
} else if (gop_len > 0) {
|
||||||
|
lambda *= state->global->QP_factor;
|
||||||
|
} else {
|
||||||
|
lambda *= 0.4624;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increase lambda if not key-frame.
|
||||||
|
if (keyframe_period > 0 && state->global->poc % keyframe_period != 0) {
|
||||||
|
lambda *= CLIP(2.0, 4.0, (state->global->QP - 12) / 6.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const double lambda = qp_factor * pow(2.0, qp_temp / 3.0);
|
|
||||||
return lambda;
|
return lambda;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue