1. 程式人生 > >用 Golang 開發 Android 應用(六)

用 Golang 開發 Android 應用(六)

用 Golang 開發 Android 應用 -- Camera 使用

計劃按以下的內容更新

Android 中的 Camera

  在 Android 中,最初的 Camera 連前置攝像頭(以下簡稱前攝,後攝同理)都沒支援,而是各廠商自行實現的,曾逆向過一個 Camera 應用,裡面有太多的反射呼叫,並不是它們寫得不好,而是現實中太多實現方式了,各種不同的類。總之要靠一些複雜、怪異的判斷來決定當前要用什麼方式來呼叫前攝。
  後來雖然官方加入了前攝的支援,但NDK都沒有提供 Camera 的方式,要在 NDK 中使用用攝像頭就需要通過參考 Android 的原始碼,找到呼叫方法。
  直到 Android 7.0 上 Camera 2 的出現,NDK 才開始有官方介面提供,這個我們可以從 NDK 的 include\camera 標頭檔案看到。
  這一篇還是基於 Camera 2 之前的版本,也如上面說的原因,在 Android 4.4 的模擬上這個

Camera Demo 是能正常執行的。

在這裡插入圖片描述

理論上它能在的如下幾個 Android 版本上跑(僅限 armeabi ):
libnative_camera_r6.0.0.so
libnative_camera_r5.1.0.so
libnative_camera_r4.4.0.so
libnative_camera_r4.3.0.so

Demo 程式碼說明

為了便於理解,先說一下 Camera 的幀資料的格式,目前我碰到的都是 yuv420sp 格式,所以這個 Demo 只處理了這一種格式的資料。 程式碼中 YuvRender 就是用來處理這種格式的,它是一個 Render 物件。

type Render interface {
	// Init render, userdata is ignore
	Init()
	Draw(pixels interface{})
	Release()

	// SetProperty
	// wW, wH is windows/client width, height
	// iW, iH is image width, height
	// x, y, w, h is draw image to rect
	// op is ROTATION?? | FLIPHOR | FLIPVER
	SetProperty(wW, wH int, iW, iH int, x, y, w, h int, op int)

	// 驗證 pixels 是否符合指定 width、height
	Validate(width, height int, pixels interface{}) bool
}

主要是用來繪製和檢查資料是否有效。
如果出現其它格式的資料,同樣實現一個 Render 。
並在這裡加上對它的支援:

	switch cam.imgFormat {
	case "yuv420sp":
		cam.irender = &render.YuvRender{}
	case "????": // 新加的格式
		cam.irender = ???? 
	default:
		log.Println("not support format:", cam.imgFormat)
		return
	}

如果 Demo 不能正常跑,一方面看 cameraInit 是否成功, 另外就是看一下資料格式。

func cameraInit(id int, usercb func(w, h int, img []byte) bool) *cameraObj {
	cam := &cameraObj{}

	cb := func(buffer []byte) bool {
		// init done
		if atomic.CompareAndSwapInt32(&cam.camStat, NONE, READY) {
			return true
		}

		wh := cam.previewSizes[cam.previewIndex]
		if cam.camStat == RESIZING && cam.irender.Validate(wh[0], wh[1], buffer) {
			cam.ResetProperty()
			atomic.CompareAndSwapInt32(&cam.camStat, RESIZING, READY)
		}

		...
		runtime.Gosched()
		return true
	}

	cam.Camera = camera.Connect(id, cb)
	if cam.Camera == 0 {
		log.Println("Cammera connect fail")
		return nil
	}

	...

	// 第一個為預設解析度
	// 因 camera 還未初始化完,需就非同步執行
	go func() {
		runtime.LockOSThread()
		for cam.camStat == NONE {
			time.Sleep(time.Millisecond * 100)
		}
		cam.setPreviewSize(0)
	}()
	return cam
}

cam.Camera = camera.Connect(id, cb) 這就是“開啟”攝像頭的函式,它的引數有一個回撥,用於傳回幀資料的。這個回撥之所以做了一些判斷邏輯,主要是因為象改解析度這種操作,和回撥傳回的資料之間是非同步的,也就是說呼叫修改解析度之後,可能還會有一兩幀資料是之前解析度的資料,所以要用cam.irender.Validate(...) 確認一下,這個資料是否符合新解析度要求的資料。只有符合最新解析度的的資料第一次收到時才去修改 render 的 property ,否則會花屏或崩潰。還有就是傳入的 buffer 可能是和之前是同一個記憶體塊,這意味著這個 buffer 的資料可能會被新的幀資料填充,因此我們不能直接拿著 buffer 來用,如果需要處理資料要做一次 copy 到自己分配的記憶體裡,考慮到這個資料 buffer 較大,最好也只做一次 copy ,否則執行效率是個大問題。
另外之所以呼叫runtime.Gosched()是為了主動進行一次 goroutine 排程,以避免回撥佔用太多CPU導致其它 goroutine 沒有機會執行(雖然對現在的多核手機來說似乎沒意義了)。

	...
	cam.SetFlashMode(camera.FLASH_MODE_TORCH)
	...
	cam.SetFlashMode(camera.FLASH_MODE_OFF)
	...
	cam.ApplyProperties()

這是開關閃光燈的地方 FLASH_MODE_TORCH 這個模式是開燈,閃光燈常亮。記得加個定時器自動關了,燒了閃光燈本人不負任何責任。呼叫cam.ApplyProperties()之後,所做的各種 property 修改才真正有效。正確用法是依次設定不同的 property ,最後呼叫 cam.ApplyProperties() 使所做設定有效。

Camera2

關於 Camera 就寫這寫了,畢竟它已被 Android 給淘汰了。Camera2 估計要一段時間才能寫個 Demo 出來,該收收心積極點找工作了。
當然這個計劃還有一篇關於 OpenCV 的,將用它結合Camera 實現一個人臉檢測(非識別),爭取這兩天完成。