Motion Recording#
Motion recording requires combined or individual recording to be initiated in sniffing mode, and actual file writing is triggered by external events such as motion detection.
The recording session captures buffered video before and after the trigger point based on the motion-pre-recording-seconds and motion-post-recording-seconds parameters specified during recording object creation.
Motion recording utilizes the following APIs:
| Classification | Start Recording | Stop Recording |
|---|---|---|
| Combined Recording | startMotionRecording |
stopMotionRecording |
| Individual Recording | startMultiChannelMotionRecording |
stopMultiChannelMotionRecording |
Motion recording can be forcibly terminated by calling stopMotionRecording or stopMultiChannelMotionRecording. In normal cases, it terminates automatically once the configured duration passes.
The callback functions triggered during motion recording are as follows:
| Classification | Interface | Start Callback | End Callback |
|---|---|---|---|
| Combined Recording | MediaObserver |
onMotionRecordingStarted |
onMotionRecordingCompleted |
| Individual Recording | MediaObserver2 |
onMotionRecordingStarted |
onMotionRecordingCompleted |
Combined Recording#
For Combined Recording, use the startMotionRecording API below:
- int32_t startMotionRecording(RecorderRef recorder, const char * event_recording_file_path, void * stream_data_header, size_t stream_data_header_size);
event_recording_file_path: Specifies the absolute path where the motion recording file will be saved. The base folder where the file resides defaults to the parameter value (motion-folder-path) passed during object creation, though a different folder path can be explicitly designated.
Individual Recording#
For Individual Recording, use the startMultiChannelMotionRecording API below:
- int32_t startMultiChannelMotionRecording(MultiChannelRecorderRef multi_channel_recorder, const std::list<MultiChannelRecorderFilePath> & file_paths, void * stream_data_header, size_t stream_data_header_size, bool stop_current_event_recording);
file_paths: List containing the channel number and the absolute path of the recording file for each channel.stop_current_event_recording: If set totrue, any ongoing event recording session will be stopped when motion recording starts.
Individual Recording also allows initiating motion recording by specifying a single channel ID:
- int32_t startMultiChannelMotionRecording(MultiChannelRecorderRef multi_channel_recorder, int32_t channel_id, const char * event_recording_file_path, void * stream_data_header, size_t stream_data_header_size);
Examples#
The following example demonstrates initiating a single video channel in sniffing mode, receiving a trigger event from a motion detector, and starting the motion recording session.
Create the recorder object. Refer to the full source code below for configuration parameters.
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;
}
Define the motion callback functions in MyRecordingObserver to print operational logs.
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));
}
Create the motion detector component.
std::shared_ptr<MyMotionDetectObserver> motion_detect_observer = std::make_shared<MyMotionDetectObserver>();
parameters.clear();
parameters["camera-id"] = "0";
parameters["enable"] = "1";
parameters["use-g2d"] = "1";
parameters["buffer-count"] = "3";
parameters["sensitivity-level"] = "5";
parameters["fps"] = "6";
parameters["version"] = "2";
parameters["height-max"] = "135";
MotionDetectorRef motion_detector = createMotionDetector(parameters);
if(motion_detector == nullptr) {
DLOG(DLOG_ERROR, "motion detector creation failed!\r\n");
destroyRecorder(recorder);
oasis::finalize();
return -1;
}
In MyMotionDetectObserver::onMotionDetected, send a motion recording command if the detected variance exceeds the threshold after an initial stabilization period (5 seconds).
class MyMotionDetectObserver : public MotionDetectObserver
{
public:
MyMotionDetectObserver() {
created_timestamp_ = systemTime();
}
virtual ~MyMotionDetectObserver() {}
virtual void onMotionDetected(int32_t camera_id, int32_t detected) {
TRACE0("camera<%d> motion detected#%d\n", camera_id, detected);
//after some period of stability passes
if(systemTime() - created_timestamp_ > 5000000) {
if(detected > 500) {
postCommand(kStartMotionRecording);
}
}
}
uint64_t created_timestamp_;
};
Create a command queue thread within the main execution thread to process the motion recording requests sent by the motion detector observer callback. When the command is popped, execute the motion recording routine.
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 kStartMotionRecording: {
if (recorderIsRunning(recorder) && isMotionRecording(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/MTN_%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/MTN_%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
startMotionRecording(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();
}
});
Start the recorder component in sniffing mode.
//RUN as SNIFFING mode!!!
err = startRecording(recorder, recording_observer, nullptr, true, &strd_data_header_, sizeof(strd_data_header_));
if(err < 0) {
DLOG(DLOG_ERROR, "start recording failed\r\n");
goto done;
}
Start the motion detector component.
err = motionDetectorStart(motion_detector, motion_detect_observer);
if(err < 0) {
DLOG0(DLOG_ERROR, "MotionDetectgor start failed\n");
goto done;
}
TRACE0("MotionDetector<%d> started\n", motionDetectorId(motion_detector));
When motion is detected, motion recording is initiated, and the callback outputs logs as shown in the example below:
[4512.331458] Motion Recording started @ "/tmp/EVENT/MTN_2000-06-27_05-17-01.mp4" (reason: 0 NoError).
[4524.231554] Motion Recording completed @ "/tmp/EVENT/MTN_2000-06-27_05-17-01.mp4" (reason: 0 NoError).
Full source code:
#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 char DrivingEventFolder[PATH_MAX] = { 0, };
static char ParkingEventFolder[PATH_MAX] = { 0, };
static char MotionFolder[PATH_MAX] = { 0, };
static bool timer_running = true;
static bool continue_sniffing = 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 {
kStartMotionRecording = 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_sniffing = 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_sniffing = 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) {}
virtual void onEventRecordingCompleted(void* user_data, media_report_reason_t reason, const char* file_path) {}
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 ×tamp) {}
};
class MyMotionDetectObserver : public MotionDetectObserver
{
public:
MyMotionDetectObserver() {
created_timestamp_ = systemTime();
}
virtual ~MyMotionDetectObserver() {}
virtual void onMotionDetected(int32_t camera_id, int32_t detected) {
TRACE0("camera<%d> motion detected#%d\n", camera_id, detected);
//after some period of stability passes
if(systemTime() - created_timestamp_ > 5000000) {
if(detected > 500) {
postCommand(kStartMotionRecording);
}
}
}
uint64_t created_timestamp_;
};
void print_usage(const char *pname)
{
DLOG0(DLOG_INFO, "USAGE: %s\n", pname);
}
void cancel_handler(int signum)
{
timer_running = false;
continue_sniffing = 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;
int32_t camera_id;
std::list<int32_t> camera_composer_ids;
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"] = "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";
//parameters["file-extension"] = "avi";
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"] = "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["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["channel-count"] = "1";
parameters["channel1-camera-id"] = "0";
parameters["channel1-ise-id"] = "-1";
parameters["channel1-resolution"] = "1080p";
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-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;
}
////////////////////////////////////////////////////////////////////////////////////////////
//motion detector settings
std::shared_ptr<MyMotionDetectObserver> motion_detect_observer = std::make_shared<MyMotionDetectObserver>();
parameters.clear();
parameters["camera-id"] = "0";
parameters["enable"] = "1";
parameters["use-g2d"] = "1";
parameters["buffer-count"] = "3";
parameters["sensitivity-level"] = "5";
parameters["fps"] = "6";
parameters["version"] = "2";
parameters["height-max"] = "135";
MotionDetectorRef motion_detector = createMotionDetector(parameters);
if(motion_detector == nullptr) {
DLOG(DLOG_ERROR, "motion detector creation failed!\r\n");
destroyRecorder(recorder);
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 kStartMotionRecording: {
if (recorderIsRunning(recorder) && isMotionRecording(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/MTN_%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/MTN_%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
startMotionRecording(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);
});
//RUN as SNIFFING mode!!!
err = startRecording(recorder, recording_observer, nullptr, true, &strd_data_header_, sizeof(strd_data_header_));
if(err < 0) {
DLOG(DLOG_ERROR, "start recording failed\r\n");
goto done;
}
err = motionDetectorStart(motion_detector, motion_detect_observer);
if(err < 0) {
DLOG0(DLOG_ERROR, "MotionDetectgor start failed\n");
goto done;
}
TRACE0("MotionDetector<%d> started\n", motionDetectorId(motion_detector));
do {
c = getch("Ctrl+C to exit...");
if(c < 0 || continue_sniffing == false) {
break;
}
if(c != '\n') {
fprintf(stdout, "%c", c);
}
fprintf(stdout, "\n");
} while (continue_sniffing == true);
done:
timer_running = false;
if (t2.joinable()) {
t2.join();
}
postCommand(kRecorderExitNow);
if (t3.joinable()) {
t3.join();
}
if(recorder) {
destroyRecorder(recorder);
}
if(motion_detector) {
destroyMotionDetector(motion_detector);
}
oasis::finalize();
printf("goodbye.\n");
return 0;
}