NDK Samples学习 [6-1] - camera之basic

title: NDK Samples学习 [6-1] - camera之basic
tags: [ndk camera]
date: 2019-03-05 17:40:44
categories: NDK Samples


camera-basic
README
该项目演示如何使用c创建camera拍照、预览。


最低要求:

  1. Android Studio 2.3.0+ ,NDK r15+
  2. Android-24+

项目主要流程:

  1. 获取权限
  2. 初始化界面,NDKCamera、曝光度、感光度
  3. 操作相机,调整曝光度、感光度预览;拍照保存

CameraActivity.java

  1. CameraSeekBar 包装SeekBar用于调整相机预览的曝光度、感光度

void setImmersiveSticky() {} //设置全屏显示
public void RequestCamera() {} //请求权限,由c++调用
public void EnableUI(final long[] params){}//由c++调用,并传入曝光度、感光度参数
public void OnPhotoTaken(String fileName) {}//由c++调用,传入拍照图片路径
native static void notifyCameraPermission(boolean granted);//权限变化后
native static void TakePhoto();//调用c++拍照
native void OnExposureChanged(long exposure);//调用c++调整曝光度
native void OnSensitivityChanged(long sensitivity);//调用c++调整感光度
native static void notifyCameraPermission(boolean granted);//通知c++权限变化

android_main.cpp c主入口

/*
 * SampleEngine global object
 */
static CameraEngine* pEngineObj = nullptr;
/*
 * 获取CameraEngine单例对象
 */
CameraEngine* GetAppEngine(void) {
  ASSERT(pEngineObj, "AppEngine has not initialized");
  return pEngineObj;
}

/**
 * 根据收到INIT_WINDOW/TERM_WINDOW事件创建或删除camera对象,
 * 使用NativeActivity的应用才会收到
 */
static void ProcessAndroidCmd(struct android_app* app, int32_t cmd) {
  CameraEngine* engine = reinterpret_cast<CameraEngine*>(app->userData);
  switch (cmd) {
    // 窗口初始化
    case APP_CMD_INIT_WINDOW:
      if (engine->AndroidApp()->window != NULL) {
        engine->SaveNativeWinRes(ANativeWindow_getWidth(app->window),
                                 ANativeWindow_getHeight(app->window),
                                 ANativeWindow_getFormat(app->window));
        // 请求权限,创建camera
        engine->OnAppInitWindow();
      }
      break;
    // 窗口关闭
    case APP_CMD_TERM_WINDOW:
      // 删除camera
      engine->OnAppTermWindow();
      ANativeWindow_setBuffersGeometry(
          app->window, engine->GetSavedNativeWinWidth(),
          engine->GetSavedNativeWinHeight(), engine->GetSavedNativeWinFormat());
      break;
    // 窗口配改变
    case APP_CMD_CONFIG_CHANGED:
      engine->OnAppConfigChange();
      break;
    // 失去焦点
    case APP_CMD_LOST_FOCUS:
      break;
  }
}

extern "C" void android_main(struct android_app* state) {
  CameraEngine engine(state);
  pEngineObj = &engine;

  state->userData = reinterpret_cast<void*>(&engine);
  state->onAppCmd = ProcessAndroidCmd;

  // loop waiting for stuff to do.
  while (1) {
    // Read all pending events.
    int events;
    struct android_poll_source* source;

    while (ALooper_pollAll(0, NULL, &events, (void**)&source) >= 0) {
      // Process this event.
      if (source != NULL) {
        source->process(state, source);
      }

      // Check if we are exiting.
      if (state->destroyRequested != 0) {
        LOGI("CameraEngine thread destroy requested!");
        engine.DeleteCamera();
        pEngineObj = nullptr;
        return;
      }
    }
    pEngineObj->DrawFrame();
  }
}

/**
 * 窗口初始化时调用,请求相机相关权限,并创建camera
 */
void CameraEngine::OnAppInitWindow(void) {
  if (!cameraGranted_) {
    // 请求相机相关权限
    RequestCameraPermission();
    return;
  }

  // 旋转角度
  rotation_ = GetDisplayRotation();

  // 创建camera
  CreateCamera();
  ASSERT(camera_, "CameraCreation Failed");

  // 启用曝光度UI
  EnableUI();

  // 预览
  cameraReady_ = true;
  camera_->StartPreview(true);
}

/**
 * Handle APP_CMD_TEMR_WINDOW
 */
void CameraEngine::OnAppTermWindow(void) {
  cameraReady_ = false;
  // 销毁camera
  DeleteCamera();
}

/**
 * Handle APP_CMD_CONFIG_CHANGED
 * 获取旋转角度
 * 旋转角度有变化,销毁camera,再重新创建camera
 */
void CameraEngine::OnAppConfigChange(void) {
  int newRotation = GetDisplayRotation();

  if (newRotation != rotation_) {
    OnAppTermWindow();

    rotation_ = newRotation;
    OnAppInitWindow();
  }
}

camera_engine.cpp

/**
 * 构造函数,初始camera资源
 * @param app native_app_glue environment
 */
CameraEngine::CameraEngine(android_app* app)

/**
 * 析构造函数,释放camera资源
 */
CameraEngine::~CameraEngine() {
  cameraReady_ = false;
  DeleteCamera();
}

/**
 * 创建后台camera
 */
void CameraEngine::CreateCamera(void) {
  // 没有权限直接返回
  if (!cameraGranted_ || !app_->window) {
    LOGW("Camera Sample requires Full Camera access");
    return;
  }

  int32_t displayRotation = GetDisplayRotation();
  rotation_ = displayRotation;

  // 创建NDKCamera
  camera_ = new NDKCamera();
  ASSERT(camera_, "Failed to Create CameraObject");

  // 旋转角度
  int32_t facing = 0, angle = 0, imageRotation = 0;
  if (camera_->GetSensorOrientation(&facing, &angle)) {
    if (facing == ACAMERA_LENS_FACING_FRONT) {
      imageRotation = (angle + rotation_) % 360;
      imageRotation = (360 - imageRotation) % 360;
    } else {
      imageRotation = (angle - rotation_ + 360) % 360;
    }
  }
  LOGI("Phone Rotation: %d, Present Rotation Angle: %d", rotation_,
       imageRotation);
  ImageFormat view{0, 0, 0}, capture{0, 0, 0};
  camera_->MatchCaptureSizeRequest(app_->window, &view, &capture);

  ASSERT(view.width && view.height, "Could not find supportable resolution");

  // Request the necessary nativeWindow to OS
  bool portraitNativeWindow =
      (savedNativeWinRes_.width < savedNativeWinRes_.height);
  ANativeWindow_setBuffersGeometry(
      app_->window, portraitNativeWindow ? view.height : view.width,
      portraitNativeWindow ? view.width : view.height, WINDOW_FORMAT_RGBA_8888);

  yuvReader_ = new ImageReader(&view, AIMAGE_FORMAT_YUV_420_888);
  yuvReader_->SetPresentRotation(imageRotation);
  jpgReader_ = new ImageReader(&capture, AIMAGE_FORMAT_JPEG);
  jpgReader_->SetPresentRotation(imageRotation);
  jpgReader_->RegisterCallback(this, [this](void* ctx, const char* str) -> void {
    reinterpret_cast<CameraEngine* >(ctx)->OnPhotoTaken(str);
  });

  // now we could create session
  camera_->CreateSession(yuvReader_->GetNativeWindow(),
                         jpgReader_->GetNativeWindow(), imageRotation);
}

// 释放camera
void CameraEngine::DeleteCamera(void) {
  cameraReady_ = false;
  if (camera_) {
    delete camera_;
    camera_ = nullptr;
  }
  if (yuvReader_) {
    delete yuvReader_;
    yuvReader_ = nullptr;
  }
  if (jpgReader_) {
    delete jpgReader_;
    jpgReader_ = nullptr;
  }
}

/**
 * 请求camera相关权限
 *  通过调用CameraActivity.notifyCameraPermission,
 *  授权结果,调用native notifyCameraPermission()
 */
void CameraEngine::RequestCameraPermission() {
  if (!app_) return;

  JNIEnv* env;
  ANativeActivity* activity = app_->activity;
  activity->vm->GetEnv((void**)&env, JNI_VERSION_1_6);

  activity->vm->AttachCurrentThread(&env, NULL);

  jobject activityObj = env->NewGlobalRef(activity->clazz);
  jclass clz = env->GetObjectClass(activityObj);
  env->CallVoidMethod(activityObj,
                      env->GetMethodID(clz, "RequestCamera", "()V"));
  env->DeleteGlobalRef(activityObj);

  activity->vm->DetachCurrentThread();
}
/**
 * 调整曝光度、感光度
 */
void CameraEngine::OnCameraParameterChanged(int32_t code, int64_t val) {
  camera_->UpdateCameraRequestParameter(code, val);
}

/**
 * The main function rendering a frame. In our case, it is yuv to RGBA8888
 * converter
 */
void CameraEngine::DrawFrame(void) {
  if (!cameraReady_ || !yuvReader_) return;
  AImage* image = yuvReader_->GetNextImage();
  if (!image) {
    return;
  }

  ANativeWindow_acquire(app_->window);
  ANativeWindow_Buffer buf;
  if (ANativeWindow_lock(app_->window, &buf, nullptr) < 0) {
    yuvReader_->DeleteImage(image);
    return;
  }

  yuvReader_->DisplayImage(&buf, image);
  ANativeWindow_unlockAndPost(app_->window);
  ANativeWindow_release(app_->window);
}

camera_ui.cpp

/**
 * 获取旋转角度Retrieve current rotation from Java side
 * 调用CameraActivity.getRotationDegree
 */
int CameraEngine::GetDisplayRotation() {
  ...
}

/**
 * 设置曝光度、感光度
 */
void CameraEngine::EnableUI(void) {
   // 1. 获取曝光度,感光度NDKCamera::GetExposureRange\NDKCamera::GetSensitivityRange
  // 2. 调用CameraActivity.EnableUI设置
}

/**
 * 拍照
 */
void CameraEngine::OnTakePhoto() {
  if (camera_) {
    camera_->TakePhoto();
  }
}

/**
 * 回传照片文件路径,CameraActivity.OnPhotoTaken
 */
void CameraEngine::OnPhotoTaken(const char* fileName) {
  ...
}
/**
 * camera和文件写入权限回调
 */
void CameraEngine::OnCameraPermission(jboolean granted) {
  //有权限则调用OnAppInitWindow初始化
}

/**
 *  native响应权限变化
 */
extern "C" JNIEXPORT void JNICALL
Java_com_sample_camera_basic_CameraActivity_notifyCameraPermission(
    JNIEnv *env, jclass type, jboolean permission) {
  //CameraEngine::OnCameraPermission权限变化
}

/**
 *  native拍照
 */
extern "C" JNIEXPORT void JNICALL
Java_com_sample_camera_basic_CameraActivity_TakePhoto(JNIEnv *env,
                                                      jclass type) {
  //CameraEngine::OnTakePhoto
}

/**
 *  native曝光度变化,调整预览
 */
extern "C" JNIEXPORT void JNICALL
Java_com_sample_camera_basic_CameraActivity_OnExposureChanged(
    JNIEnv *env, jobject instance, jlong exposurePercent) {
  //GetAppEngine()::OnCameraParameterChanged
}

/**
 *  native感光度变化,调整预览
 */
extern "C" JNIEXPORT void JNICALL
Java_com_sample_camera_basic_CameraActivity_OnSensitivityChanged(
    JNIEnv *env, jobject instance, jlong sensitivity) {
  //GetAppEngine()::OnCameraParameterChanged
}

camera_manager.cpp

  1. 初始化camera
  2. 更新属性
  3. 拍照

image_reader.cpp 图片转换相关
camera_listeners.cpp camera manager相关Listener


项目功能为:

  1. native监听窗口变化判断调用java请求camera相关权限
  2. native创建NDKCamera,并获取曝光度、感光度属性回调到Java并展示
  3. native预览
  4. java调用native调整预览曝光度、感光度
  5. java调用native拍照并回调java提示图片文件路径

android-ndk-samples学习目录

部份引用其他大牛的项目

  1. hello-jni by 七零八落问号
    演示如何从Java层调用C层代码。

  2. hello-jniCallback by 七零八落问
    演示如何在C层回调Java层方法。

  3. hello-libs by 七零八落问
    演示如何在Android Studio上使用第三方的C / C++库。

  4. audio echo by hunter800421
    演示如何如何使用OpenSL ES创建播放器和录音。

  5. bitmap-plasma by hunter800421
    演示如何使用c代码在bitmap上绘制细胞质效果。

6.1. camera-basic by Tu
演示如何使用c创建camera拍照、预览。

参考

NatvieActivity事件

转载请标明出处:
http://2tu.github.io/
本文出自Tu's blog
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350