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();
uint64_t bitstream_length = 0;
uint32_t frames_started = 0;
uint32_t frames_read = 0;
uint32_t frames_done = 0;
double psnr_sum[3] = { 0.0, 0.0, 0.0 };
// Start coding cycle while data on input and not on the last frame
while (cfg->frames == 0 || frames_started < cfg->frames) {
for (;;) {
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;
}
image_t *img_in = image_alloc(state->tile->frame->width, state->tile->frame->height);
if (!img_in) {
fprintf(stderr, "Failed to allocate image.\n");
goto exit_failure;
}
// Clear the encoder state.
encoder_next_frame(state, img_in);
// 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;
if (yuv_io_read(input, cfg->width, cfg->height, img_in)) {
frames_read += 1;
} else {
// EOF or some error
image_free(img_in);
img_in = NULL;
if (!feof(input)) {
fprintf(stderr, "Failed to read a frame %d\n", frames_read);
goto exit_failure;
}
}
}
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");
image_free(img_in);
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) {
state = &enc->states[enc->cur_state_num];
double frame_psnr[3] = { 0.0, 0.0, 0.0 };
encoder_compute_stats(state, recout, frame_psnr, &bitstream_length);
frames_done += 1;
psnr_sum[0] += frame_psnr[0];
psnr_sum[1] += frame_psnr[1];
@ -223,28 +229,7 @@ int main(int argc, char *argv[])
image_free(img_in);
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);
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->tqj_bitstream_written = NULL;
child_state->tqj_recon_done = NULL;
child_state->prepared = 0;
child_state->frame_done = 1;
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);
}
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;
unsigned height = state->encoder_control->in.real_height;
unsigned array_width = state->tile->frame->width;
unsigned array_height = state->tile->frame->height;
const encoder_control_t* const encoder = state->encoder_control;
const config_t* const cfg = encoder->cfg;
// storing GOP pictures
static int8_t gop_init = 0;
static int8_t gop_pictures_available = 0;
static videoframe_t gop_pictures[MAX_GOP];
static int8_t gop_skip_frames = 0;
static int8_t gop_skipped = 0;
// TODO: Get rid of static variables.
static image_t *gop_buffer[2 * MAX_GOP] = { NULL };
static int gop_buf_write_idx = 0;
static int gop_buf_read_idx = 0;
static int gop_pictures_available = 0;
static int gop_offset = 0;
// Initialize GOP structure when gop is enabled and not initialized
if (state->encoder_control->cfg->gop_len && !gop_init) {
int i;
for (i = 0; i < state->encoder_control->cfg->gop_len; i++) {
gop_pictures[i].source = image_alloc(array_width, array_height);
assert(gop_pictures[i].source);
}
const int gop_buf_size = 2 * cfg->gop_len;
assert(state->global->frame >= 0);
assert(state->tile->frame->source == NULL);
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;
gop_init = 1;
return 1;
}
// GOP enabled and not the first frame
// If GOP is present but no pictures found
if (state->global->frame &&
state->encoder_control->cfg->gop_len &&
!gop_pictures_available) {
for (int i = 0; i < state->encoder_control->cfg->gop_len; i++, gop_pictures_available++) {
if (state->encoder_control->cfg->frames
&& state->global->frame + gop_pictures_available >= state->encoder_control->cfg->frames) {
if (gop_pictures_available) {
gop_skip_frames = state->encoder_control->cfg->gop_len - gop_pictures_available;
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 (img_in != NULL) {
// Save the input image in the buffer.
assert(gop_pictures_available < gop_buf_size);
assert(gop_buffer[gop_buf_write_idx] == NULL);
gop_buffer[gop_buf_write_idx] = image_copy_ref(img_in);
++gop_pictures_available;
if (++gop_buf_write_idx >= gop_buf_size) {
gop_buf_write_idx = 0;
}
}
// If GOP is present, fetch the data from our GOP picture buffer
if (state->global->frame && state->encoder_control->cfg->gop_len) {
int cur_gop_idx = state->encoder_control->cfg->gop_len - (gop_pictures_available + gop_skip_frames) + gop_skipped;
int cur_gop = state->encoder_control->cfg->gop[cur_gop_idx].poc_offset - 1;
// Special case when end of the sequence and not all pictures are available
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++) {
cur_gop = state->encoder_control->cfg->gop[cur_gop_idx].poc_offset - 1;
gop_skipped++;
}
cur_gop_idx--;
gop_skipped--;
if (gop_pictures_available < cfg->gop_len) {
if (img_in != NULL || gop_pictures_available == 0) {
// Either start of the sequence with no full GOP available yet, or the
// end of the sequence with all pics encoded.
return 0;
}
// End of the sequence and a full GOP is not available.
// Skip pictures until an available one is found.
for (; gop_offset < cfg->gop_len &&
cfg->gop[gop_offset].poc_offset - 1 >= gop_pictures_available;
++gop_offset);
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;
}
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;
//Blocking call
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;
}
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;
//Blocking call
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) {
//We're at the first frame, so don't care about all this stuff;
state->global->frame = 0;
state->global->poc = 0;
assert(!state->tile->frame->source);
assert(!state->tile->frame->rec);
state->tile->frame->rec = image_alloc(state->tile->frame->width, state->tile->frame->height);
assert(state->tile->frame->rec);
state->prepared = 1;
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->poc = prev_state->global->poc + 1;
image_free(state->tile->frame->rec);
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);
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);
}
state->prepared = 1;
return;
}
@ -1021,12 +1033,17 @@ void encoder_next_frame(encoder_state_t *state, image_t *img_in)
state->global->frame++;
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);
state->tile->frame->rec = image_alloc(state->tile->frame->width, state->tile->frame->height);
assert(state->tile->frame->rec);
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;
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
* 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);
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_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,

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)
{
// 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) {
encoder_state_t *state = &enc->states[enc->cur_state_num];
enc->frames_started += 1;
// FIXME: The frame number printed here is wrong when GOP is enabled.
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);
enc->frames_started += 1;
}
// If we have finished encoding as many frames as we have started, we are done.
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);
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);
bitstream_append(payload, &state->stream);
// 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);
enc->frames_done += 1;
state->frame_done = 1;
state->prepared = 0;
enc->frames_done += 1;
}
return 1;

View file

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