통합 녹화 API

oasis::startRecording 호출 시 전달하는 oasis::MediaObserver 객체를 통하여, 녹화와 스냅샷 이벤트를 처리합니다.

헤더 파일

OasisMedia.h

함수

RecorderRef createRecorder ( key_value_map_t & parameters )
OasisMedia.h
Recorder 객체를 생성합니다.
매개변수
parameters  Recorder 객체 생성에 필요한 key-value map 입니다. 패러미터 절을 참고합니다.
리턴값
성공할 경우, Recorder 객체를 리턴하고, 실패하면 nullptr을 리턴합니다.
int32_t destroyRecorder ( RecorderRef recorder )
OasisMedia.h
Recorder 객체를 해제합니다.
매개변수
recorder  해제할 Recorder 객체입니다.
리턴값
  • 0: 성공
  • -1: 실패
int32_t changeRecorderParameters ( RecorderRef recorder , key_value_map_t & parameters )
OasisMedia.h
Recorder 객체의 설정값을 변경합니다.
매개변수
recorder  Recorder 객체입니다.
parameters  변경할 key-value map 입니다.
리턴값
  • 0: 성공
  • -1: 실패

Note

changeRecorderParameters 호출 전에 Recorder은 정지된 상태이어야 합니다. 활성 상태이면 stopRecording을 호출하여 정지 상태로 만듭니다.

bool recorderIsRunning ( RecorderRef recorder )
OasisMedia.h
녹화가 시작 되었는지 판단합니다.
매개변수
recorder  Recorder 객체입니다.
리턴값
  • true: Recorder가 동작 상태입니다.
  • false: Recorder가 중지 상태입니다.
bool isRecorderRecording ( RecorderRef recorder )
OasisMedia.h
녹화 시작 후 실제 파일 쓰기 중인지 판단합니다.
매개변수
recorder  Recorder 객체입니다.
리턴값
  • true: Recorder가 녹화 데이터를 파일에 쓰는 상태입니다.
  • false: Recorder가 녹화 데이터를 파일에 쓰지 않는 상태입니다.
int32_t startRecording ( RecorderRef recorder , const std::shared_ptr<MediaObserver> observer , void * user_data , bool sniffing = false )
OasisMedia.h
녹화를 시작합니다.
매개변수
recorder  Recorder 객체입니다.
observer  녹화 상태 Observer 객체입니다.
user_data  observer에 전달될 사용자 정의 데이터입니다.
sniffing  Sniffing 모드로 동작 할 경우에 true로 설정합니다. Sniffing 모드에서는 모션 감지나 이벤트 감지 등 외부 요인으로 녹화를 시작할 수 있고, 일반 녹화는 시작하지 않습니다.
리턴값
  • 0: 성공
  • -1: 실패
int32_t startRecording ( RecorderRef recorder , const std::shared_ptr<MediaObserver> observer , void * user_data , void * stream_data_header , size_t stream_data_header_size )
OasisMedia.h
녹화를 시작합니다.
매개변수
recorder  Recorder 객체입니다.
observer  녹화 상태 Observer 객체입니다.
user_data  observer에 전달될 사용자 정의 데이터입니다.
stream_data_header  녹화 파일에 저장될 meta data header 입니다.
stream_data_header_size  녹화 파일에 저장될 meta data header 크기입니다. 크기는 바이트 단위입니다.
리턴값
  • 0: 성공
  • -1: 실패
int32_t startRecording ( RecorderRef recorder , const std::shared_ptr<MediaObserver> observer , void * user_data , bool sniffing , void * stream_data_header , size_t stream_data_header_size )
OasisMedia.h
녹화를 시작합니다.
매개변수
recorder  Recorder 객체입니다.
observer  녹화 상태 Observer 객체입니다.
user_data  observer에 전달될 사용자 정의 데이터입니다.
sniffing  Sniffing 모드로 동작 할 경우에 true로 설정합니다. Sniffing 모드에서는 모션 감지나 이벤트 감지 등 외부 요인으로 녹화를 시작할 수 있고, 일반 녹화는 시작하지 않습니다.
stream_data_header  녹화 파일에 저장될 meta data header 입니다.
stream_data_header_size  녹화 파일에 저장될 meta data header 크기입니다. 크기는 바이트 단위입니다.
리턴값
  • 0: 성공
  • -1: 실패
int32_t stopRecording ( RecorderRef recorder )
OasisMedia.h
녹화를 중지합니다.
매개변수
recorder  Recorder 객체입니다.
리턴값
  • 0: 성공
  • -1: 실패
int32_t restartRecording ( RecorderRef recorder )
OasisMedia.h
정지된 녹화를 재시작합니다. Sniffing 모드에서는 지원되지 않습니다.
매개변수
recorder  Recorder 객체입니다.
리턴값
  • 0: 성공
  • -1: 실패
int32_t startEventRecording ( RecorderRef recorder , const char * event_recording_file_path )
OasisMedia.h
이벤트 녹화를 시작합니다. 이벤트 녹화는 event-pre-recording-seconds에 지정된 시간(초)만큼 이벤트 발생 이전 영상부터 발생 이후 event-post-recording-seconds에 지정된 시간(초) 만큼 녹화를 합니다. MediaObserver::onEventRecordingStartedMediaObserver::onEventRecordingCompleted로 이벤트 녹화 시작과 종료 여부를 알 수 있습니다. 이벤트 녹화는 현재 녹화와 동시에 진행됩니다.
매개변수
recorder  Recorder 객체입니다.
event_recording_file_path  저장할 이벤트 녹화 파일의 절대 경로입니다.
리턴값
  • 0: 성공
  • -1: 실패
int32_t startEventRecording ( RecorderRef recorder , const char * event_recording_file_path , void * stream_data_header , size_t stream_data_header_size )
OasisMedia.h
이벤트 녹화를 시작합니다. 이벤트 녹화는 event-pre-recording-seconds에 지정된 시간(초)만큼 이벤트 발생 이전 영상부터 발생 이후 event-post-recording-seconds에 지정된 시간(초) 만큼 녹화를 합니다. MediaObserver::onEventRecordingStartedMediaObserver::onEventRecordingCompleted로 이벤트 녹화 시작과 종료 여부를 알 수 있습니다. 이벤트 녹화는 현재 녹화와 동시에 진행됩니다.
매개변수
recorder  Recorder 객체입니다.
event_recording_file_path  저장할 이벤트 녹화 파일의 절대 경로입니다.
stream_data_header  녹화 파일에 저장될 meta data header 입니다.
stream_data_header_size  녹화 파일에 저장될 meta data header 크기입니다. 크기는 바이트 단위입니다.
리턴값
  • 0: 성공
  • -1: 실패
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 )
OasisMedia.h
이벤트 녹화를 시작합니다. 이벤트 녹화는 event-pre-recording-seconds에 지정된 시간(초)만큼 이벤트 발생 이전 영상부터 발생 이후 event-post-recording-seconds에 지정된 시간(초) 만큼 녹화를 합니다. MediaObserver::onEventRecordingStartedMediaObserver::onEventRecordingCompleted로 이벤트 녹화 시작과 종료 여부를 알 수 있습니다.
매개변수
recorder  Recorder 객체입니다.
event_recording_file_path  저장할 이벤트 녹화 파일의 절대 경로입니다.
stream_data_header  녹화 파일에 저장될 meta data header 입니다.
stream_data_header_size  녹화 파일에 저장될 meta data header 크기입니다. 크기는 바이트 단위입니다.
do_single_recording  true인 경우, 일반 녹화를 중지하고 이벤트 녹화를 시작합니다. 이벤트 녹화가 끝나면 일반 녹화를 다시 시작합니다.
리턴값
  • 0: 성공
  • -1: 실패
int32_t stopEventRecording ( RecorderRef recorder )
OasisMedia.h
이벤트 녹화를 중지합니다.
매개변수
recorder  Recorder 객체입니다.
리턴값
  • 0: 성공
  • -1: 실패
int32_t startMotionRecording ( RecorderRef recorder , const char * event_recording_file_path , void * stream_data_header , size_t stream_data_header_size )
OasisMedia.h
모션 녹화를 시작합니다. 모션 녹화는 motion-pre-recording-seconds에 지정된 시간(초)만큼 이벤트 발생 이전 영상부터 발생 이후 motion-post-recording-seconds에 지정된 시간(초) 만큼 녹화를 합니다. MediaObserver::onMotionRecordingStartedMediaObserver::onMotionRecordingCompleted로 모션 녹화 시작과 종료 여부를 알 수 있습니다.
매개변수
recorder  Recorder 객체입니다.
event_recording_file_path  저장할 모션 녹화 파일의 절대 경로입니다.
stream_data_header  녹화 파일에 저장될 meta data header 입니다.
stream_data_header_size  녹화 파일에 저장될 meta data header 크기입니다. 크기는 바이트 단위입니다.
리턴값
  • 0: 성공
  • -1: 실패

Note

모션 녹화는 Sniffing 모드에서만 지원됩니다.

int32_t stopMotionRecording ( RecorderRef recorder )
OasisMedia.h
모션 녹화를 중지합니다.
매개변수
recorder  Recorder 객체입니다.
리턴값
  • 0: 성공
  • -1: 실패
bool isEventOrMotionRecording ( RecorderRef recorder )
OasisMedia.h
이벤트나 모션 녹화가 실제 파일 쓰기 중인지 판단합니다.
매개변수
recorder  Recorder 객체입니다.
리턴값
  • true: Recorder가 녹화 데이터를 파일에 쓰는 상태입니다.
  • false: Recorder가 녹화 데이터를 파일에 쓰지 않는 상태입니다.
bool isEventRecording ( RecorderRef recorder )
OasisMedia.h
이벤트 녹화가 실제 파일 쓰기 중인지 판단합니다.
매개변수
recorder  Recorder 객체입니다.
리턴값
  • true: Recorder가 녹화 데이터를 파일에 쓰는 상태입니다.
  • false: Recorder가 녹화 데이터를 파일에 쓰지 않는 상태입니다.
bool isMotionRecording ( RecorderRef recorder )
OasisMedia.h
모션 녹화가 실제 파일 쓰기 중인지 판단합니다.
매개변수
recorder  Recorder 객체입니다.
리턴값
  • true: Recorder가 녹화 데이터를 파일에 쓰는 상태입니다.
  • false: Recorder가 녹화 데이터를 파일에 쓰지 않는 상태입니다.
bool isEventRunning ( RecorderRef recorder )
OasisMedia.h
이벤트 녹화 시작 되었는 지 확인합니다.
매개변수
recorder  Recorder 객체입니다.
리턴값
  • true: 이벤트 녹화가 동작 상태입니다.
  • false: 이벤트 녹화가 중지 상태입니다.
int32_t setRecordingText ( RecorderRef recorder , int32_t camera_id , text_track_id_t text_id , const std::string & text , uint64_t timestampUs = 0ull )
OasisMedia.h
OSD 텍스트를 설정합니다. OSD 텍스트는 영상의 좌측 하단에 배치됩니다.
매개변수
recorder  Recorder 객체입니다.
camera_id  OSD가 적용될 영상 트랙의 카메라 ID입니다. -1인 경우, 전 영상 트랙에 적용됩니다.
text_id  OSD 텍스트 타입을 지정합니다. kTextTrackOsd 만 허용됩니다.
text  기록할 OSD 텍스트 입니다.
timestampUs  기록 타임스탬프로 마이크로초 단위입니다. 실제 녹화 파일에 저장되지 않습니다.
리턴값
  • 0: 성공
  • -1: 실패
int32_t setRecordingOverlay ( RecorderRef recorder , int32_t camera_id , const void * overlay , size_t overlay_size )
OasisMedia.h
Overlay 데이터를 설정합니다. 이 함수는 Oasis의 setRecordingText가 아닌 하드웨어 가속 기능을 사용할 경우에 사용됩니다. Overlay 데이터 구조는 하드웨어 가속 엔진에 따릅니다. 하드웨어에 따라 지원안 될 수도 있습니다.
매개변수
recorder  Recorder 객체입니다.
camera_id  Overlay 데이터가 적용될 영상 트랙의 카메라 ID입니다. -1인 경우, 전 영상 트랙에 적용됩니다.
overlay  Overlay 데이터 포인터 입니다.
overlay_size  Overlay 데이터 크기입니다. 바이트 단위입니다.
리턴값
  • 0: 성공
  • -1: 실패
int32_t setRecordingOsdTextAt ( RecorderRef recorder , int32_t camera_id , int32_t x , int32_t y , const std::string & text , osd_text_flag_t flags )
OasisMedia.h
OSD 텍스트를 영상 이미지의 특정 위치에 기록합니다.
매개변수
recorder  Recorder 객체입니다.
camera_id  Overlay 데이터가 적용될 영상 트랙의 카메라 ID입니다. -1인 경우, 전 영상 트랙에 적용됩니다.
x  OSD 텍스트의 x 시작 위치입니다.
y  OSD 텍스트의 y 시작 위치입니다.
text  기록할 OSD 텍스트 입니다.
flags  (사용하지 않음)
리턴값
  • 0: 성공
  • -1: 실패
int32_t addRecordingVideoStreamData ( RecorderRef recorder , void * stream_data , size_t stream_data_length , uint64_t timestampUs = 0ull )
OasisMedia.h
메타 데이터를 저장합니다. 사용자는 주기적으로 또는 필요에 따라서 메타 데이터를 녹화 데이터와 함께 저장할 수 있습니다.
매개변수
recorder  Recorder 객체입니다.
stream_data  메타 데이터 포인터입니다. 메타 데이터 포맷은 사용자가 정의합니다.
stream_data_length  메타 데이터 크기입니다. 바이트 단위입니다.
timestampUs  타임스탬프로 마이크초 단위입니다. 녹화 파일에 저장되지 않습니다.
리턴값
  • 0: 성공
  • -1: 실패
int32_t enableRearCameraRecording ( RecorderRef recorder , bool enable )
OasisMedia.h
후방 카메라 녹화를 중단하거나 활성화합니다. 후방 카메라는 oasis::configCameras 함수로 카메라 장치 초기화시 카메라 위치 설정 키값 source<N>-loc 값을 rear로 설정한 카메라입니다.
매개변수
recorder  Recorder 객체입니다.
enable  true이면 활성화되고, false이면 후방 카메라 녹화가 중단됩니다.
리턴값
  • 0: 성공
  • -1: 실패
bool isRearCameraRecordingEnable ( RecorderRef recorder )
OasisMedia.h
후방 카메라 녹화 활성화 여부를 판단합니다.
매개변수
recorder  Recorder 객체입니다.
리턴값
  • true: 활성화 중
  • false: 중단 중
int32_t enableSoundRecording ( RecorderRef recorder , bool enable )
OasisMedia.h
소리 녹화 활성화 및 중지 여부를 지정합니다. 소리 녹화를 중지할 경우, 소리 트랙은 묵음 데이터가 저장됩니다.
매개변수
recorder  Recorder 객체입니다.
enable  true이면 활성화되고, false이면 묵음 데이터가 저장됩니다.
리턴값
  • 0: 성공
  • -1: 실패
bool isSoundRecordingEnabled ( RecorderRef recorder )
OasisMedia.h
소리 녹화 활성화 여부를 판단합니다.
매개변수
recorder  Recorder 객체입니다.
리턴값
  • true: 활성화 중
  • false: 중단 중
int32_t getCurrentRecordingFilePath ( RecorderRef recorder , std::string & file_path )
OasisMedia.h
현재 녹화 중인 파일 경로를 얻습니다.
매개변수
recorder  Recorder 객체입니다.
file_path  OUT 성공 시 녹화 파일 경로가 저장됩니다.
리턴값
  • 0: 성공
  • -1: 실패
int32_t takeRecordingShapshot ( RecorderRef recorder , uint32_t snapshot_id , int32_t camera_id , key_value_map_t & parameters )
OasisMedia.h
현재 녹화 중 영상 스냅샷을 찍습니다. 스냅샷 데이터 생성이 완료되면 MediaObserver::onSnapshotCompleted 콜백함수에 JPEG 이미지 데이터를 전달합니다.
매개변수
recorder  Recorder 객체입니다.
snapshot_id  MediaObserver::onSnapshotCompleted 콜백함수에서 각 snapshot을 구분하는 snapshot_id 매개변수로 사용됩니다.
camera_id  카메라 장치 ID 입니다.
parameters  JPEG 스냅샷 생성에 필요한 key-value map입니다.
리턴값
  • 0: 성공
  • -1: 실패

아래는 key-value map 목록입니다.

기본값
필수
설명
width
스냅샷 이미지의 너비입니다. "0"이면, 원본 영상 이미지 너비와 같습니다.
height
스냅샷 이미지의 높이입니다. "0"이면, 원본 영상 이미지 높이와 같습니다.
quality
100
 
JPEG 이미지 품질을 지정합니다. 품질 값은 0~100 범위 내에 있어야 합니다.
timeout
3000
 
스냅샷 마칠 때까지 대기하는 시간(밀리초)입니다. "0"이면, 마칠 때까지 무한정 기다립니다.
osd-font-size
12
 
글자의 크기를 지정합니다. 포인트 단위입니다. "0" 값이면 OSD를 사용하지 않습니다.
osd-text-color
255,255,255
 
OSD 텍스트 색상입니다. 색상 양식은 "r,g,b" 이거나 "rrggbb" 또는 "aarrggbb" 16진수 값입니다. 예를 들어 흰색이면 "255,255,255" 또는 "ffffff" 와 같습니다.
osd-use-text-color
1
 
"1" 값이면 OSD 텍스트 색상을 사용합니다. "0" 값이면 글자가 표시되지 않습니다.
osd-use-bg-color
0
 
"1" 값이면 OSD 텍스트 배경색을 사용합니다.
osd-bg-color
0,0,0
 
OSD 텍스트 배경색입니다.
osd-use-outline-color
0
 
"1" 값이면 글자 테두리 색상을 사용합니다.
osd-outline-color
255,255,255
 
글자 테두리 색상입니다.
osd-horz-align
center
 
OSD 텍스트 가로 정렬을 지정합니다. left, center, right 중 하나를 지정합니다.
osd-vert-align
bottom
 
OSD 텍스트 세로 정렬을 지정합니다. top, center, bottom 중 하나를 지정합니다.
osd-font-path
 
지정하지 않을 경우, oasis::initialize 시 정의한 system-font-path 값이 사용됩니다.
osd-use-fixed-size
0
 
고정폭 크기 글자를 사용합니다.

예제

아래는 3개 영상 채널과 소리를 녹화하는 예입니다.

USE_OFFS1이면 Oasis 파일시스템을 사용하고, 0이면 /tmp 폴더 밑에 DRIVING, EVENT 폴더를 생성하여 사용합니다. 각 폴더내 파일 크기는 20MB로 제한하고 총 5개만 녹화됩니다. Oasis 파일 시스템의 경우, 녹화 파일개수 제한은 없으며, 용량이 다 찬 경우 자동으로 오래된 파일을 덮어씁니다.

#define USE_OFFS 0

비디오 코덱은 H264이고, 오디오 코덱은 AAC를 사용합니다.

  //non-offs, use system fs
#if !USE_OFFS  
  fs::offsConfigLocalFormatInfo("/tmp/DRIVING", 20*1024*1024);
  fs::offsConfigLocalFormatInfo("/tmp/EVENT", 20*1024*1024);
#endif

Oasis를 초기화합니다.

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

입력과 출력 오디오 장치를 초기화 합니다.


////////////////////////////////////////////////////////////////////////////////////////////
// audio
parameters.clear();

parameters["types"]="source,sink";
parameters["path"]=SND_PATH;
parameters["always-on"]="0";
parameters["channels"]="2";
parameters["aec-disabled"]="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);

총 3개의 카메라 장치를 설정합니다.


////////////////////////////////////////////////////////////////////////////////////////////
// sources
parameters.clear();

parameters["source-count"] = "3";

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/";

parameters["source3-camera-id"] = "2";
parameters["source3-isp-id"] = "0";
parameters["source3-isp-wdr-mode"] = "0";
parameters["source3-capture-format"] = "YUV420";
parameters["source3-capture-buffers"] = "5";
parameters["source3-fps"] = "30";
parameters["source3-subchannel-rotation"] = "0";
parameters["source3-loc"] = "left";
parameters["source3-capture-resolution"] = "1080p";
parameters["source3-sensor-config"] = "./Resource_tp2863/VIC/1/tp2863_1920x1080_ch1.cfg";
parameters["source3-autoscene-config"] = "./Resource_tp2863/AutoScene/autoscene_conf.cfg";
parameters["source3-resource-dir"] = "./Resource_tp2863/";

configCameras(parameters);

MediaObserver 로 부터 유도된 사용자 정의 Observer 클래스를 정의합니다.

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) {
    /* (새)녹화가 시작되었습니다. */
    }

    virtual void onError(void* user_data, media_report_reason_t reason, void* details) {
    /* 에러를 콘솔에 출력하고 녹화를 종료합니다. */

        continue_recording = false;    
    }

    virtual void onInfo(void* user_data, MediaInfo* info) {
    /* 사용하지 않습니다. */
    }

    virtual void onInfoEx(void* user_data, MediaInfoEx* 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) {}
    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) {
    /* 새 녹화 파일경로를 변경하지 않고 그대로 사용하게 합니다. */
    }

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

};

녹화 객체를 생성합니다. configCameras에서 설정한 카메라 개수와 순서와 일치하지 않아도 됩니다. configCamerassource<N>- 과 녹화 객체 생성 시의 channel<N>- 은 별개입니다. 녹화 객체 생성 시 channel<N>-camera-id 값은 source<M>-camera-id 에 일치한 카메라 장치 source<M>을 사용합니다.

////////////////////////////////////////////////////////////////////////////////////////////
//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/sd/consola.ttf";
parameters["osd-use-fixed-size"] = "0";

parameters["channel-count"] = "3";

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";

parameters["channel3-camera-id"] = "2";
parameters["channel3-ise-id"] = "-1";
parameters["channel3-resolution"] = "1080p";
parameters["channel3-bitrate"] = "8000000";
parameters["channel3-fps"] = "30";
parameters["channel3-file-framerate"] = "30";
parameters["channel3-vencoder-type"] = "h264";
parameters["channel3-venc-framerate"] = "30";
parameters["channel3-venc-keyframe-interval"] = "30";
parameters["channel3-h264-profile"] = "high";
parameters["channel3-h264-level"] = "level51";
parameters["channel3-h264-enable-cabac"] = "1";
parameters["channel3-h264-min-qp"] = "10";
parameters["channel3-h264-max-qp"] = "31";
parameters["channel3-h264-enable-fixqp"] = "0";
parameters["channel3-h264-fix-iqp"] = "10";
parameters["channel3-h264-fix-pqp"] = "20";
parameters["channel3-media-wait-timeout-secs"] = "3";
parameters["channel3-media-wait-timeout-notify-oneshot"] = "1";
parameters["channel3-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;
}

타이머 쓰레드를 생성하여 메타 데이터와 OSD 텍스트를 출력하도록 합니다.

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

아래는 전체 코드입니다.

#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

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) {
        int h, m, s, u;
        char normal_qsize[128], event_qsize[128], in_size[128], out_size[128], cache_in_size[128];
        char video1_size[128], video2_size[128], meta_size[128], audio_size[128], file_size[128];
        parseUsec(info->durationUs, h, m, s, u);

        DLOG0(DLOG_RECORD, "Recording duration \033[33m%02d:%02d:%02d.%06d\033[0m, wq#%d(%d), eq#%d(%d), meq#%d(%d), nohits#%u, mb.avail#%d/%d (free mem %.3fMB, cpu %.2f%%)\r\n", h, m, s, u, info->writerStat.curSamples, info->writerStat.maxSamples, info->timeshiftStat.curSamples, info->timeshiftStat.maxSamples, info->writerStat.motionStat.curSamples, info->writerStat.motionStat.maxSamples, info->mediaCacheStat.nohits_, info->mediaCacheStat.free_buffer_count_, info->mediaCacheStat.total_buffer_count_, (double)oasis::getFreeMemorySize()/1024.0/1024.0, getCPUUsage());

        //offs state
        bytesToString(info->offsStat.qNormalSize, normal_qsize);
        bytesToString(info->offsStat.qEventSize, event_qsize);

        bytesToString(info->offsStat.inSize, in_size);
        bytesToString(info->offsStat.outSize, out_size);

        bytesToString(info->mediaCacheStat.in_size_, cache_in_size);


        TRACE0("    cache: during %d msec, in %s\r\n", info->mediaCacheStat.check_duration_, cache_in_size);
        TRACE0("    offs: n#%d, e#%d, nz#%s, ez#%s, during %d msec: in %s out %s elapsed %d msec \r\n", info->offsStat.qNormalCount, info->offsStat.qEventCount, normal_qsize, event_qsize, info->offsStat.checkDuration, in_size, out_size, info->offsStat.elapsedSum);

        //TRACE0("    cache: hits#%u, nohits#%u, free#%u, gets#%u, puts#%u\r\n", info->mediaCacheStat.hits_, info->mediaCacheStat.nohits_, info->mediaCacheStat.free_buffer_count_, info->mediaCacheStat.get_buffer_count_, info->mediaCacheStat.put_buffer_count_);

        //print recording stat in details
        bytesToString(info->writerStat.video1Length, video1_size);
        bytesToString(info->writerStat.video2Length, video2_size);
        bytesToString(info->writerStat.metaLength, meta_size);
        bytesToString(info->writerStat.audioLength, audio_size);
        bytesToString(info->writerStat.fileLength, file_size);
        TRACE0("    %s: video1 %s, video2 %s, meta %s, audio %s, file %s\n", info->sniffing?"sniffing":"recording", video1_size, video2_size, meta_size, audio_size, file_size);

        if(info->writerStat.motionStat.recording) {
            bytesToString(info->writerStat.motionStat.video1Length, video1_size);
            bytesToString(info->writerStat.motionStat.video2Length, video2_size);
            bytesToString(info->writerStat.motionStat.metaLength, meta_size);
            bytesToString(info->writerStat.motionStat.audioLength, audio_size);
            bytesToString(info->writerStat.motionStat.fileLength, file_size);
            TRACE0("    motion: video1 %s, video2 %s, meta %s, audio %s, file %s\n", video1_size, video2_size, meta_size, audio_size, file_size);
        }

        if(info->timeshiftStat.recording) {
            bytesToString(info->timeshiftStat.video1Length, video1_size);
            bytesToString(info->timeshiftStat.video2Length, video2_size);
            bytesToString(info->timeshiftStat.metaLength, meta_size);
            bytesToString(info->timeshiftStat.audioLength, audio_size);
            bytesToString(info->timeshiftStat.fileLength, file_size);
            TRACE0("    event: video1 %s, video2 %s, meta %s, audio %s, file %s\n", video1_size, video2_size, meta_size, audio_size, file_size);
        }
    }

    //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) {}
    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 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["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"] = "3";

    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/";

    parameters["source3-camera-id"] = "2";
    parameters["source3-isp-id"] = "0";
    parameters["source3-isp-wdr-mode"] = "0";
    parameters["source3-capture-format"] = "YUV420";
    parameters["source3-capture-buffers"] = "5";
    parameters["source3-fps"] = "30";
    parameters["source3-subchannel-rotation"] = "0";
    parameters["source3-loc"] = "left";
    parameters["source3-capture-resolution"] = "1080p";
    parameters["source3-sensor-config"] = "./Resource_tp2863/VIC/1/tp2863_1920x1080_ch1.cfg";
    parameters["source3-autoscene-config"] = "./Resource_tp2863/AutoScene/autoscene_conf.cfg";
    parameters["source3-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/sd/consola.ttf";
    parameters["osd-use-fixed-size"] = "0";

    parameters["channel-count"] = "3";

    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";

    parameters["channel3-camera-id"] = "2";
    parameters["channel3-ise-id"] = "-1";
    parameters["channel3-resolution"] = "1080p";
    parameters["channel3-bitrate"] = "8000000";
    parameters["channel3-fps"] = "30";
    parameters["channel3-file-framerate"] = "30";
    parameters["channel3-vencoder-type"] = "h264";
    parameters["channel3-venc-framerate"] = "30";
    parameters["channel3-venc-keyframe-interval"] = "30";
    parameters["channel3-h264-profile"] = "high";
    parameters["channel3-h264-level"] = "level51";
    parameters["channel3-h264-enable-cabac"] = "1";
    parameters["channel3-h264-min-qp"] = "10";
    parameters["channel3-h264-max-qp"] = "31";
    parameters["channel3-h264-enable-fixqp"] = "0";
    parameters["channel3-h264-fix-iqp"] = "10";
    parameters["channel3-h264-fix-pqp"] = "20";
    parameters["channel3-media-wait-timeout-secs"] = "3";
    parameters["channel3-media-wait-timeout-notify-oneshot"] = "1";
    parameters["channel3-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);
                //title += "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero.";
                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 {
        usleep(100000);
    } while (continue_recording == true);

done:

    timer_running = false;
    if (t2.joinable()) {
        t2.join();
    }

    if(recorder) {
        destroyRecorder(recorder);
    }


    oasis::finalize();

    printf("goodbye.\n");

    return 0;
}