X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=nageru%2Fquicksync_encoder.cpp;h=992bf8f83e037e3bf8df40b02df23ede0fbcd2a3;hb=0f5b8fd8420a450f5994d5f535fdce84dbb10093;hp=5200ce61b4911ab4e18692da0bfc17b483ae548d;hpb=392f9d1ccb835c05a3874c4bea163788b2c37024;p=nageru diff --git a/nageru/quicksync_encoder.cpp b/nageru/quicksync_encoder.cpp index 5200ce6..992bf8f 100644 --- a/nageru/quicksync_encoder.cpp +++ b/nageru/quicksync_encoder.cpp @@ -40,21 +40,21 @@ extern "C" { #include #include #include -#include +#include } // namespace #include "audio_encoder.h" -#include "context.h" +#include "shared/context.h" #include "defs.h" -#include "disk_space_estimator.h" -#include "ffmpeg_raii.h" +#include "shared/disk_space_estimator.h" +#include "shared/ffmpeg_raii.h" #include "flags.h" -#include "mux.h" +#include "shared/mux.h" #include "print_latency.h" #include "quicksync_encoder_impl.h" #include "ref_counted_frame.h" -#include "timebase.h" +#include "shared/timebase.h" #include "x264_encoder.h" using namespace movit; @@ -80,7 +80,7 @@ std::atomic metric_quick_sync_stalled_frames{0}; #define CHECK_VASTATUS(va_status, func) \ if (va_status != VA_STATUS_SUCCESS) { \ fprintf(stderr, "%s:%d (%s) failed with %d\n", __func__, __LINE__, func, va_status); \ - exit(1); \ + abort(); \ } #undef BUFFER_OFFSET @@ -719,110 +719,28 @@ void QuickSyncEncoderImpl::enable_zerocopy_if_possible() } else if (global_flags.x264_video_to_http) { fprintf(stderr, "Disabling zerocopy H.264 encoding due to --http-x264-video.\n"); use_zerocopy = false; + } else if (!global_flags.v4l_output_device.empty()) { + fprintf(stderr, "Disabling zerocopy H.264 encoding due to --v4l-output.\n"); + use_zerocopy = false; } else { use_zerocopy = true; } global_flags.use_zerocopy = use_zerocopy; } -VADisplayWithCleanup::~VADisplayWithCleanup() -{ - if (va_dpy != nullptr) { - vaTerminate(va_dpy); - } - if (x11_display != nullptr) { - XCloseDisplay(x11_display); - } - if (drm_fd != -1) { - close(drm_fd); - } -} - -unique_ptr va_open_display(const string &va_display) -{ - if (va_display.empty() || va_display[0] != '/') { // An X display. - Display *x11_display = XOpenDisplay(va_display.empty() ? nullptr : va_display.c_str()); - if (x11_display == nullptr) { - fprintf(stderr, "error: can't connect to X server!\n"); - return nullptr; - } - - unique_ptr ret(new VADisplayWithCleanup); - ret->x11_display = x11_display; - ret->can_use_zerocopy = true; - ret->va_dpy = vaGetDisplay(x11_display); - if (ret->va_dpy == nullptr) { - return nullptr; - } - return ret; - } else { // A DRM node on the filesystem (e.g. /dev/dri/renderD128). - int drm_fd = open(va_display.c_str(), O_RDWR); - if (drm_fd == -1) { - perror(va_display.c_str()); - return NULL; - } - unique_ptr ret(new VADisplayWithCleanup); - ret->drm_fd = drm_fd; - ret->can_use_zerocopy = false; - ret->va_dpy = vaGetDisplayDRM(drm_fd); - if (ret->va_dpy == nullptr) { - return nullptr; - } - return ret; - } -} - -unique_ptr try_open_va(const string &va_display, VAProfile *h264_profile, string *error) +static unique_ptr try_open_va_h264(const string &va_display, VAProfile *h264_profile, string *error) { - unique_ptr va_dpy = va_open_display(va_display); - if (va_dpy == nullptr) { - if (error) *error = "Opening VA display failed"; - return nullptr; - } - int major_ver, minor_ver; - VAStatus va_status = vaInitialize(va_dpy->va_dpy, &major_ver, &minor_ver); - if (va_status != VA_STATUS_SUCCESS) { - char buf[256]; - snprintf(buf, sizeof(buf), "vaInitialize() failed with status %d\n", va_status); - if (error != nullptr) *error = buf; - return nullptr; - } - - int num_entrypoints = vaMaxNumEntrypoints(va_dpy->va_dpy); - unique_ptr entrypoints(new VAEntrypoint[num_entrypoints]); - if (entrypoints == nullptr) { - if (error != nullptr) *error = "Failed to allocate memory for VA entry points"; - return nullptr; - } - - // Try the profiles from highest to lowest until we find one that can be encoded. - constexpr VAProfile profile_list[] = { VAProfileH264High, VAProfileH264Main, VAProfileH264ConstrainedBaseline }; - for (unsigned i = 0; i < sizeof(profile_list) / sizeof(profile_list[0]); ++i) { - vaQueryConfigEntrypoints(va_dpy->va_dpy, profile_list[i], entrypoints.get(), &num_entrypoints); - for (int slice_entrypoint = 0; slice_entrypoint < num_entrypoints; slice_entrypoint++) { - if (entrypoints[slice_entrypoint] != VAEntrypointEncSlice) { - continue; - } - - // We found a usable encoder, so return it. - if (h264_profile != nullptr) { - *h264_profile = profile_list[i]; - } - return va_dpy; - } - } - - if (error != nullptr) *error = "Can't find VAEntrypointEncSlice for H264 profiles"; - return nullptr; + return try_open_va(va_display, { VAProfileH264High, VAProfileH264Main, VAProfileH264ConstrainedBaseline }, + VAEntrypointEncSlice, /*desired_configs=*/{}, h264_profile, error); } int QuickSyncEncoderImpl::init_va(const string &va_display) { string error; - va_dpy = try_open_va(va_display, &h264_profile, &error); + va_dpy = try_open_va_h264(va_display, &h264_profile, &error); if (va_dpy == nullptr) { fprintf(stderr, "error: %s\n", error.c_str()); - exit(1); + abort(); } if (!va_dpy->can_use_zerocopy) { use_zerocopy = false; @@ -860,7 +778,7 @@ int QuickSyncEncoderImpl::init_va(const string &va_display) /* check the interested configattrib */ if ((attrib[VAConfigAttribRTFormat].value & VA_RT_FORMAT_YUV420) == 0) { printf("Not find desired YUV420 RT format\n"); - exit(1); + abort(); } else { config_attrib[config_attrib_num].type = VAConfigAttribRTFormat; config_attrib[config_attrib_num].value = VA_RT_FORMAT_YUV420; @@ -870,7 +788,7 @@ int QuickSyncEncoderImpl::init_va(const string &va_display) if (attrib[VAConfigAttribRateControl].value != VA_ATTRIB_NOT_SUPPORTED) { if (!(attrib[VAConfigAttribRateControl].value & VA_RC_CQP)) { fprintf(stderr, "ERROR: VA-API encoder does not support CQP mode.\n"); - exit(1); + abort(); } config_attrib[config_attrib_num].type = VAConfigAttribRateControl; @@ -1023,7 +941,7 @@ void QuickSyncEncoderImpl::update_ReferenceFrames(int current_display_frame, int pic_param.CurrPic.frame_idx = current_ref_frame_num; CurrentCurrPic.flags = VA_PICTURE_H264_SHORT_TERM_REFERENCE; - unique_lock lock(storage_task_queue_mutex); + lock_guard lock(storage_task_queue_mutex); // Insert the new frame at the start of the reference queue. reference_frames.push_front(ReferenceFrame{ CurrentCurrPic, current_display_frame }); @@ -1434,7 +1352,7 @@ void QuickSyncEncoderImpl::save_codeddata(GLSurface *surf, storage_task task) // this is weird. but it seems to put a new frame onto the queue void QuickSyncEncoderImpl::storage_task_enqueue(storage_task task) { - unique_lock lock(storage_task_queue_mutex); + lock_guard lock(storage_task_queue_mutex); storage_task_queue.push(move(task)); storage_task_queue_changed.notify_all(); } @@ -1468,7 +1386,7 @@ void QuickSyncEncoderImpl::storage_task_thread() // Unlock the frame, and all its references. { - unique_lock lock(storage_task_queue_mutex); + lock_guard lock(storage_task_queue_mutex); release_gl_surface(display_order); for (size_t frame_num : ref_display_frame_numbers) { @@ -1481,13 +1399,18 @@ void QuickSyncEncoderImpl::storage_task_thread() void QuickSyncEncoderImpl::release_encode() { for (unsigned i = 0; i < SURFACE_NUM; i++) { - vaDestroyBuffer(va_dpy->va_dpy, gl_surfaces[i].coded_buf); - vaDestroySurfaces(va_dpy->va_dpy, &gl_surfaces[i].src_surface, 1); - vaDestroySurfaces(va_dpy->va_dpy, &gl_surfaces[i].ref_surface, 1); + VAStatus va_status = vaDestroyBuffer(va_dpy->va_dpy, gl_surfaces[i].coded_buf); + CHECK_VASTATUS(va_status, "vaDestroyBuffer"); + va_status = vaDestroySurfaces(va_dpy->va_dpy, &gl_surfaces[i].src_surface, 1); + CHECK_VASTATUS(va_status, "vaDestroySurfaces"); + va_status = vaDestroySurfaces(va_dpy->va_dpy, &gl_surfaces[i].ref_surface, 1); + CHECK_VASTATUS(va_status, "vaDestroySurfaces"); } - vaDestroyContext(va_dpy->va_dpy, context_id); - vaDestroyConfig(va_dpy->va_dpy, config_id); + VAStatus va_status = vaDestroyContext(va_dpy->va_dpy, context_id); + CHECK_VASTATUS(va_status, "vaDestroyContext"); + va_status = vaDestroyConfig(va_dpy->va_dpy, config_id); + CHECK_VASTATUS(va_status, "vaDestroyConfig"); } void QuickSyncEncoderImpl::release_gl_resources() @@ -1542,6 +1465,10 @@ QuickSyncEncoderImpl::QuickSyncEncoderImpl(const std::string &filename, Resource memset(&slice_param, 0, sizeof(slice_param)); } + if (!global_flags.v4l_output_device.empty()) { + v4l_output.reset(new V4LOutput(global_flags.v4l_output_device.c_str(), width, height)); + } + call_once(quick_sync_metrics_inited, [](){ mixer_latency_histogram.init("mixer"); qs_latency_histogram.init("quick_sync"); @@ -1559,7 +1486,7 @@ QuickSyncEncoderImpl::QuickSyncEncoderImpl(const std::string &filename, Resource if (!make_current(context, this->surface)) { printf("display=%p surface=%p context=%p curr=%p err=%d\n", eglGetCurrentDisplay(), this->surface, context, eglGetCurrentContext(), eglGetError()); - exit(1); + abort(); } encode_thread_func(); delete_context(context); @@ -1701,7 +1628,7 @@ RefCountedGLsync QuickSyncEncoderImpl::end_frame() GLenum type = global_flags.x264_bit_depth > 8 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_BYTE; GLSurface *surf; { - unique_lock lock(storage_task_queue_mutex); + lock_guard lock(storage_task_queue_mutex); surf = surface_for_frame[current_storage_frame]; assert(surf != nullptr); } @@ -1740,7 +1667,7 @@ RefCountedGLsync QuickSyncEncoderImpl::end_frame() check_error(); { - unique_lock lock(frame_queue_mutex); + lock_guard lock(frame_queue_mutex); current_video_frame.fence = fence; pending_video_frames.push(move(current_video_frame)); ++current_storage_frame; @@ -1756,13 +1683,13 @@ void QuickSyncEncoderImpl::shutdown() } { - unique_lock lock(frame_queue_mutex); + lock_guard lock(frame_queue_mutex); encode_thread_should_quit = true; frame_queue_nonempty.notify_all(); } encode_thread.join(); { - unique_lock lock(storage_task_queue_mutex); + lock_guard lock(storage_task_queue_mutex); storage_thread_should_quit = true; frame_queue_nonempty.notify_all(); storage_task_queue_changed.notify_all(); @@ -1792,15 +1719,14 @@ void QuickSyncEncoderImpl::open_output_file(const std::string &filename) { AVFormatContext *avctx = avformat_alloc_context(); avctx->oformat = av_guess_format(NULL, filename.c_str(), NULL); - assert(filename.size() < sizeof(avctx->filename) - 1); - strcpy(avctx->filename, filename.c_str()); + avctx->url = strdup(filename.c_str()); string url = "file:" + filename; int ret = avio_open2(&avctx->pb, url.c_str(), AVIO_FLAG_WRITE, &avctx->interrupt_callback, NULL); if (ret < 0) { char tmp[AV_ERROR_MAX_STRING_SIZE]; fprintf(stderr, "%s: avio_open2() failed: %s\n", filename.c_str(), av_make_error_string(tmp, sizeof(tmp), ret)); - exit(1); + abort(); } string video_extradata; // FIXME: See other comment about global headers. @@ -1813,8 +1739,8 @@ void QuickSyncEncoderImpl::open_output_file(const std::string &filename) { lock_guard lock(file_audio_encoder_mutex); AVCodecParametersWithDeleter audio_codecpar = file_audio_encoder->get_codec_parameters(); - file_mux.reset(new Mux(avctx, frame_width, frame_height, Mux::CODEC_H264, video_extradata, audio_codecpar.get(), TIMEBASE, - std::bind(&DiskSpaceEstimator::report_write, disk_space_estimator, filename, _1), + file_mux.reset(new Mux(avctx, frame_width, frame_height, Mux::CODEC_H264, video_extradata, audio_codecpar.get(), get_color_space(global_flags.ycbcr_rec709_coefficients), TIMEBASE, + std::bind(&DiskSpaceEstimator::report_append, disk_space_estimator, filename, _1), Mux::WRITE_BACKGROUND, { ¤t_file_mux_metrics, &total_mux_metrics })); } @@ -1860,7 +1786,7 @@ void QuickSyncEncoderImpl::encode_thread_func() pass_frame(frame, display_frame_num, frame.pts, frame.duration); if (global_flags.x264_video_to_disk) { - unique_lock lock(storage_task_queue_mutex); + lock_guard lock(storage_task_queue_mutex); release_gl_surface(display_frame_num); continue; } @@ -1883,7 +1809,7 @@ void QuickSyncEncoderImpl::encode_thread_func() if (frame_type == FRAME_IDR) { // Release any reference frames from the previous GOP. { - unique_lock lock(storage_task_queue_mutex); + lock_guard lock(storage_task_queue_mutex); for (const ReferenceFrame &frame : reference_frames) { release_gl_surface(frame.display_number); } @@ -1939,8 +1865,6 @@ void QuickSyncEncoderImpl::add_packet_for_uncompressed_frame(int64_t pts, int64_ stream_mux->add_packet(pkt, pts, pts); } -namespace { - void memcpy_with_pitch(uint8_t *dst, const uint8_t *src, size_t src_width, size_t dst_pitch, size_t height) { if (src_width == dst_pitch) { @@ -1954,8 +1878,6 @@ void memcpy_with_pitch(uint8_t *dst, const uint8_t *src, size_t src_width, size_ } } -} // namespace - void QuickSyncEncoderImpl::pass_frame(QuickSyncEncoderImpl::PendingFrame frame, int display_frame_num, int64_t pts, int64_t duration) { // Wait for the GPU to be done with the frame. @@ -1980,7 +1902,7 @@ void QuickSyncEncoderImpl::pass_frame(QuickSyncEncoderImpl::PendingFrame frame, GLSurface *surf; { - unique_lock lock(storage_task_queue_mutex); + lock_guard lock(storage_task_queue_mutex); surf = surface_for_frame[display_frame_num]; assert(surf != nullptr); } @@ -1990,6 +1912,10 @@ void QuickSyncEncoderImpl::pass_frame(QuickSyncEncoderImpl::PendingFrame frame, } else if (global_flags.x264_video_to_http || global_flags.x264_video_to_disk) { x264_encoder->add_frame(pts, duration, frame.ycbcr_coefficients, data, received_ts); } + + if (v4l_output != nullptr) { + v4l_output->send_frame(data); + } } void QuickSyncEncoderImpl::encode_frame(QuickSyncEncoderImpl::PendingFrame frame, int encoding_frame_num, int display_frame_num, int gop_start_display_frame_num, @@ -1999,7 +1925,7 @@ void QuickSyncEncoderImpl::encode_frame(QuickSyncEncoderImpl::PendingFrame frame GLSurface *surf; { - unique_lock lock(storage_task_queue_mutex); + lock_guard lock(storage_task_queue_mutex); surf = surface_for_frame[display_frame_num]; assert(surf != nullptr); } @@ -2063,7 +1989,7 @@ void QuickSyncEncoderImpl::encode_frame(QuickSyncEncoderImpl::PendingFrame frame // Lock the references for this frame; otherwise, they could be // rendered to before this frame is done encoding. { - unique_lock lock(storage_task_queue_mutex); + lock_guard lock(storage_task_queue_mutex); for (const ReferenceFrame &frame : reference_frames) { assert(surface_for_frame.count(frame.display_number)); ++surface_for_frame[frame.display_number]->refcount; @@ -2142,7 +2068,7 @@ string QuickSyncEncoder::get_usable_va_display() } // First try the default (ie., whatever $DISPLAY is set to). - unique_ptr va_dpy = try_open_va("", nullptr, nullptr); + unique_ptr va_dpy = try_open_va_h264("", nullptr, nullptr); if (va_dpy != nullptr) { if (need_env_reset) { unsetenv("LIBVA_MESSAGING_LEVEL"); @@ -2160,7 +2086,7 @@ string QuickSyncEncoder::get_usable_va_display() } else { for (size_t i = 0; i < g.gl_pathc; ++i) { string path = g.gl_pathv[i]; - va_dpy = try_open_va(path, nullptr, nullptr); + va_dpy = try_open_va_h264(path, nullptr, nullptr); if (va_dpy != nullptr) { fprintf(stderr, "Autodetected %s as a suitable replacement; using it.\n", path.c_str()); @@ -2179,5 +2105,5 @@ string QuickSyncEncoder::get_usable_va_display() fprintf(stderr, "to expose Quick Sync. Alternatively, you can use --record-x264-video\n"); fprintf(stderr, "to use software instead of hardware H.264 encoding, at the expense\n"); fprintf(stderr, "of increased CPU usage and possibly bit rate.\n"); - exit(1); + abort(); }