🌐 English

    Event Recording#

    When an event occurs during recording, event recording begins for the duration specified before and after the event, using the parameters (event-pre-recording-seconds and event-post-recording-seconds) passed during the creation of the recording object.

    Event recording utilizes the APIs listed below:

    Classification Start Recording Stop Recording
    Combined Recording startEventRecording stopEventRecording
    Individual Recording startMultiChannelEventRecording stopMultiChannelEventRecording

    Event recording can be forcibly terminated by calling stopEventRecording or stopMultiChannelEventRecording. In normal cases, it terminates automatically once the event recording is finished.

    The event callback functions triggered during an ongoing event recording are as follows:

    Classification Interface Start Recording Stop Recording
    Combined Recording MediaObserver onEventRecordingStarted onEventRecordingCompleted
    Individual Recording MediaObserver2 onEventRecordingStarted onEventRecordingCompleted

    Combined Recording#

    For Combined Recording, use the startEventRecording API below:

    • int32_t startEventRecording(RecorderRef recorder, const char * event_recording_file_path);
      • In event_recording_file_path, the application specifies the absolute path where the event recording file will be saved. The folder where the event recording file will reside is determined by the parameter value (event-folder-path) passed during the creation of the recording object. Using a different folder path is also acceptable.
    • int32_t startEventRecording(RecorderRef recorder, const char * event_recording_file_path, void * stream_data_header, size_t stream_data_header_size);
    • int32_t startEventRecording(RecorderRef recorder, const char * event_recording_file_path, void * stream_data_header, size_t stream_data_header_size, bool do_single_recording);
      • If do_single_recording is set to true, the current recording is paused, and the event recording starts. Once the event recording ends, the current recording resumes.

    Individual Recording#

    For Individual Recording, use the startMultiChannelEventRecording API below:

    • int32_t startMultiChannelEventRecording(MultiChannelRecorderRef multi_channel_recorder, const std::list<MultiChannelRecorderFilePath> & file_paths, void * stream_data_header, size_t stream_data_header_size, bool do_single_recording);
      • Add the recording file path for each channel to file_paths to create and pass a list that matches the number of channels.

    In the case of Individual Recording, you can start event recording by specifying a particular channel.

    • int32_t startMultiChannelEventRecording(MultiChannelRecorderRef multi_channel_recorder, int32_t channel_id, const char * event_recording_file_path, void * stream_data_header, size_t stream_data_header_size, bool is_type_of_motion);
      • If is_type_of_motion is set to true, motion recording is performed. The difference from standard event recording is that it follows the time duration specified by motion-pre-recording-seconds and motion-post-recording-seconds set during the recorder's creation.
    • int32_t startMultiChannelEventRecording(MultiChannelRecorderRef multi_channel_recorder, int32_t channel_id, const char * event_recording_file_path, void * stream_data_header, size_t stream_data_header_size, bool is_type_of_motion, bool do_single_recording);
    • int32_t startMultiChannelEventRecording(MultiChannelRecorderRef multi_channel_recorder, int32_t channel_id, const char * event_recording_file_path, void * stream_data_header, size_t stream_data_header_size, bool do_single_recording);

    Examples#

    Combined Event Recording#

    Below is an example of a combined recording for two video channels. If the user presses the Enter key during recording, the event recording proceeds.

      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) {
          //start event recording
          if (isRecorderRecording(recorder) && isEventRunning(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);
          } else {
            fprintf(stdout, "Recorder not started or busy in recording an event.\n");
          }
        }
      } while (continue_recording == true);
    

    To output logs, define the callback functions in the user-defined interface derived from MediaObserver as follows:

    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));
    }
    

    The output log content is as follows:

    [2000/06/27 04:02:35.233130] Event Recording started @ "/tmp/EVENT/EVT_2000-06-27_04-02-35.mp4" (reason: 0 NoError).
    [2000/06/27 04:02:42.338952] Event Recording completed @ "/tmp/EVENT/EVT_2000-06-27_04-02-35.mp4" (reason: 0 NoError).
    

    Full code:

    #include "OasisAPI.h"
    #include "OasisLog.h"
    #include "OasisMedia.h"
    #include "OasisFS.h"
    #include "OasisUtil.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);
      }
    }
    
    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) {
    
      }
      virtual void onMotionRecordingCompleted(void *user_data, media_report_reason_t reason, const char *file_path) {
    
      }
    
      virtual void queryNewFilePath(void* user_data, oasis::recording_mode_t file_type, bool rear_camera_on, bool sound_on, std::string& file_path) {
        // use as it is
      }
      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 c, err;
      std::thread t2;
    
      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);
      //parameters["oasis-log-flags"] = std::to_string(OASIS_LOG_DEBUG/*|OASIS_LOG_ENCODE_BITRATE*/);
    
      enableLogLocalTime(true);
    
      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";
      //parameters["snd-input-sampling-rate"]="22050";
    
      createAudioDevice(parameters);
    
      ////////////////////////////////////////////////////////////////////////////////////////////
      // sources
      parameters.clear();
    
      parameters["source-count"] = "2";
    
      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"] = "2160p";
      parameters["source1-sensor-config"] = "./Resource_678/VIC/2/imx678_3840x2160_ch2.cfg";
      parameters["source1-autoscene-config"] = "./Resource_678/AutoScene/autoscene_conf.cfg";
      parameters["source1-resource-dir"] = "./Resource_678/";
    
      parameters["source2-camera-id"] = "1";
      parameters["source2-isp-id"] = "0";
      parameters["source2-isp-wdr-mode"] = "0";
      parameters["source2-capture-format"] = "YUV420";
      parameters["source2-capture-buffers"] = "5";
      parameters["source2-fps"] = "30";
      parameters["source2-subchannel-rotation"] = "0";
      parameters["source2-loc"] = "right";
      parameters["source2-capture-resolution"] = "1080p";
      parameters["source2-sensor-config"] = "./Resource_tp2863/VIC/0/tp2863_1920x1080_ch0.cfg";
      parameters["source2-autoscene-config"] = "./Resource_tp2863/AutoScene/autoscene_conf.cfg";
      parameters["source2-resource-dir"] = "./Resource_tp2863/";
    
      configCameras(parameters);
    
      ////////////////////////////////////////////////////////////////////////////////////////////
      //recorer settings
      parameters.clear();
    
      parameters["file-prefix"] =  "oasis-";
      parameters["file-extension"] = "mp4";
      parameters["file-duration-secs"] = "60";
    #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"] = "30";
      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["snd-input-sampling-rate"] = "48000";
    
      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["osd-use-fixed-size"] = "0";
    
      parameters["channel-count"] = "2";
    
      parameters["channel1-camera-id"] = "0";
      parameters["channel1-ise-id"] = "-1";
      parameters["channel1-resolution"] = "2160p";
      parameters["channel1-bitrate"] = "8000000";
      parameters["channel1-fps"] = "30";
      parameters["channel1-file-framerate"] = "30";
      parameters["channel1-vencoder-type"] = "h264";
      parameters["channel1-venc-framerate"] = "30";
      parameters["channel1-venc-keyframe-interval"] = "30";
      parameters["channel1-h264-profile"] = "high";
      parameters["channel1-h264-level"] = "level51";
      parameters["channel1-h264-enable-cabac"] = "1";
      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"] = "12";
    
      parameters["channel2-camera-id"] = "1";
      parameters["channel2-ise-id"] = "-1";
      parameters["channel2-resolution"] = "1080p";
      parameters["channel2-bitrate"] = "8000000";
      parameters["channel2-fps"] = "30";
      parameters["channel2-file-framerate"] = "30";
      parameters["channel2-vencoder-type"] = "h264";
      parameters["channel2-venc-framerate"] = "30";
      parameters["channel2-venc-keyframe-interval"] = "30";
      parameters["channel2-h264-profile"] = "high";
      parameters["channel2-h264-level"] = "level51";
      parameters["channel2-h264-enable-cabac"] = "1";
      parameters["channel2-h264-min-qp"] = "10";
      parameters["channel2-h264-max-qp"] = "31";
      parameters["channel2-h264-enable-fixqp"] = "0";
      parameters["channel2-h264-fix-iqp"] = "10";
      parameters["channel2-h264-fix-pqp"] = "20";
      parameters["channel2-media-wait-timeout-secs"] = "3";
      parameters["channel2-media-wait-timeout-notify-oneshot"] = "1";
      parameters["channel2-osd-font-size"] = "12";
    
    
      std::shared_ptr<MyRecordingObserver> recording_observer = std::make_shared<MyRecordingObserver>();
      //dumpParameters("recorder", recorder_parameters);
    
      RecorderRef recorder = createRecorder(parameters);
      if(recorder == nullptr) {
        DLOG(DLOG_ERROR, "recorder creation failed!\r\n");
        goto done;
      }
    
      memset(&strd_data_header_, 0, sizeof(strd_data_header_));
    
      ////////////////////////////////////////////////////////////////////////////////////////////
      // timer thread
      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) {
          //start event recording
          if (isRecorderRecording(recorder) && isEventRunning(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);
          } else {
            fprintf(stdout, "Recorder not started or busy in recording an event.\n");
          }
        }
    
    
      } while (continue_recording == true);
    
    done:
    
      timer_running = false;
      if (t2.joinable()) {
        t2.join();
      }
    
      if(recorder) {
        destroyRecorder(recorder);
      }
    
    
      oasis::finalize();
    
      printf("goodbye.\n");
    
      return 0;
    }