BREW カメラアプリ 〜 BREW C++ カメラプログラミング 〜
Camera クラスの実装
Camera クラスの目標
SimpleCamera アプリ以外でも再利用できる C++ の Camera クラスを作成します。
Camera クラスのヘッダー ファイル
- Camera.hpp - #ifndef __CAMERA_HPP #define __CAMERA_HPP #include <SophiaFramework.hpp> #define PICTURE_WIDTH 240 // カメラで取得する画像の幅 #define PICTURE_HEIGHT 320 // カメラで取得する画像の高さ SFMTYPEDEFCLASS(Camera) class Camera { SFMSEALCOPY(Camera) public: enum StatusEnum { // カメラの動作状態 STATUS_PREVIEW, // プレビューモードで動作中 STATUS_FRAME, // プレビューの新たなフレームが描画された STATUS_RECORD, // スナップショット撮影開始中 STATUS_ENCODE // スナップショットの撮影が完了した }; private: typedef Void (*NotifySPP)(StatusEnum status, SFCError error, VoidPtr reference); private: // カメラそのもの SFBCameraSmp _camera; // カメラの状態保存用 StatusEnum _status; // カメラで取得したビットマップ SFBBitmapSmp _bitmap; // 画面上のカメラ画像表示領域 SFXRectangle _rect; // メディア データ AEEMediaData _mediaData; // MIME TYPE ACharPtr _mimeType; // 現在プレビュー可能かどうか Bool _previewable; // スナップショットモードでの撮影が可能かどうか Bool _recordable; // このクラスを使用しているクラスの、カメラに関するコールバック関数 NotifySPP _spp; // このクラスを使用しているクラスの // コールバック関数に渡すデータへの参照 VoidPtr _reference; // スナップ ショット撮影後の処理 Void AfterSnapshot(Void); public: Camera(Void); // コンストラクタ virtual ~Camera(Void); // ディストラクタ // カメラの初期化を行う SFCError Initialize(SFXRectangleConstRef rect, NotifySPP spp, VoidPtr reference); SFCError Preview(Void); // プレビュー開始 SFCError Record(Void); // スナップショットモード開始 Void Suspend(Void); // カメラの終了処理 SFCError Resume(Void); // カメラの基本的な初期化処理 Void Terminate(Void); // 後始末 // 撮影されたビットマップを取得する SFBBitmapSmpRef GetBitmap(Void) { return _bitmap; } // 現在プレビュー可能かどうか調べる Bool IsPreviewable(Void) const { return _previewable; } // コールバックハンドラ static Void OnCameraSHP(VoidPtr reference, AEECameraNotify* notify); // コールバックハンドラ(実体) Void OnCamera(AEECameraNotify* notify); // 現在のカメラの明るさを取得する SInt16 GetBrightness(Void); // カメラに設定可能な明るさの範囲を取得する Bool GetBrightnessRange(SInt16Ptr minimum, SInt16Ptr maximum); // カメラの明るさを設定する Void SetBrightness(SInt16 value); // 現在のズーム値を取得する SInt16 GetZoom(Void); // 設定可能なズーム値の幅を取得する Bool GetZoomRange(SInt16Ptr mininum, SInt16Ptr maximum); // ズームを設定する Void SetZoom(SInt16 value); }; #endif
Camera クラスの主な関数 :
- Initialize 関数 : カメラの初期化
- Preview 関数 : プレビューを開始
- Record 関数 : 静止画撮影
- OnCamera 関数 : イベントを処理するクラス
カメラの初期化
Camera::Initialize 関数
// Camera クラスの初期化を行う // rect : プレビュー表示領域 // spp : 外部コールバック関数へのポインタ // reference : 外部コールバック関数へ渡されるデータへのポインタ SFCError Camera::Initialize(SFXRectangleConstRef rect, NotifySPP spp, VoidPtr reference) { SFCError error(SFERR_NO_ERROR); // カメラ画像保持用ビットマップを作成する _bitmap = SFXGraphics::CreateBitmap(rect.GetSize()); if (_bitmap != null) { error = Resume(); // カメラの基本的な準備をする if (error == SFERR_NO_ERROR) { _rect = rect; _spp = spp; _reference = reference; } } else { // メモリ不足のためにビットマップの作成が出来なかった error = SFERR_NO_MEMORY; } if (error != SFERR_NO_ERROR) { Terminate(); } return error; }
第 1 引数は、カメラの画像から切り出す大きさを SFXRectangle 型で与えます。
第 2 引数は、外部コールバック関数としてイベント処理後に実行される関数です。
関数の型は以下の通りです。
Void foo(Camera::StatusEnum status, SFCError error, VoidPtr reference); // foo は関数の名前
第 3 引数は、第 2 引数の関数に渡すデータへのポインタです。
Camera::Resume 関数
// カメラの基本的な初期化処理を行う SFCError Camera::Resume(Void) { SFCError error(SFERR_NO_ERROR); // カメラのインスタンスを取得 if ((_camera = SFBCamera::NewInstance()) != null) { _previewable = true; _recordable = false; // 内部コールバックエントリを登録 error = _camera->RegisterNotify(OnCameraSHP, this); if (error == SFERR_NO_ERROR) { AEESize size; size.cx = PICTURE_WIDTH; size.cy = PICTURE_HEIGHT; error = _camera->SetSize(&size); if (error == SFERR_NO_ERROR) { // カメラで取得する画像のサイズを設定 error = _camera->SetDisplaySize(&size); } } } else { // メモリ不足のためにカメラのインスタンスが取得できなかった error = SFERR_NO_MEMORY; } if (error != SFERR_NO_ERROR) { Suspend(); } return error; }
サスペンド時にはカメラを解放し、レジューム時にはカメラを再初期化してインスタンスを取得しなければいけません。
Resume 関数の処理は、SFBCamera::NewInstance 関数によるカメラのインスタンスを取得と SFBCamera::RegisterNotify 関数によるカメラ イベント用内部コールバック関数の登録です。
RegisterNotify 関数の定義は次の通りです。
SFCError RegisterNotify(PFNCAMERANOTIFY notify, VoidPtr data);
notify はコールバック関数へのポインタです。data は、コールバック関数へ渡す変数へのポインタです。
上の例では、Camera クラスの OnCameraSHP 関数を登録します。
コールバック関数は次の型の静的関数です。
Void foo(VoidPtr reference, AEECameraNotify *notify);
※ カメラの状態は AEECameraNotify 構造体の nStatus メンバに格納されています。
プレビュー モードに入る前に必ず SFBCamera::SetDisplaySize 関数を呼び出して、 取得する画像の大きさ( AEESize 型 )を設定します。
Camera クラスの例では、プレビュー用もスナップ ショット用も画像の大きさは固定にしています。
※ SFBCamera::GetDisplaySizeList や SFBCamera::GetSizeList 関数を使って、端末に対応した画像の大きさを設定することもできます。
プレビュー モードで起動
Camera::Preview 関数
// プレビューを開始する SFCError Camera::Preview(Void) { SFCError error(SFERR_NO_ERROR); if (_camera != null) { _status = STATUS_PREVIEW; _previewable = false; _recordable = false; error = _camera->Preview(); // カメラをプレビューモードで起動 } else { error = SFERR_FAILED; } return error; }
コールバック関数の第 2 引数 AEECameraNotify 構造体の nCmd や nSubCmd メンバにもカメラの状態が格納されています。
nStatus、nCmd、nSubCmd の 3 つの値からカメラの状態を把握できますが、Camera クラスのメンバ変数 _status に格納して 独自にカメラの状態を統一して保持しています。
スナップショット モードでの撮影
Camera::Record メンバ関数
// スナップショットモードで撮影する SFCError Camera::Record(Void) { SFCError error(SFERR_NO_ERROR); if (_camera != null) { _previewable = false; _recordable = false; _status = STATUS_RECORD; error = _camera->Stop(); // プレビューを終了 // プレビューが実際に終了した時点でイベントハンドラが呼ばれるので、 // スナップショットモードでの撮影はその中で行っている } else { error = SFERR_FAILED; } return error; }
プレビュー モードからスナップショット モードへ直接移行できません。一旦カメラを停止する必要があります。
そのため SFBCamera::Stop 関数を呼んで、プレビュー モードを終了します。この時、CAM_STATUS_DONE イベントが発生します。
そこでプレビュー モードを終了する直前に _status メンバ変数に STATUS_RECORD を設定します。
内部コールバック関数が、_status が STATUS_RECORD であることを認識したとき、スナップショット モードに切り替わります。
内部コールバック関数
SFBCamera::RegisterNotify 関数で登録した Camera::OnCameraSHP 関数は内部コールバックです。( コールバック関数は静的関数でなければならないという制限のためです )
OnCameraSHP 関数から OnCamera 関数が呼び出されます。つまり、内部コールバック関数の実体は OnCamera 関数なのです。
// 内部コールバックハンドラ Void Camera::OnCameraSHP(VoidPtr reference, AEECameraNotify* notify) { // 内部コールバック ハンドラの実体を呼び出す static_cast<CameraPtr>(reference)->OnCamera(notify); return; }
// 内部コールバックハンドラ(実体) Void Camera::OnCamera(AEECameraNotify* notify) { switch (notify->nStatus) { case CAM_STATUS_START: // カメラの起動が完了した if (_status == STATUS_PREVIEW) { _recordable = true; // 外部コールバック関数を呼び出す _spp(_status, SFERR_NO_ERROR, _reference); } break; case CAM_STATUS_FRAME: // 新たなフレームを取得した if (_status == STATUS_PREVIEW) { SFBBitmapSmp bitmap; // カメラの画像をビットマップとして取得する if (_camera->GetFrame(&bitmap) == SFERR_NO_ERROR) { // カメラの画像から画面表示領域を抜き出す _bitmap->BltIn(SFXRectangle(0, 0, _rect.GetSize()), bitmap, _rect.GetOrigin()); } // 外部コールバック関数を呼び出す _spp(STATUS_FRAME, SFERR_NO_ERROR, _reference); SFXGraphicsPtr pg = SFXGraphics::GetInstance(); // 取得した画像を画面に表示する pg->BitBlt(_rect, _bitmap, SFXGrid(0, 0)); pg->Update(); // 画面を更新 } break; case CAM_STATUS_DONE: // 処理が完了した if (_status == STATUS_RECORD) { _status = STATUS_ENCODE; _camera->DeferEncode(true); // 遅延エンコードモードに設定 _camera->SetMediaData(&_mediaData, _mimeType); // スナップショットモードで撮影する _camera->RecordSnapshot(); } else if (_status == STATUS_ENCODE) { AfterSnapshot(); } break; case CAM_STATUS_FAIL: case CAM_STATUS_ABORT: // 外部コールバック関数を呼び出す _spp(_status, EFAILED, _reference); break; } return; }
Camera::Initialize 関数で登録した関数にカメラの状態とエラー コードを渡して呼び出しています。
最初の case 文は、カメラが起動されるときに発生する CAM_STATUS_START イベントの処理です。
2 番目の case 文は、プレビュー モードで一定時間ごと( この間隔は SFBCamera::SetFramesPerSecond 関数で変更できる ) に新たな画像取得時に発生する CAM_STATUS_FRAME イベントの処理です。画像を取得して表示したりします。
カメラから取得した画像を表示させるだけなら、次のようになります。
//カメラのインスタンスは camera に格納されていると仮定 SFBBitmapSmp bitmap; camera->GetFrame(&bitmap); // 画像を取得 AEEBitmapInfo bi; bitmap->GetInfo(&bi); // 画像情報を取得 // 画面のインスタンスを取得 SFXGraphicsPtr pg(SFXGraphics::GetInstance()); // 画像を画面に転送 pg->BitBlt(SFXRectangle(0, 0, bi.cx, bi.cy), bitmap, SFXGrid(0, 0)); pg->Update(); // 画面の更新
※1. BREW API では、ICAMERA_GetFrame で取得したビットマップは明示的な解放が必要ですが、スコープが外れる時点で SophiaFramework UNIVERSE が自動的に解放してくれます。
※2. プレビュー モードではノイズ除去機能が無効のままなので、撮影した画像を使って処理するべきではありません。
3 番目の case 文は、CAM_STATUS_DONE イベントの処理です。プレビュー モードを終了する SFBCamera::Stop 関数を呼び出したり、スナップショット モードでの撮影を行う SFBCamera::RecordSnapshot 関数を呼び出すときに、CAM_STATUS_DONE イベントが発生します。
この性質を利用して、Camera クラスでは CameraRecord 関数内では SFBCamera::Stop 関数を呼び出さずに、スナップショット モードへの移行はコールバック関数内で行うようにしました。
遅延エンコードを利用するので、まずは SFBCamera::DeferEncode 関数に true を渡して呼び出します。その後 SFBCamera::SetMediaData 関数、SetMediaData 関数、SFBCamera::RecordSnapshot 関数を順番に呼び出します。
※ SetMediaData 関数の呼び出しですが、撮影後の画像をファイルに保存しないので、適当な値を設定しています。
SFBCamera::RecordSnapshot 関数の呼び出しで、スナップショット モードに移行し、撮影が完了すると CAM_STATUS_DONE イベントが発生し、SFBCamera::GetFrame 関数によって撮影された画像が取り出せます。
このとき、カメラはプレビュー モードでもスナップショット モードでもない待機状態です。プレビュー モードにするは、 SFBCamera::Preview 関数を呼び出します。 ( Camera クラスの場合、Preview 関数を呼び出します )
Camera クラスでは、AfterSnapshot 関数内でカメラで取得した画像を必要なサイズに切り取って、_bitmap メンバ変数に格納しておき、Camera::GetBitmap 関数でそれを取り出すようになっています。
残りの case 文はエラー処理です。
明るさの調整とズーム
// 現在の明るさを取得する SInt16 Camera::GetBrightness(Void) { SInt16 result(0); if (_camera != null) { SInt32 value; AEEParmInfo info; // 明るさを取得 if (_camera->GetParm(CAM_PARM_BRIGHTNESS, &value, (SInt32Ptr)&info) == SFERR_NO_ERROR) { result = (SInt16)value; } } return result; } // 指定できる明るさの範囲を取得する Bool Camera::GetBrightnessRange(SInt16Ptr minimum, SInt16Ptr maximum) { Bool result(false); *minimum = *maximum = 0; if (_camera != null) { Bool support; if (_camera->IsBrightness(&support) == SFERR_NO_ERROR) { if (support) { SInt32 value; AEEParmInfo info; // 範囲を取得 if (_camera->GetParm(CAM_PARM_BRIGHTNESS, &value, (SInt32Ptr)&info) == SFERR_NO_ERROR) { *minimum = (SInt16)info.nMin; *maximum = (SInt16)info.nMax; result = true; } } } } return result; } // 明るさを指定する Void Camera::SetBrightness(SInt16 value) { if (_camera != null) { if (_recordable) { _camera->SetBrightness(value); // 明るさを指定 } } } // 現在のズーム値を取得する SInt16 Camera::GetZoom(Void) { SInt16 result(0); if (_camera != null) { SInt32 value; AEEParmInfo info; // ズーム値を取得 if (_camera->GetParm(CAM_PARM_ZOOM, &value, (SInt32Ptr)&info) == SFERR_NO_ERROR) { result = (SInt16)value; } } return result; } // 指定できるズーム値の範囲を取得する Bool Camera::GetZoomRange(SInt16Ptr minimum, SInt16Ptr maximum) { Bool result(false); *minimum = *maximum = 0; if (_camera != null) { Bool support; if (_camera->IsZoom(&support) == SFERR_NO_ERROR) { if (support) { SInt32 value; AEEParmInfo info; // 範囲を取得 if (_camera->GetParm(CAM_PARM_ZOOM, &value, (SInt32Ptr)&info) == SFERR_NO_ERROR) { *minimum = (SInt16)info.nMin; *maximum = (SInt16)info.nMax; result = true; } } } } return result; } // ズーム値を設定する Void Camera::SetZoom(SInt16 value) { if (_camera != null) { if (_recordable) { _camera->SetZoom(value); // ズーム値を指定 } } }