From 5c127e80911b92a8a47b08a51e8dfb52e67fc206 Mon Sep 17 00:00:00 2001
From: Damiano Galassi <damiog@gmail.com>
Date: Thu, 15 Nov 2018 13:56:16 +0100
Subject: [PATCH] Add VideoToolbox hardware encoding thru FFmpeg.

---
 contrib/ffmpeg/A04-videotoolbox.patch      | 27 ++++++++
 contrib/ffmpeg/module.defs                 |  5 +-
 libhb/common.c                             | 29 +++++++++
 libhb/common.h                             |  8 ++-
 libhb/encavcodec.c                         | 71 ++++++++++++++++++++++
 libhb/muxavformat.c                        |  2 +
 libhb/platform/macosx/vt_common.c          | 63 +++++++++++++++++++
 libhb/platform/macosx/vt_common.h          | 16 +++++
 libhb/work.c                               | 10 +++
 macosx/Base.lproj/Video.xib                | 26 ++++++++
 macosx/HBVideo+UIAdditions.h               |  2 +
 macosx/HBVideo+UIAdditions.m               | 21 +++++++
 macosx/HBVideo.m                           | 34 ++++++++++-
 macosx/HandBrake.xcodeproj/project.pbxproj | 16 +++++
 test/module.defs                           |  2 +-
 15 files changed, 326 insertions(+), 6 deletions(-)
 create mode 100644 contrib/ffmpeg/A04-videotoolbox.patch
 create mode 100644 libhb/platform/macosx/vt_common.c
 create mode 100644 libhb/platform/macosx/vt_common.h

diff --git a/contrib/ffmpeg/A04-videotoolbox.patch b/contrib/ffmpeg/A04-videotoolbox.patch
new file mode 100644
index 000000000..387af0d66
--- /dev/null
+++ b/contrib/ffmpeg/A04-videotoolbox.patch
@@ -0,0 +1,27 @@
+diff --git a/libavcodec/videotoolboxenc.c b/libavcodec/videotoolboxenc.c
+index 7796a68..e8b6245 100644
+--- a/libavcodec/videotoolboxenc.c
++++ b/libavcodec/videotoolboxenc.c
+@@ -866,6 +866,14 @@ static int get_cv_color_primaries(AVCodecContext *avctx,
+             *primaries = NULL;
+             break;
+ 
++        case AVCOL_PRI_BT470BG:
++			*primaries = kCVImageBufferColorPrimaries_EBU_3213;
++            break;
++
++        case AVCOL_PRI_SMPTE170M:
++			*primaries = kCVImageBufferColorPrimaries_SMPTE_C;
++            break;
++
+         case AVCOL_PRI_BT709:
+             *primaries = kCVImageBufferColorPrimaries_ITU_R_709_2;
+             break;
+@@ -1302,6 +1310,7 @@ static av_cold int vtenc_init(AVCodecContext *avctx)
+         vtctx->get_param_set_func = compat_keys.CMVideoFormatDescriptionGetHEVCParameterSetAtIndex;
+         if (!vtctx->get_param_set_func) return AVERROR(EINVAL);
+         if (!get_vt_hevc_profile_level(avctx, &profile_level)) return AVERROR(EINVAL);
++        vtctx->has_b_frames = avctx->max_b_frames > 0;
+     }
+ 
+     vtctx->session = NULL;
diff --git a/contrib/ffmpeg/module.defs b/contrib/ffmpeg/module.defs
index 87f236380..d2b41be0c 100644
--- a/contrib/ffmpeg/module.defs
+++ b/contrib/ffmpeg/module.defs
@@ -75,7 +75,10 @@ FFMPEG.CONFIGURE.extra += \
     --enable-muxer=ipod
 
 ifeq (darwin,$(BUILD.system))
-    FFMPEG.CONFIGURE.extra += --disable-audiotoolbox --disable-coreimage --disable-videotoolbox
+    FFMPEG.CONFIGURE.extra += --disable-audiotoolbox --disable-coreimage \
+        --enable-encoder=h264_videotoolbox \
+        --enable-encoder=hevc_videotoolbox
+
     ifeq (x86_64,$(BUILD.arch))
         FFMPEG.CONFIGURE.extra += --arch=x86_64
     endif
diff --git a/libhb/common.c b/libhb/common.c
index 2d668f810..16085bb44 100644
--- a/libhb/common.c
+++ b/libhb/common.c
@@ -39,6 +39,10 @@
 #include "vce_common.h"
 #endif
 
+#ifdef __APPLE__
+#include "platform/macosx/vt_common.h"
+#endif
+
 static int mixdown_get_opus_coupled_stream_count(int mixdown);
 
 /**********************************************************************
@@ -246,6 +250,7 @@ hb_encoder_internal_t hb_video_encoders[]  =
     { { "H.264 (Intel QSV)",   "qsv_h264",   "H.264 (Intel Media SDK)", HB_VCODEC_QSV_H264,          HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_VCODEC_H264,   },
     { { "H.264 (AMD VCE)",     "vce_h264",   "H.264 (AMD VCE)",      HB_VCODEC_FFMPEG_VCE_H264,   HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_VCODEC_H264,   },
     { { "H.264 (NVEnc)",       "nvenc_h264", "H.264 (NVEnc)",      HB_VCODEC_FFMPEG_NVENC_H264, HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_VCODEC_H264,   },
+    { { "H.264 (VideoToolbox)","vt_h264",    "H.264 (libavcodec)",      HB_VCODEC_FFMPEG_VT_H264,    HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_VCODEC_H264,   },
     { { "H.265 (x265)",        "x265",       "H.265 (libx265)",         HB_VCODEC_X265_8BIT,         HB_MUX_AV_MP4|HB_MUX_AV_MKV,   }, NULL, 1, HB_GID_VCODEC_H265,   },
     { { "H.265 10-bit (x265)", "x265_10bit", "H.265 10-bit (libx265)",  HB_VCODEC_X265_10BIT,        HB_MUX_AV_MP4|HB_MUX_AV_MKV,   }, NULL, 1, HB_GID_VCODEC_H265,   },
     { { "H.265 12-bit (x265)", "x265_12bit", "H.265 12-bit (libx265)",  HB_VCODEC_X265_12BIT,        HB_MUX_AV_MP4|HB_MUX_AV_MKV,   }, NULL, 1, HB_GID_VCODEC_H265,   },
@@ -254,6 +259,7 @@ hb_encoder_internal_t hb_video_encoders[]  =
     { { "H.265 10-bit (Intel QSV)","qsv_h265_10bit", "H.265 10-bit (Intel Media SDK)", HB_VCODEC_QSV_H265_10BIT,     HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_VCODEC_H265,   },
     { { "H.265 (AMD VCE)",     "vce_h265",   "H.265 (AMD VCE)",      HB_VCODEC_FFMPEG_VCE_H265,   HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_VCODEC_H265,   },
     { { "H.265 (NVEnc)",       "nvenc_h265", "H.265 (NVEnc)",      HB_VCODEC_FFMPEG_NVENC_H265, HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_VCODEC_H265,   },
+    { { "H.265 (VideoToolbox)","vt_h265",    "H.265 (libavcodec)",      HB_VCODEC_FFMPEG_VT_H265,    HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_VCODEC_H265,   },
     { { "MPEG-4",              "mpeg4",      "MPEG-4 (libavcodec)",     HB_VCODEC_FFMPEG_MPEG4,      HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_VCODEC_MPEG4,  },
     { { "MPEG-2",              "mpeg2",      "MPEG-2 (libavcodec)",     HB_VCODEC_FFMPEG_MPEG2,      HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_VCODEC_MPEG2,  },
     { { "VP8",                 "VP8",        "VP8 (libvpx)",            HB_VCODEC_FFMPEG_VP8,                        HB_MUX_MASK_MKV, }, NULL, 1, HB_GID_VCODEC_VP8,    },
@@ -293,6 +299,13 @@ static int hb_video_encoder_is_enabled(int encoder)
             return hb_nvenc_h265_available();
 #endif
 
+#ifdef __APPLE__
+        case HB_VCODEC_FFMPEG_VT_H264:
+            return hb_vt_h264_is_available();
+        case HB_VCODEC_FFMPEG_VT_H265:
+            return hb_vt_h265_is_available();
+#endif
+
 #ifdef USE_X265
         case HB_VCODEC_X265_8BIT:
         case HB_VCODEC_X265_10BIT:
@@ -1365,6 +1378,14 @@ void hb_video_quality_get_limits(uint32_t codec, float *low, float *high,
             *high        = 63.;
             break;
 
+        case HB_VCODEC_FFMPEG_VT_H264:
+        case HB_VCODEC_FFMPEG_VT_H265:
+            *direction   = 1;
+            *granularity = 0.1;
+            *low         = 0.;
+            *high        = 0.;
+            break;
+
         case HB_VCODEC_FFMPEG_MPEG2:
         case HB_VCODEC_FFMPEG_MPEG4:
         default:
@@ -1511,6 +1532,8 @@ const char* const* hb_video_encoder_get_profiles(int encoder)
 
         case HB_VCODEC_FFMPEG_NVENC_H264:
         case HB_VCODEC_FFMPEG_NVENC_H265:
+        case HB_VCODEC_FFMPEG_VT_H264:
+        case HB_VCODEC_FFMPEG_VT_H265:
             return hb_av_profile_get_names(encoder);
         default:
             return NULL;
@@ -1531,6 +1554,7 @@ const char* const* hb_video_encoder_get_levels(int encoder)
         case HB_VCODEC_X264_8BIT:
         case HB_VCODEC_X264_10BIT:
         case HB_VCODEC_FFMPEG_NVENC_H264:
+        case HB_VCODEC_FFMPEG_VT_H264:
             return hb_h264_level_names;
 
 #ifdef USE_VCE
@@ -1546,6 +1570,11 @@ const char* const* hb_video_encoder_get_levels(int encoder)
         case HB_VCODEC_FFMPEG_VCE_H265:
             return hb_h265_level_names;
 
+#ifdef __APPLE__
+        case HB_VCODEC_FFMPEG_VT_H265:
+            return hb_vt_h265_level_names;
+#endif
+
         default:
             return NULL;
     }
diff --git a/libhb/common.h b/libhb/common.h
index 25b2bab99..a3dca1e59 100644
--- a/libhb/common.h
+++ b/libhb/common.h
@@ -512,7 +512,9 @@ struct hb_job_s
 #define HB_VCODEC_FFMPEG_VCE_H265 0x00080000
 #define HB_VCODEC_FFMPEG_NVENC_H264 0x00100000
 #define HB_VCODEC_FFMPEG_NVENC_H265 0x00200000
-#define HB_VCODEC_FFMPEG_MASK  (0x00000F0|HB_VCODEC_FFMPEG_VCE_H264|HB_VCODEC_FFMPEG_VCE_H265|HB_VCODEC_FFMPEG_NVENC_H264|HB_VCODEC_FFMPEG_NVENC_H265)
+#define HB_VCODEC_FFMPEG_VT_H264 0x00400000
+#define HB_VCODEC_FFMPEG_VT_H265 0x00800000
+#define HB_VCODEC_FFMPEG_MASK  (0x00000F0|HB_VCODEC_FFMPEG_VCE_H264|HB_VCODEC_FFMPEG_VCE_H265|HB_VCODEC_FFMPEG_NVENC_H264|HB_VCODEC_FFMPEG_NVENC_H265|HB_VCODEC_FFMPEG_VT_H264|HB_VCODEC_FFMPEG_VT_H265)
 #define HB_VCODEC_QSV_H264     0x0000100
 #define HB_VCODEC_QSV_H265_8BIT     0x0000200
 #define HB_VCODEC_QSV_H265_10BIT    0x0000400
@@ -523,14 +525,14 @@ struct hb_job_s
 #define HB_VCODEC_X264         HB_VCODEC_X264_8BIT
 #define HB_VCODEC_X264_10BIT   0x0020000
 #define HB_VCODEC_X264_MASK    0x0030000
-#define HB_VCODEC_H264_MASK    (HB_VCODEC_X264_MASK|HB_VCODEC_QSV_H264|HB_VCODEC_FFMPEG_VCE_H264|HB_VCODEC_FFMPEG_NVENC_H264)
+#define HB_VCODEC_H264_MASK    (HB_VCODEC_X264_MASK|HB_VCODEC_QSV_H264|HB_VCODEC_FFMPEG_VCE_H264|HB_VCODEC_FFMPEG_NVENC_H264|HB_VCODEC_FFMPEG_VT_H264)
 #define HB_VCODEC_X265_8BIT    0x0001000
 #define HB_VCODEC_X265         HB_VCODEC_X265_8BIT
 #define HB_VCODEC_X265_10BIT   0x0002000
 #define HB_VCODEC_X265_12BIT   0x0004000
 #define HB_VCODEC_X265_16BIT   0x0008000
 #define HB_VCODEC_X265_MASK    0x000F000
-#define HB_VCODEC_H265_MASK    (HB_VCODEC_X265_MASK|HB_VCODEC_QSV_H265_MASK|HB_VCODEC_FFMPEG_VCE_H265|HB_VCODEC_FFMPEG_NVENC_H265)
+#define HB_VCODEC_H265_MASK    (HB_VCODEC_X265_MASK|HB_VCODEC_QSV_H265_MASK|HB_VCODEC_FFMPEG_VCE_H265|HB_VCODEC_FFMPEG_NVENC_H265|HB_VCODEC_FFMPEG_VT_H265)
 
 /* define an invalid CQ value compatible with all CQ-capable codecs */
 #define HB_INVALID_VIDEO_QUALITY (-1000.)
diff --git a/libhb/encavcodec.c b/libhb/encavcodec.c
index dc94310c6..159421533 100644
--- a/libhb/encavcodec.c
+++ b/libhb/encavcodec.c
@@ -83,6 +83,20 @@ static const char * const h265_nvenc_profile_names[] =
     "auto", "main", NULL // "main10", "rext"  We do not currently support 10bit encodes with this encoder. 
 };
 
+static const char * const h26x_vt_preset_name[] =
+{
+    "default", NULL
+};
+
+static const char * const h264_vt_profile_name[] =
+{
+    "auto", "baseline", "main", "high", NULL
+};
+
+static const char * const h265_vt_profile_name[] =
+{
+    "auto", "main", "main10", NULL
+};
 
 int encavcodecInit( hb_work_object_t * w, hb_job_t * job )
 {
@@ -136,6 +150,10 @@ int encavcodecInit( hb_work_object_t * w, hb_job_t * job )
                     hb_log("encavcodecInit: H.264 (AMD VCE)");
                     codec = avcodec_find_encoder_by_name("h264_amf");
                     break;
+                case HB_VCODEC_FFMPEG_VT_H264:
+                    hb_log("encavcodecInit: H.264 (VideoToolbox)");
+                    codec = avcodec_find_encoder_by_name("h264_videotoolbox");
+                    break;
             }
         }break;
         case AV_CODEC_ID_HEVC:
@@ -149,6 +167,10 @@ int encavcodecInit( hb_work_object_t * w, hb_job_t * job )
                     hb_log("encavcodecInit: H.265 (AMD VCE)");
                     codec = avcodec_find_encoder_by_name("hevc_amf");
                     break;
+                case HB_VCODEC_FFMPEG_VT_H265:
+                    hb_log("encavcodecInit: H.265 (VideoToolbox)");
+                    codec = avcodec_find_encoder_by_name("hevc_videotoolbox");
+                    break;
             }
         }break;
         default:
@@ -406,6 +428,47 @@ int encavcodecInit( hb_work_object_t * w, hb_job_t * job )
         context->flags |= AV_CODEC_FLAG_GRAY;
     }
 
+    if (job->vcodec == HB_VCODEC_FFMPEG_VT_H264)
+    {
+        // Set profile and level
+        if (job->encoder_profile != NULL && *job->encoder_profile)
+        {
+            if (!strcasecmp(job->encoder_profile, "baseline"))
+                av_dict_set(&av_opts, "profile", "baseline", 0);
+            else if (!strcasecmp(job->encoder_profile, "main"))
+                av_dict_set(&av_opts, "profile", "main", 0);
+            else if (!strcasecmp(job->encoder_profile, "high"))
+                av_dict_set(&av_opts, "profile", "high", 0);
+        }
+
+        if (job->encoder_level != NULL && *job->encoder_level)
+        {
+            int i = 1;
+            while (hb_h264_level_names[i] != NULL)
+            {
+                if (!strcasecmp(job->encoder_level, hb_h264_level_names[i]))
+                    av_dict_set(&av_opts, "level", job->encoder_level, 0);
+                ++i;
+            }
+        }
+
+        context->max_b_frames = 16;
+    }
+
+    if (job->vcodec == HB_VCODEC_FFMPEG_VT_H265)
+    {
+        // Set profile and level
+        if (job->encoder_profile != NULL && *job->encoder_profile)
+        {
+            if (!strcasecmp(job->encoder_profile, "main"))
+                av_dict_set(&av_opts, "profile", "main", 0);
+            else if (!strcasecmp(job->encoder_profile, "main10"))
+                av_dict_set(&av_opts, "profile", "main10", 0);
+        }
+
+        context->max_b_frames = 16;
+    }
+
     if (job->vcodec == HB_VCODEC_FFMPEG_VCE_H264)
     {
         // Set profile and level
@@ -935,6 +998,10 @@ const char* const* hb_av_preset_get_names(int encoder)
         case HB_VCODEC_FFMPEG_NVENC_H265:
             return h26x_nvenc_preset_names;
 
+        case HB_VCODEC_FFMPEG_VT_H264:
+        case HB_VCODEC_FFMPEG_VT_H265:
+            return h26x_vt_preset_name;
+
         default:
             return NULL;
     }
@@ -948,6 +1015,10 @@ const char* const* hb_av_profile_get_names(int encoder)
             return h264_nvenc_profile_names;
         case HB_VCODEC_FFMPEG_NVENC_H265:
             return h265_nvenc_profile_names;
+        case HB_VCODEC_FFMPEG_VT_H264:
+            return h264_vt_profile_name;
+        case HB_VCODEC_FFMPEG_VT_H265:
+            return h265_vt_profile_name;
 
          default:
              return NULL;
diff --git a/libhb/muxavformat.c b/libhb/muxavformat.c
index 5f2d7fe87..9f15f52c1 100644
--- a/libhb/muxavformat.c
+++ b/libhb/muxavformat.c
@@ -252,6 +252,7 @@ static int avformatInit( hb_mux_object_t * m )
 
         case HB_VCODEC_FFMPEG_VCE_H264:
         case HB_VCODEC_FFMPEG_NVENC_H264:
+        case HB_VCODEC_FFMPEG_VT_H264:
             track->st->codecpar->codec_id = AV_CODEC_ID_H264;
             if (job->mux == HB_MUX_AV_MP4 && job->inline_parameter_sets)
             {
@@ -387,6 +388,7 @@ static int avformatInit( hb_mux_object_t * m )
 
         case HB_VCODEC_FFMPEG_VCE_H265:
         case HB_VCODEC_FFMPEG_NVENC_H265:
+        case HB_VCODEC_FFMPEG_VT_H265:
             track->st->codecpar->codec_id  = AV_CODEC_ID_HEVC;
             if (job->mux == HB_MUX_AV_MP4 && job->inline_parameter_sets)
             {
diff --git a/libhb/platform/macosx/vt_common.c b/libhb/platform/macosx/vt_common.c
new file mode 100644
index 000000000..be75018ea
--- /dev/null
+++ b/libhb/platform/macosx/vt_common.c
@@ -0,0 +1,63 @@
+/* vt_common.c
+
+   Copyright (c) 2003-2018 HandBrake Team
+   This file is part of the HandBrake source code
+   Homepage: <http://handbrake.fr/>.
+   It may be used under the terms of the GNU General Public License v2.
+   For full terms see the file COPYING file or visit http://www.gnu.org/licenses/gpl-2.0.html
+ */
+
+#include "hb.h"
+#include "vt_common.h"
+
+#include <VideoToolbox/VideoToolbox.h>
+#include <CoreMedia/CoreMedia.h>
+#include <CoreVideo/CoreVideo.h>
+
+//#define VT_STATS
+
+#ifdef VT_STATS
+static void toggle_vt_gva_stats(bool state)
+{
+    CFPropertyListRef cf_state = state ? kCFBooleanTrue : kCFBooleanFalse;
+    CFPreferencesSetValue(CFSTR("gvaEncoderPerf"), cf_state, CFSTR("com.apple.GVAEncoder"), kCFPreferencesCurrentUser, kCFPreferencesCurrentHost);
+    CFPreferencesSetValue(CFSTR("gvaEncoderPSNR"), cf_state, CFSTR("com.apple.GVAEncoder"), kCFPreferencesCurrentUser, kCFPreferencesCurrentHost);
+    CFPreferencesSetValue(CFSTR("gvaEncoderSSIM"), cf_state, CFSTR("com.apple.GVAEncoder"), kCFPreferencesCurrentUser, kCFPreferencesCurrentHost);
+
+    //CFPreferencesSetValue(CFSTR("gvaEncoderStats"), cf_state, CFSTR("com.apple.GVAEncoder"), kCFPreferencesCurrentUser, kCFPreferencesCurrentHost);
+    //CFPreferencesSetValue(CFSTR("gvaDebug"), cf_state, CFSTR("com.apple.AppleGVA"), kCFPreferencesCurrentUser, kCFPreferencesCurrentHost);
+}
+#endif
+
+static const CFStringRef encoder_id_h264 = CFSTR("com.apple.videotoolbox.videoencoder.h264.gva");
+static const CFStringRef encoder_id_h265 = CFSTR("com.apple.videotoolbox.videoencoder.hevc.gva");
+
+int encvt_available(CFStringRef encoder)
+{
+    CFArrayRef encoder_list;
+    VTCopyVideoEncoderList(NULL, &encoder_list);
+    CFIndex size = CFArrayGetCount(encoder_list);
+
+    for (CFIndex i = 0; i < size; i++ )
+    {
+        CFDictionaryRef encoder_dict = CFArrayGetValueAtIndex(encoder_list, i);
+        CFStringRef encoder_id = CFDictionaryGetValue(encoder_dict, kVTVideoEncoderSpecification_EncoderID);
+        if (CFEqual(encoder_id, encoder))
+        {
+            CFRelease(encoder_list);
+            return 1;
+        }
+    }
+    CFRelease(encoder_list);
+    return 0;
+}
+
+int hb_vt_h264_is_available()
+{
+    return encvt_available(encoder_id_h264);
+}
+
+int hb_vt_h265_is_available()
+{
+    return encvt_available(encoder_id_h265);
+}
diff --git a/libhb/platform/macosx/vt_common.h b/libhb/platform/macosx/vt_common.h
new file mode 100644
index 000000000..22187d0cc
--- /dev/null
+++ b/libhb/platform/macosx/vt_common.h
@@ -0,0 +1,16 @@
+/* vt_common.h
+
+   Copyright (c) 2003-2018 HandBrake Team
+   This file is part of the HandBrake source code
+   Homepage: <http://handbrake.fr/>.
+   It may be used under the terms of the GNU General Public License v2.
+   For full terms see the file COPYING file or visit http://www.gnu.org/licenses/gpl-2.0.html
+ */
+
+int  hb_vt_h264_is_available();
+int  hb_vt_h265_is_available();
+
+static const char * const hb_vt_h265_level_names[] =
+{
+    "auto",  NULL,
+};
diff --git a/libhb/work.c b/libhb/work.c
index 9a3eb55a7..350af0cc7 100644
--- a/libhb/work.c
+++ b/libhb/work.c
@@ -284,6 +284,16 @@ hb_work_object_t* hb_video_encoder(hb_handle_t *h, int vcodec)
             w->codec_param = AV_CODEC_ID_HEVC;
             break;
 #endif
+#ifdef __APPLE__
+        case HB_VCODEC_FFMPEG_VT_H264:
+            w = hb_get_work(h, WORK_ENCAVCODEC);
+            w->codec_param = AV_CODEC_ID_H264;
+            break;
+        case HB_VCODEC_FFMPEG_VT_H265:
+            w = hb_get_work(h, WORK_ENCAVCODEC);
+            w->codec_param = AV_CODEC_ID_HEVC;
+            break;
+#endif
 
         default:
             hb_error("Unknown video codec (0x%x)", vcodec );
diff --git a/macosx/Base.lproj/Video.xib b/macosx/Base.lproj/Video.xib
index 8f69b26bb..8bd3cd262 100644
--- a/macosx/Base.lproj/Video.xib
+++ b/macosx/Base.lproj/Video.xib
@@ -86,6 +86,7 @@
                     </textFieldCell>
                     <connections>
                         <binding destination="-2" name="textColor" keyPath="self.labelColor" id="EsL-F5-tAO"/>
+                        <binding destination="-2" name="enabled" keyPath="self.video.isConstantQualitySupported" id="vfD-XL-S4P"/>
                         <binding destination="-2" name="value" keyPath="self.video.quality" id="tMZ-Xb-TuF"/>
                     </connections>
                 </textField>
@@ -124,6 +125,14 @@
                         <font key="font" metaFont="smallSystem"/>
                     </buttonCell>
                     <connections>
+                        <binding destination="-2" name="enabled3" keyPath="self.video.twoPassSupported" previousBinding="c4g-dz-q05" id="rwS-GN-5Dd">
+                            <dictionary key="options">
+                                <integer key="NSMultipleValuesPlaceholder" value="-1"/>
+                                <integer key="NSNoSelectionPlaceholder" value="-1"/>
+                                <integer key="NSNotApplicablePlaceholder" value="-1"/>
+                                <integer key="NSNullPlaceholder" value="-1"/>
+                            </dictionary>
+                        </binding>
                         <binding destination="-2" name="enabled2" keyPath="self.video" previousBinding="7aV-7j-MzB" id="c4g-dz-q05">
                             <dictionary key="options">
                                 <integer key="NSMultipleValuesPlaceholder" value="-1"/>
@@ -253,6 +262,14 @@ x264 is lossless at RF 0.</string>
                                 <string key="NSValueTransformerName">NSIsNotNil</string>
                             </dictionary>
                         </binding>
+                        <binding destination="-2" name="enabled3" keyPath="self.video.isConstantQualitySupported" previousBinding="ywk-WQ-GNY" id="riO-a0-Jfa">
+                            <dictionary key="options">
+                                <integer key="NSMultipleValuesPlaceholder" value="-1"/>
+                                <integer key="NSNoSelectionPlaceholder" value="-1"/>
+                                <integer key="NSNotApplicablePlaceholder" value="-1"/>
+                                <integer key="NSNullPlaceholder" value="-1"/>
+                            </dictionary>
+                        </binding>
                         <binding destination="-2" name="value" keyPath="self.video.quality" previousBinding="nAO-gB-Jbd" id="C3d-pR-fJ2">
                             <dictionary key="options">
                                 <string key="NSValueTransformerName">HBQualityTransformer</string>
@@ -273,6 +290,7 @@ x264 is lossless at RF 0.</string>
                     <connections>
                         <binding destination="-2" name="textColor" keyPath="self.labelColor" id="S90-zY-jeW"/>
                         <binding destination="-2" name="value" keyPath="self.video.constantQualityLabel" id="ri5-aE-FP5"/>
+                        <binding destination="-2" name="enabled" keyPath="self.video.isConstantQualitySupported" id="6YN-nu-K6b"/>
                     </connections>
                 </textField>
                 <box autoresizesSubviews="NO" boxType="custom" borderType="none" title="x264 Presets" titlePosition="noTitle" transparent="YES" translatesAutoresizingMaskIntoConstraints="NO" id="A4U-3F-pYq">
@@ -289,6 +307,14 @@ x264 is lossless at RF 0.</string>
                         <font key="font" metaFont="smallSystem"/>
                     </buttonCell>
                     <connections>
+                        <binding destination="-2" name="enabled2" keyPath="self.video.isConstantQualitySupported" previousBinding="Ewd-OO-T3Z" id="cEw-6S-gon">
+                            <dictionary key="options">
+                                <integer key="NSMultipleValuesPlaceholder" value="-1"/>
+                                <integer key="NSNoSelectionPlaceholder" value="-1"/>
+                                <integer key="NSNotApplicablePlaceholder" value="-1"/>
+                                <integer key="NSNullPlaceholder" value="-1"/>
+                            </dictionary>
+                        </binding>
                         <binding destination="-2" name="enabled" keyPath="self.video" id="Ewd-OO-T3Z">
                             <dictionary key="options">
                                 <string key="NSValueTransformerName">NSIsNotNil</string>
diff --git a/macosx/HBVideo+UIAdditions.h b/macosx/HBVideo+UIAdditions.h
index 5ce7648a7..236096916 100644
--- a/macosx/HBVideo+UIAdditions.h
+++ b/macosx/HBVideo+UIAdditions.h
@@ -21,6 +21,7 @@
 @property (nonatomic, readonly) NSArray *levels;
 
 @property (nonatomic, readonly) BOOL fastDecodeSupported;
+@property (nonatomic, readonly) BOOL twoPassSupported;
 @property (nonatomic, readonly) BOOL turboTwoPassSupported;
 
 @property (nonatomic, readonly) NSString *unparseOptions;
@@ -29,6 +30,7 @@
 
 @property (nonatomic, readonly) double qualityMinValue;
 @property (nonatomic, readonly) double qualityMaxValue;
+@property (nonatomic, readonly) BOOL isConstantQualitySupported;
 
 @end
 
diff --git a/macosx/HBVideo+UIAdditions.m b/macosx/HBVideo+UIAdditions.m
index e23c1ab72..d7cbc0eda 100644
--- a/macosx/HBVideo+UIAdditions.m
+++ b/macosx/HBVideo+UIAdditions.m
@@ -104,6 +104,17 @@
             (self.encoder & HB_VCODEC_X265_MASK));
 }
 
++ (NSSet<NSString *> *)keyPathsForValuesAffectingTwoPassSupported
+{
+    return [NSSet setWithObjects:@"encoder", nil];
+}
+
+- (BOOL)twoPassSupported
+{
+    return !((self.encoder & HB_VCODEC_FFMPEG_VT_H264) ||
+            (self.encoder & HB_VCODEC_FFMPEG_VT_H265));
+}
+
 + (NSSet<NSString *> *)keyPathsForValuesAffectingConstantQualityLabel
 {
     return [NSSet setWithObjects:@"encoder", nil];
@@ -114,6 +125,16 @@
     return @(hb_video_quality_get_name(self.encoder));
 }
 
++ (NSSet<NSString *> *)keyPathsForValuesAffectingIsConstantQualitySupported
+{
+    return [NSSet setWithObjects:@"encoder", nil];
+}
+
+- (BOOL)isConstantQualitySupported
+{
+    return (self.qualityMaxValue == 0 && self.qualityMinValue == 0) == NO;
+}
+
 + (NSSet<NSString *> *)keyPathsForValuesAffectingUnparseOptions
 {
     return [NSSet setWithObjects:@"encoder", @"preset", @"tune", @"profile", @"level",
diff --git a/macosx/HBVideo.m b/macosx/HBVideo.m
index 15c2f67ae..1119aaa5c 100644
--- a/macosx/HBVideo.m
+++ b/macosx/HBVideo.m
@@ -105,6 +105,7 @@ NSString * const HBVideoChangedNotification = @"HBVideoChangedNotification";
 
     if (!(self.undo.isUndoing || self.undo.isRedoing))
     {
+        [self validateQualityType];
         [self validatePresetsSettings];
         [self validateVideoOptionExtra:previousEncoder];
     }
@@ -281,11 +282,42 @@ NSString * const HBVideoChangedNotification = @"HBVideoChangedNotification";
     [self postChangedNotification];
 }
 
+- (void)validateQualityType
+{
+    if (self.qualityType != 0)
+    {
+        int direction;
+        float minValue, maxValue, granularity;
+        hb_video_quality_get_limits(self.encoder,
+                                    &minValue, &maxValue, &granularity, &direction);
+
+        if (minValue == 0 && maxValue == 0)
+        {
+            self.qualityType = 0;
+        }
+    }
+    else
+    {
+        if ((self.encoder & HB_VCODEC_FFMPEG_VT_H264) ||
+            (self.encoder & HB_VCODEC_FFMPEG_VT_H265))
+        {
+            self.twoPass = NO;
+        }
+    }
+}
+
 - (void)validatePresetsSettings
 {
     NSArray *presets = self.presets;
     if (presets.count && ![presets containsObject:self.preset]) {
-        self.preset = presets[self.mediumPresetIndex];
+        if (presets.count > self.mediumPresetIndex)
+        {
+            self.preset = presets[self.mediumPresetIndex];
+        }
+        else
+        {
+            self.preset = presets.firstObject;
+        }
     }
 
     NSArray *tunes = self.tunes;
diff --git a/macosx/HandBrake.xcodeproj/project.pbxproj b/macosx/HandBrake.xcodeproj/project.pbxproj
index 586fcae64..f0ad8c6d2 100644
--- a/macosx/HandBrake.xcodeproj/project.pbxproj
+++ b/macosx/HandBrake.xcodeproj/project.pbxproj
@@ -225,6 +225,8 @@
 		A9906B2C1A710920001D82D5 /* HBQueueController.m in Sources */ = {isa = PBXBuildFile; fileRef = A9906B2B1A710920001D82D5 /* HBQueueController.m */; };
 		A99F40CF1B624E7E00750170 /* HBPictureViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = A99F40CD1B624E7E00750170 /* HBPictureViewController.m */; };
 		A9A0CBE81CCEA3670045B3DF /* HBPlayerTrack.m in Sources */ = {isa = PBXBuildFile; fileRef = A9A0CBE61CCEA1D10045B3DF /* HBPlayerTrack.m */; };
+		A9A25D9C21182741005A8A0F /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9A25D9B21182741005A8A0F /* CoreMedia.framework */; };
+		A9A25D9D21182753005A8A0F /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9A25D9B21182741005A8A0F /* CoreMedia.framework */; };
 		A9A7E27C1FE2A0B5006BE79F /* HBPreviewViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = A9A7E27A1FE2A0B5006BE79F /* HBPreviewViewController.m */; };
 		A9A96B8220CAD2C200A39AFB /* HBPictureHUDController.xib in Resources */ = {isa = PBXBuildFile; fileRef = A9A96B8420CAD2C200A39AFB /* HBPictureHUDController.xib */; };
 		A9A96B8520CAD2CC00A39AFB /* HBEncodingProgressHUDController.xib in Resources */ = {isa = PBXBuildFile; fileRef = A9A96B8720CAD2CC00A39AFB /* HBEncodingProgressHUDController.xib */; };
@@ -247,6 +249,9 @@
 		A9A96BDD20CAD66000A39AFB /* OutputPanel.xib in Resources */ = {isa = PBXBuildFile; fileRef = A9A96BDB20CAD66000A39AFB /* OutputPanel.xib */; };
 		A9A96BE020CAD66500A39AFB /* Preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = A9A96BDE20CAD66500A39AFB /* Preferences.xib */; };
 		A9A96BE320CAD6CD00A39AFB /* HBPreviewViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = A9A96BE120CAD6CD00A39AFB /* HBPreviewViewController.xib */; };
+		A9AB9AA5211819A500BB3C7E /* VideoToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9AB9AA4211819A500BB3C7E /* VideoToolbox.framework */; };
+		A9AB9AA621181CA900BB3C7E /* VideoToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9AB9AA4211819A500BB3C7E /* VideoToolbox.framework */; };
+		A9AB9AA821181CC700BB3C7E /* CoreVideo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9AB9AA721181CC700BB3C7E /* CoreVideo.framework */; };
 		A9ABD1A61E2A0F0700EC8B65 /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9ABD1A51E2A0F0700EC8B65 /* CoreText.framework */; };
 		A9ABD1A71E2A0F7500EC8B65 /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9ABD1A51E2A0F0700EC8B65 /* CoreText.framework */; };
 		A9ABD1A91E2A0F8200EC8B65 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9ABD1A81E2A0F8200EC8B65 /* CoreGraphics.framework */; };
@@ -579,6 +584,7 @@
 		A99F40CD1B624E7E00750170 /* HBPictureViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBPictureViewController.m; sourceTree = "<group>"; };
 		A9A0CBE51CCEA1D10045B3DF /* HBPlayerTrack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBPlayerTrack.h; sourceTree = "<group>"; };
 		A9A0CBE61CCEA1D10045B3DF /* HBPlayerTrack.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBPlayerTrack.m; sourceTree = "<group>"; };
+		A9A25D9B21182741005A8A0F /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; };
 		A9A7E2791FE2A0B5006BE79F /* HBPreviewViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HBPreviewViewController.h; sourceTree = "<group>"; };
 		A9A7E27A1FE2A0B5006BE79F /* HBPreviewViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HBPreviewViewController.m; sourceTree = "<group>"; };
 		A9A96B8320CAD2C200A39AFB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/HBPictureHUDController.xib; sourceTree = "<group>"; };
@@ -610,6 +616,8 @@
 		A9AA447B1970724D00D7DEFC /* HBAdvancedController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HBAdvancedController.h; sourceTree = "<group>"; };
 		A9AA447C1970726500D7DEFC /* HBQueueController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HBQueueController.h; sourceTree = "<group>"; };
 		A9AA447D1970729300D7DEFC /* HBPreviewGenerator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HBPreviewGenerator.h; sourceTree = "<group>"; };
+		A9AB9AA4211819A500BB3C7E /* VideoToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = VideoToolbox.framework; path = System/Library/Frameworks/VideoToolbox.framework; sourceTree = SDKROOT; };
+		A9AB9AA721181CC700BB3C7E /* CoreVideo.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreVideo.framework; path = System/Library/Frameworks/CoreVideo.framework; sourceTree = SDKROOT; };
 		A9ABD1A51E2A0F0700EC8B65 /* CoreText.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreText.framework; path = System/Library/Frameworks/CoreText.framework; sourceTree = SDKROOT; };
 		A9ABD1A81E2A0F8200EC8B65 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
 		A9B34D74197696FE00871B7D /* DiskArbitration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = DiskArbitration.framework; path = System/Library/Frameworks/DiskArbitration.framework; sourceTree = SDKROOT; };
@@ -676,6 +684,9 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				A9A25D9C21182741005A8A0F /* CoreMedia.framework in Frameworks */,
+				A9AB9AA821181CC700BB3C7E /* CoreVideo.framework in Frameworks */,
+				A9AB9AA621181CA900BB3C7E /* VideoToolbox.framework in Frameworks */,
 				A9ABD1A91E2A0F8200EC8B65 /* CoreGraphics.framework in Frameworks */,
 				A9ABD1A71E2A0F7500EC8B65 /* CoreText.framework in Frameworks */,
 				A9E165521C523016003EF30E /* libavfilter.a in Frameworks */,
@@ -731,6 +742,8 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				A9A25D9D21182753005A8A0F /* CoreMedia.framework in Frameworks */,
+				A9AB9AA5211819A500BB3C7E /* VideoToolbox.framework in Frameworks */,
 				A9ABD1AA1E2A0F8F00EC8B65 /* CoreGraphics.framework in Frameworks */,
 				A9ABD1A61E2A0F0700EC8B65 /* CoreText.framework in Frameworks */,
 				A91119A31C7DD591001C463C /* IOKit.framework in Frameworks */,
@@ -868,6 +881,9 @@
 		273F203414ADBAC30021BE6D /* Frameworks */ = {
 			isa = PBXGroup;
 			children = (
+				A9A25D9B21182741005A8A0F /* CoreMedia.framework */,
+				A9AB9AA721181CC700BB3C7E /* CoreVideo.framework */,
+				A9AB9AA4211819A500BB3C7E /* VideoToolbox.framework */,
 				A9ABD1A81E2A0F8200EC8B65 /* CoreGraphics.framework */,
 				A9ABD1A51E2A0F0700EC8B65 /* CoreText.framework */,
 				A91CE2D31C7DABE40068F46F /* libiconv.tbd */,
diff --git a/test/module.defs b/test/module.defs
index eaa10ea69..92ae40c45 100644
--- a/test/module.defs
+++ b/test/module.defs
@@ -74,7 +74,7 @@ endif
 TEST.GCC.I += $(LIBHB.GCC.I)
 
 ifeq ($(BUILD.system),darwin)
-    TEST.GCC.f += IOKit CoreServices CoreText CoreGraphics AudioToolbox Foundation
+    TEST.GCC.f += IOKit CoreServices CoreText CoreGraphics AudioToolbox VideoToolbox CoreMedia CoreVideo Foundation
     TEST.GCC.l += iconv
 else ifeq ($(BUILD.system),linux)
     TEST.GCC.l += pthread dl m
-- 
2.40.0