Merge branch 'lowdelay_GOP'

Conflicts:
	README.md
This commit is contained in:
Ari Koivula 2015-11-14 00:05:13 +02:00
commit cfe834bb53
8 changed files with 158 additions and 16 deletions

View file

@ -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.

View file

@ -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;

View file

@ -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) {

View file

@ -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

View file

@ -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) {

View file

@ -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;
} }

View file

@ -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;

View file

@ -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;
} }