Make kvazaar_encode consume one frame on each call.

- Replaces read_one_frame by encoder_feed_frame.
- Adds field "prepared" to encoderstate_t to indicate that
  encoder_next_frame has been called.
- Input frames are read in the main function and passed to
  encoder_encode.
This commit is contained in:
Arttu Ylä-Outinen 2015-06-18 09:10:47 +03:00
parent 012c0580df
commit 7e20e62cc7
6 changed files with 149 additions and 130 deletions

View file

@ -172,46 +172,52 @@ int main(int argc, char *argv[])
encoding_start_cpu_time = clock(); encoding_start_cpu_time = clock();
uint64_t bitstream_length = 0; uint64_t bitstream_length = 0;
uint32_t frames_started = 0; uint32_t frames_read = 0;
uint32_t frames_done = 0; uint32_t frames_done = 0;
double psnr_sum[3] = { 0.0, 0.0, 0.0 }; double psnr_sum[3] = { 0.0, 0.0, 0.0 };
// Start coding cycle while data on input and not on the last frame for (;;) {
while (cfg->frames == 0 || frames_started < cfg->frames) {
encoder_state_t *state = &enc->states[enc->cur_state_num]; encoder_state_t *state = &enc->states[enc->cur_state_num];
frames_started += 1; image_t *img_in = NULL;
if (!feof(input) && (cfg->frames == 0 || frames_read < cfg->frames)) {
// Try to read an input frame.
img_in = image_alloc(encoder->in.width, encoder->in.height);
if (!img_in) {
fprintf(stderr, "Failed to allocate image.\n");
goto exit_failure;
}
if (yuv_io_read(input, cfg->width, cfg->height, img_in)) {
image_t *img_in = image_alloc(state->tile->frame->width, state->tile->frame->height); frames_read += 1;
if (!img_in) { } else {
fprintf(stderr, "Failed to allocate image.\n"); // EOF or some error
goto exit_failure; image_free(img_in);
} img_in = NULL;
if (!feof(input)) {
// Clear the encoder state. fprintf(stderr, "Failed to read a frame %d\n", frames_read);
encoder_next_frame(state, img_in); goto exit_failure;
}
// Read one frame from the input }
if (!read_one_frame(input, &enc->states[enc->cur_state_num], img_in)) {
if (!feof(input))
fprintf(stderr, "Failed to read a frame %d\n", enc->states[enc->cur_state_num].global->frame);
image_free(img_in);
break;
} }
image_t *img_out = NULL; image_t *img_out = NULL;
if (1 != api->encoder_encode(enc, img_in, &img_out, &output_stream)) { if (!api->encoder_encode(enc, img_in, &img_out, &output_stream)) {
fprintf(stderr, "Failed to encode image.\n"); fprintf(stderr, "Failed to encode image.\n");
image_free(img_in); image_free(img_in);
goto exit_failure; goto exit_failure;
} }
if (img_out == NULL && img_in == NULL) {
// We are done since there is no more input and output left.
break;
}
if (img_out != NULL) { if (img_out != NULL) {
state = &enc->states[enc->cur_state_num]; state = &enc->states[enc->cur_state_num];
double frame_psnr[3] = { 0.0, 0.0, 0.0 }; double frame_psnr[3] = { 0.0, 0.0, 0.0 };
encoder_compute_stats(state, recout, frame_psnr, &bitstream_length); encoder_compute_stats(state, recout, frame_psnr, &bitstream_length);
frames_done += 1; frames_done += 1;
psnr_sum[0] += frame_psnr[0]; psnr_sum[0] += frame_psnr[0];
psnr_sum[1] += frame_psnr[1]; psnr_sum[1] += frame_psnr[1];
@ -223,28 +229,7 @@ int main(int argc, char *argv[])
image_free(img_in); image_free(img_in);
image_free(img_out); image_free(img_out);
} }
//Compute stats for the remaining encoders
{
image_t *img_out = NULL;
while (1 == api->encoder_encode(enc, NULL, &img_out, &output_stream)) {
if (img_out != NULL) {
double frame_psnr[3] = { 0.0, 0.0, 0.0 };
encoder_state_t *state = &enc->states[enc->cur_state_num];
encoder_compute_stats(state, recout, frame_psnr, &bitstream_length);
print_frame_info(state, frame_psnr);
frames_done += 1;
psnr_sum[0] += frame_psnr[0];
psnr_sum[1] += frame_psnr[1];
psnr_sum[2] += frame_psnr[2];
image_free(img_out);
img_out = NULL;
}
}
}
GET_TIME(&encoding_end_real_time); GET_TIME(&encoding_end_real_time);
encoding_end_cpu_time = clock(); encoding_end_cpu_time = clock();

View file

@ -285,6 +285,7 @@ int encoder_state_init(encoder_state_t * const child_state, encoder_state_t * co
child_state->children[0].encoder_control = NULL; child_state->children[0].encoder_control = NULL;
child_state->tqj_bitstream_written = NULL; child_state->tqj_bitstream_written = NULL;
child_state->tqj_recon_done = NULL; child_state->tqj_recon_done = NULL;
child_state->prepared = 0;
child_state->frame_done = 1; child_state->frame_done = 1;
if (!parent_state) { if (!parent_state) {

View file

@ -860,84 +860,97 @@ void encode_one_frame(encoder_state_t * const state)
//threadqueue_flush(main_state->encoder_control->threadqueue); //threadqueue_flush(main_state->encoder_control->threadqueue);
} }
int read_one_frame(FILE* file, const encoder_state_t * const state, image_t *img_out) /**
* \brief Pass an input frame to the encoder state.
*
* Sets the source image of the encoder state if there is a suitable image
* available.
*
* The caller must not modify img_in after calling this function.
*
* \param state a main encoder state
* \param img_in input frame or NULL
* \return 1 if the source image was set, 0 if not
*/
int encoder_feed_frame(encoder_state_t* const state, image_t* const img_in)
{ {
unsigned width = state->encoder_control->in.real_width; const encoder_control_t* const encoder = state->encoder_control;
unsigned height = state->encoder_control->in.real_height; const config_t* const cfg = encoder->cfg;
unsigned array_width = state->tile->frame->width;
unsigned array_height = state->tile->frame->height;
// storing GOP pictures // TODO: Get rid of static variables.
static int8_t gop_init = 0; static image_t *gop_buffer[2 * MAX_GOP] = { NULL };
static int8_t gop_pictures_available = 0; static int gop_buf_write_idx = 0;
static videoframe_t gop_pictures[MAX_GOP]; static int gop_buf_read_idx = 0;
static int8_t gop_skip_frames = 0; static int gop_pictures_available = 0;
static int8_t gop_skipped = 0; static int gop_offset = 0;
// Initialize GOP structure when gop is enabled and not initialized const int gop_buf_size = 2 * cfg->gop_len;
if (state->encoder_control->cfg->gop_len && !gop_init) {
int i; assert(state->global->frame >= 0);
for (i = 0; i < state->encoder_control->cfg->gop_len; i++) { assert(state->tile->frame->source == NULL);
gop_pictures[i].source = image_alloc(array_width, array_height);
assert(gop_pictures[i].source); if (cfg->gop_len == 0 || state->global->frame == 0) {
} if (img_in == NULL) return 0;
state->tile->frame->source = image_copy_ref(img_in);
state->global->gop_offset = 0; state->global->gop_offset = 0;
gop_init = 1; return 1;
} }
// GOP enabled and not the first frame
// If GOP is present but no pictures found if (img_in != NULL) {
if (state->global->frame && // Save the input image in the buffer.
state->encoder_control->cfg->gop_len && assert(gop_pictures_available < gop_buf_size);
!gop_pictures_available) { assert(gop_buffer[gop_buf_write_idx] == NULL);
for (int i = 0; i < state->encoder_control->cfg->gop_len; i++, gop_pictures_available++) { gop_buffer[gop_buf_write_idx] = image_copy_ref(img_in);
if (state->encoder_control->cfg->frames
&& state->global->frame + gop_pictures_available >= state->encoder_control->cfg->frames) { ++gop_pictures_available;
if (gop_pictures_available) { if (++gop_buf_write_idx >= gop_buf_size) {
gop_skip_frames = state->encoder_control->cfg->gop_len - gop_pictures_available; gop_buf_write_idx = 0;
break;
}
else return 0;
}
if (!yuv_io_read(file, width, height, gop_pictures[i].source)) {
if (gop_pictures_available) {
gop_skip_frames = state->encoder_control->cfg->gop_len - gop_pictures_available;
break;
} else {
return 0;
}
}
} }
} }
// If GOP is present, fetch the data from our GOP picture buffer if (gop_pictures_available < cfg->gop_len) {
if (state->global->frame && state->encoder_control->cfg->gop_len) { if (img_in != NULL || gop_pictures_available == 0) {
int cur_gop_idx = state->encoder_control->cfg->gop_len - (gop_pictures_available + gop_skip_frames) + gop_skipped; // Either start of the sequence with no full GOP available yet, or the
int cur_gop = state->encoder_control->cfg->gop[cur_gop_idx].poc_offset - 1; // end of the sequence with all pics encoded.
// Special case when end of the sequence and not all pictures are available return 0;
if (gop_skip_frames && cur_gop >= state->encoder_control->cfg->gop_len - gop_skip_frames) { }
for (; cur_gop >= state->encoder_control->cfg->gop_len - gop_skip_frames; cur_gop_idx++) { // End of the sequence and a full GOP is not available.
cur_gop = state->encoder_control->cfg->gop[cur_gop_idx].poc_offset - 1; // Skip pictures until an available one is found.
gop_skipped++; for (; gop_offset < cfg->gop_len &&
} cfg->gop[gop_offset].poc_offset - 1 >= gop_pictures_available;
cur_gop_idx--; ++gop_offset);
gop_skipped--;
if (gop_offset >= cfg->gop_len) {
// All available pictures used.
gop_offset = 0;
gop_pictures_available = 0;
return 0;
} }
state->global->gop_offset = cur_gop_idx;
memcpy(img_out->y, gop_pictures[cur_gop].source->y, width * height);
memcpy(img_out->u, gop_pictures[cur_gop].source->u, (width >> 1) * (height >> 1));
memcpy(img_out->v, gop_pictures[cur_gop].source->v, (width >> 1) * (height >> 1));
gop_pictures_available--;
} else {
return yuv_io_read(file, width, height, img_out);
} }
// Move image from buffer to state.
int buffer_index = gop_buf_read_idx + cfg->gop[gop_offset].poc_offset - 1;
assert(gop_buffer[buffer_index] != NULL);
assert(state->tile->frame->source == NULL);
state->tile->frame->source = gop_buffer[buffer_index];
gop_buffer[buffer_index] = NULL;
state->global->gop_offset = gop_offset;
if (++gop_offset >= cfg->gop_len) {
gop_offset = 0;
gop_pictures_available = MAX(0, gop_pictures_available - cfg->gop_len);
gop_buf_read_idx = (gop_buf_read_idx + cfg->gop_len) % gop_buf_size;
}
return 1; return 1;
} }
void encoder_compute_stats(encoder_state_t *state, FILE * const recout, double frame_psnr[3], uint64_t *bitstream_length) void encoder_compute_stats(encoder_state_t *state, FILE * const recout, double frame_psnr[3], uint64_t *bitstream_length)
{ {
const encoder_control_t * const encoder = state->encoder_control; const encoder_control_t * const encoder = state->encoder_control;
//Blocking call //Blocking call
threadqueue_waitfor(encoder->threadqueue, state->tqj_bitstream_written); threadqueue_waitfor(encoder->threadqueue, state->tqj_bitstream_written);
@ -953,24 +966,21 @@ void encoder_compute_stats(encoder_state_t *state, FILE * const recout, double f
*bitstream_length += state->stats_bitstream_length; *bitstream_length += state->stats_bitstream_length;
} }
void encoder_next_frame(encoder_state_t *state, image_t *img_in) void encoder_next_frame(encoder_state_t *state)
{ {
const encoder_control_t * const encoder = state->encoder_control; const encoder_control_t * const encoder = state->encoder_control;
//Blocking call //Blocking call
threadqueue_waitfor(encoder->threadqueue, state->tqj_bitstream_written); threadqueue_waitfor(encoder->threadqueue, state->tqj_bitstream_written);
if (state->tile->frame->source) {
image_free(state->tile->frame->source);
}
state->tile->frame->source = image_copy_ref(img_in);
if (state->global->frame == -1) { if (state->global->frame == -1) {
//We're at the first frame, so don't care about all this stuff; //We're at the first frame, so don't care about all this stuff;
state->global->frame = 0; state->global->frame = 0;
state->global->poc = 0; state->global->poc = 0;
assert(!state->tile->frame->source);
assert(!state->tile->frame->rec); assert(!state->tile->frame->rec);
state->tile->frame->rec = image_alloc(state->tile->frame->width, state->tile->frame->height); state->tile->frame->rec = image_alloc(state->tile->frame->width, state->tile->frame->height);
assert(state->tile->frame->rec); assert(state->tile->frame->rec);
state->prepared = 1;
return; return;
} }
@ -981,9 +991,10 @@ void encoder_next_frame(encoder_state_t *state, image_t *img_in)
state->global->frame = prev_state->global->frame + 1; state->global->frame = prev_state->global->frame + 1;
state->global->poc = prev_state->global->poc + 1; state->global->poc = prev_state->global->poc + 1;
image_free(state->tile->frame->rec);
cu_array_free(state->tile->frame->cu_array); cu_array_free(state->tile->frame->cu_array);
image_free(state->tile->frame->source);
state->tile->frame->source = NULL;
image_free(state->tile->frame->rec);
state->tile->frame->rec = image_alloc(state->tile->frame->width, state->tile->frame->height); state->tile->frame->rec = image_alloc(state->tile->frame->width, state->tile->frame->height);
assert(state->tile->frame->rec); assert(state->tile->frame->rec);
{ {
@ -1003,6 +1014,7 @@ void encoder_next_frame(encoder_state_t *state, image_t *img_in)
prev_state->global->poc); prev_state->global->poc);
} }
state->prepared = 1;
return; return;
} }
@ -1021,12 +1033,17 @@ void encoder_next_frame(encoder_state_t *state, image_t *img_in)
state->global->frame++; state->global->frame++;
state->global->poc++; state->global->poc++;
//Remove current reconstructed picture, and alloc a new one // Remove current source picture.
image_free(state->tile->frame->source);
state->tile->frame->source = NULL;
// Remove current reconstructed picture, and alloc a new one
image_free(state->tile->frame->rec); image_free(state->tile->frame->rec);
state->tile->frame->rec = image_alloc(state->tile->frame->width, state->tile->frame->height); state->tile->frame->rec = image_alloc(state->tile->frame->width, state->tile->frame->height);
assert(state->tile->frame->rec); assert(state->tile->frame->rec);
videoframe_set_poc(state->tile->frame, state->global->poc); videoframe_set_poc(state->tile->frame, state->global->poc);
state->prepared = 1;
} }

View file

@ -184,6 +184,12 @@ typedef struct encoder_state_t {
bitstream_t stream; bitstream_t stream;
cabac_data_t cabac; cabac_data_t cabac;
/**
* \brief Indicates that this encoder state is ready for encoding the
* next frame i.e. encoder_next_frame has been called.
*/
int prepared;
/** /**
* \brief Indicates that the previous frame has been encoded and the * \brief Indicates that the previous frame has been encoded and the
* encoded data written and the encoding the next frame has not been * encoded data written and the encoding the next frame has not been
@ -201,10 +207,10 @@ typedef struct encoder_state_t {
void encode_one_frame(encoder_state_t *state); void encode_one_frame(encoder_state_t *state);
int read_one_frame(FILE* file, const encoder_state_t *state, image_t *img_out); int encoder_feed_frame(encoder_state_t* const state, image_t* const img_in);
void encoder_compute_stats(encoder_state_t *state, FILE * const recout, double psnr[3], uint64_t *bitstream_length); void encoder_compute_stats(encoder_state_t *state, FILE * const recout, double psnr[3], uint64_t *bitstream_length);
void encoder_next_frame(encoder_state_t *state, image_t *img_in); void encoder_next_frame(encoder_state_t *state);
void encode_coding_tree(encoder_state_t *state, uint16_t x_ctb, void encode_coding_tree(encoder_state_t *state, uint16_t x_ctb,

View file

@ -111,29 +111,38 @@ kvazaar_open_failure:
static int kvazaar_encode(kvz_encoder *enc, kvz_picture *img_in, kvz_picture **img_out, kvz_payload *payload) static int kvazaar_encode(kvz_encoder *enc, kvz_picture *img_in, kvz_picture **img_out, kvz_payload *payload)
{ {
// If img_in is NULL, just return the next unfinished frame. *img_out = NULL;
encoder_state_t *state = &enc->states[enc->cur_state_num];
if (!state->prepared) {
encoder_next_frame(state);
}
if (img_in != NULL) { if (img_in != NULL) {
encoder_state_t *state = &enc->states[enc->cur_state_num]; // FIXME: The frame number printed here is wrong when GOP is enabled.
enc->frames_started += 1;
CHECKPOINT_MARK("read source frame: %d", state->global->frame + enc->control->cfg->seek); CHECKPOINT_MARK("read source frame: %d", state->global->frame + enc->control->cfg->seek);
}
// The actual coding happens here, after this function we have a coded frame if (encoder_feed_frame(state, img_in)) {
assert(state->global->frame == enc->frames_started);
// Start encoding.
encode_one_frame(state); encode_one_frame(state);
enc->frames_started += 1;
} }
// If we have finished encoding as many frames as we have started, we are done. // If we have finished encoding as many frames as we have started, we are done.
if (enc->frames_done == enc->frames_started) { if (enc->frames_done == enc->frames_started) {
return 0; return 1;
} }
// Move to the next encoder state.
enc->cur_state_num = (enc->cur_state_num + 1) % (enc->num_encoder_states); enc->cur_state_num = (enc->cur_state_num + 1) % (enc->num_encoder_states);
encoder_state_t *state = &enc->states[enc->cur_state_num]; state = &enc->states[enc->cur_state_num];
if (enc->frames_started >= enc->num_encoder_states && !state->frame_done) { if (!state->frame_done) {
threadqueue_waitfor(enc->control->threadqueue, state->tqj_bitstream_written); threadqueue_waitfor(enc->control->threadqueue, state->tqj_bitstream_written);
bitstream_append(payload, &state->stream); bitstream_append(payload, &state->stream);
// Flush the output in case someone is reading the file on the other end. // Flush the output in case someone is reading the file on the other end.
@ -143,8 +152,9 @@ static int kvazaar_encode(kvz_encoder *enc, kvz_picture *img_in, kvz_picture **i
*img_out = image_copy_ref(state->tile->frame->rec); *img_out = image_copy_ref(state->tile->frame->rec);
enc->frames_done += 1;
state->frame_done = 1; state->frame_done = 1;
state->prepared = 0;
enc->frames_done += 1;
} }
return 1; return 1;

View file

@ -97,11 +97,11 @@ typedef struct kvz_api {
void (*encoder_close)(kvz_encoder *); void (*encoder_close)(kvz_encoder *);
// \brief Encode one picture. // \brief Encode one picture.
// \param encoder // \param encoder Encoder
// \param pic_in Picture containing the encoded data. // \param pic_in Input frame
// \param pic_out Picture containing the reconstructed data. // \param pic_out Returns the reconstructed picture.
// \param nals_out The first NAL containing bitstream generated, or NULL. // \param payload Bitstream for writing the encoded data
// \return 1 on success, negative on error. // \return 1 on success, 0 on error.
int (*encoder_encode)(kvz_encoder *encoder, kvz_picture *pic_in, kvz_picture **pic_out, kvz_payload *payload); int (*encoder_encode)(kvz_encoder *encoder, kvz_picture *pic_in, kvz_picture **pic_out, kvz_payload *payload);
} kvz_api; } kvz_api;