Motion-Project / motion

Motion, a software motion detector. Home page: https://motion-project.github.io/
GNU General Public License v2.0
3.67k stars 549 forks source link

Preferred codec as new config item #471

Closed jasaw closed 7 years ago

jasaw commented 7 years ago
  1. version [x.y.z, hash, other]: Git master ab9e800d5984f2907f00bebabc794d1dba9682ad
  2. installed as a package or compiled from sources [deb, rpm, git, other]: compiled from source
  3. standalone or part of third party [motion, MotionEyeOS, other]: MotionEyeOS
  4. video stream source [V4L (card or USB), net cam (mjpeg, rtsp, other), mmal]: mmal
  5. hardware [x86, ARM, other]: ARM
  6. operating system [Linux (which), FreeBSD, other]: Linux

ffmpeg on a system may support many different codecs. Currently there is no way for motion to prefer one codec over another, i.e. motion just lets ffmpeg defaults to whichever. It would be useful for motion to prefer a hardware accelerated codec rather than a system default.

What do you guys think?

tosiara commented 7 years ago

That's why my primary choice is extpipe

jasaw commented 7 years ago

I've tested both methods (extpipe and API) on several embedded systems, and found that extpipe is never satisfactory. There are reasons why API is better than extpipe on resource constrained platforms.

  1. Motion has better control over the movie playback time whereas with extpipe, it's up to ffmpeg program. In the event where CPU is struggling and drops frames every now and then, extpipe encoded movie will playback will be fast forwarded, but barely noticeable with the API encoded movie.
  2. API has less overhead whereas extpipe takes up more of the precious resources (memory and CPU). On low cost devices, this is quite significant.
tosiara commented 7 years ago

I disagree with both of your concerns, but motion is flexible and everyone finds his own pros and cons

  1. When you record only motion frames you will anyway get chunks of frames with random gaps. So I never expect smooth movies from motion and extpipe simply is saving them all, no need to try to fix frame timing
  2. Extpipe wins on the multicore ARM devices by moving encoding to a separate thread(s). Memory cost is very low in this case. You can write your own encoder and you don't need to make any changes on the motion side
jasaw commented 7 years ago
  1. The reason why extpipe playback is fast forwarded is because ffmpeg program tries to encode the frames to the specified frame rate and when actual frame rate falls behind a little, those missing frames are not taken into account. I've tested this, and that's what I got.
  2. Extpipe is the better choice for multi-core machines, no doubt about that. Unfortunately, there are lots of new devices that are still single core, like the Raspberry Pi Zero W. Giving the user option of API or extpipe seems like a reasonable thing to do. On 256MB devices, memory cost of extpipe is high, which increases the chance of OOM events.
Mr-Dave commented 7 years ago

Getting back to the OP, is the suggestion to have motion select the OMX variant of the codec if available? If so, we'd need to ensure that the apt distributed versions of ffmpeg include that option on the platforms that could support it.

jasaw commented 7 years ago

@Mr-Dave Yes, that's exactly the idea. If OMX codec is not available, then motion can fallback to system default.

This can be generalized to make it more flexible, which lead me to this new motion config idea.

jasaw commented 7 years ago

What do you guys think of extending the _ffmpeg_videocodec config option to take the optional _preferredcodec in this format: ffmpeg_video_codec video_format[:preferred_codec] Example: ffmpeg_video_codec mp4:h264_omx

The modifications to the code is quite small:

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 <stdarg.h>
 #include <sys/time.h>
 #include <stdint.h>
+#include <alloca.h>
 #include "config.h"

 enum TIMELAPSE_TYPE {
jasaw commented 7 years ago

PR merged. Closing issue.