/***************************************************************************** * This file is part of Kvazaar HEVC encoder. * * Copyright (C) 2013-2015 Tampere University of Technology and others (see * COPYING file). * * Kvazaar is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the * Free Software Foundation; either version 2.1 of the License, or (at your * option) any later version. * * Kvazaar is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with Kvazaar. If not, see . ****************************************************************************/ /* * \file * */ #ifdef _WIN32 /* The following two defines must be located before the inclusion of any system header files. */ #define WINVER 0x0500 #define _WIN32_WINNT 0x0500 #include /* _setmode() */ #include /* _O_BINARY */ #endif #include "kvazaar_internal.h" #include #include #include #include #include #include "checkpoint.h" #include "global.h" #include "encoder.h" #include "cli.h" #include "yuv_io.h" /** * \brief Open a file for reading. * * If the file is "-", stdin is used. * * \param filename name of the file to open or "-" * \return the opened file or NULL if opening fails */ static FILE* open_input_file(const char* filename) { if (!strcmp(filename, "-")) return stdin; return fopen(filename, "rb"); } /** * \brief Open a file for writing. * * If the file is "-", stdout is used. * * \param filename name of the file to open or "-" * \return the opened file or NULL if opening fails */ static FILE* open_output_file(const char* filename) { if (!strcmp(filename, "-")) return stdout; return fopen(filename, "wb"); } static unsigned get_padding(unsigned width_or_height){ if (width_or_height % CU_MIN_SIZE_PIXELS){ return CU_MIN_SIZE_PIXELS - (width_or_height % CU_MIN_SIZE_PIXELS); }else{ return 0; } } #if KVZ_BIT_DEPTH == 8 #define PSNRMAX (255.0 * 255.0) #else #define PSNRMAX ((double)PIXEL_MAX * (double)PIXEL_MAX) #endif /** * \brief Calculates image PSNR value * * \param src source picture * \param rec reconstructed picture * \prama psnr returns the PSNR */ static void compute_psnr(const kvz_picture *const src, const kvz_picture *const rec, double psnr[NUM_COLORS]) { assert(src->width == rec->width); assert(src->height == rec->height); int32_t pixels = src->width * src->height; for (int32_t c = 0; c < NUM_COLORS; ++c) { int32_t num_pixels = pixels; if (c != COLOR_Y) { num_pixels >>= 2; } psnr[c] = 0; for (int32_t i = 0; i < num_pixels; ++i) { const int32_t error = src->data[c][i] - rec->data[c][i]; psnr[c] += error * error; } // Avoid division by zero if (psnr[c] == 0) psnr[c] = 99.0; psnr[c] = 10 * log10((num_pixels * PSNRMAX) / ((double)psnr[c]));; } } typedef struct { FILE* input; pthread_mutex_t* input_mutex; 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; uint8_t padding_y; const kvz_api * api; int retval; } input_handler_args; #define PTHREAD_LOCK(l) if (pthread_mutex_lock((l)) != 0) { fprintf(stderr, "pthread_mutex_lock(%s) failed!\n", #l); assert(0); return 0; } #define PTHREAD_UNLOCK(l) if (pthread_mutex_unlock((l)) != 0) { fprintf(stderr, "pthread_mutex_unlock(%s) failed!\n", #l); assert(0); return 0; } #define RETVAL_RUNNING 0 #define RETVAL_FAILURE 1 #define RETVAL_EOF 2 /** * \brief Handles input reading in a thread * * \param in_args pointer to argument struct */ static void* input_read_thread(void* in_args) { input_handler_args* args = (input_handler_args*)in_args; kvz_picture *frame_in = NULL; int frames_read = 0; for (;;) { 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; frames_read ++; frame_in = NULL; } 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); args->field_parity ^= 1; //0->1 or 1->0 } } else { goto exit_eof; } // 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: args->retval = RETVAL_EOF; args->img_in[frames_read & 1] = NULL; exit_failure: // Do some cleaning up args->api->picture_free(frame_in); if (!args->retval) { args->retval = RETVAL_FAILURE; } pthread_exit(NULL); return 0; } /** * \brief Program main function. * \param argc Argument count from commandline * \param argv Argument list * \return Program exit state */ int main(int argc, char *argv[]) { int retval = EXIT_SUCCESS; cmdline_opts_t *opts = NULL; //!< Command line options kvz_encoder* enc = NULL; FILE *input = NULL; //!< input file (YUV) FILE *output = NULL; //!< output file (HEVC NAL stream) FILE *recout = NULL; //!< reconstructed YUV output, --debug clock_t start_time = clock(); clock_t encoding_start_cpu_time; KVZ_CLOCK_T encoding_start_real_time; clock_t encoding_end_cpu_time; KVZ_CLOCK_T encoding_end_real_time; // Stdin and stdout need to be binary for input and output to work. // Stderr needs to be text mode to convert \n to \r\n in Windows. #ifdef _WIN32 _setmode( _fileno( stdin ), _O_BINARY ); _setmode( _fileno( stdout ), _O_BINARY ); _setmode( _fileno( stderr ), _O_TEXT ); #endif CHECKPOINTS_INIT(); const kvz_api * const api = kvz_api_get(8); opts = cmdline_opts_parse(api, argc, argv); // If problem with command line options, print banner and shutdown. if (!opts) { print_version(); print_help(); goto exit_failure; } input = open_input_file(opts->input); if (input == NULL) { fprintf(stderr, "Could not open input file, shutting down!\n"); goto exit_failure; } output = open_output_file(opts->output); if (output == NULL) { fprintf(stderr, "Could not open output file, shutting down!\n"); goto exit_failure; } if (opts->debug != NULL) { recout = open_output_file(opts->debug); if (recout == NULL) { fprintf(stderr, "Could not open reconstruction file (%s), shutting down!\n", opts->debug); goto exit_failure; } } enc = api->encoder_open(opts->config); if (!enc) { fprintf(stderr, "Failed to open encoder.\n"); goto exit_failure; } encoder_control_t *encoder = enc->control; fprintf(stderr, "Input: %s, output: %s\n", opts->input, opts->output); fprintf(stderr, " Video size: %dx%d (input=%dx%d)\n", encoder->in.width, encoder->in.height, encoder->in.real_width, encoder->in.real_height); if (opts->seek > 0 && !yuv_io_seek(input, opts->seek, opts->config->width, opts->config->height)) { fprintf(stderr, "Failed to seek %d frames.\n", opts->seek); goto exit_failure; } encoder->vui.field_seq_flag = encoder->cfg->source_scan_type != 0; encoder->vui.frame_field_info_present_flag = encoder->cfg->source_scan_type != 0; //Now, do the real stuff { KVZ_GET_TIME(&encoding_start_real_time); encoding_start_cpu_time = clock(); uint64_t bitstream_length = 0; uint32_t frames_read = 0; 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); pthread_t input_thread; pthread_mutex_t input_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t main_thread_mutex = PTHREAD_MUTEX_INITIALIZER; // Lock both mutexes at startup PTHREAD_LOCK(&main_thread_mutex); PTHREAD_LOCK(&input_mutex); // Give arguments via struct to the input thread input_handler_args in_args; kvz_picture *img_in[2] = { NULL }; in_args.input = input; in_args.img_in = img_in; 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; in_args.api = api; in_args.retval = RETVAL_RUNNING; if (pthread_create(&input_thread, NULL, input_read_thread, (void*)&in_args) != 0) { fprintf(stderr, "pthread_create failed!\n"); assert(0); return 0; } kvz_picture *cur_in_img; for (;;) { // Skip mutex locking when thread is no longer in run state if (in_args.retval == RETVAL_RUNNING) { // Wait for input to be read // unlock the input thread to be able to continue to the next picture PTHREAD_UNLOCK(&input_mutex); PTHREAD_LOCK(&main_thread_mutex); } cur_in_img = img_in[frames_read & 1]; frames_read++; if (in_args.retval == EXIT_FAILURE) { goto exit_failure; } kvz_data_chunk* chunks_out = NULL; kvz_picture *img_rec = NULL; kvz_picture *img_src = NULL; uint32_t len_out = 0; kvz_frame_info info_out; if (!api->encoder_encode(enc, cur_in_img, &chunks_out, &len_out, &img_rec, &img_src, &info_out)) { fprintf(stderr, "Failed to encode image.\n"); api->picture_free(cur_in_img); goto exit_failure; } if (chunks_out == NULL && cur_in_img == NULL) { // We are done since there is no more input and output left. break; } if (chunks_out != NULL) { uint64_t written = 0; // Write data into the output file. for (kvz_data_chunk *chunk = chunks_out; chunk != NULL; chunk = chunk->next) { assert(written + chunk->len <= len_out); if (fwrite(chunk->data, sizeof(uint8_t), chunk->len, output) != chunk->len) { fprintf(stderr, "Failed to write data to file.\n"); api->picture_free(cur_in_img); api->chunk_free(chunks_out); goto exit_failure; } written += chunk->len; } fflush(output); bitstream_length += len_out; // Compute and print stats. double frame_psnr[3] = { 0.0, 0.0, 0.0 }; if (encoder->cfg->calc_psnr) { compute_psnr(img_src, img_rec, frame_psnr); } if (recout) { // Since chunks_out was not NULL, img_rec should have been set. assert(img_rec); if (!yuv_io_write(recout, img_rec, opts->config->width, opts->config->height)) { fprintf(stderr, "Failed to write reconstructed picture!\n"); } } frames_done += 1; psnr_sum[0] += frame_psnr[0]; psnr_sum[1] += frame_psnr[1]; psnr_sum[2] += frame_psnr[2]; print_frame_info(&info_out, frame_psnr, len_out); } api->picture_free(cur_in_img); api->chunk_free(chunks_out); api->picture_free(img_rec); api->picture_free(img_src); } KVZ_GET_TIME(&encoding_end_real_time); encoding_end_cpu_time = clock(); // Coding finished // Print statistics of the coding fprintf(stderr, " Processed %d frames, %10llu bits", frames_done, (long long unsigned int)bitstream_length * 8); if (frames_done > 0) { fprintf(stderr, " AVG PSNR: %2.4f %2.4f %2.4f", psnr_sum[0] / frames_done, psnr_sum[1] / frames_done, psnr_sum[2] / frames_done); } fprintf(stderr, "\n"); fprintf(stderr, " Total CPU time: %.3f s.\n", ((float)(clock() - start_time)) / CLOCKS_PER_SEC); { double encoding_time = ( (double)(encoding_end_cpu_time - encoding_start_cpu_time) ) / (double) CLOCKS_PER_SEC; double wall_time = KVZ_CLOCK_T_AS_DOUBLE(encoding_end_real_time) - KVZ_CLOCK_T_AS_DOUBLE(encoding_start_real_time); fprintf(stderr, " Encoding time: %.3f s.\n", encoding_time); fprintf(stderr, " Encoding wall time: %.3f s.\n", wall_time); fprintf(stderr, " Encoding CPU usage: %.2f%%\n", encoding_time/wall_time*100.f); fprintf(stderr, " FPS: %.2f\n", ((double)frames_done)/wall_time); } pthread_join(input_thread, NULL); } goto done; exit_failure: retval = EXIT_FAILURE; done: // deallocate structures if (enc) api->encoder_close(enc); if (opts) cmdline_opts_free(api, opts); // close files if (input) fclose(input); if (output) fclose(output); if (recout) fclose(recout); CHECKPOINTS_FINALIZE(); return retval; }