From 6aae4d39d510ed4d30c029b15d01166380f63e47 Mon Sep 17 00:00:00 2001 From: Ander Conselvan de Oliveira Date: Fri, 23 Aug 2013 17:15:48 +0300 Subject: [PATCH] compositor-drm: Add hardware accelerated capture of screen using libva This patch adds a feature to the DRM backend that uses libva for encoding the screen contents in H.264. Screen recording can be activated by pressing mod-shift-space q. A file named capture.h264 will be created in the current directory, which can be muxed into an MP4 file with gstreamer using gst-launch filesrc location=capture.h264 ! h264parse ! mp4mux ! \ filesink location=file.mp4 This is limitted to the DRM compositor in order to avoid a copy when submitting the front buffer to libva. The code in vaapi-recorder.c takes a dma_buf fd referencing it, does a colorspace conversion using the video post processing pipeline and then uses that as input to the encoder. I'm sending this now so I get comments, but this is not ready for prime time yet. I have a somewhat consistent GPU hang when using i915 with SandyBridge. Sometimes a page flip never completes. If you want to try this anyway and your system get stuck, you might need to run the following: # echo 1 > /sys/kernel/debug/dri/0/i915_wedged After that, alt-sysrq [rv] should work. Once that's fixed it would also be good to make the parameters used by the encoder more flexible. For now the QP parameter is hardcoded to 0 and we have only I and P frames (no B frames), which causes the resulting files to be very large. --- configure.ac | 6 + src/Makefile.am | 6 + src/compositor-drm.c | 109 ++++++ src/vaapi-recorder.c | 1062 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/vaapi-recorder.h | 35 ++ 5 files changed, 1218 insertions(+) create mode 100644 src/vaapi-recorder.c create mode 100644 src/vaapi-recorder.h diff --git a/configure.ac b/configure.ac index 43e5e5c..05d1186 100644 --- a/configure.ac +++ b/configure.ac @@ -240,6 +240,11 @@ PKG_CHECK_MODULES(WEBP, [libwebp], [have_webp=yes], [have_webp=no]) AS_IF([test "x$have_webp" = "xyes"], [AC_DEFINE([HAVE_WEBP], [1], [Have webp])]) +PKG_CHECK_MODULES(LIBVA, [libva >= 0.34.0 libva-drm >= 0.34.0], [have_libva=yes], [have_libva=no]) +AS_IF([test "x$have_libva" = "xyes"], + [AC_DEFINE([HAVE_LIBVA], [1], [Have libva])]) +AM_CONDITIONAL(ENABLE_LIBVA, test "x$have_libva" = "xyes") + AC_CHECK_LIB([jpeg], [jpeg_CreateDecompress], have_jpeglib=yes) if test x$have_jpeglib = xyes; then JPEG_LIBS="-ljpeg" @@ -480,4 +485,5 @@ AC_MSG_RESULT([ LCMS2 Support ${have_lcms} libwebp Support ${have_webp} libunwind Support ${have_libunwind} + VA H.264 encoding Support ${have_libva} ]) diff --git a/src/Makefile.am b/src/Makefile.am index 80c4b87..affa774 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -152,6 +152,12 @@ drm_backend_la_SOURCES = \ launcher-util.h \ libbacklight.c \ libbacklight.h + +if ENABLE_LIBVA +drm_backend_la_SOURCES += vaapi-recorder.c +drm_backend_la_LIBADD += $(LIBVA_LIBS) +drm_backend_la_CFLAGS += $(LIBVA_CFLAGS) +endif endif if ENABLE_WAYLAND_COMPOSITOR diff --git a/src/compositor-drm.c b/src/compositor-drm.c index ef2771f..23d6f0f 100644 --- a/src/compositor-drm.c +++ b/src/compositor-drm.c @@ -47,6 +47,7 @@ #include "pixman-renderer.h" #include "udev-seat.h" #include "launcher-util.h" +#include "vaapi-recorder.h" #ifndef DRM_CAP_TIMESTAMP_MONOTONIC #define DRM_CAP_TIMESTAMP_MONOTONIC 0x6 @@ -75,6 +76,7 @@ struct drm_compositor { struct { int id; int fd; + char *filename; } drm; struct gbm_device *gbm; uint32_t *crtcs; @@ -159,6 +161,9 @@ struct drm_output { pixman_image_t *image[2]; int current_image; pixman_region32_t previous_damage; + + struct vaapi_recorder *recorder; + struct wl_listener recorder_frame_listener; }; /* @@ -717,6 +722,11 @@ page_flip_handler(int fd, unsigned int frame, if (!output->vblank_pending) { msecs = sec * 1000 + usec / 1000; weston_output_finish_frame(&output->base, msecs); + + /* We can't call this from frame_notify, because the output's + * repaint needed flag is cleared just after that */ + if (output->recorder) + weston_output_schedule_repaint(&output->base); } } @@ -1215,6 +1225,7 @@ init_drm(struct drm_compositor *ec, struct udev_device *device) weston_log("using %s\n", filename); ec->drm.fd = fd; + ec->drm.filename = strdup(filename); ret = drmGetCap(fd, DRM_CAP_TIMESTAMP_MONOTONIC, &cap); if (ret == 0 && cap == 1) @@ -2435,6 +2446,102 @@ planes_binding(struct weston_seat *seat, uint32_t time, uint32_t key, void *data } } +#ifdef HAVE_LIBVA +static void +recorder_frame_notify(struct wl_listener *listener, void *data) +{ + struct drm_output *output; + struct drm_compositor *c; + int fd, ret; + + output = container_of(listener, struct drm_output, + recorder_frame_listener); + c = (struct drm_compositor *) output->base.compositor; + + if (!output->recorder) + return; + + ret = drmPrimeHandleToFD(c->drm.fd, output->current->handle, + DRM_CLOEXEC, &fd); + if (ret) { + weston_log("[libva recorder] " + "failed to create prime fd for front buffer\n"); + return; + } + + vaapi_recorder_frame(output->recorder, fd, output->current->stride / 4); + + close(fd); +} + +static void * +create_recorder(struct drm_compositor *c, int width, int height, + const char *filename) +{ + int fd; + drm_magic_t magic; + + fd = open(c->drm.filename, O_RDWR | O_CLOEXEC); + if (fd < 0) + return NULL; + + drmGetMagic(fd, &magic); + drmAuthMagic(c->drm.fd, magic); + + return vaapi_recorder_create(fd, width, height, filename); +} + +static void +recorder_binding(struct weston_seat *seat, uint32_t time, uint32_t key, + void *data) +{ + struct drm_compositor *c = data; + struct drm_output *output; + int width, height; + + output = container_of(c->base.output_list.next, + struct drm_output, base.link); + + if (!output->recorder) { + width = output->base.current->width; + height = output->base.current->height; + + output->recorder = + create_recorder(c, width, height, "capture.h264"); + if (!output->recorder) { + weston_log("failed to create vaapi recorder\n"); + return; + } + + output->base.disable_planes++; + + output->recorder_frame_listener.notify = recorder_frame_notify; + wl_signal_add(&output->base.frame_signal, + &output->recorder_frame_listener); + + weston_output_schedule_repaint(&output->base); + + weston_log("[libva recorder] initialized\n"); + } else { + vaapi_recorder_destroy(output->recorder); + /* FIXME: close drm fd passed to recorder */ + output->recorder = NULL; + + output->base.disable_planes--; + + wl_list_remove(&output->recorder_frame_listener.link); + weston_log("[libva recorder] done\n"); + } +} +#else +static void +recorder_binding(struct weston_seat *seat, uint32_t time, uint32_t key, + void *data) +{ + weston_log("Compiled without libva support\n"); +} +#endif + static struct weston_compositor * drm_compositor_create(struct wl_display *display, int connector, const char *seat_id, int tty, int pixman, @@ -2568,6 +2675,8 @@ drm_compositor_create(struct wl_display *display, planes_binding, ec); weston_compositor_add_debug_binding(&ec->base, KEY_V, planes_binding, ec); + weston_compositor_add_debug_binding(&ec->base, KEY_Q, + recorder_binding, ec); return &ec->base; diff --git a/src/vaapi-recorder.c b/src/vaapi-recorder.c new file mode 100644 index 0000000..c0210f0 --- /dev/null +++ b/src/vaapi-recorder.c @@ -0,0 +1,1062 @@ +/* + * Copyright © 2013 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* Copyright (c) 2012 Intel Corporation. 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. + */ + +#include +#include +#include +#include + +#include +#include +#include + + +#include +#include +#include +#include +#include + +#include "compositor.h" +#include "vaapi-recorder.h" + +#define NAL_REF_IDC_NONE 0 +#define NAL_REF_IDC_LOW 1 +#define NAL_REF_IDC_MEDIUM 2 +#define NAL_REF_IDC_HIGH 3 + +#define NAL_NON_IDR 1 +#define NAL_IDR 5 +#define NAL_SPS 7 +#define NAL_PPS 8 +#define NAL_SEI 6 + +#define SLICE_TYPE_P 0 +#define SLICE_TYPE_B 1 +#define SLICE_TYPE_I 2 + +#define ENTROPY_MODE_CAVLC 0 +#define ENTROPY_MODE_CABAC 1 + +#define PROFILE_IDC_BASELINE 66 +#define PROFILE_IDC_MAIN 77 +#define PROFILE_IDC_HIGH 100 + +struct vaapi_recorder { + int output_fd; + int width, height; + int frame_count; + + VADisplay va_dpy; + + /* video post processing is used for colorspace conversion */ + struct { + VAConfigID cfg; + VAContextID ctx; + VABufferID pipeline_buf; + VASurfaceID output; + } vpp; + + struct { + VAConfigID cfg; + VAContextID ctx; + VASurfaceID reference_picture[3]; + + int intra_period; + int output_size; + int constraint_set_flag; + + struct { + VAEncSequenceParameterBufferH264 seq; + VAEncPictureParameterBufferH264 pic; + VAEncSliceParameterBufferH264 slice; + } param; + } encoder; +}; + +/* bistream code used for writing the packed headers */ + +#define BITSTREAM_ALLOCATE_STEPPING 4096 + +struct bitstream { + unsigned int *buffer; + int bit_offset; + int max_size_in_dword; +}; + +static unsigned int +va_swap32(unsigned int val) +{ + unsigned char *pval = (unsigned char *)&val; + + return ((pval[0] << 24) | + (pval[1] << 16) | + (pval[2] << 8) | + (pval[3] << 0)); +} + +static void +bitstream_start(struct bitstream *bs) +{ + bs->max_size_in_dword = BITSTREAM_ALLOCATE_STEPPING; + bs->buffer = calloc(bs->max_size_in_dword * sizeof(int), 1); + bs->bit_offset = 0; +} + +static void +bitstream_end(struct bitstream *bs) +{ + int pos = (bs->bit_offset >> 5); + int bit_offset = (bs->bit_offset & 0x1f); + int bit_left = 32 - bit_offset; + + if (bit_offset) { + bs->buffer[pos] = va_swap32((bs->buffer[pos] << bit_left)); + } +} + +static void +bitstream_put_ui(struct bitstream *bs, unsigned int val, int size_in_bits) +{ + int pos = (bs->bit_offset >> 5); + int bit_offset = (bs->bit_offset & 0x1f); + int bit_left = 32 - bit_offset; + + if (!size_in_bits) + return; + + bs->bit_offset += size_in_bits; + + if (bit_left > size_in_bits) { + bs->buffer[pos] = (bs->buffer[pos] << size_in_bits | val); + return; + } + + size_in_bits -= bit_left; + bs->buffer[pos] = + (bs->buffer[pos] << bit_left) | (val >> size_in_bits); + bs->buffer[pos] = va_swap32(bs->buffer[pos]); + + if (pos + 1 == bs->max_size_in_dword) { + bs->max_size_in_dword += BITSTREAM_ALLOCATE_STEPPING; + bs->buffer = + realloc(bs->buffer, + bs->max_size_in_dword * sizeof(unsigned int)); + } + + bs->buffer[pos + 1] = val; +} + +static void +bitstream_put_ue(struct bitstream *bs, unsigned int val) +{ + int size_in_bits = 0; + int tmp_val = ++val; + + while (tmp_val) { + tmp_val >>= 1; + size_in_bits++; + } + + bitstream_put_ui(bs, 0, size_in_bits - 1); // leading zero + bitstream_put_ui(bs, val, size_in_bits); +} + +static void +bitstream_put_se(struct bitstream *bs, int val) +{ + unsigned int new_val; + + if (val <= 0) + new_val = -2 * val; + else + new_val = 2 * val - 1; + + bitstream_put_ue(bs, new_val); +} + +static void +bitstream_byte_aligning(struct bitstream *bs, int bit) +{ + int bit_offset = (bs->bit_offset & 0x7); + int bit_left = 8 - bit_offset; + int new_val; + + if (!bit_offset) + return; + + if (bit) + new_val = (1 << bit_left) - 1; + else + new_val = 0; + + bitstream_put_ui(bs, new_val, bit_left); +} + +static VAStatus +encoder_create_config(struct vaapi_recorder *r) +{ + VAConfigAttrib attrib[2]; + VAStatus status; + + /* FIXME: should check if VAEntrypointEncSlice is supported */ + + /* FIXME: should check if specified attributes are supported */ + + attrib[0].type = VAConfigAttribRTFormat; + attrib[0].value = VA_RT_FORMAT_YUV420; + + attrib[1].type = VAConfigAttribRateControl; + attrib[1].value = VA_RC_CQP; + + status = vaCreateConfig(r->va_dpy, VAProfileH264Main, + VAEntrypointEncSlice, attrib, 2, + &r->encoder.cfg); + if (status != VA_STATUS_SUCCESS) + return status; + + status = vaCreateContext(r->va_dpy, r->encoder.cfg, + r->width, r->height, VA_PROGRESSIVE, 0, 0, + &r->encoder.ctx); + if (status != VA_STATUS_SUCCESS) { + vaDestroyConfig(r->va_dpy, r->encoder.cfg); + return status; + } + + return VA_STATUS_SUCCESS; +} + +static void +encoder_destroy_config(struct vaapi_recorder *r) +{ + vaDestroyContext(r->va_dpy, r->encoder.ctx); + vaDestroyConfig(r->va_dpy, r->encoder.cfg); +} + +static void +encoder_init_seq_parameters(struct vaapi_recorder *r) +{ + int width_in_mbs, height_in_mbs; + int frame_cropping_flag = 0; + int frame_crop_bottom_offset = 0; + + width_in_mbs = (r->width + 15) / 16; + height_in_mbs = (r->height + 15) / 16; + + r->encoder.param.seq.level_idc = 41; + r->encoder.param.seq.intra_period = r->encoder.intra_period; + r->encoder.param.seq.max_num_ref_frames = 4; + r->encoder.param.seq.picture_width_in_mbs = width_in_mbs; + r->encoder.param.seq.picture_height_in_mbs = height_in_mbs; + r->encoder.param.seq.seq_fields.bits.frame_mbs_only_flag = 1; + + /* Tc = num_units_in_tick / time_scale */ + r->encoder.param.seq.time_scale = 1800; + r->encoder.param.seq.num_units_in_tick = 15; + + if (height_in_mbs * 16 - r->height > 0) { + frame_cropping_flag = 1; + frame_crop_bottom_offset = (height_in_mbs * 16 - r->height) / 2; + } + + r->encoder.param.seq.frame_cropping_flag = frame_cropping_flag; + r->encoder.param.seq.frame_crop_bottom_offset = frame_crop_bottom_offset; + + r->encoder.param.seq.seq_fields.bits.log2_max_pic_order_cnt_lsb_minus4 = 2; +} + +static VABufferID +encoder_update_seq_parameters(struct vaapi_recorder *r) +{ + VABufferID seq_buf; + VAStatus status; + + status = vaCreateBuffer(r->va_dpy, r->encoder.ctx, + VAEncSequenceParameterBufferType, + sizeof(r->encoder.param.seq), + 1, &r->encoder.param.seq, + &seq_buf); + + if (status == VA_STATUS_SUCCESS) + return seq_buf; + else + return VA_INVALID_ID; +} + +static void +encoder_init_pic_parameters(struct vaapi_recorder *r) +{ + VAEncPictureParameterBufferH264 *pic = &r->encoder.param.pic; + + pic->pic_init_qp = 0; + + /* ENTROPY_MODE_CABAC */ + pic->pic_fields.bits.entropy_coding_mode_flag = 1; + + pic->pic_fields.bits.deblocking_filter_control_present_flag = 1; +} + +static VABufferID +encoder_update_pic_parameters(struct vaapi_recorder *r, + VABufferID output_buf) +{ + VAEncPictureParameterBufferH264 *pic = &r->encoder.param.pic; + VAStatus status; + VABufferID pic_param_buf; + VASurfaceID curr_pic, pic0; + + curr_pic = r->encoder.reference_picture[r->frame_count % 2]; + pic0 = r->encoder.reference_picture[(r->frame_count + 1) % 2]; + + pic->CurrPic.picture_id = curr_pic; + pic->CurrPic.TopFieldOrderCnt = r->frame_count * 2; + pic->ReferenceFrames[0].picture_id = pic0; + pic->ReferenceFrames[1].picture_id = r->encoder.reference_picture[2]; + pic->ReferenceFrames[2].picture_id = VA_INVALID_ID; + + pic->coded_buf = output_buf; + pic->frame_num = r->frame_count; + + pic->pic_fields.bits.idr_pic_flag = (r->frame_count == 0); + pic->pic_fields.bits.reference_pic_flag = 1; + + status = vaCreateBuffer(r->va_dpy, r->encoder.ctx, + VAEncPictureParameterBufferType, + sizeof(VAEncPictureParameterBufferH264), 1, + pic, &pic_param_buf); + + if (status == VA_STATUS_SUCCESS) + return pic_param_buf; + else + return VA_INVALID_ID; +} + +static VABufferID +encoder_update_slice_parameter(struct vaapi_recorder *r, int slice_type) +{ + VABufferID slice_param_buf; + VAStatus status; + + int width_in_mbs = (r->width + 15) / 16; + int height_in_mbs = (r->height + 15) / 16; + + memset(&r->encoder.param.slice, 0, sizeof r->encoder.param.slice); + + r->encoder.param.slice.num_macroblocks = width_in_mbs * height_in_mbs; + r->encoder.param.slice.slice_type = slice_type; + + r->encoder.param.slice.slice_alpha_c0_offset_div2 = 2; + r->encoder.param.slice.slice_beta_offset_div2 = 2; + + status = vaCreateBuffer(r->va_dpy, r->encoder.ctx, + VAEncSliceParameterBufferType, + sizeof(r->encoder.param.slice), 1, + &r->encoder.param.slice, + &slice_param_buf); + + if (status == VA_STATUS_SUCCESS) + return slice_param_buf; + else + return VA_INVALID_ID; +} + +static VABufferID +encoder_update_misc_hdr_parameter(struct vaapi_recorder *r) +{ + VAEncMiscParameterBuffer *misc_param; + VAEncMiscParameterHRD *hrd; + VABufferID buffer; + VAStatus status; + + int total_size = + sizeof(VAEncMiscParameterBuffer) + + sizeof(VAEncMiscParameterRateControl); + + status = vaCreateBuffer(r->va_dpy, r->encoder.ctx, + VAEncMiscParameterBufferType, total_size, + 1, NULL, &buffer); + if (status != VA_STATUS_SUCCESS) + return VA_INVALID_ID; + + status = vaMapBuffer(r->va_dpy, buffer, (void **) &misc_param); + if (status != VA_STATUS_SUCCESS) { + vaDestroyBuffer(r->va_dpy, buffer); + return VA_INVALID_ID; + } + + misc_param->type = VAEncMiscParameterTypeHRD; + hrd = (VAEncMiscParameterHRD *) misc_param->data; + + hrd->initial_buffer_fullness = 0; + hrd->buffer_size = 0; + + vaUnmapBuffer(r->va_dpy, buffer); + + return buffer; +} + +static int +setup_encoder(struct vaapi_recorder *r) +{ + VAStatus status; + + status = encoder_create_config(r); + if (status != VA_STATUS_SUCCESS) { + return -1; + } + + status = vaCreateSurfaces(r->va_dpy, VA_RT_FORMAT_YUV420, + r->width, r->height, + r->encoder.reference_picture, 3, + NULL, 0); + if (status != VA_STATUS_SUCCESS) { + encoder_destroy_config(r); + return -1; + } + + /* VAProfileH264Main */ + r->encoder.constraint_set_flag |= (1 << 1); /* Annex A.2.2 */ + + r->encoder.output_size = r->width * r->height; + + r->encoder.intra_period = 30; + + encoder_init_seq_parameters(r); + encoder_init_pic_parameters(r); + + return 0; +} + +static void +encoder_destroy(struct vaapi_recorder *r) +{ + vaDestroySurfaces(r->va_dpy, r->encoder.reference_picture, 3); + + encoder_destroy_config(r); +} + +static void +nal_start_code_prefix(struct bitstream *bs) +{ + bitstream_put_ui(bs, 0x00000001, 32); +} + +static void +nal_header(struct bitstream *bs, int nal_ref_idc, int nal_unit_type) +{ + /* forbidden_zero_bit: 0 */ + bitstream_put_ui(bs, 0, 1); + + bitstream_put_ui(bs, nal_ref_idc, 2); + bitstream_put_ui(bs, nal_unit_type, 5); +} + +static void +rbsp_trailing_bits(struct bitstream *bs) +{ + bitstream_put_ui(bs, 1, 1); + bitstream_byte_aligning(bs, 0); +} + +static void sps_rbsp(struct bitstream *bs, + VAEncSequenceParameterBufferH264 *seq, + int constraint_set_flag) +{ + int i; + + bitstream_put_ui(bs, PROFILE_IDC_MAIN, 8); + + /* constraint_set[0-3] flag */ + for (i = 0; i < 4; i++) { + int set = (constraint_set_flag & (1 << i)) ? 1 : 0; + bitstream_put_ui(bs, set, 1); + } + + /* reserved_zero_4bits */ + bitstream_put_ui(bs, 0, 4); + bitstream_put_ui(bs, seq->level_idc, 8); + bitstream_put_ue(bs, seq->seq_parameter_set_id); + + bitstream_put_ue(bs, seq->seq_fields.bits.log2_max_frame_num_minus4); + bitstream_put_ue(bs, seq->seq_fields.bits.pic_order_cnt_type); + bitstream_put_ue(bs, + seq->seq_fields.bits.log2_max_pic_order_cnt_lsb_minus4); + + bitstream_put_ue(bs, seq->max_num_ref_frames); + + /* gaps_in_frame_num_value_allowed_flag */ + bitstream_put_ui(bs, 0, 1); + + /* pic_width_in_mbs_minus1, pic_height_in_map_units_minus1 */ + bitstream_put_ue(bs, seq->picture_width_in_mbs - 1); + bitstream_put_ue(bs, seq->picture_height_in_mbs - 1); + + bitstream_put_ui(bs, seq->seq_fields.bits.frame_mbs_only_flag, 1); + bitstream_put_ui(bs, seq->seq_fields.bits.direct_8x8_inference_flag, 1); + + bitstream_put_ui(bs, seq->frame_cropping_flag, 1); + + if (seq->frame_cropping_flag) { + bitstream_put_ue(bs, seq->frame_crop_left_offset); + bitstream_put_ue(bs, seq->frame_crop_right_offset); + bitstream_put_ue(bs, seq->frame_crop_top_offset); + bitstream_put_ue(bs, seq->frame_crop_bottom_offset); + } + + /* vui_parameters_present_flag */ + bitstream_put_ui(bs, 1, 1); + + /* aspect_ratio_info_present_flag */ + bitstream_put_ui(bs, 0, 1); + /* overscan_info_present_flag */ + bitstream_put_ui(bs, 0, 1); + + /* video_signal_type_present_flag */ + bitstream_put_ui(bs, 0, 1); + /* chroma_loc_info_present_flag */ + bitstream_put_ui(bs, 0, 1); + + /* timing_info_present_flag */ + bitstream_put_ui(bs, 1, 1); + bitstream_put_ui(bs, seq->num_units_in_tick, 32); + bitstream_put_ui(bs, seq->time_scale, 32); + /* fixed_frame_rate_flag */ + bitstream_put_ui(bs, 1, 1); + + /* nal_hrd_parameters_present_flag */ + bitstream_put_ui(bs, 0, 1); + + /* vcl_hrd_parameters_present_flag */ + bitstream_put_ui(bs, 0, 1); + + /* low_delay_hrd_flag */ + bitstream_put_ui(bs, 0, 1); + + /* pic_struct_present_flag */ + bitstream_put_ui(bs, 0, 1); + /* bitstream_restriction_flag */ + bitstream_put_ui(bs, 0, 1); + + rbsp_trailing_bits(bs); +} + +static void pps_rbsp(struct bitstream *bs, + VAEncPictureParameterBufferH264 *pic) +{ + /* pic_parameter_set_id, seq_parameter_set_id */ + bitstream_put_ue(bs, pic->pic_parameter_set_id); + bitstream_put_ue(bs, pic->seq_parameter_set_id); + + bitstream_put_ui(bs, pic->pic_fields.bits.entropy_coding_mode_flag, 1); + + /* pic_order_present_flag: 0 */ + bitstream_put_ui(bs, 0, 1); + + /* num_slice_groups_minus1 */ + bitstream_put_ue(bs, 0); + + bitstream_put_ue(bs, pic->num_ref_idx_l0_active_minus1); + bitstream_put_ue(bs, pic->num_ref_idx_l1_active_minus1); + + bitstream_put_ui(bs, pic->pic_fields.bits.weighted_pred_flag, 1); + bitstream_put_ui(bs, pic->pic_fields.bits.weighted_bipred_idc, 2); + + /* pic_init_qp_minus26, pic_init_qs_minus26, chroma_qp_index_offset */ + bitstream_put_se(bs, pic->pic_init_qp - 26); + bitstream_put_se(bs, 0); + bitstream_put_se(bs, 0); + + bitstream_put_ui(bs, pic->pic_fields.bits.deblocking_filter_control_present_flag, 1); + + /* constrained_intra_pred_flag, redundant_pic_cnt_present_flag */ + bitstream_put_ui(bs, 0, 1); + bitstream_put_ui(bs, 0, 1); + + bitstream_put_ui(bs, pic->pic_fields.bits.transform_8x8_mode_flag, 1); + + /* pic_scaling_matrix_present_flag */ + bitstream_put_ui(bs, 0, 1); + bitstream_put_se(bs, pic->second_chroma_qp_index_offset ); + + rbsp_trailing_bits(bs); +} + +static int +build_packed_pic_buffer(struct vaapi_recorder *r, + void **header_buffer) +{ + struct bitstream bs; + + bitstream_start(&bs); + nal_start_code_prefix(&bs); + nal_header(&bs, NAL_REF_IDC_HIGH, NAL_PPS); + pps_rbsp(&bs, &r->encoder.param.pic); + bitstream_end(&bs); + + *header_buffer = bs.buffer; + return bs.bit_offset; +} + +static int +build_packed_seq_buffer(struct vaapi_recorder *r, + void **header_buffer) +{ + struct bitstream bs; + + bitstream_start(&bs); + nal_start_code_prefix(&bs); + nal_header(&bs, NAL_REF_IDC_HIGH, NAL_SPS); + sps_rbsp(&bs, &r->encoder.param.seq, r->encoder.constraint_set_flag); + bitstream_end(&bs); + + *header_buffer = bs.buffer; + return bs.bit_offset; +} + +static int +create_packed_header_buffers(struct vaapi_recorder *r, VABufferID *buffers, + VAEncPackedHeaderType type, + void *data, int bit_length) +{ + VAEncPackedHeaderParameterBuffer packed_header; + VAStatus status; + + packed_header.type = type; + packed_header.bit_length = bit_length; + packed_header.has_emulation_bytes = 0; + + status = vaCreateBuffer(r->va_dpy, r->encoder.ctx, + VAEncPackedHeaderParameterBufferType, + sizeof packed_header, 1, &packed_header, + &buffers[0]); + if (status != VA_STATUS_SUCCESS) + return 0; + + status = vaCreateBuffer(r->va_dpy, r->encoder.ctx, + VAEncPackedHeaderDataBufferType, + (bit_length + 7) / 8, 1, data, &buffers[1]); + if (status != VA_STATUS_SUCCESS) { + vaDestroyBuffer(r->va_dpy, buffers[0]); + return 0; + } + + return 2; +} + +static int +encoder_prepare_headers(struct vaapi_recorder *r, VABufferID *buffers) +{ + VABufferID *p; + + int bit_length; + void *data; + + p = buffers; + + bit_length = build_packed_seq_buffer(r, &data); + p += create_packed_header_buffers(r, p, VAEncPackedHeaderSequence, + data, bit_length); + free(data); + + bit_length = build_packed_pic_buffer(r, &data); + p += create_packed_header_buffers(r, p, VAEncPackedHeaderPicture, + data, bit_length); + free(data); + + return p - buffers; +} + +static VAStatus +encoder_render_picture(struct vaapi_recorder *r, VASurfaceID input, + VABufferID *buffers, int count) +{ + VAStatus status; + + status = vaBeginPicture(r->va_dpy, r->encoder.ctx, input); + if (status != VA_STATUS_SUCCESS) + return status; + + status = vaRenderPicture(r->va_dpy, r->encoder.ctx, buffers, count); + if (status != VA_STATUS_SUCCESS) + return status; + + status = vaEndPicture(r->va_dpy, r->encoder.ctx); + if (status != VA_STATUS_SUCCESS) + return status; + + return vaSyncSurface(r->va_dpy, input); +} + +static VABufferID +encoder_create_output_buffer(struct vaapi_recorder *r) +{ + VABufferID output_buf; + VAStatus status; + + status = vaCreateBuffer(r->va_dpy, r->encoder.ctx, + VAEncCodedBufferType, r->encoder.output_size, + 1, NULL, &output_buf); + if (status == VA_STATUS_SUCCESS) + return output_buf; + else + return VA_INVALID_ID; +} + +static int +encoder_write_output(struct vaapi_recorder *r, VABufferID output_buf) +{ + VACodedBufferSegment *segment; + VAStatus status; + int count; + + status = vaMapBuffer(r->va_dpy, output_buf, (void **) &segment); + if (status != VA_STATUS_SUCCESS) + return -1; + + if (segment->status & VA_CODED_BUF_STATUS_SLICE_OVERFLOW_MASK) { + r->encoder.output_size *= 2; + vaUnmapBuffer(r->va_dpy, output_buf); + return -1; + } + + count = write(r->output_fd, segment->buf, segment->size); + + vaUnmapBuffer(r->va_dpy, output_buf); + + return count; +} + +static void +encoder_encode(struct vaapi_recorder *r, VASurfaceID input) +{ + VABufferID output_buf = VA_INVALID_ID; + + VABufferID buffers[8]; + int count = 0; + + int slice_type; + int ret, i; + + if ((r->frame_count % r->encoder.intra_period) == 0) + slice_type = SLICE_TYPE_I; + else + slice_type = SLICE_TYPE_P; + + buffers[count++] = encoder_update_seq_parameters(r); + buffers[count++] = encoder_update_misc_hdr_parameter(r); + buffers[count++] = encoder_update_slice_parameter(r, slice_type); + + for (i = 0; i < count; i++) + if (buffers[i] == VA_INVALID_ID) + goto bail; + + if (r->frame_count == 0) + count += encoder_prepare_headers(r, buffers + count); + + do { + output_buf = encoder_create_output_buffer(r); + if (output_buf == VA_INVALID_ID) + goto bail; + + buffers[count++] = + encoder_update_pic_parameters(r, output_buf); + if (buffers[count - 1] == VA_INVALID_ID) + goto bail; + + encoder_render_picture(r, input, buffers, count); + ret = encoder_write_output(r, output_buf); + + vaDestroyBuffer(r->va_dpy, output_buf); + output_buf = VA_INVALID_ID; + + vaDestroyBuffer(r->va_dpy, buffers[--count]); + } while (ret < 0); + + for (i = 0; i < count; i++) + vaDestroyBuffer(r->va_dpy, buffers[i]); + + r->frame_count++; + return; + +bail: + for (i = 0; i < count; i++) + vaDestroyBuffer(r->va_dpy, buffers[i]); + if (output_buf != VA_INVALID_ID) + vaDestroyBuffer(r->va_dpy, output_buf); +} + + +static int +setup_vpp(struct vaapi_recorder *r) +{ + VAStatus status; + + status = vaCreateConfig(r->va_dpy, VAProfileNone, + VAEntrypointVideoProc, NULL, 0, + &r->vpp.cfg); + if (status != VA_STATUS_SUCCESS) { + weston_log("vaapi: failed to create VPP config\n"); + return -1; + } + + status = vaCreateContext(r->va_dpy, r->vpp.cfg, r->width, r->height, + 0, NULL, 0, &r->vpp.ctx); + if (status != VA_STATUS_SUCCESS) { + weston_log("vaapi: failed to create VPP context\n"); + goto err_cfg; + } + + status = vaCreateBuffer(r->va_dpy, r->vpp.ctx, + VAProcPipelineParameterBufferType, + sizeof(VAProcPipelineParameterBuffer), + 1, NULL, &r->vpp.pipeline_buf); + if (status != VA_STATUS_SUCCESS) { + weston_log("vaapi: failed to create VPP pipeline buffer\n"); + goto err_ctx; + } + + status = vaCreateSurfaces(r->va_dpy, VA_RT_FORMAT_YUV420, + r->width, r->height, &r->vpp.output, 1, + NULL, 0); + if (status != VA_STATUS_SUCCESS) { + weston_log("vaapi: failed to create YUV surface\n"); + goto err_buf; + } + + return 0; + +err_buf: + vaDestroyBuffer(r->va_dpy, r->vpp.pipeline_buf); +err_ctx: + vaDestroyConfig(r->va_dpy, r->vpp.ctx); +err_cfg: + vaDestroyConfig(r->va_dpy, r->vpp.cfg); + + return -1; +} + +static void +vpp_destroy(struct vaapi_recorder *r) +{ + vaDestroySurfaces(r->va_dpy, &r->vpp.output, 1); + vaDestroyBuffer(r->va_dpy, r->vpp.pipeline_buf); + vaDestroyConfig(r->va_dpy, r->vpp.ctx); + vaDestroyConfig(r->va_dpy, r->vpp.cfg); +} + +struct vaapi_recorder * +vaapi_recorder_create(int drm_fd, int width, int height, const char *filename) +{ + struct vaapi_recorder *r; + VAStatus status; + int major, minor; + int flags; + + r = calloc(1, sizeof *r); + if (!r) + return NULL; + + r->width = width; + r->height = height; + + flags = O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC; + r->output_fd = open(filename, flags, 0644); + + if (r->output_fd < 0) + goto err_free; + + r->va_dpy = vaGetDisplayDRM(drm_fd); + if (!r->va_dpy) { + weston_log("failed to create VA display\n"); + goto err_fd; + } + + status = vaInitialize(r->va_dpy, &major, &minor); + if (status != VA_STATUS_SUCCESS) { + weston_log("vaapi: failed to initialize display\n"); + goto err_fd; + } + + if (setup_vpp(r) < 0) { + weston_log("vaapi: failed to initialize VPP pipeline\n"); + goto err_va_dpy; + } + + if (setup_encoder(r) < 0) { + goto err_vpp; + } + + return r; + +err_vpp: + vpp_destroy(r); +err_va_dpy: + vaTerminate(r->va_dpy); +err_fd: + close(r->output_fd); +err_free: + free(r); + + return NULL; +} + +void +vaapi_recorder_destroy(struct vaapi_recorder *r) +{ + encoder_destroy(r); + vpp_destroy(r); + + vaTerminate(r->va_dpy); + + close(r->output_fd); + + free(r); +} + +static VAStatus +create_surface_from_fd(struct vaapi_recorder *r, int prime_fd, + int stride, VASurfaceID *surface) +{ + VASurfaceAttrib va_attribs[2]; + VASurfaceAttribExternalBuffers va_attrib_extbuf; + VAStatus status; + + unsigned long buffer_fd = prime_fd; + + va_attrib_extbuf.pixel_format = VA_FOURCC_BGRX; + va_attrib_extbuf.width = r->width; + va_attrib_extbuf.height = r->height; + va_attrib_extbuf.data_size = r->height * stride; + va_attrib_extbuf.num_planes = 1; + va_attrib_extbuf.pitches[0] = stride; + va_attrib_extbuf.offsets[0] = 0; + va_attrib_extbuf.buffers = &buffer_fd; + va_attrib_extbuf.num_buffers = 1; + va_attrib_extbuf.flags = 0; + va_attrib_extbuf.private_data = NULL; + + va_attribs[0].type = VASurfaceAttribMemoryType; + va_attribs[0].flags = VA_SURFACE_ATTRIB_SETTABLE; + va_attribs[0].value.type = VAGenericValueTypeInteger; + va_attribs[0].value.value.i = VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME; + + va_attribs[1].type = VASurfaceAttribExternalBufferDescriptor; + va_attribs[1].flags = VA_SURFACE_ATTRIB_SETTABLE; + va_attribs[1].value.type = VAGenericValueTypePointer; + va_attribs[1].value.value.p = &va_attrib_extbuf; + + status = vaCreateSurfaces(r->va_dpy, VA_RT_FORMAT_RGB32, + r->width, r->height, surface, 1, + va_attribs, 2); + + return status; +} + +static VAStatus +convert_rgb_to_yuv(struct vaapi_recorder *r, VASurfaceID rgb_surface) +{ + VAProcPipelineParameterBuffer *pipeline_param; + VAStatus status; + + status = vaMapBuffer(r->va_dpy, r->vpp.pipeline_buf, + (void **) &pipeline_param); + if (status != VA_STATUS_SUCCESS) + return status; + + memset(pipeline_param, 0, sizeof *pipeline_param); + + pipeline_param->surface = rgb_surface; + pipeline_param->surface_color_standard = VAProcColorStandardNone; + + pipeline_param->output_background_color = 0xff000000; + pipeline_param->output_color_standard = VAProcColorStandardNone; + + status = vaUnmapBuffer(r->va_dpy, r->vpp.pipeline_buf); + if (status != VA_STATUS_SUCCESS) + return status; + + status = vaBeginPicture(r->va_dpy, r->vpp.ctx, r->vpp.output); + if (status != VA_STATUS_SUCCESS) + return status; + + status = vaRenderPicture(r->va_dpy, r->vpp.ctx, + &r->vpp.pipeline_buf, 1); + if (status != VA_STATUS_SUCCESS) + return status; + + status = vaEndPicture(r->va_dpy, r->vpp.ctx); + if (status != VA_STATUS_SUCCESS) + return status; + + return status; +} + +void +vaapi_recorder_frame(struct vaapi_recorder *r, int prime_fd, + int stride) +{ + VASurfaceID rgb_surface; + VAStatus status; + + status = create_surface_from_fd(r, prime_fd, stride, &rgb_surface); + if (status != VA_STATUS_SUCCESS) { + weston_log("[libva recorder] " + "failed to create surface from bo\n"); + return; + } + + status = convert_rgb_to_yuv(r, rgb_surface); + if (status != VA_STATUS_SUCCESS) { + weston_log("[libva recorder] " + "color space conversion failed\n"); + return; + } + + encoder_encode(r, r->vpp.output); + + vaDestroySurfaces(r->va_dpy, &rgb_surface, 1); +} + + diff --git a/src/vaapi-recorder.h b/src/vaapi-recorder.h new file mode 100644 index 0000000..664b1f9 --- /dev/null +++ b/src/vaapi-recorder.h @@ -0,0 +1,35 @@ +/* + * Copyright © 2013 Intel Corporation + * + * Permission to use, copy, modify, distribute, and sell this software and + * its documentation for any purpose is hereby granted without fee, provided + * that the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation, and that the name of the copyright holders not be used in + * advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. The copyright holders make + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF + * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _VAAPI_RECORDER_H_ +#define _VAAPI_RECORDER_H_ + +struct vaapi_recorder; + +struct vaapi_recorder * +vaapi_recorder_create(int drm_fd, int width, int height, const char *filename); +void +vaapi_recorder_destroy(struct vaapi_recorder *r); +void +vaapi_recorder_frame(struct vaapi_recorder *r, int fd, int stride); + +#endif /* _VAAPI_RECORDER_H_ */ -- 2.7.4