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.
- In
- 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_recordingis set totrue, the current recording is paused, and the event recording starts. Once the event recording ends, the current recording resumes.
- If
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_pathsto create and pass a list that matches the number of channels.
- Add the recording file path for each channel to
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_motionis set totrue, motion recording is performed. The difference from standard event recording is that it follows the time duration specified bymotion-pre-recording-secondsandmotion-post-recording-secondsset during the recorder's creation.
- If
- 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 ×tamp) {}
};
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;
}