스냅샷 API#
녹화 컴포넌트에서 제공하는 takeRecordingSnapshot과는 별개로 직접 카메라 이미지를 캡춰합니다.
스냅샷 API는 Non-blocking 방식과 Blocking 방식이 있습니다.
Photographer객체를 생성하고PhotographerObserver인터페이스를 사용하는 방식으로 캡쳐 파일을 계속 생성할 수 있습니다.takePicture함수를 호출하여 스냅샷이 완료될 때까지 기다리는 방식으로 캡춰 파일을 하나만 생성할 수 있습니다.
헤더 파일#
OasisMedia.h
Blocking 함수#
Blocking 방식으로 스냅샷 데이터를 생성합니다. 스냅샷이 완료되거나 타임아웃이 발생할 때까지 기다립니다.
configCameras에서 설정한 카메라 ID입니다.
left,center,right 중 하나 값을 지정합니다.
top, vcenter, bottom 중 하나 값을 지정합니다.
nullptr 이나 비어있는 문자열을 지정하면 OSD 텍스트가 표시되지 않습니다.
true이면, outline_color 색상으로 외곽선을 표시합니다.
- 0: 성공
- -1: 실패
key_value_map_t &
parameters
,
std::vector<char> &
image_data
,
struct timeval *
timestmap
= nullptr
)
configCameras에서 설정한 카메라 ID입니다.
- 0: 성공
- -1: 실패
스냅샷 생성에 필요한 key-value map은 아래와 같습니다.
left,center,right 중 하나의 값을 지정합니다.top,vcenter,bottom 중 하나의 값을 지정합니다.JPEG 이미지 생성 시 아래 EXIF 태깅 패러미터를 지정할 수 있습니다.
Non-Blocking 함수#
사용자는 createPhotographer로 Photographer 객체를 생성합니다. 이후 startPhotographer 로 시작한 후, 스냅샷이 필요할 경우 마다 photographerGeneratePicture를 호출합니다. Photographer를 사용을 마치려면, stopPhotographer를 호출합니다.
PhotographerObserver 인터페이스#
createPhotographer 호출 시에 매개변수로 PhotographerObserver을 base class로 정의한 사용자 인터페이스 객체를 전달합니다. 이후 콜백함수를 통하여 스냅샷 작업을 수행합니다.
class PhotographerObserver : public std::enable_shared_from_this<PhotographerObserver>
{
public:
PhotographerObserver();
virtual ~PhotographerObserver();
virtual void onCameraSensorMetaData(int32_t camera_id, const CameraSensorMetaData &metadata);
virtual void onIspMetaData(int32_t camera_id, const IspMetaData &metadata);
virtual void onCaptureImageAvailable(bool subchannel_image);
virtual void onImageDataCompleted(const key_value_map_t ¶meters, bool subchannel_image, int32_t width, int32_t height, int32_t format, const std::vector<char> &data);
virtual void onImageDataCompletedWithError(const key_value_map_t ¶meters, bool subchannel_image, int32_t error);
};
Note
onCameraSensorMetaData 와 onIspMetaData 는 Rasberry Pi 에만 적용됩니다.
false이면 main channel 이미지가 준비되었음을 의미합니다. true이면 subchannel image가 준비되었음을 의미합니다. 카메라 센서 기기에 따라서 subchannel 이미지도 함께 생성하는 경우가 있습니다.
key_value_map_t &
parameters
,
bool
subchannel_image
,
int32_t
width
,
int32_t
height
,
int32_t
format
,
const std::vector<char> &
data
)
photographerGeneratePicture 호출 결과로 JPEG 캡춰 이미지 데이터가 생성되었음을 알립니다.
photographerGeneratePicture에 매개변수로 전달한 key-value map 입니다.
false이면 main channel 이미지 데이터를 의미합니다. true이면 subchannel 이미지 데이터입니다.
kImageTypeJpeg(0) 값입니다.
key_value_map_t &
parameters
,
bool
subchannel_image
,
int32_t
error
)
photographerGeneratePicture 호출 결과로 오류가 발생했음을 알립니다.
photographerGeneratePicture에 매개변수로 전달한 key-value map 입니다.
false이면 main channel 이미지 데이터를 의미합니다. true이면 subchannel 이미지 데이터입니다.
Photographer API#
key_value_map_t &
parameters
,
const std::shared_ptr<PhotographerObserver> &
observer
)
configCameras에서 설정한 카메라 장치 ID입니다.
PhotographerObserver에서 유도된 사용자 정의 observer 객체입니다.
nullptr이 반환됩니다.
- 0: 성공
- -1: 실패
- 0: 성공
- -1: 실패
- 0: 성공
- -1: 실패
- true: 시작상태입니다.
- false: 정지상태입니다.
key_value_map_t &
parameters
,
bool
subchannel_image
,
const uint8_t *
exif_user_data
= nullptr
,
size_t
exif_user_data_length
= 0
)
false이면 main channel 이미지를 캡춰합니다. true이면 subchannel 이미지를 캡춰합니다.
- 0: 성공
- -1: 실패
아래 key-value map 으로 옵션 사항입니다. 이외에 osd 관련 패러미터와 EXIF 관련 패러미터를 지정할 수 있습니다.
Note
photographerSetIspParameters는 Rasberry Pi 에만 적용됩니다.
예제#
선택한 카메라 장치로 부터 영상을 캡춰하고 OSD를 적용한 다음 JPEG 파일을 생성하는 예입니다. -a 옵션을 선택할 경우, Non-Blocking 함수를 사용합니다.
Non-Blocking 함수를 적용할 경우, 1초 간격으로 각기 다른 OSD 텍스트를 지정하여 2회 스냅샷을 생성합니다.
#include "OasisAPI.h"
#include "OasisLog.h"
#include "OasisMedia.h"
#include "OasisUI.h"
#include "OasisUtil.h"
#include "OasisDisplay.h"
#include <thread>
#include <mutex>
#include <memory>
#include <condition_variable>
#include <signal.h>
#define DLOG_APP 0x00010000
#undef DLOG_FLAGS
#define DLOG_FLAGS (DLOG_FLAGS_DEFAULT|DLOG_APP/**/)
using namespace oasis;
static bool continue_capturing = true;
enum {
kCaptureStateIdle = 0,
kCaptureStateImageAvailable,
kCaptureStateImageDataReady,
kCaptureStateError,
};
static std::list<int32_t> capture_state_q;
static std::mutex capture_mutex;
static std::condition_variable capture_cond;
static std::vector<char> captured_image_data;
void cancel_handler(int signum)
{
continue_capturing = false;
capture_cond.notify_one();
}
class MyPhotographerObserver : public PhotographerObserver
{
public:
MyPhotographerObserver() {}
virtual ~MyPhotographerObserver() {}
virtual void onCameraSensorMetaData(int32_t camera_id, const CameraSensorMetaData &metadata) {}
virtual void onIspMetaData(int32_t camera_id, const IspMetaData &metadata) {}
virtual void onCaptureImageAvailable(bool subchannel_image);
virtual void onImageDataCompleted(const key_value_map_t ¶meters, bool subchannel_image, int32_t width, int32_t height, int32_t format, const std::vector<char> &data);
virtual void onImageDataCompletedWithError(const key_value_map_t ¶meters, bool subchannel_image, int32_t error);
};
void MyPhotographerObserver::onCaptureImageAvailable(bool subchannel_image)
{
fprintf(stdout, ">>> image available\n");
{
std::lock_guard<std::mutex> lock(capture_mutex);
capture_state_q.push_back(kCaptureStateImageAvailable);
}
capture_cond.notify_one();
}
void MyPhotographerObserver::onImageDataCompleted(const key_value_map_t ¶meters, bool subchannel_image, int32_t width, int32_t height, int32_t format, const std::vector<char> &data)
{
fprintf(stdout, ">>>>> image data completed: %dx%d, format %d, data size %zd\n", width, height, format, data.size());
{
std::lock_guard<std::mutex> lock(capture_mutex);
capture_state_q.push_back(kCaptureStateImageDataReady);
captured_image_data = data;
}
capture_cond.notify_one();
}
void MyPhotographerObserver::onImageDataCompletedWithError(const key_value_map_t ¶meters, bool subchannel_image, int32_t error)
{
fprintf(stdout, ">>>>> image data completed with error: %d\n", error);
{
std::lock_guard<std::mutex> lock(capture_mutex);
capture_state_q.push_back(kCaptureStateError);
}
capture_cond.notify_one();
}
void print_usage(const char *prog_name)
{
fprintf(stdout, "USAGE: %s [-a] [-w width] [-h height] [-q quality] [-t wait-timeout in seconds] [-s skip frame count] <camera id> <output-jpeg-file-path>\n", prog_name);
}
int main(int argc, char* argv[])
{
int32_t err, opt;
int32_t camera_id = 0, width = 0, height = 0, wait = 5, quality = 100, skip = 6;
char output_path[PATH_MAX];
bool use_async = false;
while((opt = getopt(argc, argv, "aw:h:q:t:s:")) != -1 ) {
switch ( opt ) {
case 'a':
use_async = true;
break;
case 'w':
if(optarg == nullptr) {
print_usage(argv[0]);
return -1;
}
width = atoi(optarg);
break;
case 'h':
if(optarg == nullptr) {
print_usage(argv[0]);
return -1;
}
height = atoi(optarg);
break;
case 'q':
if(optarg == nullptr) {
print_usage(argv[0]);
return -1;
}
quality = atoi(optarg);
if(quality > 100) quality = 100;
else if(quality < 0) quality = 1;
break;
case 't':
if(optarg == nullptr) {
print_usage(argv[0]);
return -1;
}
wait = atoi(optarg);
break;
case 's':
if(optarg == nullptr) {
print_usage(argv[0]);
return -1;
}
skip = atoi(optarg);
break;
default:
fprintf(stderr, "error: unknown option '%c'\n", optopt);
print_usage(argv[0]);
return -1;
}
}
if(argc-optind < 2) {
fprintf(stderr, "error: invalid or insufficient arguments\n");
print_usage(argv[0]);
return -1;
}
camera_id = atoi(argv[optind]);
strncpy(output_path, argv[optind+1], sizeof(output_path)-1);
TRACE0("camera %d, size %dx%d, quality %d, wait timeout %d, skip %d frames, save to %s\n", camera_id, width, height, quality, wait, skip, output_path);
signal(SIGINT, cancel_handler);
////////////////////////////////////////////////////////////////////////////////////////////
// init
oasis::key_value_map_t parameters;
parameters["offs-disable"] = "1";
//parameters["oasis-log-flags"] = std::to_string(OASIS_LOG_DEBUG/*|OASIS_LOG_ENCODE_BITRATE*/);
if(oasis::initialize(parameters) < 0) {
DLOG(DLOG_ERROR, "Oasis init failed\n");
return -1;
}
////////////////////////////////////////////////////////////////////////////////////////////
// config cameras
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);
////////////////////////////////////////////////////////////////////////////////////////////
// display setup
parameters.clear();
parameters["memory-type"] = "ion"; //cma
parameters["screen-width"] = "480";
parameters["screen-height"] = "320";
display::setup(parameters);
////////////////////////////////////////////////////////////////////////////////////////////
parameters["camera-id"] = std::to_string(camera_id);
parameters["width"] = std::to_string(width);
parameters["height"] = std::to_string(height);
parameters["quality"] = std::to_string(quality);
parameters["timeout"] = std::to_string(wait*1000);
parameters["drop-frame-count"] = std::to_string(skip);
if(use_async) {
std::shared_ptr<MyPhotographerObserver> observer = std::make_shared<MyPhotographerObserver>();
PhotographerRef photographer = createPhotographer(camera_id, parameters, observer);
if(photographer) {
parameters.clear();
//parameters["jpeg-quality"] = std::to_string(quality);
startPhotographer(photographer);
bool is_capturing = false;
int32_t image_count = 0;
do {
int32_t state;
{
std::unique_lock<std::mutex> lock(capture_mutex);
while(continue_capturing && capture_state_q.empty()) {
if(wait > 0) {
std::cv_status status = capture_cond.wait_for(lock, std::chrono::seconds(wait));
if(status == std::cv_status::timeout) {
fprintf(stderr, "wait for image available timeout\n");
continue_capturing = false;
break;
}
} else {
capture_cond.wait(lock);
}
}
if(capture_state_q.empty()) {
continue;
} else {
state = capture_state_q.front();
capture_state_q.pop_front();
}
}
auto triggerSnapshot = [&](int32_t image_index) -> int32_t {
key_value_map_t parameters;
time_t tm;
struct tm tm_t;
std::string osd_string;
time(&tm);
localtime_r(&tm, &tm_t);
osd_string = oasis::format("snapshot<%d> %4d/%02d/%02d %02d:%02d:%02d", image_index, 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);
parameters["osd"] = osd_string;
parameters["osd-font-size"] = "26";
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"] = "0,0,0";
parameters["osd-horz-align"] = "left";
parameters["osd-vert-align"] = "bottom";
parameters["osd-font-path"] = "/mnt/sd/consola.ttf";
parameters["osd-use-fixed-size"] = "0";
return photographerGeneratePicture(photographer, parameters, false, nullptr, 0);
};
if(state == kCaptureStateImageAvailable) {
if(is_capturing == false) {
is_capturing = 0 == triggerSnapshot(image_count);
if(!is_capturing) {
fprintf(stderr, "failed to generate picture\n");
break;
} else {
image_count++;
}
}
} else if(state == kCaptureStateImageDataReady) {
if(captured_image_data.empty() == false) {
std::string path = output_path;
size_t dot_pos = path.find_last_of(".");
std::string prefix = dot_pos != std::string::npos ? path.substr(0, dot_pos) : path;
std::string suffix = dot_pos != std::string::npos ? path.substr(dot_pos) : "";
std::string this_image_path = prefix + "_" + std::to_string(image_count) + suffix;
FILE *fp = fopen(this_image_path.c_str(), "wb");
if(fp) {
size_t n, len;
for(n=0; n<captured_image_data.size(); n += len) {
len = fwrite( captured_image_data.data()+n, 1, captured_image_data.size()-n, fp );
if(len == 0) break;
}
fclose(fp);
DLOG0(DLOG_INFO, "%s saved\n", this_image_path.c_str());
}
}
is_capturing = false;
if(image_count == 2) {
break;
} else {
//take another picture
usleep(1000000);
if(is_capturing == false) {
is_capturing = 0 == triggerSnapshot(image_count);
if(!is_capturing) {
fprintf(stderr, "failed to generate picture\n");
break;
} else {
image_count++;
}
}
}
} else if(state == kCaptureStateError) {
is_capturing = false;
break;
}
} while (continue_capturing);
stopPhotographer(photographer);
destroyPhotographer(photographer);
}
} else {
time_t tm;
struct tm tm_t;
std::string osd_string;
time(&tm);
localtime_r(&tm, &tm_t);
osd_string = oasis::format("%4d/%02d/%02d %02d:%02d:%02d 12.0V 12H/11.9V", 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);
parameters["osd"] = osd_string;
parameters["osd-font-size"] = "16";
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"] = "0,0,0";
parameters["osd-horz-align"] = "left";
parameters["osd-vert-align"] = "bottom";
parameters["osd-font-path"] = "/mnt/sd/consola.ttf";
parameters["osd-use-fixed-size"] = "0";
err = takePicture(camera_id, parameters, captured_image_data);
if(err == 0) {
DLOG0(DLOG_INFO, "snapshot jpeg data %d bytes returned\n", captured_image_data.size());
FILE *fp = fopen(output_path, "wb");
if(fp) {
size_t n, len;
for(n=0; n<captured_image_data.size(); n += len) {
len = fwrite( captured_image_data.data()+n, 1, captured_image_data.size()-n, fp );
if(len == 0) break;
}
fclose(fp);
DLOG0(DLOG_INFO, "%s saved\n", output_path);
}
} else {
DLOG0(DLOG_ERROR, "error %d\n", err);
}
}
oasis::finalize();
return 0;
}