/* * Copyright (c) 2018 Georg Ottinger. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sub license, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial portions * of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. * IN NO EVENT SHALL PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* This file includes code taken from vp9enc.c (libva-utils) copyright 2017 * by Intel Cooperation and licensed under same conditions. * This file includes code ported from vaapiencoder_vp8.cpp (libyami) copyright * by 2014-2016 Intel Corporation. https://github.com/intel/libyami * The original copyright and licence statement as below. */ /* * Copyright (C) 2014-2016 Intel Corporation. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "va_display.h" #define MAX_XY_RESOLUTION 16364 #define KEY_FRAME 0 #define INTER_FRAME 1 #define NUM_REF_SURFACES 4 #define NUM_INPUT_SURFACES 2 #define NUM_SURFACES_TOTAL (NUM_REF_SURFACES+NUM_INPUT_SURFACES) #define SID_INPUT_PICTURE_0 (NUM_REF_SURFACES) #define SID_INPUT_PICTURE_1 (NUM_REF_SURFACES+1) #define NUM_BUFFERS 10 #define VP8ENC_OK 0 #define VP8ENC_FAIL -1 #define PARSE_OPTIONS_OK 0 #define PARSE_OPTIONS_FAIL -1 #ifndef N_ELEMENTS #define N_ELEMENTS(array) (sizeof(array)/sizeof(array[0])) #endif #ifndef CHECK_VASTATUS #define CHECK_VASTATUS(va_status,func) \ if (va_status != VA_STATUS_SUCCESS) { \ fprintf(stderr,"%s:%s (%d) failed,exit\n", __func__, func, __LINE__); \ exit(1); \ } #endif static const struct option long_opts[] = { {"help", no_argument, NULL, 0 }, {"rcmode", required_argument, NULL, 1 }, {"qp", required_argument, NULL, 2 }, {"intra_period", required_argument, NULL, 3 }, {"fb", required_argument, NULL, 4 }, {"lf_level", required_argument, NULL, 5 }, {"hrd_win", required_argument, NULL, 6}, {"vbr_max", required_argument, NULL, 7}, {"fn_num", required_argument, NULL, 8}, {"error_resilient", no_argument, NULL, 9}, {"debug", no_argument, NULL, 10}, {"temp_svc", required_argument, NULL, 11}, {"repeat", required_argument, NULL, 12}, {NULL, no_argument, NULL, 0 } }; static const int default_rc_modes[4] = { VA_RC_CQP, // 0 VA_RC_CBR, // 1 VA_RC_VBR, // 2 VA_RC_NONE }; struct vp8enc_settings { int width; int height; int frame_rate; int frame_size; int loop_filter_level; int clamp_qindex_low; int clamp_qindex_high; int intra_period; int quantization_parameter; int frame_bitrate; int max_variable_bitrate; int rc_mode; int num_frames; VAEntrypoint vaapi_entry_point; int codedbuf_size; int hrd_window; int error_resilient; int debug; int temporal_svc_layers; int repeat_times; }; static struct vp8enc_settings settings = { //Default Values - unless otherwise specified with command line options .frame_rate = 30, .loop_filter_level = 19, .clamp_qindex_low = 9, .clamp_qindex_high = 127, .intra_period = 30, .quantization_parameter = 60, .frame_bitrate = -1, .max_variable_bitrate = -1, .num_frames = 0, .rc_mode = VA_RC_CQP, .vaapi_entry_point = VAEntrypointEncSlice, //VAEntrypointEncSliceLP would be LowPower Mode - but not supported with VP8Encoder .hrd_window = 1500, .error_resilient = 0, .debug = 0, .temporal_svc_layers = 1, .repeat_times = 1, }; struct vp8enc_vaapi_context { VADisplay display; VAProfile profile; VAContextID context_id; VAConfigID config_id; VAEncSequenceParameterBufferVP8 seq_param; VAEncPictureParameterBufferVP8 pic_param; VAQMatrixBufferVP8 q_matrix; VASurfaceID surfaces[NUM_SURFACES_TOTAL]; VASurfaceID recon_surface, last_ref_surface, golden_ref_surface, alt_ref_surface; VASurfaceID input_surface; VABufferID codedbuf_buf_id; VABufferID va_buffers[NUM_BUFFERS]; int num_va_buffers; int is_golden_refreshed; struct { VAEncMiscParameterBuffer header; VAEncMiscParameterHRD data; } hrd_param; struct { VAEncMiscParameterBuffer header; VAEncMiscParameterFrameRate data; } frame_rate_param; struct { VAEncMiscParameterBuffer header; VAEncMiscParameterRateControl data; } rate_control_param; struct { pthread_t id; int value; FILE *input_fp; int input_surface_num; VASurfaceID input_surface; int processed_frame; } upload_thread; }; static struct vp8enc_vaapi_context vaapi_context; /******************************************** * * START: IVF Container Releated Stuff * ********************************************/ static void vp8enc_write_word(char *ptr, uint32_t value) { uint8_t *tmp; tmp = (uint8_t *)ptr; *(tmp) = (value >> 0) & 0XFF; *(tmp + 1) = (value >> 8) & 0XFF; } static void vp8enc_write_dword(char *ptr, uint32_t value) { uint8_t *tmp; tmp = (uint8_t *)ptr; *(tmp) = (value >> 0) & 0XFF; *(tmp + 1) = (value >> 8) & 0XFF; *(tmp + 2) = (value >> 16) & 0XFF; *(tmp + 3) = (value >> 24) & 0XFF; } static void vp8enc_write_qword(char *ptr, uint64_t value) { uint8_t *tmp; tmp = (uint8_t *)ptr; *(tmp) = (value >> 0) & 0XFF; *(tmp + 1) = (value >> 8) & 0XFF; *(tmp + 2) = (value >> 16) & 0XFF; *(tmp + 3) = (value >> 24) & 0XFF; *(tmp + 4) = (value >> 32) & 0XFF; *(tmp + 5) = (value >> 40) & 0XFF; *(tmp + 6) = (value >> 48) & 0XFF; *(tmp + 7) = (value >> 56) & 0XFF; } static void vp8enc_write_frame_header(FILE *vp8_output,uint32_t data_length, uint64_t timestamp) { char header[12]; vp8enc_write_dword(header, data_length); vp8enc_write_qword(header + 4, timestamp ); fwrite(header, 1, 12, vp8_output); } static void vp8enc_write_ivf_header(FILE *vp8_file) { #define VP8_FOURCC 0x30385056 char header[32]; header[0] = 'D'; header[1] = 'K'; header[2] = 'I'; header[3] = 'F'; vp8enc_write_word(header + 4, 0); vp8enc_write_word(header + 6, 32); vp8enc_write_dword(header + 8, VP8_FOURCC); vp8enc_write_word(header + 12, settings.width); vp8enc_write_word(header + 14, settings.height); vp8enc_write_dword(header + 16, settings.frame_rate); vp8enc_write_dword(header + 20, 1); vp8enc_write_dword(header + 24, settings.num_frames * settings.repeat_times); vp8enc_write_dword(header + 28, 0); fwrite(header, 1, 32, vp8_file); } /******************************************** * * END: IVF Container Releated Stuff * ********************************************/ /******************************************** * * START: Read YUV Input File Releated Stuff * ********************************************/ static void vp8enc_upload_yuv_to_surface(FILE *yuv_fp, VASurfaceID surface_id, int current_frame) { VAImage surface_image; VAStatus va_status; void *surface_p = NULL; uint8_t *y_src, *u_src, *v_src; uint8_t *y_dst, *u_dst, *v_dst; int y_size = settings.width * settings.height; int u_size = (settings.width >> 1) * (settings.height >> 1); int row, col; char *yuv_mmap_ptr = NULL; unsigned long long frame_start_pos, mmap_start; int mmap_size; frame_start_pos = (unsigned long long)current_frame * settings.frame_size; mmap_start = frame_start_pos & (~0xfff); mmap_size = (settings.frame_size + (frame_start_pos & 0xfff) + 0xfff) & (~0xfff); yuv_mmap_ptr = mmap(0, mmap_size, PROT_READ, MAP_SHARED, fileno(yuv_fp), mmap_start); if (yuv_mmap_ptr == MAP_FAILED) { fprintf(stderr,"Error: Failed to mmap YUV file.\n"); assert(0); } y_src = (uint8_t*)yuv_mmap_ptr+(frame_start_pos & 0xfff); u_src = y_src + y_size; /* UV offset for NV12 */ v_src = y_src + y_size + u_size; va_status = vaDeriveImage(vaapi_context.display, surface_id, &surface_image); CHECK_VASTATUS(va_status,"vaDeriveImage"); vaMapBuffer(vaapi_context.display, surface_image.buf, &surface_p); assert(VA_STATUS_SUCCESS == va_status); y_dst = surface_p + surface_image.offsets[0]; u_dst = surface_p + surface_image.offsets[1]; /* UV offset for NV12 */ v_dst = surface_p + surface_image.offsets[2]; /* Y plane */ for (row = 0; row < surface_image.height; row++) { memcpy(y_dst, y_src, surface_image.width); y_dst += surface_image.pitches[0]; y_src += settings.width; } if (surface_image.format.fourcc == VA_FOURCC_NV12) { /* UV plane */ for (row = 0; row < surface_image.height / 2; row++) { for (col = 0; col < surface_image.width / 2; col++) { u_dst[col * 2] = u_src[col]; u_dst[col * 2 + 1] = v_src[col]; } u_dst += surface_image.pitches[1]; u_src += (settings.width / 2); v_src += (settings.width / 2); } } else if (surface_image.format.fourcc == VA_FOURCC_YV12 || surface_image.format.fourcc == VA_FOURCC_I420) { const int U = surface_image.format.fourcc == VA_FOURCC_I420 ? 1 : 2; const int V = surface_image.format.fourcc == VA_FOURCC_I420 ? 2 : 1; u_dst = surface_p + surface_image.offsets[U]; v_dst = surface_p + surface_image.offsets[V]; for (row = 0; row < surface_image.height / 2; row++) { memcpy(u_dst, u_src, surface_image.width / 2); memcpy(v_dst, v_src, surface_image.width / 2); u_dst += surface_image.pitches[U]; v_dst += surface_image.pitches[V]; u_src += (settings.width / 2); v_src += (settings.width / 2); } } vaUnmapBuffer(vaapi_context.display, surface_image.buf); vaDestroyImage(vaapi_context.display, surface_image.image_id); if (yuv_mmap_ptr) munmap(yuv_mmap_ptr, mmap_size); } static void * vp8enc_upload_thread_function(void *data) { vp8enc_upload_yuv_to_surface(vaapi_context.upload_thread.input_fp, vaapi_context.upload_thread.input_surface,vaapi_context.upload_thread.processed_frame); return NULL; } /******************************************** * * END: Read YUV Input File Releated Stuff * ********************************************/ void vp8enc_init_QMatrix(VAQMatrixBufferVP8 *qMatrix) { // When segmentation is disabled, only quantization_index[0] will be used size_t i; for (i = 0; i < N_ELEMENTS(qMatrix->quantization_index); i++) { qMatrix->quantization_index[i] = settings.quantization_parameter; } for (i = 0; i < N_ELEMENTS(qMatrix->quantization_index_delta); i++) { qMatrix->quantization_index_delta[i] = 0; } } void vp8enc_init_SequenceParameterBuffer(VAEncSequenceParameterBufferVP8* seqParam) { size_t i; memset(seqParam, 0, sizeof(VAEncSequenceParameterBufferVP8)); seqParam->frame_width = settings.width; seqParam->frame_height = settings.height; if(settings.frame_bitrate > 0) seqParam->bits_per_second = settings.frame_bitrate * 1000; else seqParam->bits_per_second = 0; seqParam->intra_period = settings.intra_period; seqParam->error_resilient = settings.error_resilient; for (i = 0; i < N_ELEMENTS(seqParam->reference_frames); i++) seqParam->reference_frames[i] = VA_INVALID_ID; } void vp8enc_init_PictureParameterBuffer(VAEncPictureParameterBufferVP8 *picParam) { size_t i; memset(picParam, 0, sizeof(VAEncPictureParameterBufferVP8)); picParam->ref_last_frame = VA_INVALID_SURFACE; picParam->ref_gf_frame = VA_INVALID_SURFACE; picParam->ref_arf_frame = VA_INVALID_SURFACE; /* always show it */ picParam->pic_flags.bits.show_frame = 1; for (i = 0; i < N_ELEMENTS(picParam->loop_filter_level); i++) { picParam->loop_filter_level[i] = settings.loop_filter_level; } picParam->clamp_qindex_low = settings.clamp_qindex_low; picParam->clamp_qindex_high = settings.clamp_qindex_high; } void vp8enc_set_refreshparameter_for_svct_2layers(VAEncPictureParameterBufferVP8 *picParam, int current_frame, int *is_golden_refreshed) { //Pattern taken from libyami picParam->ref_flags.bits.no_ref_arf = 1; if(! *is_golden_refreshed) picParam->ref_flags.bits.no_ref_gf = 1; switch(current_frame % 2) { case 0: //Layer 0 picParam->pic_flags.bits.refresh_last = 1; picParam->ref_flags.bits.no_ref_gf = 1; picParam->ref_flags.bits.temporal_id = 0; break; case 1: //Layer 1 picParam->pic_flags.bits.refresh_golden_frame = 1; *is_golden_refreshed = 1; picParam->ref_flags.bits.temporal_id = 1; break; } } void vp8enc_set_refreshparameter_for_svct_3layers(VAEncPictureParameterBufferVP8 *picParam, int current_frame,int *is_golden_refreshed) { //Pattern taken from libyami - Note that the alternate frame is never referenced, //this is because, libyami implementation suggests to be able to drop individual //frames from Layer 2 on bad network connections picParam->ref_flags.bits.no_ref_arf = 1; if(! *is_golden_refreshed) picParam->ref_flags.bits.no_ref_gf = 1; switch(current_frame % 4) { case 0: //Layer 0 picParam->pic_flags.bits.refresh_last = 1; picParam->ref_flags.bits.no_ref_gf = 1; picParam->ref_flags.bits.temporal_id = 0; break; case 1: case 3: //Layer 2 picParam->pic_flags.bits.refresh_alternate_frame = 1; picParam->ref_flags.bits.temporal_id = 2; break; case 2: //Layer 1 picParam->pic_flags.bits.refresh_golden_frame = 1; *is_golden_refreshed = 1; picParam->ref_flags.bits.temporal_id = 1; break; } } void vp8enc_reset_picture_parameter_references(VAEncPictureParameterBufferVP8 *picParam) { picParam->ref_last_frame = VA_INVALID_SURFACE; picParam->ref_gf_frame = VA_INVALID_SURFACE; picParam->ref_arf_frame = VA_INVALID_SURFACE; picParam->pic_flags.bits.refresh_last = 0; picParam->pic_flags.bits.refresh_golden_frame = 0; picParam->pic_flags.bits.refresh_alternate_frame = 0; picParam->pic_flags.bits.copy_buffer_to_golden = 0; picParam->pic_flags.bits.copy_buffer_to_alternate = 0; picParam->ref_flags.bits.no_ref_last = 0; picParam->ref_flags.bits.no_ref_gf = 0; picParam->ref_flags.bits.no_ref_arf = 0; } void vp8enc_update_picture_parameter(int frame_type, int current_frame) { VAEncPictureParameterBufferVP8 *picParam = &vaapi_context.pic_param; picParam->reconstructed_frame = vaapi_context.recon_surface; vp8enc_reset_picture_parameter_references(picParam); if (frame_type == KEY_FRAME) { picParam->ref_flags.bits.force_kf = 1; picParam->pic_flags.bits.frame_type = KEY_FRAME; vaapi_context.is_golden_refreshed = 0; return; } // INTER_FRAME picParam->ref_flags.bits.force_kf = 0; picParam->pic_flags.bits.frame_type = INTER_FRAME; switch(settings.temporal_svc_layers) { case 1: //Standard behavoir only 1 Temporal Layer picParam->pic_flags.bits.refresh_last = 1; picParam->pic_flags.bits.copy_buffer_to_golden = 1; picParam->pic_flags.bits.copy_buffer_to_alternate = 2; picParam->ref_flags.bits.temporal_id = 0; break; case 2: //2 Temporal Layers vp8enc_set_refreshparameter_for_svct_2layers(picParam,current_frame,&vaapi_context.is_golden_refreshed); break; case 3: //3 Temporal Layers vp8enc_set_refreshparameter_for_svct_3layers(picParam,current_frame,&vaapi_context.is_golden_refreshed); break; default: //should never happen fprintf(stderr,"Error: Only 1,2 or 3 TemporalLayers supported.\n"); assert(0); break; } if(!picParam->ref_flags.bits.no_ref_last) picParam->ref_last_frame = vaapi_context.last_ref_surface; if(!picParam->ref_flags.bits.no_ref_gf) picParam->ref_gf_frame = vaapi_context.golden_ref_surface; if(!picParam->ref_flags.bits.no_ref_arf) picParam->ref_arf_frame = vaapi_context.alt_ref_surface; } VASurfaceID vp8enc_get_unused_surface() { VASurfaceID current_surface; size_t i = 0; for (i = 0; i < NUM_REF_SURFACES; i++) { current_surface = vaapi_context.surfaces[i]; if(current_surface != vaapi_context.last_ref_surface && current_surface != vaapi_context.golden_ref_surface && current_surface != vaapi_context.alt_ref_surface) return current_surface; } //No unused surface found - should never happen. fprintf(stderr, "Error: No unused surface found!\n"); assert(0); } VASurfaceID vp8enc_update_reference(VASurfaceID current_surface, VASurfaceID second_copy_surface, bool refresh_with_recon, int copy_flag) { if(refresh_with_recon) return vaapi_context.recon_surface; switch(copy_flag) { case 0: return current_surface; case 1: return vaapi_context.last_ref_surface; case 2: return second_copy_surface; default: // should never happen fprintf(stderr, "Error: Invalid copy_buffer_to_X flag\n"); assert(0); } return VA_INVALID_ID; // should never happen } void vp8enc_update_reference_list(int frame_type) { VAEncPictureParameterBufferVP8 *picParam = &vaapi_context.pic_param; if (frame_type == KEY_FRAME) { vaapi_context.last_ref_surface = vaapi_context.recon_surface; vaapi_context.golden_ref_surface = vaapi_context.recon_surface; vaapi_context.alt_ref_surface = vaapi_context.recon_surface; } else { // INTER_FRAME //check refresh_X and copy_buffer_to_golden_X and update references accordingly if(picParam->pic_flags.bits.refresh_last) vaapi_context.last_ref_surface = vaapi_context.recon_surface; vaapi_context.golden_ref_surface = vp8enc_update_reference(vaapi_context.golden_ref_surface,vaapi_context.alt_ref_surface,picParam->pic_flags.bits.refresh_golden_frame, picParam->pic_flags.bits.copy_buffer_to_golden ); vaapi_context.alt_ref_surface = vp8enc_update_reference(vaapi_context.alt_ref_surface,vaapi_context.golden_ref_surface,picParam->pic_flags.bits.refresh_alternate_frame, picParam->pic_flags.bits.copy_buffer_to_alternate ); } vaapi_context.recon_surface = vp8enc_get_unused_surface(); } void vp8enc_init_MiscParameterBuffers(VAEncMiscParameterHRD *hrd, VAEncMiscParameterFrameRate *frame_rate, VAEncMiscParameterRateControl *rate_control) { if(hrd != NULL) { if(settings.frame_bitrate) { hrd->initial_buffer_fullness = settings.frame_bitrate * settings.hrd_window / 2; hrd->buffer_size = settings.frame_bitrate * settings.hrd_window; } else { hrd->initial_buffer_fullness = 0; hrd->buffer_size = 0; } } if(frame_rate != NULL) { frame_rate->framerate = settings.frame_rate; } if (rate_control != NULL) { rate_control->window_size = settings.hrd_window; rate_control->initial_qp = settings.quantization_parameter; rate_control->min_qp = settings.clamp_qindex_low; //rate_control->rc_flags.bits.disable_bit_stuffing = 1; if(settings.rc_mode == VA_RC_VBR) { rate_control->bits_per_second = settings.max_variable_bitrate * 1000; rate_control->target_percentage = (settings.frame_bitrate * 100) / settings.max_variable_bitrate; } else { rate_control->bits_per_second = settings.frame_bitrate * 1000; rate_control->target_percentage = 95; } } } void vp8enc_create_EncoderPipe() { VAEntrypoint entrypoints[5]; int num_entrypoints; int i; bool entrypoint_found; VAConfigAttrib conf_attrib[2]; VASurfaceAttrib surface_attrib; int major_ver, minor_ver; VAStatus va_status; vaapi_context.display = va_open_display(); va_status = vaInitialize(vaapi_context.display, &major_ver, &minor_ver); CHECK_VASTATUS(va_status, "vaInitialize"); vaQueryConfigEntrypoints(vaapi_context.display, vaapi_context.profile, entrypoints, &num_entrypoints); entrypoint_found = true; for(i = 0; i < num_entrypoints;i++) { if (entrypoints[i] == settings.vaapi_entry_point) entrypoint_found = true; } if(entrypoint_found == false) { fprintf(stderr,"Error: VAEntrypoint not found!\n"); assert(0); } /* find out the format for the render target, and rate control mode */ conf_attrib[0].type = VAConfigAttribRTFormat; conf_attrib[1].type = VAConfigAttribRateControl; vaGetConfigAttributes(vaapi_context.display, vaapi_context.profile, settings.vaapi_entry_point, &conf_attrib[0], 2); if ((conf_attrib[0].value & VA_RT_FORMAT_YUV420) == 0) { fprintf(stderr, "Error: Input colorspace YUV420 not supported, exit\n"); assert(0); } if ((conf_attrib[1].value & settings.rc_mode) == 0) { /* Can't find matched RC mode */ fprintf(stderr, "Error: Can't find the desired RC mode, exit\n"); assert(0); } conf_attrib[0].value = VA_RT_FORMAT_YUV420; /* set to desired RT format */ conf_attrib[1].value = settings.rc_mode; /* set to desired RC mode */ va_status = vaCreateConfig(vaapi_context.display, vaapi_context.profile, settings.vaapi_entry_point, &conf_attrib[0], 2,&vaapi_context.config_id); CHECK_VASTATUS(va_status, "vaCreateConfig"); surface_attrib.type = VASurfaceAttribPixelFormat; surface_attrib.flags = VA_SURFACE_ATTRIB_SETTABLE; surface_attrib.value.type = VAGenericValueTypeInteger; surface_attrib.value.value.i = VA_FOURCC_NV12; // Create surface (Reference Surfaces + Input Surfaces) va_status = vaCreateSurfaces( vaapi_context.display, VA_RT_FORMAT_YUV420, settings.width, settings.height, vaapi_context.surfaces, NUM_SURFACES_TOTAL, &surface_attrib, 1 ); CHECK_VASTATUS(va_status, "vaCreateSurfaces"); vaapi_context.recon_surface = vaapi_context.surfaces[0]; vaapi_context.last_ref_surface = VA_INVALID_SURFACE; vaapi_context.golden_ref_surface = VA_INVALID_SURFACE; vaapi_context.alt_ref_surface = VA_INVALID_SURFACE; vaapi_context.input_surface = vaapi_context.surfaces[NUM_REF_SURFACES]; // input surfaces trail the reference surfaces /* Create a context for this Encoder pipe */ /* the surface is added to the render_target list when creating the context */ va_status = vaCreateContext(vaapi_context.display, vaapi_context.config_id, settings.width, settings.height, VA_PROGRESSIVE, vaapi_context.surfaces, NUM_SURFACES_TOTAL, &vaapi_context.context_id); CHECK_VASTATUS(va_status, "vaCreateContext"); } void vp8enc_destory_EncoderPipe() { pthread_join(vaapi_context.upload_thread.id,NULL); vaDestroySurfaces(vaapi_context.display, vaapi_context.surfaces, NUM_SURFACES_TOTAL); vaDestroyContext(vaapi_context.display,vaapi_context.context_id); vaDestroyConfig(vaapi_context.display,vaapi_context.config_id); vaTerminate(vaapi_context.display); va_close_display(vaapi_context.display); } void vp8enc_init_VaapiContext() { size_t i; vaapi_context.profile = VAProfileVP8Version0_3; vp8enc_init_SequenceParameterBuffer(&vaapi_context.seq_param); vp8enc_init_PictureParameterBuffer(&vaapi_context.pic_param); vp8enc_init_QMatrix(&vaapi_context.q_matrix); vaapi_context.hrd_param.header.type = VAEncMiscParameterTypeHRD; vaapi_context.frame_rate_param.header.type = VAEncMiscParameterTypeFrameRate; vaapi_context.rate_control_param.header.type = VAEncMiscParameterTypeRateControl; vp8enc_init_MiscParameterBuffers(&vaapi_context.hrd_param.data, &vaapi_context.frame_rate_param.data,&vaapi_context.rate_control_param.data); for(i = 0; i < N_ELEMENTS(vaapi_context.va_buffers);i++) vaapi_context.va_buffers[i] = VA_INVALID_ID; vaapi_context.num_va_buffers = 0; vaapi_context.is_golden_refreshed = 0; } static int vp8enc_store_coded_buffer(FILE *vp8_fp,uint64_t timestamp) { VACodedBufferSegment *coded_buffer_segment; uint8_t *coded_mem; int data_length; VAStatus va_status; VASurfaceStatus surface_status; size_t w_items; va_status = vaSyncSurface(vaapi_context.display, vaapi_context.recon_surface); CHECK_VASTATUS(va_status,"vaSyncSurface"); surface_status = 0; va_status = vaQuerySurfaceStatus(vaapi_context.display, vaapi_context.recon_surface, &surface_status); CHECK_VASTATUS(va_status,"vaQuerySurfaceStatus"); va_status = vaMapBuffer(vaapi_context.display, vaapi_context.codedbuf_buf_id, (void **)(&coded_buffer_segment)); CHECK_VASTATUS(va_status,"vaMapBuffer"); coded_mem = coded_buffer_segment->buf; if (coded_buffer_segment->status & VA_CODED_BUF_STATUS_SLICE_OVERFLOW_MASK) { fprintf(stderr,"Error: CodeBuffer Size too small\n"); vaUnmapBuffer(vaapi_context.display, vaapi_context.codedbuf_buf_id); assert(0); } data_length = coded_buffer_segment->size; vp8enc_write_frame_header(vp8_fp, data_length,timestamp); do { w_items = fwrite(coded_mem, data_length, 1, vp8_fp); } while (w_items != 1); if(settings.debug) fprintf(stderr,"Timestamp: %ld Bytes written %d\n",timestamp,data_length); vaUnmapBuffer(vaapi_context.display, vaapi_context.codedbuf_buf_id); return 0; } size_t vp8enc_get_FileSize(FILE *fp) { struct stat st; fstat(fileno(fp), &st); return st.st_size; } int vp8enc_prepare_buffers(int frame_type) { int num_buffers = 0; VABufferID *va_buffers; VAStatus va_status; VAEncPictureParameterBufferVP8 *picParam = &vaapi_context.pic_param; va_buffers = vaapi_context.va_buffers; /* coded buffer */ va_status = vaCreateBuffer(vaapi_context.display, vaapi_context.context_id, VAEncCodedBufferType, settings.codedbuf_size, 1, NULL, &vaapi_context.codedbuf_buf_id); CHECK_VASTATUS(va_status,"vaCreateBuffer"); /* sequence parameter set */ va_status = vaCreateBuffer(vaapi_context.display, vaapi_context.context_id, VAEncSequenceParameterBufferType, sizeof(vaapi_context.seq_param), 1, &vaapi_context.seq_param, va_buffers); CHECK_VASTATUS(va_status,"vaCreateBuffer"); va_buffers ++; num_buffers++; /* picture parameter set */ picParam->coded_buf = vaapi_context.codedbuf_buf_id; va_status = vaCreateBuffer(vaapi_context.display, vaapi_context.context_id, VAEncPictureParameterBufferType, sizeof(vaapi_context.pic_param), 1, &vaapi_context.pic_param, va_buffers); CHECK_VASTATUS(va_status,"vaCreateBuffer"); va_buffers ++; num_buffers++; /* hrd parameter */ vaCreateBuffer(vaapi_context.display, vaapi_context.context_id, VAEncMiscParameterBufferType, sizeof(vaapi_context.hrd_param), 1, &vaapi_context.hrd_param, va_buffers); CHECK_VASTATUS(va_status, "vaCreateBuffer"); va_buffers ++; num_buffers++; /* QMatrix */ va_status = vaCreateBuffer(vaapi_context.display, vaapi_context.context_id, VAQMatrixBufferType, sizeof(vaapi_context.q_matrix), 1, &vaapi_context.q_matrix, va_buffers); CHECK_VASTATUS(va_status,"vaCreateBuffer"); va_buffers ++; num_buffers++; /* Create the Misc FR/RC buffer under non-CQP mode */ if (settings.rc_mode != VA_RC_CQP && frame_type == KEY_FRAME) { vaCreateBuffer(vaapi_context.display, vaapi_context.context_id, VAEncMiscParameterBufferType, sizeof(vaapi_context.frame_rate_param),1,&vaapi_context.frame_rate_param, va_buffers); CHECK_VASTATUS(va_status, "vaCreateBuffer"); va_buffers ++; num_buffers++; vaCreateBuffer(vaapi_context.display, vaapi_context.context_id, VAEncMiscParameterBufferType, sizeof(vaapi_context.rate_control_param),1,&vaapi_context.rate_control_param, va_buffers); CHECK_VASTATUS(va_status, "vaCreateBuffer"); va_buffers ++; num_buffers++; } vaapi_context.num_va_buffers = num_buffers; return num_buffers; } static void vp8enc_render_picture() { VAStatus va_status; va_status = vaBeginPicture(vaapi_context.display, vaapi_context.context_id, vaapi_context.input_surface); CHECK_VASTATUS(va_status,"vaBeginPicture"); va_status = vaRenderPicture(vaapi_context.display, vaapi_context.context_id, vaapi_context.va_buffers, vaapi_context.num_va_buffers); CHECK_VASTATUS(va_status,"vaRenderPicture"); va_status = vaEndPicture(vaapi_context.display, vaapi_context.context_id); CHECK_VASTATUS(va_status,"vaEndPicture"); } void vp8enc_destroy_buffers() { int i; VAStatus va_status; for(i = 0; i < vaapi_context.num_va_buffers; i++) { if (vaapi_context.va_buffers[i] != VA_INVALID_ID) { va_status = vaDestroyBuffer(vaapi_context.display, vaapi_context.va_buffers[i]); CHECK_VASTATUS(va_status,"vaDestroyBuffer"); vaapi_context.va_buffers[i] = VA_INVALID_ID; } } if (vaapi_context.codedbuf_buf_id != VA_INVALID_ID) { va_status = vaDestroyBuffer(vaapi_context.display, vaapi_context.codedbuf_buf_id); CHECK_VASTATUS(va_status,"vaDestroyBuffer"); vaapi_context.codedbuf_buf_id = VA_INVALID_ID; } } void vp8enc_show_help () { printf("Usage: vp8enc additional_option\n"); printf("output_vp8 should use *.ivf\n"); printf("The additional option is listed\n"); printf("-f \n"); printf("--intra_period \n"); printf("--qp \n"); printf("--rcmode 0: CQP, 1: CBR, 2: VBR\n"); printf("--fb (kbps unit)\n"); printf("--lf_level [0-63]\n"); printf("--hrd_win [1000-8000]\n"); printf("--vbr_max (kbps unit. It should be greater than fb)\n"); printf("--fn_num \n how many frames to be encoded\n"); printf("--error_resilient Turn on Error resilient mode\n"); printf("--debug Turn debug info on\n"); printf("--temp_svc Number of temporal layers 2 or 3\n"); printf("--repeat Number of times to repeat the encoding\n"); } void parameter_check(const char *param, int val, int min, int max) { if(val < min || val > max) { fprintf(stderr,"Error: %s out of range (%d..%d) \n",param,min,max); exit(VP8ENC_FAIL); } } void parameter_check_positive(const char *param, int val, int min) { if(val < 1) { fprintf(stderr,"Error: %s demands a positive value greater than %d \n",param,min); exit(VP8ENC_FAIL); } } int parse_options(int ac,char *av[]) { int c, long_index, tmp_input; while (1) { c = getopt_long_only(ac,av,"hf:?",long_opts,&long_index); if (c == -1) break; switch(c) { case 'f': tmp_input = atoi(optarg); parameter_check_positive("-f",tmp_input,1); settings.frame_rate = tmp_input; break; case 1: tmp_input = atoi(optarg); parameter_check("--rcmode", tmp_input, 0, 2); settings.rc_mode = default_rc_modes[tmp_input]; break; case 2: tmp_input = atoi(optarg); parameter_check("--qp", tmp_input, 0, 255); settings.quantization_parameter = tmp_input; break; case 3: tmp_input = atoi(optarg); parameter_check_positive("--intra_period",tmp_input,1); settings.intra_period = tmp_input; break; case 4: tmp_input = atoi(optarg); parameter_check_positive("--fb",tmp_input,1); settings.frame_bitrate = tmp_input; break; case 5: tmp_input = atoi(optarg); parameter_check("--lf_level",tmp_input, 0, 63); settings.loop_filter_level = tmp_input; break; case 6: tmp_input = atoi(optarg); parameter_check("--hrd_win",tmp_input,1000,8000); settings.hrd_window = tmp_input; break; case 7: tmp_input = atoi(optarg); parameter_check_positive("--vbr_max",tmp_input,1); settings.max_variable_bitrate = tmp_input; break; case 8: tmp_input = atoi(optarg); parameter_check_positive("--fn_num" ,tmp_input,1); settings.num_frames = tmp_input; break; case 9: settings.error_resilient = 1; break; case 10: settings.debug = 1; break; case 11: tmp_input = atoi(optarg); parameter_check("--temp_svc",tmp_input,2,3); settings.temporal_svc_layers = tmp_input; break; case 12: tmp_input = atoi(optarg); parameter_check("--repeat", tmp_input, 1, 1000000); settings.repeat_times = tmp_input; break; case 'h': case 0: default: return PARSE_OPTIONS_FAIL; break; } } return PARSE_OPTIONS_OK; } int main(int argc, char *argv[]) { int current_frame, frame_type; FILE *fp_vp8_output = NULL; FILE *fp_yuv_input = NULL; uint64_t timestamp; struct timeval t1,t2; double fps, elapsed_time; if(argc < 5) { vp8enc_show_help(); return VP8ENC_FAIL; } if(parse_options(argc-4, &argv[4]) != PARSE_OPTIONS_OK) { vp8enc_show_help(); return VP8ENC_FAIL; } settings.width = atoi(argv[1]); parameter_check("Width", settings.width, 16, MAX_XY_RESOLUTION); settings.height = atoi(argv[2]); parameter_check("Height", settings.height, 16, MAX_XY_RESOLUTION); if( settings.rc_mode == VA_RC_VBR && settings.max_variable_bitrate < settings.frame_bitrate) { fprintf(stderr,"Error: max. variable bitrate should be greater than frame bitrate (--vbr_max >= --fb)\n"); return VP8ENC_FAIL; } if(argv[3]) { fp_yuv_input = fopen(argv[3],"rb"); } if(fp_yuv_input == NULL) { fprintf(stderr,"Error: Couldn't open input file.\n"); return VP8ENC_FAIL; } vaapi_context.upload_thread.input_fp = fp_yuv_input; fp_vp8_output = fopen(argv[4],"wb"); if(fp_vp8_output == NULL) { fprintf(stderr,"Error: Couldn't open output file.\n"); return VP8ENC_FAIL; } if( settings.temporal_svc_layers == 2 && settings.intra_period % 2) fprintf(stderr,"Warning: Choose Key-Frame interval (--intra_period) to be integer mutliply of 2 to match temporal layer pattern"); if ( settings.temporal_svc_layers == 3 && settings.intra_period % 4 ) fprintf(stderr,"Warning: Choose Key-Frame interval (--intra_period) to be integer mutliply of 4 to match temporal layer pattern"); settings.frame_size = settings.width * settings.height * 3 / 2; //NV12 Colorspace - For a 2x2 group of pixels, you have 4 Y samples and 1 U and 1 V sample. if(!settings.num_frames) settings.num_frames = vp8enc_get_FileSize(fp_yuv_input)/(size_t)settings.frame_size; settings.codedbuf_size = settings.width * settings.height; //just a generous assumptions fprintf(stderr,"Info: Encoding total of %d frames.\n",settings.num_frames); gettimeofday(&t1,0); //Measure Runtime vp8enc_init_VaapiContext(); vp8enc_create_EncoderPipe(); vp8enc_write_ivf_header(fp_vp8_output); current_frame = 0; timestamp = 0; vaapi_context.input_surface = vaapi_context.surfaces[SID_INPUT_PICTURE_0]; vaapi_context.upload_thread.input_surface_num = SID_INPUT_PICTURE_0; while (current_frame < settings.num_frames * settings.repeat_times) { fprintf(stderr,"\rProcessing frame: %d",current_frame); if ( (current_frame % settings.intra_period) == 0) frame_type = KEY_FRAME; else frame_type = INTER_FRAME; if(current_frame == 0) { // Preload first input_surface vp8enc_upload_yuv_to_surface(fp_yuv_input, vaapi_context.input_surface, current_frame); //prefill } else { // wait for input processing thread to finish pthread_join(vaapi_context.upload_thread.id, NULL); vaapi_context.input_surface = vaapi_context.upload_thread.input_surface; } // Start Upload thread if((current_frame + 1) < settings.num_frames * settings.repeat_times) { vaapi_context.upload_thread.processed_frame = (current_frame % settings.num_frames - 1) + 1; if(vaapi_context.upload_thread.input_surface_num == SID_INPUT_PICTURE_0) vaapi_context.upload_thread.input_surface_num = SID_INPUT_PICTURE_1; else vaapi_context.upload_thread.input_surface_num = SID_INPUT_PICTURE_0; vaapi_context.upload_thread.input_surface = vaapi_context.surfaces[vaapi_context.upload_thread.input_surface_num]; vaapi_context.upload_thread.value = pthread_create(&vaapi_context.upload_thread.id,NULL,vp8enc_upload_thread_function,NULL); } vp8enc_update_picture_parameter(frame_type, current_frame); vp8enc_prepare_buffers(frame_type); vp8enc_render_picture(); vp8enc_store_coded_buffer(fp_vp8_output, timestamp); vp8enc_destroy_buffers(); vp8enc_update_reference_list(frame_type); current_frame ++; timestamp ++; } vp8enc_destory_EncoderPipe(); fclose(fp_vp8_output); fclose(fp_yuv_input); gettimeofday(&t2,0); elapsed_time = (double)(t2.tv_sec-t1.tv_sec)+(double)(t2.tv_usec-t1.tv_usec)/1000000.0; fps = (double)current_frame/elapsed_time; fprintf(stderr, "\nProcessed %d frames in %.0f ms (%.2f FPS)\n",current_frame,elapsed_time*1000.0,fps); return VP8ENC_OK; }