mirror of
https://github.com/ultravideo/uvg266.git
synced 2024-11-30 12:44:07 +00:00
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:
parent
012c0580df
commit
7e20e62cc7
|
@ -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();
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in a new issue