MediaObserver 인터페이스#
재생과 녹화 컴포넌트는 MediaObserver에서 유도된 사용자 정의 Observer 객체를 통하여 이벤트 등을 처리합니다.
oasis::MediaObserver는 OasisMedia.h에 아래와 같이 정의되어 있습니다.
class MediaObserver : public std::enable_shared_from_this<MediaObserver>
{
public:
MediaObserver();
virtual ~MediaObserver();
//recording or playing started and stopped
virtual void onStarted(void *user_data, const char *file_path);
virtual void onStopped(void *user_data, media_report_reason_t reason, void *details);
//recording a new file
virtual void onFileChanged(void *user_data, const char *new_file_path);
virtual void onFileDeleted(void *user_data, const char *deleted_file_path);
virtual void onError(void *user_data, media_report_reason_t reason, void *details);
//periodic report every 1 second.
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);
//event recording started and completed(aborted)
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);
//motion recording started and completed(aborted)
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, 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> &jpeg_image_data, const struct timeval ×tamp);
};
Note
MediaObserver::onPaused와 MediaObserver::onResumed는 녹화 파일 재생시에 호출됩니다.
헤더 파일#
OasisMedia.h
함수#
startRecording 호출 시 전달받은 user_data 매개변수 값입니다.
일반 media_report_reason_t 상수 값은 아래와 같습니다.
| Reason | 상수값 | 의미 | details |
|---|---|---|---|
| kMediaReportGenericError | -1 | 일반 오류입니다. | nullptr |
| kMediaReportNoError | 0 | 오류가 발생하지 않았습니다. | nullptr |
녹화 컴포넌트의 경우, media_report_reason_t 상수 값은 아래와 같습니다.
| Reason | 상수값 | 의미 | details |
|---|---|---|---|
| kRecordingAllFilesRecorded | 201 | 모든 녹화를 마쳤습니다. | nullptr |
| kRecordingErrorNotReady | 400 | 녹화 준비가 되지 않는 상태에서 녹화 시작을 요청하였습니다. | nullptr |
| kRecordingErrorAlreadyStarted | 401 | 녹화가 이미 시작되었습니다. | nullptr |
| kRecordingErrorWriterPreparationFailed | 402 | 녹화 파일 준비 실패했습니다. | nullptr |
| kRecordingErrorWriterStartFailed | 403 | 녹화 파일 쓰기 시작 실패했습니다. | nullptr |
| kRecordingErrorSourceStartFailed | 404 | 카메라 장치 시작 실패했습니다. | nullptr |
| kRecordingErrorNotPrepared | 405 | 준비되지 않은 상태입니다 (사용하지 않음). | nullptr |
| kRecordingErrorNotStarted | 406 | 시작되지 않은 상태입니다 (사용하지 않음). | nullptr |
| kRecordingErrorDiskAlmostFull | 407 | 디스크가 거의 꽉찬 상태입니다 (사용하지 않음). | nullptr |
| kRecordingErrorNoDiskMount | 408 | 마운팅되어 있지 않습니다 (사용하지 않음). | nullptr |
| kRecordingErrorFileIO | 409 | 파일 I/O 오류 입니다. | nullptr |
| kRecordingErrorResumeFailed | 410 | 녹화 재시작에 실패했습니다. | nullptr |
| kRecordingErrorMediaDataTimeout | 411 | 영상 데이터가 일정 시간 동안 발생하지 않았습니다. | int32_t camera_id |
| kRecordingErrorIndexTableOverflow | 412 | 파일의 index 테이블 용량 초과입니다. | nullptr |
| kRecordingErrorEventWriterPreparationFailed | 430 | 이벤트 녹화 파일 준비 실패했습니다. | nullptr |
| kRecordingErrorEventWriterStartFailed | 431 | 이벤트 녹화 파일 시작 실패했습니다. | nullptr |
| kRecordingErrorMotionWriterPreparationFailed | 450 | 모션 녹화 파일 준비 실패했습니다. | nullptr |
| kRecordingErrorMotionWriterStartFailed | 451 | 모션 녹화 파일 시작 실패했습니다. | nullptr |
| kRecordingErrorMediaCacheReadErrorBegin | 460 | 미디어 캐쉬 읽기에 실패가 처음으로 발생했습니다. | nullptr |
| kRecordingErrorMediaCacheReadErrorEnd | 461 | 미디어 캐쉬 읽기에 실패가 끝났습니다. | nullptr |
| kSnapshotErrorTimeout | 600 | 스냅샷 타임아웃이 발생하였습니다. | nullptr |
재생 컴포넌트의 경우, media_report_reason_t 상수 값은 아래와 같습니다.
| Reason | 상수값 | 의미 | details |
|---|---|---|---|
| kPlayingEndOfFileReached | 200 | 녹화 파일 재생이 끝났습니다. | nullptr |
| kPlayingErrorFileNotFound | 500 | 녹화 파일을 찾을 수 없습니다. | nullptr |
| kPlayingErrorFileExtNotSupported | 501 | 지원되지 않는 녹화 파일 확장자입니다. | nullptr |
| kPlayingErrorSourceNotReady | 502 | 녹화 파일이 준비되지 않았습니다. | nullptr |
| kPlayingErrorNoTracks | 503 | 재생할 트랙이 없습니다. | nullptr |
| kPlayingErrorSoundNotReady | 504 | 소리가 준비되지 않았습니다. | nullptr |
| kPlayingErrorSoundDecoderNotReady | 505 | 소리 디코더가 준비되지 않았습니다. | nullptr |
| kPlayingErrorVideoRendererNotReady | 506 | 비디오 렌더러가 준비되지 않았습니다. | nullptr |
| kPlayingErrorVideoDecoderNotReady | 507 | 비디오 디코더가 준비되지 않았습니다. | nullptr |
| kPlayingErrorVideoTrackNotReady | 508 | 영상 트랙이 준비되지 않았습니다. | nullptr |
| kPlayingErrorSubtitleSourceNotReady | 509 | 캡션 소스가 준비되지 않았습니다. | nullptr |
| kPlayingErrorTextTrackNotReady | 510 | 텍스트 트랙이 준비되지 않았습니다. | nullptr |
| kPlayingErrorTextDecoderNotReady | 511 | 텍스트 디코더가 준비되지 않았습니다. | nullptr |
| kPlayingErrorTextRendererNotReady | 512 | 텍스트 렌더러가 준비되지 않았습니다. | nullptr |
| kPlayingErrorVideoRendererStartFailed | 513 | 비디오 렌더러를 시작할 수 없습니다. | nullptr |
| kPlayingErrorAudioRendererStartFailed | 514 | 소리 렌더러를 시작할 수 없습니다. | nullptr |
| kPlayingErrorTextRendererStartFailed | 515 | 텍스트 렌더러를 시작할 수 없습니다. | nullptr |
| kPlayingErrorSoundDeviceBusy | 516 | 소리 장치가 사용 중입니다. | nullptr |
| kPlayingErrorDisplayLayerNotReady | 517 | 디스플레이 레이어가 준비되지 않았습니다. | nullptr |
| kPlayingErrorZeroDuration | 518 | 재생 시간이 0초입니다. | nullptr |
| kPlayingErrorVideoSetupException | 519 | 영상 초기화 오류입니다. | nullptr |
| kPlayingErrorAudioSetupException | 520 | 소리 초기화 오류입니다. | nullptr |
| kPlayingErrorAdasNotReady | 521 | ADAS가 준비되지 않았습니다. | nullptr |
| kPlayingErrorAdasStartFailed | 522 | ADAS 시작 오류입니다. | nullptr |
file-duration-secs 만큼 경과하거나 할당된 파일 크기를 초과할 경우, 자동으로 새 파일로 녹화를 재시작합니다. 새 파일 경로는 MediaObserver::queryNewFilePath 호출 될 때 변경할 수 있습니다.
stopRecording을 호출하여 녹화를 중지하거나 오류 문제를 해결한 후 녹화를 재시작할 수 있습니다.
startRecording 호출 시 전달받은 user_data 매개변수 값입니다.
Note
kRecordingErrorMediaCacheReadErrorBegin ~ kRecordingErrorMediaCacheReadErrorEnd는 미디어 캐쉬에서 녹화에 필요한 데이터를 읽어 올 수 없는 경우 발생됩니다. 무시하고 녹화를 계속 진행할 경우, 발생 시간대 범위의 데이터는 모두 "0" 값으로 채워지게 됩니다. 영상의 경우는 재생시 녹색 화면이 보일 수 있으며, 음성의 경우는 묵음이 발생합니다.
report-media-info-ex를 "0" 로 설정하고 createRecorder로 Recoder 객체를 생성할 경우, 녹화 시작 후 1초 단위로 호출됩니다.
report-media-info-ex를 "1" 로 설정하고 createRecorder로 Recoder 객체를 생성할 경우, 녹화 시작 후 1초 단위로 호출됩니다.
startRecording 호출 시 전달받은 user_data 매개변수 값입니다.
startRecording 호출 시 전달받은 user_data 매개변수 값입니다.
startRecording 호출 시 전달받은 user_data 매개변수 값입니다.
startRecording 호출 시 전달받은 user_data 매개변수 값입니다.
startRecording 호출 시 전달받은 user_data 매개변수 값입니다.
kRecordingModeNormal(0) 이나 kRecordingModeSniffing(1) 값 중 하나입니다.
true 입니다.
true 입니다.
normal-folder-path 키 값입니다.
takeRecordingSnapshot 호출 후 스냅샷 생성이 종료되거나 오류가 발생할 경우, 호출됩니다.
startRecording 호출 시 전달받은 user_data 매개변수 값입니다.
takeRecordingSnapshot 호출 시 전달받은 snapshot_id 값입니다.
takeRecordingSnapshot 호출 시 전달받은 스냅샷 대상 camera ID입니다.
kMediaReportNoError(0) 값을 갖습니다.
error가 0인 경우에만 유효합니다. 사용자는 별도 쓰레드에서 이 데이터를 파일에 저장할 수 있습니다.
구조체#
MediaObserver::onInfo 이벤트 호출 콜백 함수에서 매개 변수로 전달됩니다.
true 이면 Sniffing 모드입니다. false이면 일반 녹화 모드입니다.durationUs 값과 동일합니다.MediaObserver::onInfoEx 이벤트 호출 콜백 함수에서 매개 변수로 전달됩니다. 녹화 객체 생성시 report-media-info-ex를 "1"로 설정해야 됩니다.
true 이면 Sniffing 모드입니다. false이면 일반 녹화 모드입니다.durationUs 값과 동일합니다.
예제#
녹화 컴포넌트의 사용자는 MediaObserver를 base class로 하여 클래스를 정의하고, oasis::startRecording 함수 호출 시 매개변수로 전달합니다. 아래는 MyRecordingObserver를 생성하고 oasis::startRecording에 전달하는 예입니다.
std::shared_ptr<MyRecordingObserver> recording_observer = std::make_shared<MyRecordingObserver>();
RecorderRef recorder = createRecorder(parameters);
startRecording(recorder, recording_observer, nullptr /*user_data*/);
아래는 MediaInfoEx를 콘솔에 출력하는 예입니다. dumpInfoEx 함수는 다른 예제 코드에서 사용됩니다.
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);
}
}
void dumpInfoEx(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);
TRACE0("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);
}
}