Merge branch 'interlace'

This commit is contained in:
Ari Koivula 2016-01-28 21:19:02 +02:00
commit d6c1d0e0ae
10 changed files with 205 additions and 88 deletions

View file

@ -25,6 +25,9 @@ matrix:
# These valgrind tests are slow, so they are performed with the minimum
# number of small frames and fast settings.
# Tests for interlace
- env: VALGRIND_TEST="--source-scan-type=tff -p0 --preset=ultrafast --threads=2 --owf=1 --wpp"
# Tests for owf, wpp and tiles. There is lots of separate branches of
# code related to owf=0 and owf!=0, which is why all permutations are
# tried.

View file

@ -23,7 +23,7 @@ AC_CONFIG_SRCDIR([src/encmain.c])
#
# Here is a somewhat sane guide to lib versioning: http://apr.apache.org/versioning.html
ver_major=3
ver_minor=1
ver_minor=2
ver_release=0
# not used, but it prevents configure from adding a lot of defines to the CFLAGS

View file

@ -128,7 +128,6 @@ typedef struct {
pthread_mutex_t* main_thread_mutex;
kvz_picture **img_in;
uint8_t field_parity;
cmdline_opts_t *opts;
encoder_control_t *encoder;
uint8_t padding_x;
@ -149,54 +148,59 @@ typedef struct {
*
* \param in_args pointer to argument struct
*/
static void* input_read_thread(void* in_args) {
static void* input_read_thread(void* in_args)
{
// Reading a frame works as follows:
// - read full frame
// if progressive: set read frame as output
// if interlaced:
// - allocate two fields and fill them according to field order
// - deallocate the initial full frame
input_handler_args* args = (input_handler_args*)in_args;
kvz_picture *frame_in = NULL;
int frames_read = 0;
for (;;) {
// Each iteration of this loop puts either a single frame or a field into
// args->img_in for main thread to process.
if (!feof(args->input) && (args->opts->frames == 0 || frames_read < args->opts->frames || args->field_parity == 1)) {
// Try to read an input frame.
if (args->field_parity == 0) frame_in = args->api->picture_alloc(args->opts->config->width + args->padding_x, args->opts->config->height + args->padding_y);
if (!frame_in) {
fprintf(stderr, "Failed to allocate image.\n");
goto exit_failure;
}
if (args->field_parity == 0) {
if (yuv_io_read(args->input, args->opts->config->width, args->opts->config->height, frame_in)) {
args->img_in[frames_read & 1] = frame_in;
} else {
// EOF or some error
if (!feof(args->input)) {
fprintf(stderr, "Failed to read a frame %d\n", frames_read);
goto exit_failure;
}
goto exit_eof;
}
}
if (args->encoder->cfg->source_scan_type != 0) {
args->img_in[frames_read & 1] = args->api->picture_alloc(args->encoder->in.width, args->encoder->in.height);
yuv_io_extract_field(frame_in, args->encoder->cfg->source_scan_type, args->field_parity, args->img_in[frames_read & 1]);
if (args->field_parity == 1) {
args->api->picture_free(frame_in);
frame_in = NULL;
}
args->field_parity ^= 1; //0->1 or 1->0
} else {
frame_in = NULL;
}
frames_read++;
} else {
bool input_empty = !(args->opts->frames == 0 // number of frames to read is unknown
|| frames_read < args->opts->frames); // not all frames have been read
if (feof(args->input) || input_empty) {
goto exit_eof;
}
frame_in = args->api->picture_alloc(args->opts->config->width + args->padding_x, args->opts->config->height + args->padding_y);
if (!frame_in) {
fprintf(stderr, "Failed to allocate image.\n");
goto exit_failure;
}
if (!yuv_io_read(args->input, args->opts->config->width, args->opts->config->height, frame_in)) {
// reading failed
if (feof(args->input)) {
goto exit_eof;
} else {
fprintf(stderr, "Failed to read a frame %d\n", frames_read);
goto exit_failure;
}
}
if (args->encoder->cfg->source_scan_type != 0) {
// Set source scan type for frame, so that it will be turned into fields.
frame_in->interlacing = args->encoder->cfg->source_scan_type;
}
args->img_in[frames_read & 1] = frame_in;
frame_in = NULL;
frames_read++;
// Wait until main thread is ready to receive input and then release main thread
PTHREAD_LOCK(args->input_mutex);
PTHREAD_UNLOCK(args->main_thread_mutex);
}
exit_eof:
@ -305,7 +309,6 @@ int main(int argc, char *argv[])
uint32_t frames_done = 0;
double psnr_sum[3] = { 0.0, 0.0, 0.0 };
int8_t field_parity = 0;
uint8_t padding_x = get_padding(opts->config->width);
uint8_t padding_y = get_padding(opts->config->height);
@ -326,7 +329,6 @@ int main(int argc, char *argv[])
in_args.main_thread_mutex = &main_thread_mutex;
in_args.input_mutex = &input_mutex;
in_args.opts = opts;
in_args.field_parity = field_parity;
in_args.encoder = encoder;
in_args.padding_x = padding_x;
in_args.padding_y = padding_y;
@ -400,7 +402,9 @@ int main(int argc, char *argv[])
// Compute and print stats.
double frame_psnr[3] = { 0.0, 0.0, 0.0 };
if (encoder->cfg->calc_psnr) {
if (encoder->cfg->calc_psnr && encoder->cfg->source_scan_type == KVZ_INTERLACING_NONE) {
// Do not compute PSNR for interlaced frames, because img_rec does not contain
// the deinterlaced frame yet.
compute_psnr(img_src, img_rec, frame_psnr);
}

View file

@ -125,6 +125,13 @@ encoder_control_t* kvz_encoder_control_init(const kvz_config *const cfg) {
encoder->owf = select_owf_auto(cfg);
fprintf(stderr, "--owf=auto value set to %d.\n", encoder->owf);
}
if (cfg->source_scan_type != KVZ_INTERLACING_NONE) {
// If using interlaced coding with OWF, the OWF has to be an even number
// to ensure that the pair of fields will be output for the same picture.
if (encoder->owf % 2 == 1) {
encoder->owf += 1;
}
}
encoder->threadqueue = MALLOC(threadqueue_queue_t, 1);
if (!encoder->threadqueue ||
@ -440,6 +447,11 @@ encoder_control_t* kvz_encoder_control_init(const kvz_config *const cfg) {
encoder->vui.timing_info_present_flag = 1;
encoder->vui.num_units_in_tick = cfg->framerate_denom;
encoder->vui.time_scale = cfg->framerate_num;
if (cfg->source_scan_type != KVZ_INTERLACING_NONE) {
// when field_seq_flag=1, the time_scale and num_units_in_tick refer to
// field rate rather than frame rate.
encoder->vui.time_scale *= 2;
}
}
// AUD

View file

@ -570,7 +570,7 @@ static void encoder_state_write_picture_timing_sei_message(encoder_state_t * con
int8_t pic_struct = 0; //0: progressive picture, 1: top field, 2: bottom field, 3...
int8_t source_scan_type = 1; //0: interlaced, 1: progressive
switch (state->encoder_control->in.source_scan_type){
switch (state->tile->frame->source->interlacing){
case 0: //Progressive frame
pic_struct = 0;
source_scan_type = 1;

View file

@ -67,6 +67,8 @@ kvz_picture *kvz_image_alloc(const int32_t width, const int32_t height)
im->pts = 0;
im->dts = 0;
im->interlacing = KVZ_INTERLACING_NONE;
return im;
}

View file

@ -144,6 +144,49 @@ static int kvazaar_headers(kvz_encoder *enc,
}
/**
* \brief Separate a single field from a frame.
*
* \param frame_in input frame to extract field from
* \param source_scan_type scan type of input material (0: progressive, 1:top field first, 2:bottom field first)
* \param field parity
* \param field_out
*
* \return 1 on success, 0 on failure
*/
static int yuv_io_extract_field(const kvz_picture *frame_in, unsigned source_scan_type, unsigned field_parity, kvz_picture *field_out)
{
if ((source_scan_type != 1) && (source_scan_type != 2)) return 0;
if ((field_parity != 0) && (field_parity != 1)) return 0;
unsigned offset = 0;
if (source_scan_type == 1) offset = field_parity ? 1 : 0;
else if (source_scan_type == 2) offset = field_parity ? 0 : 1;
//Luma
for (int i = 0; i < field_out->height; ++i){
kvz_pixel *row_in = frame_in->y + MIN(frame_in->height - 1, 2 * i + offset) * frame_in->stride;
kvz_pixel *row_out = field_out->y + i * field_out->stride;
memcpy(row_out, row_in, sizeof(kvz_pixel) * frame_in->width);
}
//Chroma
for (int i = 0; i < field_out->height / 2; ++i){
kvz_pixel *row_in = frame_in->u + MIN(frame_in->height / 2 - 1, 2 * i + offset) * frame_in->stride / 2;
kvz_pixel *row_out = field_out->u + i * field_out->stride / 2;
memcpy(row_out, row_in, sizeof(kvz_pixel) * frame_in->width / 2);
}
for (int i = 0; i < field_out->height / 2; ++i){
kvz_pixel *row_in = frame_in->v + MIN(frame_in->height / 2 - 1, 2 * i + offset) * frame_in->stride / 2;
kvz_pixel *row_out = field_out->v + i * field_out->stride / 2;
memcpy(row_out, row_in, sizeof(kvz_pixel) * frame_in->width / 2);
}
return 1;
}
static int kvazaar_encode(kvz_encoder *enc,
kvz_picture *pic_in,
kvz_data_chunk **data_out,
@ -212,6 +255,90 @@ static int kvazaar_encode(kvz_encoder *enc,
}
static int kvazaar_field_encoding_adapter(kvz_encoder *enc,
kvz_picture *pic_in,
kvz_data_chunk **data_out,
uint32_t *len_out,
kvz_picture **pic_out,
kvz_picture **src_out,
kvz_frame_info *info_out)
{
if (enc->control->cfg->source_scan_type == KVZ_INTERLACING_NONE) {
// For progressive, simply call the normal encoding function.
return kvazaar_encode(enc, pic_in, data_out, len_out, pic_out, src_out, info_out);
}
// For interlaced, make two fields out of the input frame and call encode on them separately.
encoder_state_t *state = &enc->states[enc->cur_state_num];
kvz_picture *first_field = NULL, *second_field = NULL;
struct {
kvz_data_chunk* data_out;
uint32_t len_out;
} first = { 0 }, second = { 0 };
if (pic_in != NULL) {
first_field = kvz_image_alloc(state->encoder_control->in.width, state->encoder_control->in.height);
if (first_field == NULL) {
goto kvazaar_field_encoding_adapter_failure;
}
second_field = kvz_image_alloc(state->encoder_control->in.width, state->encoder_control->in.height);
if (second_field == NULL) {
goto kvazaar_field_encoding_adapter_failure;
}
yuv_io_extract_field(pic_in, pic_in->interlacing, 0, first_field);
yuv_io_extract_field(pic_in, pic_in->interlacing, 1, second_field);
first_field->pts = pic_in->pts;
first_field->dts = pic_in->dts;
first_field->interlacing = pic_in->interlacing;
// Should the second field have higher pts and dts? It shouldn't affect anything.
second_field->pts = pic_in->pts;
second_field->dts = pic_in->dts;
second_field->interlacing = pic_in->interlacing;
}
if (!kvazaar_encode(enc, first_field, &first.data_out, &first.len_out, pic_out, NULL, info_out)) {
goto kvazaar_field_encoding_adapter_failure;
}
if (!kvazaar_encode(enc, second_field, &second.data_out, &second.len_out, NULL, NULL, NULL)) {
goto kvazaar_field_encoding_adapter_failure;
}
kvz_image_free(first_field);
kvz_image_free(second_field);
// Concatenate bitstreams.
if (len_out != NULL) {
*len_out = first.len_out + second.len_out;
}
if (data_out != NULL) {
*data_out = first.data_out;
if (first.data_out != NULL) {
kvz_data_chunk *chunk = first.data_out;
while (chunk->next != NULL) {
chunk = chunk->next;
}
chunk->next = second.data_out;
}
}
if (src_out != NULL) {
// TODO: deinterlace the fields to one picture.
}
return 1;
kvazaar_field_encoding_adapter_failure:
kvz_image_free(first_field);
kvz_image_free(second_field);
kvz_bitstream_free_chunks(first.data_out);
kvz_bitstream_free_chunks(second.data_out);
return 0;
}
static const kvz_api kvz_8bit_api = {
.config_alloc = kvz_config_alloc,
.config_init = kvz_config_init,
@ -226,7 +353,7 @@ static const kvz_api kvz_8bit_api = {
.encoder_open = kvazaar_open,
.encoder_close = kvazaar_close,
.encoder_headers = kvazaar_headers,
.encoder_encode = kvazaar_encode,
.encoder_encode = kvazaar_field_encoding_adapter,
};

View file

@ -90,6 +90,17 @@ enum kvz_ime_algorithm {
KVZ_IME_FULL = 2,
};
/**
* \brief Interlacing methods.
* \since 3.2.0
*/
enum kvz_interlacing
{
KVZ_INTERLACING_NONE = 0,
KVZ_INTERLACING_TFF = 1, // top field first
KVZ_INTERLACING_BFF = 2, // bottom field first
};
/**
* \brief GoP picture configuration.
*/
@ -218,6 +229,8 @@ typedef struct kvz_picture {
int64_t pts; //!< \brief Presentation timestamp. Should be set for input frames.
int64_t dts; //!< \brief Decompression timestamp.
enum kvz_interlacing interlacing; //!< \since 3.2.0 \brief Field order for interlaced pictures.
} kvz_picture;
/**

View file

@ -203,45 +203,3 @@ int yuv_io_write(FILE* file,
return 1;
}
/**
* \brief Separate a single field from a frame.
*
* \param frame_in input frame to extract field from
* \param source_scan_type scan type of input material (0: progressive, 1:top field first, 2:bottom field first)
* \param field parity
* \param field_out
*
* \return 1 on success, 0 on failure
*/
int yuv_io_extract_field(const kvz_picture *frame_in, unsigned source_scan_type, unsigned field_parity, kvz_picture *field_out)
{
if ((source_scan_type != 1) && (source_scan_type != 2)) return 0;
if ((field_parity != 0) && (field_parity != 1)) return 0;
unsigned offset = 0;
if (source_scan_type == 1) offset = field_parity ? frame_in->stride : 0;
else if (source_scan_type == 2) offset = field_parity ? 0 : frame_in->stride;
//Luma
for (int i = 0; i < field_out->height; ++i){
kvz_pixel *row_in = frame_in->y + CLIP(0, frame_in->height - 1, 2 * i) * frame_in->stride + offset;
kvz_pixel *row_out = field_out->y + i * field_out->stride;
memcpy(row_out, row_in, sizeof(kvz_pixel) * frame_in->width);
}
//Chroma
for (int i = 0; i < field_out->height / 2; ++i){
kvz_pixel *row_in = frame_in->u + CLIP(0, frame_in->height / 2 - 1, 2 * i) * frame_in->stride / 2 + offset / 2;
kvz_pixel *row_out = field_out->u + i * field_out->stride / 2;
memcpy(row_out, row_in, sizeof(kvz_pixel) * frame_in->width / 2);
}
for (int i = 0; i < field_out->height / 2; ++i){
kvz_pixel *row_in = frame_in->v + CLIP(0, frame_in->height / 2 - 1, 2 * i) * frame_in->stride / 2 + offset / 2;
kvz_pixel *row_out = field_out->v + i * field_out->stride / 2;
memcpy(row_out, row_in, sizeof(kvz_pixel) * frame_in->width / 2);
}
return 1;
}

View file

@ -39,6 +39,4 @@ int yuv_io_write(FILE* file,
const kvz_picture *img,
unsigned output_width, unsigned output_height);
int yuv_io_extract_field(const kvz_picture *frame_in, unsigned source_scan_type, unsigned field_parity, kvz_picture *field_out);
#endif // YUV_IO_H_