🌐 English

    Timelapse Recording#

    Timelapse recording is a technique where the video is recorded over a long period by keeping the encoding frame rate lower than the actual video frame rate, while the playback frame rate is higher than the encoding frame rate. This creates a fast-forward effect during playback.

    For instance, if you record at only 1 frame per second from a camera that captures video at a 30 frame rate, and play it back at a 30 frame rate, the actual 30 seconds of recorded video will be played back in 1 second. During playback, 1 frame represents 1 actual second.

    Note

    Audio is not saved during timelapse recording.

    Note

    Event recording triggered during timelapse mode proceeds as normal recording, saving both audio and video using the file-framerate.

    Proceed in the following sequence:

    1. Stop the recording if individual or combined recording is active.
    2. Apply changes to the following recording parameter key values on the created recording object, or apply them when creating a recording object:
      • normal-folder-path
      • event-folder-path
      • file-duration-secs
      • channel<N>-file-framerate
      • channel<N>-venc-framerate
    3. Start the individual or combined recording.

    Restart the recording after stopping it.

    The calculation for the file-duration-secs value is shown in the formula below:

    \[ \begin{gather} parameters[\text{"file-duration-secs"}] \\ = \text{timelapse recording duration} \\ = \frac{\text{file play duration} \times \text{file play frame rate}}{\text{encoding frame rate}} \end{gather} \]

    Example of Switching from Normal Recording to Timelapse Recording#

    Below is an example of switching from normal recording to timelapse recording during execution on the channel1 camera.

    First, stop the recording.

    if(recorderIsRunning(recorder_)) {
        err = stopRecording(recorder_);
    }
    

    If necessary, change the locations of the normal recording folder and event recording folder to the timelapse folders.

    parameters["normal-folder-path"] = timelapse_folder_;
    parameters["event-folder-path"] = timelapse_event_folder_;
    

    Change the recording file playback frame rate and the video encoding frame rate to 30 and 1, respectively.

    parameters["channel1-file-framerate"] = std::to_string(30);
    parameters["channel1-venc-framerate"] = std::to_string(1);
    

    Change the recording duration of the recording file to the timelapse base time.

    If the total playback duration of the recording file is 60 seconds, the recording file playback frame rate is 30fps, and the encoding frame rate is 1fps, the total timelapse duration is \(60 \times (30 / 1) = 1800\) seconds. If the encoding frame rate is 5fps, it is \(60 \times (30 / 5) = 360\) seconds.

    parameters["file-duration-secs"] = std::to_string(1800);
    

    Modify the configuration settings of the recorder object.

    changeRecorderParameters(recorder_, parameters);
    

    Restart the recording.

    err = startRecording(recorder_, observer_, nullptr);
    
    oasis::key_value_map_t parameters;
    
    if(recorderIsRunning(recorder_)) {
      stopRecording(recorder_);
    }
    
    // file-duration-secs = 60
    // timelapse-file-framerate=30
    // timelapse-venc-framerate=1
    // ; play duration formula: 
    // ;   total frames = file-duration-secs*timelapse-file-framerate = 60 * 30 = 1800 frames
    // ;   recording duration = total-frames/timelapse-venc-framerate = 1800 / 1 = 1800 seconds
    // timelapse-file-duration-secs=1800
    
    parameters["normal-folder-path"] = timelapse_folder_;
    parameters["event-folder-path"] = timelapse_event_folder_;
    parameters["file-duration-secs"] = std::to_string(timelapse_record_duration_);
    for(int32_t camera_id=1; camera_id<=channel_count_; camera_id++) {
      sprintf(key, "channel%d-file-framerate", camera_id);
      parameters[key] = std::to_string(timelapse_file_framerate_);
      sprintf(key, "channel%d-venc-framerate", camera_id);
      parameters[key] = std::to_string(timelapse_venc_framerate_);
    }
    
    changeRecorderParameters(recorder_, parameters);
    startRecording(recorder_, observer_, nullptr);
    

    Example of Event Recording During Timelapse Recording#

    The example below demonstrates timelapse recording for a single video channel. When the user presses the Enter key, event recording is executed. Event recording operates as normal recording and includes both video and audio.

    #include "OasisAPI.h"
    #include "OasisLog.h"
    #include "OasisMedia.h"
    #include "OasisFS.h"
    #include "OasisUtil.h"
    #include "OasisMotion.h"
    
    #include <thread>
    #include <mutex>
    #include <memory>
    #include <condition_variable>
    
    #include <signal.h>
    
    #define DLOG_THIS   0x00010000
    #define DLOG_RECORD 0x00020000
    #define DLOG_FRAME  0x00040000
    #define DLOG_TRACE  0x00080000
    
    #undef DLOG_FLAGS
    #define  DLOG_FLAGS (DLOG_FLAGS_DEFAULT|DLOG_RECORD|DLOG_TRACE|DLOG_THIS/*|DLOG_FRAME*/)
    
    #undef DLOG_TAG
    #define DLOG_TAG "RECORDER"
    
    #define OFFS_QUEUE_SIZE_KBYTES  (20*1024)
    #define OFFS_CACHE_SIZE_KBYTES  (1024)
    #define OFFS_WRITE_ALIGNMENT_BYTES  8192
    #define MEDIA_CACHE_SIZE_KBYTES (40*1024)
    
    #define USE_OFFS 0
    
    extern "C" int getch(const char *prompt);
    
    using namespace oasis;
    
    static bool timer_running = true;
    static bool continue_recording = true;
    
    static void bytesToString(size_t size, char *size_string)
    {
      if(size >= 1024*1024) {
        sprintf(size_string, "%.2fMB", (double)size/1048576);
      } else if(size >= 1024) {
        sprintf(size_string, "%.2fKB", (double)size/1024);
      } else {
        sprintf(size_string, "%dB", size);
      }
    }
    
    enum {
      kStartEventRecording = 1,
      kRecorderExitNow = 2,
    };
    
    struct RecorderCommand {
      int32_t cmd_;
      RecorderCommand() = default;
      RecorderCommand(int32_t cmd) : cmd_(cmd) {}
      RecorderCommand(const RecorderCommand& src) : cmd_(src.cmd_) {}
      RecorderCommand& operator=(const RecorderCommand& src) {
        if(this != &src) {
          cmd_ = src.cmd_;
        }
        return *this;
      }
    };
    
    
    static std::condition_variable cmdq_cond_;
    static std::mutex cmdq_lock_;
    static std::list<RecorderCommand> cmdq_;
    
    
    static void postCommand(int32_t cmd) {
      try {
        std::lock_guard<std::mutex> _l(cmdq_lock_);
        cmdq_.emplace_back(cmd);
        cmdq_cond_.notify_one();
      } catch(...) {
    
      }
    }
    
    static uint8_t strd_data_header_[24] = {0, };
    
    class MyRecordingObserver : public MediaObserver {
      public:
      virtual void onStarted(void* user_data, const char* file_path) {
        DLOG(DLOG_RECORD|DLOG_INFO, "Recording started, file path \"%s\"\r\n", file_path);
      }
    
      virtual void onStopped(void* user_data, media_report_reason_t reason, void* details) {
        DLOG(DLOG_RECORD|DLOG_INFO, "Recording stopped, reason: %d(%s)\r\n", reason, mediaReportReasonString(reason));
        if(reason != kMediaReportNoError) {
          continue_recording = false;
        }
      }
    
      virtual void onFileChanged(void* user_data, const char* new_file_path) {
        DLOG(DLOG_RECORD|DLOG_INFO, "New file used: \"%s\"\r\n", new_file_path);
      }
    
      virtual void onError(void* user_data, media_report_reason_t reason, void* details) {
        if(reason == kRecordingErrorMediaDataTimeout) {
          int32_t camera_id = (intptr_t)details;
          DLOG(DLOG_RECORD|DLOG_ERROR, "Recording error, reason: %d(%s), camera<%d>\r\n", reason, mediaReportReasonString(reason), camera_id);
        } else if(reason == kRecordingErrorMediaCacheReadErrorBegin) {
          DLOG(DLOG_RECORD|DLOG_ERROR, "Recording error, reason: %d(%s) ===> stopping\r\n", reason, mediaReportReasonString(reason));
        } else if(reason == kRecordingErrorMediaCacheReadErrorEnd) {
          DLOG(DLOG_RECORD|DLOG_ERROR, "Recording error, reason: %d(%s)\r\n", reason, mediaReportReasonString(reason));
        }
    
        continue_recording = false;
      }
    
      virtual void onInfo(void* user_data, MediaInfo* info) {
        DLOG(DLOG_RECORD, "Recording duration %lld.%06lld sec\r\n", info->durationUs / 1000000ll, info->durationUs % 1000000ll);
      }
    
      virtual void onInfoEx(void* user_data, MediaInfoEx* info) {
        dumpInfoEx(info);    
      }
    
      //player paused and resumed
      virtual void onPaused(void *user_data, MediaInfo *info) {}
      virtual void onResumed(void *user_data, MediaInfo *info) {}
      virtual void onEventRecordingStarted(void* user_data, media_report_reason_t reason, const char* file_path) {
        DLOG0(DLOG_THIS, "Event Recording started @ \"%s\" (reason: %d %s).\r\n", file_path, reason, mediaReportReasonString(reason));
      }
      virtual void onEventRecordingCompleted(void* user_data, media_report_reason_t reason, const char* file_path) {
        DLOG0(DLOG_THIS, "Event Recording completed @ \"%s\" (reason: %d %s).\r\n", file_path, reason, mediaReportReasonString(reason));
      }
      virtual void onMotionRecordingStarted(void *user_data, media_report_reason_t reason, const char *file_path) {
        DLOG0(DLOG_THIS, "Motion Recording started @ \"%s\" (reason: %d %s).\r\n", file_path, reason, mediaReportReasonString(reason));
      }
      virtual void onMotionRecordingCompleted(void *user_data, media_report_reason_t reason, const char *file_path) {
        DLOG0(DLOG_THIS, "Motion Recording completed @ \"%s\" (reason: %d %s).\r\n", file_path, reason, mediaReportReasonString(reason));
      }
    
      virtual void queryNewFilePath(void* user_data, oasis::recording_mode_t file_type, bool rear_camera_on, bool sound_on, std::string& file_path) {}
      virtual void onSnapshotCompleted(void *user_data, uint32_t snapshot_id, int32_t camera_id, int error, const std::vector<char> &image_data, const struct timeval &timestamp) {}
    
    };
    
    
    void print_usage(const char *pname)
    {
      DLOG0(DLOG_INFO, "USAGE: %s\n", pname);
    }
    
    void cancel_handler(int signum)
    {
      timer_running = false;
      continue_recording = false;
    }
    
    #if 0
    #define SND_PATH "default"
    #else
    #define SND_PATH "hw:0,0"
    #endif
    
    int main(int argc, char* argv[])
    {
      int32_t err;
      int32_t i, c, n;
      std::thread t1, t2, t3;
    
      oasis::key_value_map_t parameters;
    
      srand(time(NULL));
    
      signal(SIGINT, cancel_handler);
    
       ////////////////////////////////////////////////////////////////////////////////////////////
      // init
    
      bool offs_disabled = false;
      if(!offs_disabled) {
        parameters["offs-qsize-max"] = std::to_string(OFFS_QUEUE_SIZE_KBYTES);
        parameters["offs-overwrite-if-exist"] = "1";
        parameters["offs-cache-size"] = std::to_string(OFFS_CACHE_SIZE_KBYTES);
      } else {
        parameters["offs-disable"] = "1";
      }
      parameters["media-cache-size"] = std::to_string(MEDIA_CACHE_SIZE_KBYTES);
    
      if(oasis::initialize(parameters) < 0) {
        DLOG(DLOG_ERROR, "Oasis init failed\n");
        return -1;
      }
    
      //non-offs, use system fs
    #if !USE_OFFS  
      fs::offsConfigLocalFormatInfo("/tmp/DRIVING", 20*1024*1024);
      fs::offsConfigLocalFormatInfo("/tmp/EVENT", 20*1024*1024);
    #endif
      ////////////////////////////////////////////////////////////////////////////////////////////
      // audio
      parameters.clear();
    
      parameters["types"]="source,sink";
      parameters["path"]=SND_PATH;
      parameters["always-on"]="0";
      parameters["channels"]="2";
      parameters["aec-disabled"]="1";
      parameters["denoise-enabled"]="1";
      parameters["snd-input-channels"]="2";
      parameters["snd-input-sample-size"]="16";
      parameters["snd-input-sampling-duration-msec"]="40";
      parameters["snd-input-sampling-rate"]="48000";
    
      createAudioDevice(parameters);
    
      ////////////////////////////////////////////////////////////////////////////////////////////
      // sources
      parameters.clear();
    
      parameters["source-count"] = "1";
    
      parameters["source1-camera-id"] = "0";
      parameters["source1-isp-id"] = "0";
      parameters["source1-isp-wdr-mode"] = "0";
      parameters["source1-capture-format"] = "YUV420";
      parameters["source1-capture-buffers"] = "5";
      parameters["source1-fps"] = "30";
      parameters["source1-subchannel-rotation"] = "0";
      parameters["source1-loc"] = "front";
      parameters["source1-capture-resolution"] = "1080p";
      parameters["source1-sensor-config"] = "./Resource_tp2863/VIC/0/tp2863_1920x1080_ch0.cfg";
      parameters["source1-autoscene-config"] = "./Resource_tp2863/AutoScene/autoscene_conf.cfg";
      parameters["source1-resource-dir"] = "./Resource_tp2863/";
    
      configCameras(parameters);
    
      ////////////////////////////////////////////////////////////////////////////////////////////
      //recorer settings
      parameters.clear();
    
      parameters["file-prefix"] =  "oasis-";
      parameters["file-extension"] = "mp4";
    
      //Set as the total timelapse recording duration
      parameters["file-duration-secs"] = "1800";
    
    #if USE_OFFS
      parameters["normal-folder-path"] = "/mnt/sd/DRIVING";
      parameters["event-folder-path"] = "/mnt/sd/EVENT";
      parameters["motion-folder-path"] = "/mnt/sd/EVENT";
    #else
      parameters["normal-folder-path"] = "/tmp/DRIVING";
      parameters["event-folder-path"] = "/tmp/EVENT";
      parameters["motion-folder-path"] = "/tmp/EVENT";
    #endif
    
      parameters["event-pre-recording-seconds"] = "10";
      parameters["event-post-recording-seconds"] = "10";
      parameters["motion-pre-recording-seconds"] = "10";
      parameters["motion-post-recording-seconds"] = "10";
      parameters["disable-event-recording"] = "0";
      parameters["disable-offs-recording"] = "0";
    #if USE_OFFS
      parameters["max-files"] = "0";
    #else  
      parameters["max-files"] = "5";
    #endif  
      parameters["delete-oldest-file-on-max-files"] = "1";
      parameters["recording-size-limit-threshold-seconds"] = "1";
      parameters["report-media-info-ex"] = "1";
    
      parameters["avi-strd-size-max"] = "65536";
      parameters["mp4-udta-size-max"] = "65536";
      parameters["enable-persistent-cache"] = "0";
      parameters["recording-file-header-write-interval-secs"] = "1";
    
      parameters["snd-path"] = "hw:0,0";
      parameters["snd-input-channels"] = "2";
      parameters["snd-input-sample-size"] = "16";
      parameters["snd-input-sampling-duration-msec"] = "120";
      parameters["snd-input-sampling-rate"] = "44100";
    
      parameters["aencoder-type"] = "aac"; //aac, raw, mp3
      parameters["aencoder-bitrate"] = "128000";
    
      parameters["osd-font-size"] = "0"; // disable (default)
      parameters["osd-font-face"] = "Consolas";
      parameters["osd-text-color"] = "255,255,255";
      parameters["osd-use-text-color"] = "1";
      parameters["osd-use-bg-color"] = "0";
      parameters["osd-bg-color"] = "0,0,0";
      parameters["osd-use-outline-color"] = "1";
      parameters["osd-outline-color"] = "255,255,255";
      parameters["osd-horz-align"] = "left";
      parameters["osd-vert-align"] = "bottom";
      parameters["osd-font-path"] = "/mnt/flash/leipzig/consola.ttf";
    
      parameters["channel-count"] = "1";
    
      parameters["channel1-camera-id"] = "0";
      parameters["channel1-ise-id"] = "-1";
      parameters["channel1-resolution"] = "1080p";
      parameters["channel1-bitrate"] = "8000000";
    
      //Video input framerate
      parameters["channel1-fps"] = "30";
      //File playback framerate
      parameters["channel1-file-framerate"] = "30";
      //Encoding framerate
      parameters["channel1-venc-framerate"] = "1";
    
      parameters["channel1-vencoder-type"] = "h264";
      parameters["channel1-venc-keyframe-interval"] = "30";
      parameters["channel1-h264-profile"] = "high";
      parameters["channel1-h264-level"] = "level51";
      parameters["channel1-h264-min-qp"] = "10";
      parameters["channel1-h264-max-qp"] = "31";
      parameters["channel1-h264-enable-fixqp"] = "0";
      parameters["channel1-h264-fix-iqp"] = "10";
      parameters["channel1-h264-fix-pqp"] = "20";
    
      parameters["channel1-media-wait-timeout-secs"] = "3";
      parameters["channel1-media-wait-timeout-notify-oneshot"] = "1";
      parameters["channel1-osd-font-size"] = "0"; //disable;
    
      std::shared_ptr<MyRecordingObserver> recording_observer = std::make_shared<MyRecordingObserver>();
    
      RecorderRef recorder = createRecorder(parameters);
      if(recorder == nullptr) {
        DLOG(DLOG_ERROR, "recorder creation failed!\r\n");
        oasis::finalize();
        return -1;
      }
    
    
      memset(&strd_data_header_, 0, sizeof(strd_data_header_));
    
    
      t3 = std::thread([&]() {
    
        RecorderCommand cmd;
    
        do {
          {
            std::unique_lock<std::mutex> _l(cmdq_lock_);
            while(cmdq_.empty()) {
              cmdq_cond_.wait(_l);
            }
            cmd = cmdq_.front();
            cmdq_.pop_front();
          }
    
          if(cmd.cmd_ == kRecorderExitNow) {
            DLOG(DLOG_TRACE, "got kRecorderExitNow\r\n");
            break;
          }
    
          DLOG(DLOG_INFO, "cmd %d\n", cmd.cmd_);
    
          switch(cmd.cmd_) {
            case kStartEventRecording: {
              if (recorderIsRunning(recorder) && isEventRecording(recorder) == false) {
                time_t t;
                struct tm tm_t;
                char path[PATH_MAX];
    
                time(&t);
                localtime_r(&t, &tm_t);
    
        #if USE_OFFS
                sprintf(path, "/mnt/sd/EVENT/EVT_%04d-%02d-%02d_%02d-%02d-%02d.mp4", tm_t.tm_year + 1900, tm_t.tm_mon + 1, tm_t.tm_mday, tm_t.tm_hour, tm_t.tm_min, tm_t.tm_sec);
        #else
                sprintf(path, "/tmp/EVENT/EVT_%04d-%02d-%02d_%02d-%02d-%02d.mp4", tm_t.tm_year + 1900, tm_t.tm_mon + 1, tm_t.tm_mday, tm_t.tm_hour, tm_t.tm_min, tm_t.tm_sec);
        #endif
                startEventRecording(recorder, path, nullptr, 0);
              } else {
                fprintf(stdout, "Recorder not started (%d) or busy in recording an motion (%d).\n", recorderIsRunning(recorder), isMotionRecording(recorder));
              }
              break;
            }
          default:
              break;
          }
    
        } while(1);
    
        {
          std::lock_guard<std::mutex> _l(cmdq_lock_);
          cmdq_.clear();
        }
      });
    
    
      ////////////////////////////////////////////////////////////////////////////////////////////
      t2 = std::thread([&]() {
        time_t t;
        struct tm tm_t;
        uint64_t usec, preview_usec;
        std::string title;
        uint32_t count = 0;
    
        char stream_data[512] = { 0, };
    
        preview_usec = systemTime();
    
        do {
    
          usec = systemTime();
    
          time(&t);
          localtime_r(&t, &tm_t);
    
          if (isRecorderRecording(recorder)) {
            sprintf(stream_data, "Main %4d/%02d/%02d %02d:%02d:%02d", tm_t.tm_year + 1900, tm_t.tm_mon + 1, tm_t.tm_mday, tm_t.tm_hour, tm_t.tm_min, tm_t.tm_sec);
            addRecordingVideoStreamData(recorder, (void*)stream_data, strlen(stream_data) + 1);
          }
    
          if(/*count == 0 &&*/ isRecorderRecording(recorder)) {
            title = oasis::format("OASIS %4d/%02d/%02d %02d:%02d:%02d\n", tm_t.tm_year + 1900, tm_t.tm_mon + 1, tm_t.tm_mday, tm_t.tm_hour, tm_t.tm_min, tm_t.tm_sec);
            setRecordingText(recorder, kTextTrackOsd, title, usec);
            count++;
          }
    
          //30 msec
          usleep(40000);
    
        } while (timer_running);
      });
    
      err = startRecording(recorder, recording_observer, nullptr, false, &strd_data_header_, sizeof(strd_data_header_));
      if(err < 0) {
        DLOG(DLOG_ERROR, "start recording failed\r\n");
        goto done;
      }  
    
      printf("Ctrl+C to exit...\n");
    
      do {
    
        c = getch("Press Enter key to start event recording...");
        if(c < 0 || continue_recording == false) {
          break;
        }
        if(c != '\n') {
          fprintf(stdout, "%c", c);
        }
        fprintf(stdout, "\n");
    
        if(c == 10) {
          postCommand(kStartEventRecording);
        }
    
      } while (continue_recording == true);
    
    done:
      timer_running = false;
      if (t2.joinable()) {
        t2.join();
      }
    
      postCommand(kRecorderExitNow);
      if (t3.joinable()) {
        t3.join();
      }
    
      if(recorder) {
        destroyRecorder(recorder);
      }
    
      oasis::finalize();
    
      printf("goodbye.\n");
    
      return 0;
    }