스냅샷 API

녹화 컴포넌트에서 제공하는 takeRecordingShapshot과는 별개로 직접 카메라 이미지를 캡춰합니다.

스냅샷 API는 Non-blocking 방식과 Blocking 방식이 있습니다.

  • Photographer 객체를 생성하고 PhotographerObserver 인터페이스를 사용하는 방식으로 캡쳐 파일을 계속 생성할 수 있습니다.
  • takePicture 함수를 호출하여 스냅샷이 완료될 때까지 기다리는 방식으로 캡춰 파일을 하나만 생성할 수 있습니다.

헤더 파일

OasisMedia.h

Blocking 함수

Blocking 방식으로 스냅샷 데이터를 생성합니다. 스냅샷이 완료되거나 타임아웃이 발생할 때까지 기다립니다.

int32_t takePicture ( int32_t camera_id , int32_t quality , int32_t width , int32_t height , int32_t wait_timeout , const char * osd_horz_align , int32_t osd_vert_align , const char * osd_string , uint32_t text_color , bool use_outline_color , uint32_t outline_color , int32_t font_point_size , const char * font_path , std::vector<char> & image_data , struct timeval * timestmap = nullptr )
OasisMedia.h
매개변수
camera_id  configCameras에서 설정한 카메라 ID입니다.
quality  JPEG 이미지 품질입니다. 0~100 사이 값을 지정합니다.
width  저장할 JPEG 이미지의 너비입니다. "0"인 경우, 카메라 센서에 설정된 이미지 너비를 사용합니다.
height  저장할 JPEG 이미지의 높이입니다. "0"인 경우, 카메라 센서에 설정된 이미지 높이를 사용합니다.
wait_timeout  스냅샷이 생성될 때까지 기다리는 최대 시간입니다. "0" 값이면, 무한정 기다립니다.
osd_horz_align  OSD 텍스트의 가로 위치입니다. left,center,right 중 하나 값을 지정합니다.
osd_vert_align  OSD 텍스트의 세로 위치입니다. top, vcenter, bottom 중 하나 값을 지정합니다.
osd_string  OSD 텍스트입니다. nullptr 이나 비어있는 문자열을 지정하면 OSD 텍스트가 표시되지 않습니다.
text_color  OSD 텍스트 색상입니다. 0xrrggbb 형식을 갖습니다.
use_outline_color  OSD 텍스트 글자에 테두리 색상을 적용할 지 여부를 지정합니다. true이면, outline_color 색상으로 외곽선을 표시합니다.
outline_color  OSD 텍스트 글자의 테두리 색상입니다. 0xrrggbb 형식을 갖습니다.
font_point_size  OSD 텍스트 크기입니다. 포인트 단위입니다.
font_path  OSD 텍스트 폰트의 절대 경로입니다.
image_data  OUT 캡춰하여 생성된 JPEG 이미지 데이터입니다.
timestmap  OUT 캡춰 생성시 시간값이 저장됩니다. nullptr 값을 지정하면 시간 정보를 저장하지 않습니다.
리턴값
  • 0: 성공
  • -1: 실패
int32_t takePicture ( int32_t camera_id , key_value_map_t & parameters , std::vector<char> & image_data , struct timeval * timestmap = nullptr )
OasisMedia.h
매개변수
camera_id  configCameras에서 설정한 카메라 ID입니다.
parameters  스냅샷 생성에 필요한 key-value map 입니다.
image_data  OUT 캡춰하여 생성된 JPEG 이미지 데이터입니다.
timestmap  OUT 캡춰 생성시 시간값이 저장됩니다. nullptr 값을 지정하면 시간 정보를 저장하지 않습니다.
리턴값
  • 0: 성공
  • -1: 실패

스냅샷 생성에 필요한 key-value map은 아래와 같습니다.

기본값
필수
설명
camera-id
configCameras에서 설정한 카메라 장치 ID 입니다.
width
스냅샷 이미지의 너비입니다. "0"이면, 원본 영상 이미지 너비와 같습니다.
height
스냅샷 이미지의 높이입니다. "0"이면, 원본 영상 이미지 높이와 같습니다.
quality
100
 
JPEG 이미지 품질을 지정합니다. 품질 값은 0~100 범위 내에 있어야 합니다.
timeout
3000
 
스냅샷 마칠 때까지 대기하는 시간(밀리초)입니다. "0"이면, 마칠 때까지 무한정 기다립니다.
drop-frame-count
0
 
스냅샷 하기 전에 버릴 프레임 갯수입니다.
snapshot-use-sw-encoder
0
 
HW Jpeg 인코더 대신 SW Jpeg 인코더를 사용합니다.
osd
OSD 텍스트입니다.
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-outline-color
0
 
"1" 값이면 글자 테두리 색상을 사용합니다.
osd-outline-color
255,255,255
 
글자 테두리 색상입니다.
osd-horz-align
left
 
left,center,right 중 하나의 값을 지정합니다.
osd-vert-align
bottom
 
top,vcenter,bottom 중 하나의 값을 지정합니다.
osd-font-path
 
지정하지 않을 경우, oasis::initialize 시 정의한 system-font-path 값이 사용됩니다.

JPEG 이미지 생성 시 아래 EXIF 태깅 패러미터를 지정할 수 있습니다.

기본값
필수
설명
exif-IFD0.DateTime
 
JPEG EXIF 태깅용 JPEG 최근 수정 날짜와 시간 문자열 입니다. "2023:02:23 11:06:03"와 같은 형식입니다.
exif-IFD0.Software
 
JPEG EXIF 태깅용 JPEG 이미지를 생성한 소프트웨어 이름입니다.
exif-EXIF.ExifImageWidth
 
JPEG EXIF 태깅용 너비 값입니다.
exif-EXIF.ExifImageHeight
 
JPEG EXIF 태깅용 높이 값입니다.
exif-EXIF.ImageUniqueID
 
JPEG EXIF 태깅용 이미지의 고유 ID 입니다. 사용자가 편의에 따라 지정합니다.
exif-EXIF.DateTimeOriginal
 
JPEG EXIF 태깅용 원본 생성 날짜와 시간 문자열 입니다. "2023:02:23 11:06:03"와 같은 형식입니다.
exif-EXIF.DateTimeDigitized
 
JPEG EXIF 태깅용 이미지 스캔되거나 처리된 날짜와 시간 문자열 입니다. "2023:02:23 11:06:03"와 같은 형식입니다.
exif-EXIF.MakerNote
 
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 &parameters, 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 &parameters, bool subchannel_image, int32_t error);

};

Note

onCameraSensorMetaDataonIspMetaData 는 Rasberry Pi 에만 적용됩니다.

void onCaptureImageAvailable ( bool subchannel_image )
OasisMedia.h
캡춰할 이미지가 준비되었음을 알립니다. 계속 호출될 수 있습니다. 최초 알림 이후부터 스냅샷을 생성할 수 있습니다.
매개변수
subchannel_image  false이면 main channel 이미지가 준비되었음을 의미합니다. true이면 subchannel image가 준비되었음을 의미합니다. 카메라 센서 기기에 따라서 subchannel 이미지도 함께 생성하는 경우가 있습니다.
void onImageDataCompleted ( const key_value_map_t & parameters , bool subchannel_image , int32_t width , int32_t height , int32_t format , const std::vector<char> & data )
OasisMedia.h
photographerGeneratePicture 호출 결과로 JPEG 캡춰 이미지 데이터가 생성되었음을 알립니다.
매개변수
parameters  photographerGeneratePicture에 매개변수로 전달한 key-value map 입니다.
subchannel_image  false이면 main channel 이미지 데이터를 의미합니다. true이면 subchannel 이미지 데이터입니다.
width  스냅샷 이미지의 너비입니다.
height  스냅샷 이미지의 높이입니다.
format  kImageTypeJpeg(0) 값입니다.
data  JPEG 이미지 데이터입니다.
void onImageDataCompletedWithError ( const key_value_map_t & parameters , bool subchannel_image , int32_t error )
OasisMedia.h
photographerGeneratePicture 호출 결과로 오류가 발생했음을 알립니다.
매개변수
parameters  photographerGeneratePicture에 매개변수로 전달한 key-value map 입니다.
subchannel_image  false이면 main channel 이미지 데이터를 의미합니다. true이면 subchannel 이미지 데이터입니다.
error  에러값(-1)입니다.

Photographer API

PhotographerRef createPhotographer ( int32_t camera_id , key_value_map_t & parameters , onst std::shared_ptr<PhotographerObserver> & observer )
OasisMedia.h
Photographer 객체를 생성합니다.
매개변수
camera_id  configCameras에서 설정한 카메라 장치 ID입니다.
parameters  Photographer 객체 생성에 필요한 key-value map 입니다.
observer  PhotographerObserver에서 유도된 사용자 정의 observer 객체입니다.
리턴값
성공하면 PhotographerRef 객체가 반환됩니다. 실패하면 nullptr이 반환됩니다.
int32_t destroyPhotographer ( PhotographerRef photographer )
OasisMedia.h
Photographer 객체를 해제합니다.
매개변수
photographer  Photographer 객체입니다.
리턴값
  • 0: 성공
  • -1: 실패
int32_t startPhotographer ( PhotographerRef photographer )
OasisMedia.h
Photographer 객체를 시작합니다.
매개변수
photographer  Photographer 객체입니다.
리턴값
  • 0: 성공
  • -1: 실패
int32_t stopPhotographer ( PhotographerRef photographer )
OasisMedia.h
Photographer 객체를 중지합니다.
매개변수
photographer  Photographer 객체입니다.
리턴값
  • 0: 성공
  • -1: 실패
bool isPhotographerRunning ( PhotographerRef photographer )
OasisMedia.h
Photographer 객체가 시작 상태인지 판단합니다.
매개변수
photographer  Photographer 객체입니다.
리턴값
  • true: 시작상태입니다.
  • false: 정지상태입니다.
int32_t photographerGeneratePicture ( PhotographerRef photographer , key_value_map_t & parameters , bool subchannel_image , const uint8_t * exif_user_data = nullptr , size_t exif_user_data_length = 0 )
OasisMedia.h
Photographer 객체가 스냅샷을 생성합니다.
매개변수
photographer  Photographer 객체입니다.
parameters  스냅샷 생성에 추가로 필요한 key-value map 입니다.
subchannel_image  false이면 main channel 이미지를 캡춰합니다. true이면 subchannel 이미지를 캡춰합니다.
exif_user_data  JPEG EXIF에 기록될 사용자 데이터입니다. 유효한 경우, EXIF USER COMMENT(0x9286)에 Base64 형태로 저장됩니다.
exif_user_data_length  JPEG EXIF에 기록될 사용자 데이터입니다.
리턴값
  • 0: 성공
  • -1: 실패

아래 key-value map 으로 옵션 사항입니다. 이외에 osd 관련 패러미터와 EXIF 관련 패러미터를 지정할 수 있습니다.

기본값
필수
설명
jpeg-quality
100
 
JPEG 이미지 품질을 지정합니다. 품질 값은 0~100 범위 내에 있어야 합니다.
snapshot-use-sw-encoder
0
 
HW Jpeg 인코더 대신 SW Jpeg 인코더를 사용합니다.

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 &parameters, 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 &parameters, 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 &parameters, 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 &parameters, 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;
}