diff --git a/include/enc/h264-encoder.h b/include/enc/h264-encoder.h index 14e5941b..3bc290ce 100644 --- a/include/enc/h264-encoder.h +++ b/include/enc/h264-encoder.h @@ -27,7 +27,7 @@ typedef void (*h264_encoder_packet_handler_fn)(const void* payload, size_t size, struct h264_encoder_impl { struct h264_encoder* (*create)(uint32_t width, uint32_t height, - uint32_t format, int quality); + uint32_t format, int quality, bool hw); void (*destroy)(struct h264_encoder*); void (*feed)(struct h264_encoder*, struct nvnc_frame*); }; @@ -40,7 +40,7 @@ struct h264_encoder { }; struct h264_encoder* h264_encoder_create(uint32_t width, uint32_t height, - uint32_t format, int quality); + uint32_t format, int quality, bool hw); void h264_encoder_destroy(struct h264_encoder*); diff --git a/src/enc/h264/encoder.c b/src/enc/h264/encoder.c index 6635786a..43c31526 100644 --- a/src/enc/h264/encoder.c +++ b/src/enc/h264/encoder.c @@ -26,19 +26,22 @@ extern struct h264_encoder_impl h264_encoder_v4l2m2m_impl; #endif struct h264_encoder* h264_encoder_create(uint32_t width, uint32_t height, - uint32_t format, int quality) + uint32_t format, int quality, bool hw) { struct h264_encoder* encoder = NULL; #ifdef HAVE_V4L2 - encoder = h264_encoder_v4l2m2m_impl.create(width, height, format, quality); - if (encoder) { - return encoder; + // V4L2 is useless for sw frames + if (!hw) { + encoder = h264_encoder_v4l2m2m_impl.create(width, height, format, quality, /* hw = */ true); + if (encoder) { + return encoder; + } } #endif #ifdef HAVE_FFMPEG - encoder = h264_encoder_ffmpeg_impl.create(width, height, format, quality); + encoder = h264_encoder_ffmpeg_impl.create(width, height, format, quality, hw); if (encoder) { return encoder; } diff --git a/src/enc/h264/ffmpeg-impl.c b/src/enc/h264/ffmpeg-impl.c index 0479a47b..4e237055 100644 --- a/src/enc/h264/ffmpeg-impl.c +++ b/src/enc/h264/ffmpeg-impl.c @@ -17,9 +17,11 @@ #include "enc/h264-encoder.h" #include "neatvnc.h" #include "frame.h" +#include "pixels.h" #include "sys/queue.h" #include "vec.h" #include "usdt.h" +#include "rfb-proto.h" #include #include @@ -63,6 +65,9 @@ struct h264_encoder_ffmpeg { AVRational timebase; enum AVPixelFormat av_pixel_format; + /** Flag whether to use hardware encoding. */ + bool hw; + /* type: AVHWDeviceContext */ AVBufferRef* hw_device_ctx; @@ -107,6 +112,13 @@ static enum AVPixelFormat drm_to_av_pixel_format(uint32_t format) return AV_PIX_FMT_NONE; } +static inline void log_libav_error(char* msg, int rc) +{ + char err[256]; + av_strerror(rc, err, sizeof(err)); + nvnc_log(NVNC_LOG_ERROR, "%s: %s", msg, err); +} + static void hw_frame_desc_free(void* opaque, uint8_t* data) { struct AVDRMFrameDescriptor* desc = (void*)data; @@ -119,55 +131,71 @@ static void hw_frame_desc_free(void* opaque, uint8_t* data) } // TODO: Maybe do this once per frame inside nvnc_frame? -static AVFrame* fb_to_avframe(struct nvnc_frame* fb) +static AVFrame* fb_to_avframe(struct h264_encoder_ffmpeg* self, struct nvnc_frame* fb) { - struct gbm_bo* bo = fb->buffer->bo; + AVDRMFrameDescriptor* desc = NULL; + if (self->hw) { + struct gbm_bo* bo = fb->buffer->bo; - int n_planes = gbm_bo_get_plane_count(bo); + int n_planes = gbm_bo_get_plane_count(bo); - AVDRMFrameDescriptor* desc = calloc(1, sizeof(*desc)); - desc->nb_objects = n_planes; + desc = calloc(1, sizeof(*desc)); + desc->nb_objects = n_planes; - desc->nb_layers = 1; - desc->layers[0].format = gbm_bo_get_format(bo); - desc->layers[0].nb_planes = n_planes; + desc->nb_layers = 1; + desc->layers[0].format = gbm_bo_get_format(bo); + desc->layers[0].nb_planes = n_planes; - for (int i = 0; i < n_planes; ++i) { - uint32_t stride = gbm_bo_get_stride_for_plane(bo, i); + for (int i = 0; i < n_planes; ++i) { + uint32_t stride = gbm_bo_get_stride_for_plane(bo, i); - desc->objects[i].fd = gbm_bo_get_fd_for_plane(bo, i); - desc->objects[i].size = stride * fb->height; - desc->objects[i].format_modifier = gbm_bo_get_modifier(bo); + desc->objects[i].fd = gbm_bo_get_fd_for_plane(bo, i); + desc->objects[i].size = stride * fb->height; + desc->objects[i].format_modifier = gbm_bo_get_modifier(bo); - desc->layers[0].format = gbm_bo_get_format(bo); - desc->layers[0].planes[i].object_index = i; - desc->layers[0].planes[i].offset = gbm_bo_get_offset(bo, i); - desc->layers[0].planes[i].pitch = stride; + desc->layers[0].format = gbm_bo_get_format(bo); + desc->layers[0].planes[i].object_index = i; + desc->layers[0].planes[i].offset = gbm_bo_get_offset(bo, i); + desc->layers[0].planes[i].pitch = stride; + } } AVFrame* frame = av_frame_alloc(); if (!frame) { - hw_frame_desc_free(NULL, (void*)desc); + if (self->hw) + hw_frame_desc_free(NULL, (void*)desc); return NULL; } - frame->opaque = fb; + if (self->hw) + frame->opaque = fb; frame->width = fb->width; frame->height = fb->height; - frame->format = AV_PIX_FMT_DRM_PRIME; + frame->format = self->hw ? AV_PIX_FMT_DRM_PRIME : self->av_pixel_format; frame->sample_aspect_ratio = (AVRational){1, 1}; frame->pts = fb->pts; - AVBufferRef* desc_ref = av_buffer_create((void*)desc, sizeof(*desc), - hw_frame_desc_free, NULL, 0); - if (!desc_ref) { - hw_frame_desc_free(NULL, (void*)desc); - av_frame_free(&frame); - return NULL; - } + if (self->hw) { + AVBufferRef* desc_ref = av_buffer_create((void*)desc, sizeof(*desc), + hw_frame_desc_free, NULL, 0); + if (!desc_ref) { + hw_frame_desc_free(NULL, (void*)desc); + av_frame_free(&frame); + return NULL; + } + + frame->buf[0] = desc_ref; + frame->data[0] = (void*)desc_ref->data; + } else { + if (nvnc_frame_map(fb) < 0) { + nvnc_log(NVNC_LOG_ERROR, "nvnc_frame_map failed"); + av_frame_free(&frame); + return NULL; + } - frame->buf[0] = desc_ref; - frame->data[0] = (void*)desc_ref->data; + frame->linesize[0] = fb->stride * nvnc_frame_get_pixel_size(fb); + frame->data[0] = fb->buffer->addr; + } // sRGB: frame->colorspace = AVCOL_SPC_RGB; @@ -217,12 +245,13 @@ static int h264_encoder__init_buffersrc(struct h264_encoder_ffmpeg* self) if (!params) return -1; - params->format = AV_PIX_FMT_DRM_PRIME; + params->format = self->hw ? AV_PIX_FMT_DRM_PRIME : self->av_pixel_format; params->width = self->width; params->height = self->height; params->sample_aspect_ratio = (AVRational){1, 1}; params->time_base = self->timebase; - params->hw_frames_ctx = self->hw_frames_ctx; + if (self->hw) + params->hw_frames_ctx = self->hw_frames_ctx; #if LIBAVFILTER_VERSION_INT >= AV_VERSION_INT(9, 16, 100) params->color_space = AVCOL_SPC_RGB; params->color_range = AVCOL_RANGE_JPEG; @@ -279,20 +308,29 @@ static int h264_encoder__init_filters(struct h264_encoder_ffmpeg* self) outputs->pad_idx = 0; outputs->next = NULL; - rc = avfilter_graph_parse(self->filter_graph, - "hwmap=mode=direct:derive_device=vaapi" - ",scale_vaapi=format=nv12:mode=fast" - ":out_color_matrix=bt709:out_range=limited" - ":out_color_primaries=bt709:out_color_transfer=bt709", - outputs, inputs, NULL); + if (self->hw) { + rc = avfilter_graph_parse(self->filter_graph, + "hwmap=mode=direct:derive_device=vaapi" + ",scale_vaapi=format=nv12:mode=fast" + ":out_color_matrix=bt709:out_range=limited" + ":out_color_primaries=bt709:out_color_transfer=bt709", + outputs, inputs, NULL); + } else { + rc = avfilter_graph_parse(self->filter_graph, + "format=pix_fmts=yuv420p", + outputs, inputs, NULL); + } + if (rc != 0) goto failure; - assert(self->hw_device_ctx); + if (self->hw) { + assert(self->hw_device_ctx); - for (unsigned int i = 0; i < self->filter_graph->nb_filters; ++i) { - self->filter_graph->filters[i]->hw_device_ctx = - av_buffer_ref(self->hw_device_ctx); + for (unsigned int i = 0; i < self->filter_graph->nb_filters; ++i) { + self->filter_graph->filters[i]->hw_device_ctx = + av_buffer_ref(self->hw_device_ctx); + } } rc = avfilter_graph_config(self->filter_graph, NULL); @@ -318,7 +356,7 @@ static int h264_encoder__init_codec_context(struct h264_encoder_ffmpeg* self, c->height = self->height; c->time_base = self->timebase; c->sample_aspect_ratio = (AVRational){1, 1}; - c->pix_fmt = AV_PIX_FMT_VAAPI; + c->pix_fmt = self->hw ? AV_PIX_FMT_VAAPI : AV_PIX_FMT_YUV420P; c->gop_size = INT32_MAX; /* We'll select key frames manually */ c->max_b_frames = 0; /* B-frames are bad for latency */ c->global_quality = quality; @@ -374,24 +412,29 @@ static void h264_encoder__teardown_pipeline(struct h264_encoder_ffmpeg* self) avfilter_graph_free(&self->filter_graph); self->filter_in = NULL; self->filter_out = NULL; - av_buffer_unref(&self->hw_frames_ctx); - av_buffer_unref(&self->hw_device_ctx); + if (self->hw) { + av_buffer_unref(&self->hw_frames_ctx); + av_buffer_unref(&self->hw_device_ctx); + } } static int h264_encoder__init_pipeline(struct h264_encoder_ffmpeg* self, const char* render_node) { - const AVCodec* codec = avcodec_find_encoder_by_name("h264_vaapi"); + const AVCodec* codec = avcodec_find_encoder_by_name(self-> hw ? "h264_vaapi" : "libx264"); if (!codec) return -1; - int rc = av_hwdevice_ctx_create(&self->hw_device_ctx, - AV_HWDEVICE_TYPE_DRM, render_node, NULL, 0); - if (rc != 0) - return -1; + int rc = 0; + if (self->hw) { + rc = av_hwdevice_ctx_create(&self->hw_device_ctx, + AV_HWDEVICE_TYPE_DRM, render_node, NULL, 0); + if (rc != 0) + return -1; - if (h264_encoder__init_hw_frames_context(self) < 0) - goto hw_frames_failure; + if (h264_encoder__init_hw_frames_context(self) < 0) + goto hw_frames_failure; + } if (h264_encoder__init_filters(self) < 0) goto filter_failure; @@ -399,11 +442,18 @@ static int h264_encoder__init_pipeline(struct h264_encoder_ffmpeg* self, if (h264_encoder__init_codec_context(self, codec, self->quality) < 0) goto codec_ctx_failure; - self->codec_ctx->hw_frames_ctx = - av_buffer_ref(av_buffersink_get_hw_frames_ctx(self->filter_out)); + if (self->hw) { + self->codec_ctx->hw_frames_ctx = + av_buffer_ref(av_buffersink_get_hw_frames_ctx(self->filter_out)); + } AVDictionary *opts = NULL; - av_dict_set_int(&opts, "async_depth", 1, 0); + if (self->hw) { + av_dict_set_int(&opts, "async_depth", 1, 0); + } else { + av_dict_set(&opts, "preset", "ultrafast", 0); /* Reduce encoding latency. */ + av_dict_set(&opts, "tune", "zerolatency", 0); /* Generate packets immediately. */ + } rc = avcodec_open2(self->codec_ctx, codec, &opts); av_dict_free(&opts); @@ -411,8 +461,10 @@ static int h264_encoder__init_pipeline(struct h264_encoder_ffmpeg* self, if (rc != 0) goto avcodec_open_failure; - strncpy(self->render_node, render_node, sizeof(self->render_node) - 1); - self->render_node[sizeof(self->render_node) - 1] = '\0'; + if (self->hw) { + strncpy(self->render_node, render_node, sizeof(self->render_node) - 1); + self->render_node[sizeof(self->render_node) - 1] = '\0'; + } return 0; @@ -423,9 +475,11 @@ static int h264_encoder__init_pipeline(struct h264_encoder_ffmpeg* self, self->filter_in = NULL; self->filter_out = NULL; filter_failure: - av_buffer_unref(&self->hw_frames_ctx); + if (self->hw) + av_buffer_unref(&self->hw_frames_ctx); hw_frames_failure: - av_buffer_unref(&self->hw_device_ctx); + if (self->hw) + av_buffer_unref(&self->hw_device_ctx); return -1; } @@ -498,31 +552,34 @@ static void h264_encoder__do_work(struct aml_work* work) { struct h264_encoder_ffmpeg* self = aml_get_userdata(work); - char render_node[64]; - if (get_render_node_from_bo(self->current_fb->buffer->bo, render_node, - sizeof(render_node)) < 0) { - nvnc_log(NVNC_LOG_ERROR, "Failed to get render node from gbm_bo"); - return; - } + if (self->hw) { + char render_node[64]; + if (get_render_node_from_bo(self->current_fb->buffer->bo, render_node, + sizeof(render_node)) < 0) { + nvnc_log(NVNC_LOG_ERROR, "Failed to get render node from gbm_bo"); + return; + } - if (strcmp(render_node, self->render_node) != 0) { - nvnc_log(NVNC_LOG_INFO, - "Render node changed to %s, reinitialising encoder", - render_node); - h264_encoder__teardown_pipeline(self); - if (h264_encoder__init_pipeline(self, render_node) < 0) { - nvnc_log(NVNC_LOG_ERROR, - "Failed to reinitialise encoder on %s", + if (strcmp(render_node, self->render_node) != 0) { + nvnc_log(NVNC_LOG_INFO, + "Render node changed to %s, reinitialising encoder", render_node); - return; + h264_encoder__teardown_pipeline(self); + if (h264_encoder__init_pipeline(self, render_node) < 0) { + nvnc_log(NVNC_LOG_ERROR, + "Failed to reinitialise encoder on %s", + render_node); + return; + } + self->current_frame_is_keyframe = true; } - self->current_frame_is_keyframe = true; } - AVFrame* frame = fb_to_avframe(self->current_fb); + AVFrame* frame = fb_to_avframe(self, self->current_fb); assert(frame); // TODO - frame->hw_frames_ctx = av_buffer_ref(self->hw_frames_ctx); + if (self->hw) + frame->hw_frames_ctx = av_buffer_ref(self->hw_frames_ctx); if (self->current_frame_is_keyframe) { #if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(58, 7, 100) @@ -542,13 +599,13 @@ static void h264_encoder__do_work(struct aml_work* work) int rc = h264_encoder__encode(self, frame); if (rc != 0) { - char err[256]; - av_strerror(rc, err, sizeof(err)); - nvnc_log(NVNC_LOG_ERROR, "Failed to encode packet: %s", err); + log_libav_error("Failed to encode packet", rc); goto failure; } failure: + if (!self->hw) + nvnc_frame_unmap(self->current_fb); av_frame_unref(frame); av_frame_free(&frame); } @@ -610,12 +667,13 @@ static int find_render_node(char *node, size_t maxlen) { } static struct h264_encoder* h264_encoder_ffmpeg_create(uint32_t width, - uint32_t height, uint32_t format, int quality) + uint32_t height, uint32_t format, int quality, bool hw) { struct h264_encoder_ffmpeg* self = calloc(1, sizeof(*self)); if (!self) return NULL; + self->hw = hw; self->base.impl = &h264_encoder_ffmpeg_impl; if (vec_init(&self->current_packet, 65536) < 0) @@ -639,8 +697,10 @@ static struct h264_encoder* h264_encoder_ffmpeg_create(uint32_t width, goto pix_fmt_failure; char render_node[64]; - if (find_render_node(render_node, sizeof(render_node)) < 0) - goto pix_fmt_failure; + if (self->hw) { + if (find_render_node(render_node, sizeof(render_node)) < 0) + goto pix_fmt_failure; + } if (h264_encoder__init_pipeline(self, render_node) < 0) goto pix_fmt_failure; @@ -675,7 +735,8 @@ static void h264_encoder_ffmpeg_feed(struct h264_encoder* base, struct nvnc_frame* fb) { struct h264_encoder_ffmpeg* self = (struct h264_encoder_ffmpeg*)base; - assert(fb->buffer->type == NVNC_BUFFER_GBM_BO); + if (self->hw) + assert(fb->buffer->type == NVNC_BUFFER_GBM_BO); // TODO: Add transform filter assert(fb->transform == NVNC_TRANSFORM_NORMAL); diff --git a/src/enc/h264/open-h264.c b/src/enc/h264/open-h264.c index badc3a0d..5b3fa896 100644 --- a/src/enc/h264/open-h264.c +++ b/src/enc/h264/open-h264.c @@ -55,6 +55,8 @@ struct open_h264_context { bool quality_changed; uint64_t last_pts; + + bool hw; }; struct open_h264 { @@ -236,12 +238,12 @@ static void open_h264_destroy(struct encoder* enc) free(self); } -static int open_h264_resize(struct open_h264_context* self, struct nvnc_frame* fb) +static int open_h264_resize(struct open_h264_context* self, struct nvnc_frame* fb, bool hw) { int quality = 51 - round((50.0 / 9.0) * (float)self->parent->quality); struct h264_encoder* encoder = h264_encoder_create(fb->width, - fb->height, fb->fourcc_format, quality); + fb->height, fb->fourcc_format, quality, hw); if (!encoder) return -1; @@ -258,18 +260,19 @@ static int open_h264_resize(struct open_h264_context* self, struct nvnc_frame* f self->format = fb->fourcc_format; self->needs_reset = true; self->quality_changed = false; + self->hw = hw; return 0; } -static int open_h264_ctx_encode(struct open_h264_context* self, struct nvnc_frame* fb) +static int open_h264_ctx_encode(struct open_h264_context* self, struct nvnc_frame* fb, bool hw) { DTRACE_PROBE1(neatvnc, open_h264_encode, fb->pts); if (fb->width != self->width || fb->height != self->height || fb->fourcc_format != self->format || - self->quality_changed) { - if (open_h264_resize(self, fb) < 0) + self->quality_changed || self->hw != hw) { + if (open_h264_resize(self, fb, hw) < 0) return -1; } @@ -363,7 +366,8 @@ static int open_h264_encode(struct encoder* enc, struct open_h264_context* ctx = open_h264_get_context(self, fb->x_off, fb->y_off); - int rc = open_h264_ctx_encode(ctx, fb); + nvnc_log(NVNC_LOG_INFO, "open_h264_encode: hw = %u.", composite->fbs[i]->buffer->type == NVNC_BUFFER_GBM_BO); + int rc = open_h264_ctx_encode(ctx, fb, composite->fbs[i]->buffer->type == NVNC_BUFFER_GBM_BO); nvnc_assert(rc == 0, "Failed to encode frame"); self->frame_barrier++; diff --git a/src/enc/h264/v4l2m2m-impl.c b/src/enc/h264/v4l2m2m-impl.c index c9be452e..8ea461fe 100644 --- a/src/enc/h264/v4l2m2m-impl.c +++ b/src/enc/h264/v4l2m2m-impl.c @@ -642,7 +642,7 @@ static int find_capable_device(uint32_t width, uint32_t height) } static struct h264_encoder* h264_encoder_v4l2m2m_create(uint32_t width, - uint32_t height, uint32_t format, int quality) + uint32_t height, uint32_t format, int quality, bool hw) { struct h264_encoder_v4l2m2m* self = calloc(1, sizeof(*self)); if (!self) diff --git a/src/server.c b/src/server.c index 4cc4f006..05916d48 100644 --- a/src/server.c +++ b/src/server.c @@ -126,23 +126,23 @@ static uint64_t gettime_us(clockid_t clock) } #ifdef ENABLE_OPEN_H264 -static bool have_working_h264_encoder(void) +static bool have_working_h264_encoder(bool hw) { - static int cached_result; + static int cached_result[2]; - if (cached_result) { - return cached_result == 1; + if (cached_result[hw]) { + return cached_result[hw] == 1; } struct h264_encoder *encoder = h264_encoder_create(1920, 1080, - DRM_FORMAT_XRGB8888, 5); - cached_result = encoder ? 1 : -1; + DRM_FORMAT_XRGB8888, 5, hw); + cached_result[hw] = encoder ? 1 : -1; h264_encoder_destroy(encoder); nvnc_log(NVNC_LOG_DEBUG, "H.264 encoding is %s", - cached_result == 1 ? "available" : "unavailable"); + cached_result[hw] == 1 ? "available" : "unavailable"); - return cached_result == 1; + return cached_result[hw] == 1; } #endif // ENABLE_OPEN_H264 @@ -2784,20 +2784,23 @@ static enum rfb_encodings choose_frame_encoding(struct nvnc_client* client, return client->encodings[i]; #ifdef ENABLE_OPEN_H264 case RFB_ENCODING_OPEN_H264: - // h264 is useless for sw frames + { + // Hardware or software frames? + bool hw = false; for (int i = 0; i < fb->n_fbs; ++i) - if (fb->fbs[i]->buffer->type != NVNC_BUFFER_GBM_BO) - goto skip; - if (!have_working_h264_encoder()) + if (fb->fbs[i]->buffer->type == NVNC_BUFFER_GBM_BO) { + hw = true; + break; + } + nvnc_log(NVNC_LOG_INFO, "choose_frame_encoding: hw = %u.", hw); + if (!have_working_h264_encoder(hw)) break; return client->encodings[i]; + } #endif default: break; } -#ifdef ENABLE_OPEN_H264 -skip:; -#endif } return RFB_ENCODING_RAW;