diff --git a/board/raspberrypi/overlay/opt/vc/lib b/board/raspberrypi/overlay/opt/vc/lib new file mode 120000 index 00000000000..2e9ebb527e0 --- /dev/null +++ b/board/raspberrypi/overlay/opt/vc/lib @@ -0,0 +1 @@ +../../usr/lib \ No newline at end of file diff --git a/board/raspberrypi2/overlay/opt/vc/lib b/board/raspberrypi2/overlay/opt/vc/lib new file mode 120000 index 00000000000..2e9ebb527e0 --- /dev/null +++ b/board/raspberrypi2/overlay/opt/vc/lib @@ -0,0 +1 @@ +../../usr/lib \ No newline at end of file diff --git a/board/raspberrypi3/overlay/opt/vc/lib b/board/raspberrypi3/overlay/opt/vc/lib new file mode 120000 index 00000000000..2e9ebb527e0 --- /dev/null +++ b/board/raspberrypi3/overlay/opt/vc/lib @@ -0,0 +1 @@ +../../usr/lib \ No newline at end of file diff --git a/configs/raspberrypi2_defconfig b/configs/raspberrypi2_defconfig index 664418fc44d..39280f06a4c 100644 --- a/configs/raspberrypi2_defconfig +++ b/configs/raspberrypi2_defconfig @@ -27,6 +27,7 @@ BR2_PACKAGE_FFMPEG=y BR2_PACKAGE_FFMPEG_GPL=y BR2_PACKAGE_FFMPEG_NONFREE=y BR2_PACKAGE_FFMPEG_SWSCALE=y +BR2_PACKAGE_FFMPEG_RPI_HW_CODECS=y BR2_PACKAGE_LIBWEBCAM=y BR2_PACKAGE_MOTION=y BR2_PACKAGE_STREAMEYE=y diff --git a/configs/raspberrypi3_defconfig b/configs/raspberrypi3_defconfig index a394d3b6ef6..a9972ff7828 100644 --- a/configs/raspberrypi3_defconfig +++ b/configs/raspberrypi3_defconfig @@ -27,6 +27,7 @@ BR2_PACKAGE_FFMPEG=y BR2_PACKAGE_FFMPEG_GPL=y BR2_PACKAGE_FFMPEG_NONFREE=y BR2_PACKAGE_FFMPEG_SWSCALE=y +BR2_PACKAGE_FFMPEG_RPI_HW_CODECS=y BR2_PACKAGE_LIBWEBCAM=y BR2_PACKAGE_MOTION=y BR2_PACKAGE_STREAMEYE=y diff --git a/configs/raspberrypi_defconfig b/configs/raspberrypi_defconfig index 4290b4c2ede..df711bd3b58 100644 --- a/configs/raspberrypi_defconfig +++ b/configs/raspberrypi_defconfig @@ -28,6 +28,7 @@ BR2_PACKAGE_FFMPEG=y BR2_PACKAGE_FFMPEG_GPL=y BR2_PACKAGE_FFMPEG_NONFREE=y BR2_PACKAGE_FFMPEG_SWSCALE=y +BR2_PACKAGE_FFMPEG_RPI_HW_CODECS=y BR2_PACKAGE_LIBWEBCAM=y BR2_PACKAGE_MOTION=y BR2_PACKAGE_STREAMEYE=y diff --git a/package/ffmpeg/Config.in b/package/ffmpeg/Config.in index b5850db7222..187432d49ab 100644 --- a/package/ffmpeg/Config.in +++ b/package/ffmpeg/Config.in @@ -167,6 +167,12 @@ config BR2_PACKAGE_FFMPEG_OUTDEVS bool "Enable output devices" default y +config BR2_PACKAGE_FFMPEG_RPI_HW_CODECS + bool "Enable rpi hardware accelerated codecs" + depends on BR2_PACKAGE_RPI_USERLAND + help + Enable HW accelerated codecs on Raspberry pi. + config BR2_PACKAGE_FFMPEG_EXTRACONF string "Additional parameters for ./configure" default "" diff --git a/package/ffmpeg/disable-rpi-omx-input-zerocopy.patch b/package/ffmpeg/disable-rpi-omx-input-zerocopy.patch new file mode 100644 index 00000000000..6733d8bafc8 --- /dev/null +++ b/package/ffmpeg/disable-rpi-omx-input-zerocopy.patch @@ -0,0 +1,15 @@ +diff --git a/libavcodec/omx.c b/libavcodec/omx.c +index 19b4f33836..4641dc79e2 100644 +--- a/libavcodec/omx.c ++++ b/libavcodec/omx.c +@@ -644,10 +644,6 @@ static av_cold int omx_encode_init(AVCodecContext *avctx) + OMX_BUFFERHEADERTYPE *buffer; + OMX_ERRORTYPE err; + +-#if CONFIG_OMX_RPI +- s->input_zerocopy = 1; +-#endif +- + s->omx_context = omx_init(avctx, s->libname, s->libprefix); + if (!s->omx_context) + return AVERROR_ENCODER_NOT_FOUND; diff --git a/package/ffmpeg/ffmpeg.hash b/package/ffmpeg/ffmpeg.hash index 6adf7e440a2..77e185f5ed5 100644 --- a/package/ffmpeg/ffmpeg.hash +++ b/package/ffmpeg/ffmpeg.hash @@ -1,2 +1,3 @@ # Locally calculated sha256 54ce502aca10b7e6059f19220ea2f68fa0c9c4c4d255ae13e615f08f0c94dcc5 ffmpeg-3.2.3.tar.xz +sha256 1998de1ab32616cbf2ff86efc3f1f26e76805ec5dc51e24c041c79edd8262785 ffmpeg-3.3.2.tar.xz diff --git a/package/ffmpeg/ffmpeg.mk b/package/ffmpeg/ffmpeg.mk index 6540d41c525..4bdd9e6f509 100644 --- a/package/ffmpeg/ffmpeg.mk +++ b/package/ffmpeg/ffmpeg.mk @@ -4,7 +4,7 @@ # ################################################################################ -FFMPEG_VERSION = 3.2.3 +FFMPEG_VERSION = 3.3.2 FFMPEG_SOURCE = ffmpeg-$(FFMPEG_VERSION).tar.xz FFMPEG_SITE = http://ffmpeg.org/releases FFMPEG_INSTALL_STAGING = YES @@ -26,7 +26,6 @@ FFMPEG_CONF_OPTS = \ --enable-avdevice \ --enable-avcodec \ --enable-avformat \ - --disable-x11grab \ --enable-network \ --disable-gray \ --enable-swscale-alpha \ @@ -39,7 +38,6 @@ FFMPEG_CONF_OPTS = \ --disable-dxva2 \ --enable-runtime-cpudetect \ --disable-hardcoded-tables \ - --disable-memalign-hack \ --disable-mipsdsp \ --disable-mipsdspr2 \ --disable-msa \ @@ -473,6 +471,11 @@ else ifneq ($(call qstrip,$(BR2_GCC_TARGET_ARCH)),) FFMPEG_CONF_OPTS += --cpu=$(BR2_GCC_TARGET_ARCH) endif +ifeq ($(BR2_PACKAGE_FFMPEG_RPI_HW_CODECS),y) +FFMPEG_DEPENDENCIES += rpi-userland +FFMPEG_CONF_OPTS += --enable-omx --enable-omx-rpi --enable-mmal --extra-cflags=-I../../staging/usr/include/IL/ +endif + FFMPEG_CONF_OPTS += $(call qstrip,$(BR2_PACKAGE_FFMPEG_EXTRACONF)) diff --git a/package/motion/motion.mk b/package/motion/motion.mk index f3815e37a43..46479cf5725 100644 --- a/package/motion/motion.mk +++ b/package/motion/motion.mk @@ -4,7 +4,7 @@ # ################################################################################ -MOTION_VERSION = 37b3595 +MOTION_VERSION = ab9e800d5984f2907f00bebabc794d1dba9682ad MOTION_SITE = $(call github,motion-project,motion,$(MOTION_VERSION)) MOTION_AUTORECONF = YES MOTION_CONF_OPTS = --without-pgsql --without-sdl --without-sqlite3 --without-mysql --with-ffmpeg=$(STAGING_DIR)/usr/lib \ diff --git a/package/motion/preferred-encoder.patch b/package/motion/preferred-encoder.patch new file mode 100644 index 00000000000..2781da99086 --- /dev/null +++ b/package/motion/preferred-encoder.patch @@ -0,0 +1,145 @@ +diff --git a/ffmpeg.c b/ffmpeg.c +index 87b4f75..3e7787c 100644 +--- a/ffmpeg.c ++++ b/ffmpeg.c +@@ -236,10 +236,21 @@ static void ffmpeg_free_context(struct ffmpeg *ffmpeg){ + + static int ffmpeg_get_oformat(struct ffmpeg *ffmpeg){ + ++ size_t codec_name_len = strcspn(ffmpeg->codec_name, ":"); ++ char *codec_name = alloca(codec_name_len + 1); ++ ++ if (codec_name == NULL) { ++ MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "Failed to allocate memory for codec name"); ++ ffmpeg_free_context(ffmpeg); ++ return -1; ++ } ++ memcpy(codec_name, ffmpeg->codec_name, codec_name_len); ++ codec_name[codec_name_len] = 0; ++ + /* Only the newer codec and containers can handle the really fast FPS */ +- if (((strcmp(ffmpeg->codec_name, "msmpeg4") == 0) || +- (strcmp(ffmpeg->codec_name, "mpeg4") == 0) || +- (strcmp(ffmpeg->codec_name, "swf") == 0) ) && (ffmpeg->fps >50)){ ++ if (((strcmp(codec_name, "msmpeg4") == 0) || ++ (strcmp(codec_name, "mpeg4") == 0) || ++ (strcmp(codec_name, "swf") == 0) ) && (ffmpeg->fps >50)){ + MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "The frame rate specified is too high for the ffmpeg movie type specified. Choose a different ffmpeg container or lower framerate."); + ffmpeg_free_context(ffmpeg); + return -1; +@@ -250,59 +261,59 @@ static int ffmpeg_get_oformat(struct ffmpeg *ffmpeg){ + if (ffmpeg->oc->oformat) ffmpeg->oc->oformat->video_codec = MY_CODEC_ID_MPEG2VIDEO; + strncat(ffmpeg->filename, ".mpg", 4); + if (!ffmpeg->oc->oformat) { +- MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "ffmpeg_video_codec option value %s is not supported", ffmpeg->codec_name); ++ MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "ffmpeg_video_codec option value %s is not supported", codec_name); + ffmpeg_free_context(ffmpeg); + return -1; + } + return 0; + } + +- if (strcmp(ffmpeg->codec_name, "mpeg4") == 0) { ++ if (strcmp(codec_name, "mpeg4") == 0) { + ffmpeg->oc->oformat = av_guess_format("avi", NULL, NULL); + strncat(ffmpeg->filename, ".avi", 4); + } + +- if (strcmp(ffmpeg->codec_name, "msmpeg4") == 0) { ++ if (strcmp(codec_name, "msmpeg4") == 0) { + ffmpeg->oc->oformat = av_guess_format("avi", NULL, NULL); + strncat(ffmpeg->filename, ".avi", 4); + if (ffmpeg->oc->oformat) ffmpeg->oc->oformat->video_codec = MY_CODEC_ID_MSMPEG4V2; + } + +- if (strcmp(ffmpeg->codec_name, "swf") == 0) { ++ if (strcmp(codec_name, "swf") == 0) { + ffmpeg->oc->oformat = av_guess_format("swf", NULL, NULL); + strncat(ffmpeg->filename, ".swf", 4); + } + +- if (strcmp(ffmpeg->codec_name, "flv") == 0) { ++ if (strcmp(codec_name, "flv") == 0) { + ffmpeg->oc->oformat = av_guess_format("flv", NULL, NULL); + strncat(ffmpeg->filename, ".flv", 4); + if (ffmpeg->oc->oformat) ffmpeg->oc->oformat->video_codec = MY_CODEC_ID_FLV1; + } + +- if (strcmp(ffmpeg->codec_name, "ffv1") == 0) { ++ if (strcmp(codec_name, "ffv1") == 0) { + ffmpeg->oc->oformat = av_guess_format("avi", NULL, NULL); + strncat(ffmpeg->filename, ".avi", 4); + if (ffmpeg->oc->oformat) ffmpeg->oc->oformat->video_codec = MY_CODEC_ID_FFV1; + } + +- if (strcmp(ffmpeg->codec_name, "mov") == 0) { ++ if (strcmp(codec_name, "mov") == 0) { + ffmpeg->oc->oformat = av_guess_format("mov", NULL, NULL); + strncat(ffmpeg->filename, ".mov", 4); + } + +- if (strcmp(ffmpeg->codec_name, "mp4") == 0) { ++ if (strcmp(codec_name, "mp4") == 0) { + ffmpeg->oc->oformat = av_guess_format("mp4", NULL, NULL); + strncat(ffmpeg->filename, ".mp4", 4); + if (ffmpeg->oc->oformat) ffmpeg->oc->oformat->video_codec = MY_CODEC_ID_H264; + } + +- if (strcmp(ffmpeg->codec_name, "mkv") == 0) { ++ if (strcmp(codec_name, "mkv") == 0) { + ffmpeg->oc->oformat = av_guess_format("matroska", NULL, NULL); + strncat(ffmpeg->filename, ".mkv", 4); + if (ffmpeg->oc->oformat) ffmpeg->oc->oformat->video_codec = MY_CODEC_ID_H264; + } + +- if (strcmp(ffmpeg->codec_name, "hevc") == 0) { ++ if (strcmp(codec_name, "hevc") == 0) { + ffmpeg->oc->oformat = av_guess_format("mp4", NULL, NULL); + strncat(ffmpeg->filename, ".mp4", 4); + if (ffmpeg->oc->oformat) ffmpeg->oc->oformat->video_codec = MY_CODEC_ID_HEVC; +@@ -310,7 +321,7 @@ static int ffmpeg_get_oformat(struct ffmpeg *ffmpeg){ + + //Check for valid results + if (!ffmpeg->oc->oformat) { +- MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "codec option value %s is not supported", ffmpeg->codec_name); ++ MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "codec option value %s is not supported", codec_name); + ffmpeg_free_context(ffmpeg); + return -1; + } +@@ -484,13 +495,23 @@ static int ffmpeg_set_codec(struct ffmpeg *ffmpeg){ + int retcd; + char errstr[128]; + int chkrate; ++ size_t codec_name_len = strcspn(ffmpeg->codec_name, ":"); + +- ffmpeg->codec = avcodec_find_encoder(ffmpeg->oc->oformat->video_codec); ++ ffmpeg->codec = NULL; ++ if (ffmpeg->codec_name[codec_name_len]) { ++ ffmpeg->codec = avcodec_find_encoder_by_name(&ffmpeg->codec_name[codec_name_len+1]); ++ if (!ffmpeg->codec) ++ MOTION_LOG(WRN, TYPE_ENCODER, NO_ERRNO, "Preferred codec %s not found", &ffmpeg->codec_name[codec_name_len+1]); ++ } ++ if (!ffmpeg->codec) ++ ffmpeg->codec = avcodec_find_encoder(ffmpeg->oc->oformat->video_codec); + if (!ffmpeg->codec) { + MOTION_LOG(ERR, TYPE_ENCODER, NO_ERRNO, "Codec %s not found", ffmpeg->codec_name); + ffmpeg_free_context(ffmpeg); + return -1; + } ++ if (ffmpeg->codec_name[codec_name_len]) ++ MOTION_LOG(NTC, TYPE_ENCODER, NO_ERRNO, "Using codec %s", ffmpeg->codec->name); + + #if (LIBAVFORMAT_VERSION_MAJOR >= 58) || ((LIBAVFORMAT_VERSION_MAJOR == 57) && (LIBAVFORMAT_VERSION_MINOR >= 41)) + //If we provide the codec to this, it results in a memory leak. ffmpeg ticket: 5714 +diff --git a/ffmpeg.h b/ffmpeg.h +index 78a2785..95383a7 100644 +--- a/ffmpeg.h ++++ b/ffmpeg.h +@@ -5,6 +5,7 @@ + #include + #include + #include ++#include + #include "config.h" + + enum TIMELAPSE_TYPE { diff --git a/package/motion/temp-fix-for-extpipe-subdir-creation.patch b/package/motion/temp-fix-for-extpipe-subdir-creation.patch new file mode 100644 index 00000000000..06637cc1d61 --- /dev/null +++ b/package/motion/temp-fix-for-extpipe-subdir-creation.patch @@ -0,0 +1,17 @@ +diff --git a/event.c b/event.c +index f67214e..44d8096 100644 +--- a/event.c ++++ b/event.c +@@ -572,6 +572,12 @@ static void event_create_extpipe(struct context *cnt, + } + } + ++ /* Temporary solution for https://github.com/Motion-Project/motion/issues/360#issuecomment-320849158 ++ Always create any path specified as file name */ ++ if (create_path(cnt->extpipefilename) == -1) ++ return ; ++ /* end of fix */ ++ + mystrftime(cnt, stamp, sizeof(stamp), cnt->conf.extpipe, currenttime_tv, cnt->extpipefilename, 0); + + MOTION_LOG(NTC, TYPE_EVENTS, NO_ERRNO, "pipe: %s", stamp); diff --git a/package/motion/tune-h264-encode-quality.patch b/package/motion/tune-h264-encode-quality.patch new file mode 100644 index 00000000000..994754f022a --- /dev/null +++ b/package/motion/tune-h264-encode-quality.patch @@ -0,0 +1,42 @@ +diff --git a/ffmpeg.c b/ffmpeg.c +index a4d3757..368c855 100644 +--- a/ffmpeg.c ++++ b/ffmpeg.c +@@ -451,7 +451,7 @@ static int ffmpeg_set_pts(struct ffmpeg *ffmpeg, const struct timeval *tv1){ + + static int ffmpeg_set_quality(struct ffmpeg *ffmpeg){ + +- char crf[4]; ++ int bit_rate; + + ffmpeg->opts = 0; + if (ffmpeg->vbr > 100) ffmpeg->vbr = 100; +@@ -459,13 +459,24 @@ static int ffmpeg_set_quality(struct ffmpeg *ffmpeg){ + ffmpeg->ctx_codec->codec_id == MY_CODEC_ID_HEVC){ + if (ffmpeg->vbr > 0) { + ffmpeg->vbr = (int)(( (100-ffmpeg->vbr) * 51)/100); ++ //bit_rate = ffmpeg->width * ffmpeg->height * ffmpeg->fps * quality_factor ++ bit_rate = (ffmpeg->width * ffmpeg->height * ffmpeg->fps * ffmpeg->vbr) >> 8; + } else { ++ bit_rate = ffmpeg->bps; + ffmpeg->vbr = 28; + } +- snprintf(crf, 4, "%d",ffmpeg->vbr); +- av_dict_set(&ffmpeg->opts, "preset", "ultrafast", 0); +- av_dict_set(&ffmpeg->opts, "tune", "zerolatency", 0); +- av_dict_set(&ffmpeg->opts, "crf", crf, 0); ++ av_dict_set(&ffmpeg->opts, "preset", "ultrafast", 0); ++ av_dict_set(&ffmpeg->opts, "tune", "zerolatency", 0); ++ av_dict_set_int(&ffmpeg->opts, "crf", ffmpeg->vbr, 0); ++ if ((strcmp(ffmpeg->codec->name, "h264_omx") == 0) || (strcmp(ffmpeg->codec->name, "mpeg4_omx") == 0)) { ++ // Clip bit rate to min and max ++ if (bit_rate < 40000) ++ bit_rate = 40000; ++ else if (bit_rate > 3000000) ++ bit_rate = 3000000; ++ ffmpeg->ctx_codec->profile = FF_PROFILE_H264_HIGH; ++ ffmpeg->ctx_codec->bit_rate = bit_rate; ++ } + } else { + /* The selection of 8000 in the else is a subjective number based upon viewing output files */ + if (ffmpeg->vbr > 0){ diff --git a/package/motioneye/add-hwaccel-video-encoding-option.patch b/package/motioneye/add-hwaccel-video-encoding-option.patch new file mode 100644 index 00000000000..6d87327b357 --- /dev/null +++ b/package/motioneye/add-hwaccel-video-encoding-option.patch @@ -0,0 +1,183 @@ +commit 508d177f9f62a33f1062443243d8c1b9c6172edf +Author: Joo Aun Saw +Date: Wed Aug 23 16:12:47 2017 +1000 + + Add option to enable hardware accelerated video encoding + +diff --git a/motioneye/config.py b/motioneye/config.py +index 48e8cf5..b810588 100644 +--- a/motioneye/config.py ++++ b/motioneye/config.py +@@ -57,6 +57,7 @@ _EXPONENTIAL_QUALITY_CODECS = ['mpeg4', 'msmpeg4', 'swf', 'flv', 'mov', 'mkv'] + _EXPONENTIAL_QUALITY_FACTOR = 100000 # voodoo + _EXPONENTIAL_DEF_QUALITY = 511 # about 75% + _MAX_FFMPEG_VARIABLE_BITRATE = 32767 ++_HW_ACCEL_CODECS = ['mp4', 'mkv'] + + _KNOWN_MOTION_OPTIONS = set([ + 'auto_brightness', +@@ -737,6 +738,7 @@ def motion_camera_ui_to_dict(ui, old_config=None): + 'movie_filename': ui['movie_file_name'], + 'max_movie_time': ui['max_movie_length'], + '@preserve_movies': int(ui['preserve_movies']), ++ '@movie_hwaccel': ui['movie_hwaccel'], + + # motion detection + '@motion_detection': ui['motion_detection'], +@@ -930,6 +932,9 @@ def motion_camera_ui_to_dict(ui, old_config=None): + + else: + vbr = max(1, q) ++ if motionctl.has_hwaccel_movie_encoding_support() and ui['movie_hwaccel']: ++ if data['ffmpeg_video_codec'] in _HW_ACCEL_CODECS: ++ data['ffmpeg_video_codec'] = data['ffmpeg_video_codec']+':h264_omx' + + data['ffmpeg_variable_bitrate'] = int(vbr) + +@@ -1120,6 +1125,7 @@ def motion_camera_dict_to_ui(data): + 'movie_file_name': data['movie_filename'], + 'max_movie_length': data['max_movie_time'], + 'preserve_movies': data['@preserve_movies'], ++ 'movie_hwaccel': data['@movie_hwaccel'], + + # motion detection + 'motion_detection': data['@motion_detection'], +@@ -1313,6 +1319,7 @@ def motion_camera_dict_to_ui(data): + ui['recording_mode'] = 'motion-triggered' + + ui['movie_format'] = data['ffmpeg_video_codec'] ++ ui['movie_format'] = [x.strip() for x in ui['movie_format'].split(':')][0] + + bitrate = data['ffmpeg_variable_bitrate'] + if motionctl.needs_ffvb_quirks(): +@@ -1897,6 +1904,7 @@ def _set_default_motion_camera(camera_id, data): + data.setdefault('ffmpeg_variable_bitrate', _EXPONENTIAL_DEF_QUALITY) + + data.setdefault('@preserve_movies', 0) ++ data.setdefault('@movie_hwaccel', 0) + + data.setdefault('@working_schedule', '') + data.setdefault('@working_schedule_type', 'outside') +diff --git a/motioneye/handlers.py b/motioneye/handlers.py +index 051621c..4044d73 100644 +--- a/motioneye/handlers.py ++++ b/motioneye/handlers.py +@@ -221,6 +221,7 @@ class MainHandler(BaseHandler): + admin_username=config.get_main().get('@admin_username'), + has_streaming_auth=motionctl.has_streaming_auth(), + has_new_movie_format_support=motionctl.has_new_movie_format_support(), ++ has_hwaccel_movie_encoding_support=motionctl.has_hwaccel_movie_encoding_support(), + has_motion=bool(motionctl.find_motion()[0]), + mask_width=utils.MASK_WIDTH) + +diff --git a/motioneye/motionctl.py b/motioneye/motionctl.py +index 832ed38..da61c23 100644 +--- a/motioneye/motionctl.py ++++ b/motioneye/motionctl.py +@@ -38,6 +38,7 @@ _LAST_OLD_CONFIG_VERSIONS = (490, '3.2.12') + _started = False + _motion_binary_cache = None + _motion_detected = {} ++_hwaccel_codec_cache = None + + + def find_motion(): +@@ -75,6 +76,38 @@ def find_motion(): + return _motion_binary_cache + + ++def hwaccel_codec_supported(): ++ global _hwaccel_codec_cache ++ if _hwaccel_codec_cache: ++ return _hwaccel_codec_cache ++ ++ if settings.FFMPEG_BINARY: ++ if os.path.exists(settings.FFMPEG_BINARY): ++ binary = settings.FFMPEG_BINARY ++ else: ++ return False ++ ++ else: # autodetect ffmpeg binary path ++ try: ++ binary = subprocess.check_output(['which', 'ffmpeg'], stderr=utils.DEV_NULL).strip() ++ except subprocess.CalledProcessError: # not found ++ return False ++ ++ try: ++ help = subprocess.check_output(binary + ' -codecs -hide_banner', shell=True) ++ ++ except subprocess.CalledProcessError: # not found ++ return False ++ ++ result = re.findall('h264_omx', help, re.IGNORECASE) ++ if result: ++ logging.debug('Hardware accelerated codec found: %s' % result) ++ _hwaccel_codec_cache = True ++ else: ++ _hwaccel_codec_cache = False ++ return _hwaccel_codec_cache ++ ++ + def start(deferred=False): + import config + import mjpgclient +@@ -354,6 +387,10 @@ def has_new_movie_format_support(): + return version.lower().count('git') or update.compare_versions(version, '3.4') >= 0 + + ++def has_hwaccel_movie_encoding_support(): ++ return hwaccel_codec_supported() ++ ++ + def get_rtsp_support(): + binary, version = find_motion() + if not binary: +diff --git a/motioneye/settings.py b/motioneye/settings.py +index cfd8488..8ba1363 100644 +--- a/motioneye/settings.py ++++ b/motioneye/settings.py +@@ -124,3 +124,6 @@ ADD_REMOVE_CAMERAS = True + + # enable HTTPS certificate validation + VALIDATE_CERTS = True ++ ++# path to the ffmpeg binary (automatically detected by default) ++FFMPEG_BINARY = None +diff --git a/motioneye/static/js/main.js b/motioneye/static/js/main.js +index ac43386..1a25d76 100644 +--- a/motioneye/static/js/main.js ++++ b/motioneye/static/js/main.js +@@ -1869,6 +1869,7 @@ function cameraUi2Dict() { + 'movie_file_name': $('#movieFileNameEntry').val(), + 'movie_quality': $('#movieQualitySlider').val(), + 'movie_format': $('#movieFormatSelect').val(), ++ 'movie_hwaccel': $('#hwaccelEncoding')[0].checked, + 'recording_mode': $('#recordingModeSelect').val(), + 'max_movie_length': $('#maxMovieLengthEntry').val(), + 'preserve_movies': $('#preserveMoviesSelect').val() >= 0 ? $('#preserveMoviesSelect').val() : $('#moviesLifetimeEntry').val(), +@@ -2219,6 +2220,7 @@ function dict2CameraUi(dict) { + $('#movieFileNameEntry').val(dict['movie_file_name']); markHideIfNull('movie_file_name', 'movieFileNameEntry'); + $('#movieQualitySlider').val(dict['movie_quality']); markHideIfNull('movie_quality', 'movieQualitySlider'); + $('#movieFormatSelect').val(dict['movie_format']); markHideIfNull('movie_format', 'movieFormatSelect'); ++ $('#hwaccelEncoding')[0].checked = dict['movie_hwaccel']; markHideIfNull('movie_hwaccel', 'hwaccelEncoding'); + $('#recordingModeSelect').val(dict['recording_mode']); markHideIfNull('recording_mode', 'recordingModeSelect'); + $('#maxMovieLengthEntry').val(dict['max_movie_length']); markHideIfNull('max_movie_length', 'maxMovieLengthEntry'); + $('#preserveMoviesSelect').val(dict['preserve_movies']); +diff --git a/motioneye/templates/main.html b/motioneye/templates/main.html +index e2b38d8..3fc6183 100644 +--- a/motioneye/templates/main.html ++++ b/motioneye/templates/main.html +@@ -762,6 +762,13 @@ + + ? + ++ {% if has_hwaccel_movie_encoding_support %} ++ ++ Hardware Acceleration ++ ++ ? ++ ++ {% endif %} + + Recording Mode +