Android應用調用網路攝影機開發例子

來源:互聯網
上載者:User


這兩天玩Android玩的廢寢忘食,Blog都好幾天沒加東西了,慚愧!記錄一下這兩天最崩潰的一個問題。

好早就裝了開發環境,真正著手還是這兩天,非常的生疏,雖然有SDK文檔,那麼多蚊子一般的字,實在沒心思慢慢研究。這不想調用網路攝影機,原以為很容易就能搞定的,累計花了大概有一天的時間才只能保證不出錯……至於效果嘛,難說啊!

先看API-examples裡有調用 網路攝影機的例子,在模擬器上雖然看不出什麼效果,畢竟還是能執行的,就是一個方塊在黑白相間的背景上移動唄。

就這麼一個Google提供的範例,傳到我的HTC G2上也能一執行就報錯,我對Google的尊敬之情頓時減少了0.0001%啊……(當然有可能是G2不夠標準,但畢竟其他的軟體都是能用的,看來是有不少健壯代碼了啊)。聯機調試看了一下,出錯的這一行(android-7裡的):


parameters.setPreviewSize(w, h);

查一下,網路攝影機不是所有隨便的(w, h)都能夠認識的,所以呢,我們有了下面這樣的增強版:

List<Size> mSupportedPreviewSizes;
mSupportedPreviewSizes = mCamera.getParameters().getSupportedPreviewSizes();
mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes, width, height);
 
private Size getOptimalPreviewSize(List<Size> sizes, int w, int h) {
        final double ASPECT_TOLERANCE = 0.1;
        double targetRatio = (double) w / h;
        if (sizes == null) return null;
 
        Size optimalSize = null;
        double minDiff = Double.MAX_VALUE;
 
        int targetHeight = h;
 
        // Try to find an size match aspect ratio and size
        for (Size size : sizes) {
            double ratio = (double) size.width / size.height;
            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
            if (Math.abs(size.height - targetHeight) < minDiff) {
                optimalSize = size;
                minDiff = Math.abs(size.height - targetHeight);
            }
        }
 
        // Cannot find the one match the aspect ratio, ignore the requirement
        if (optimalSize == null) {
            minDiff = Double.MAX_VALUE;
            for (Size size : sizes) {
                if (Math.abs(size.height - targetHeight) < minDiff) {
                    optimalSize = size;
                    minDiff = Math.abs(size.height - targetHeight);
                }
            }
        }
        return optimalSize;
    }
後來的Sample裡有了這段代碼,看起來強大了不少。然而非常不幸的,首先getSupportedPreviewSizes()這個函數在2.1之後才有,我一開始是打算用1.6開發的……好吧我改,這個先不說,自己的手機已經刷到2.1了,這個函數的傳回值居然是null?!如果確實想老版本上也用的話,怎麼辦??

有鑒於有軟體可以達成,所以肯定是有方法的!得這麼寫:

public class SupportedSizesReflect {
 private static Method Parameters_getSupportedPreviewSizes = null; 
 private static Method Parameters_getSupportedPictureSizes = null;
 
 static {
  initCompatibility();
 };
 
 private static void initCompatibility() {
  try {
   Parameters_getSupportedPreviewSizes = Camera.Parameters.class.getMethod(
     "getSupportedPreviewSizes", new Class[] {});
 
   Parameters_getSupportedPictureSizes = Camera.Parameters.class.getMethod(
     "getSupportedPictureSizes", new Class[] {});
 
  } catch (NoSuchMethodException nsme) {
   nsme.printStackTrace();
   Parameters_getSupportedPreviewSizes = Parameters_getSupportedPictureSizes = null;   
  }  
 }
 
 /**
  * Android 2.1之後有效
  * @param p
  * @return Android1.x返回null
  */
 public static List<Size> getSupportedPreviewSizes(Camera.Parameters p) {
  return getSupportedSizes(p, Parameters_getSupportedPreviewSizes);
 }
 
 public static List<Size> getSupportedPictureSizes(Camera.Parameters p){
  return getSupportedSizes(p, Parameters_getSupportedPictureSizes);
 } 
 
 @SuppressWarnings("unchecked")
 private static List<Size> getSupportedSizes(Camera.Parameters p, Method method){
  try {
   if (method != null) {
    return (List<Size>) method.invoke(p);
   } else {
    return null;
   }
  } catch (InvocationTargetException ite) {
   Throwable cause = ite.getCause();
   if (cause instanceof RuntimeException) {
    throw (RuntimeException) cause;
   } else if (cause instanceof Error) {
    throw (Error) cause;
   } else {
    throw new RuntimeException(ite);
   }
  } catch (IllegalAccessException ie) {
   return null;
  }
 } 
}

啊啊~,リフレクションなんか、大嫌い……然後還要用類似這樣的方法調用~


@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,int height) {
 
 Camera.Parameters params = camera.getParameters();
 
 List<Size> supportedPictureSizes
    = SupportedSizesReflect.getSupportedPictureSizes(params);
 List<Size> supportedPreviewSizes
    = SupportedSizesReflect.getSupportedPreviewSizes(params);
 
 if ( supportedPictureSizes != null &&
  supportedPreviewSizes != null &&
  supportedPictureSizes.size() > 0 &&
  supportedPreviewSizes.size() > 0) {
 
  //2.x
  pictureSize = supportedPictureSizes.get(0);
 
  int maxSize = 1280;
  if(maxSize > 0){
   for(Size size : supportedPictureSizes){       
    if(maxSize >= Math.max(size.width,size.height)){
     pictureSize = size;
     break;
    }      
   }
  }
 
  WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);  
  Display display = windowManager.getDefaultDisplay();  
  DisplayMetrics displayMetrics = new DisplayMetrics();
  display.getMetrics(displayMetrics);
 
  previewSize = getOptimalPreviewSize(
       supportedPreviewSizes,
       display.getWidth(),
       display.getHeight());
 
  params.setPictureSize(pictureSize.width, pictureSize.height);  
  params.setPreviewSize(previewSize.width, previewSize.height);        
 
 }
 this.camera.setParameters(params);
 try {
  this.camera.setPreviewDisplay(holder);
 } catch (IOException e) {
  e.printStackTrace();
 }
 this.camera.startPreview();
}

死機無數次之後總結出來的啊,發現程式寫的一個不好強制結束了,網路攝影機都無法再次啟用了,kill都不行,只能重新啟動手機才好。重啟一次還那麼慢,誰知道有比較適合G2的row?

哦還有一個,預覽畫面90°的,2.X後可以用parameters.set(“rotation”, “90″),之前的話得寫成parameters.set(“orientation”, “portrait”)。但是據說不是所有的機器都可以的…


上面講的是網路攝影機的初始化,如果覺得這麼就萬事OK的話,那就大錯特錯了。接下來的東西讓人感到更加頭痛。

在我的這個應用裡,不需要把拍下來的圖片儲存,只需要把預覽的圖片資料處理一下就好,很自然的我只是用了onPreviewFrame調用,考慮處理傳遞進來的data資料流就是了。

網上很多文章都說,然後用BitmapFactory的decodeByteArray()函數來解析圖片就行了,我試了一下,發現這真是徹頭徹尾的謊言,data位元組流預設是YCbCr_420_SP(雖然可以改,但其他的格式未必相容),decodeByteArray()壓根兒不認!SDK2.2之後,似乎提供了一個YuvImage的類來轉一下(那Google一開始提供這個借口是做什麼的?),難道就要把老機給拋棄了嗎??萬萬不能啊(窮人最理解窮人們了)!

好在這個世界總是不缺少好人和牛人的,有人提供了這麼一段轉換的代碼:

static public void decodeYUV420SP(int[] rgb, byte[] yuv420sp, int width, int height) {
    final int frameSize = width * height;
 
    for (int j = 0, yp = 0; j < height; j++) {
        int uvp = frameSize + (j >> 1) * width, u = 0, v = 0;
        for (int i = 0; i < width; i++, yp++) {
            int y = (0xff & ((int) yuv420sp[yp])) - 16;
            if (y < 0) y = 0;
            if ((i & 1) == 0) {
                v = (0xff & yuv420sp[uvp++]) - 128;
                u = (0xff & yuv420sp[uvp++]) - 128;
            }
 
            int y1192 = 1192 * y;
            int r = (y1192 + 1634 * v);
            int g = (y1192 - 833 * v - 400 * u);
            int b = (y1192 + 2066 * u);
 
            if (r < 0) r = 0; else if (r > 262143) r = 262143;
            if (g < 0) g = 0; else if (g > 262143) g = 262143;
            if (b < 0) b = 0; else if (b > 262143) b = 262143;
 
            rgb[yp] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff);
        }
    }
}

我不是很清楚這裡面的原理,但是它能在我這裡工作,暫時可以了……然後你才可以吧處理完的rgb[]傳給decodeByteArray()。

順便好心的把使用SDK2.2之後的也貼上吧,萬一有用呢……

public void onPreviewFrame(byte[] data, Camera arg1) {
    FileOutputStream outStream = null;
    try {
        YuvImage yuvimage = new YuvImage(data,ImageFormat.NV21,arg1.getParameters().getPreviewSize().width,arg1.getParameters().getPreviewSize().height,null);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        yuvimage.compressToJpeg(new Rect(0,0,arg1.getParameters().getPreviewSize().width,arg1.getParameters().getPreviewSize().height), 80, baos);
 
        outStream = new FileOutputStream(String.format("/sdcard/%d.jpg", System.currentTimeMillis()));       
        outStream.write(baos.toByteArray());
        outStream.close();
 
        Log.d(TAG, "onPreviewFrame - wrote bytes: " + data.length);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
    }
    Preview.this.invalidate();
}


哦,得到的映像旋轉了90°(似乎有的機型設定一下setRotation(90)可以搞定,但還是那句話,不通用啊,況且這個是2.1之後的API)。手動轉一下吧……


Matrix matrix = new Matrix();
matrix.postRotate(90);
// 這裡的rgb就是剛剛轉換處理的東東
Bitmap bmp = Bitmap.createBitmap(rgb, 0, w, w, h, Bitmap.Config.ARGB_4444);
Bitmap nbmp = Bitmap.createBitmap(bmp,
          0, 0, bmp.getWidth(),  bmp.getHeight(), matrix, true);
終於正常了~~~

考慮到需要做識別,自然得先把它轉成灰階映像,經典心理公式Gray = R*0.299 + G*0.587 + B*0.114出場了,但是手機的計算速度不那麼快,這樣的浮點運算還是盡量避免吧~ 於是考慮Gray = (R*299 + G*587 + B*114 + 500) / 1000或者Gray = (R*30 + G*59 + B*11 + 50) / 100。但是除法總是還是不夠快,用移位吧……Gray = (R*19595 + G*38469 + B*7472) >> 16,稍微小一點,用Gray = (R*38 + G*75 + B*15) >> 7也足夠了。

經過一番努力學習,把寫就的代碼興緻勃勃的在手機上跑了一下,雖然不夠快結果出來了,想想也是大負荷運算啊,自我安慰客戶應該可以有這樣的耐心吧。

就在這個時候,我突然想起一件很重要的事情!
我需要的是灰階圖,也就是亮度風量,而最開始的YUV,不就是亮度色度飽和度嗎?!那麼Y分類不就是我需要的灰階值嗎!!我在做什麼,辛辛苦苦轉成RGB,再轉成亮度,吃飽了撐著不是。想到這裡我立刻用頭撞牆九九一百八十一次,一悼念我那白白死去的腦細胞的在天之靈。立刻重寫,刪除大量代碼,快多了,效果也好~~ 鄙視一下兩小時前的自己!

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.