sábado, 4 de junio de 2011

Tiempos - Usando la SpeedCam programáticamente

En este punto, la experiencia se convirtió en algo bastante pesado. La SpeedCam no tiene una API de acceso programática. Tampoco permite sacar fotogramas de forma unitaria, sólo se puede sacar una ráfaga que es almacenada en el buffer interno de la cámara y luego transmitido a la PC.
La cámara posee código hecho en Java del cual se pueden obtener los .jar para emplearlos en un código de prueba.
Consultamos entonces a los fabricantes: nos responden que la compañía cambió de manos. Consultamos a los nuevos responsables y obtenemos como información que tenemos tres caminos:
  • utilizar una API a nivel registro con la cual se accede a la cámara enviando códigos de operación
  • revisar los archivos minivis.jar y minivis.dll
  • comprar otra cámara o adquirir el nuevo software.
Obviamente nos quedamos con la segunda por "simplicidad" (si es que se puede decir que esto sea simple). Entonces, armamos un archivo .Java y tratamos de adivinar cómo se usa la API que contiene el .jar.
Luego de varios días de revisar los resultados del Java Decompiler. Obtenemos una sucesión de pasos que permite obtener las imágenes con resultados bastante malos:
  • Snapshots tomados: 200
  • fps entre Snapshots: 2500
  • Tiempo Total: 7800ms (sólo en la transferencia, ya que la toma de imágenes se debe hacer previa a la este paso)
  • Tiempo Promedio: 39ms (es decir, 25 fps)
Conclusión: muy mala velocidad para una cámara tan potente. Claramente, no está optimizada para utilizarla de forma programática en entornos real-time. Por el contrario, el buffer parece consumir mucho tiempo de la transferencia.
Pero entonces surge una duda ¿Y si los tiempos son lentos por estar utilizando java? Para tratar de falsear esta teoría, recurrimos a código C++ que intente levantar las DLLs directamente y no utilizar minivis.jar. Nuevamente tenemos que decompilar, en este caso decompilamos el archivo minivis.dll y hacemos un matcheo contra lo observado en minivis.jar. Igualmente, debemos notar que ambos archivos se comunican gracias a que se empleó JNI en C++. Lamentablemente esto simplemente complica la tarea de decompilación y reuso de la DLL original.
Con una sola prueba de concepto nos queda claro que ese no es el mejor camino:

Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call.  This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention

Aquí el código Java:


   1:  class DummyConsumer implements CallbackConsumer {
   2:      public void modeChanged(int paramInt){
   3:      }
   4:      public void statusChanged(int paramInt) {
   5:      }
   6:      public void shutterChanged(boolean paramBoolean, short paramShort) {
   7:      }
   8:  }
   9:   
  10:  public class Application {
  11:      public static void main(String[] arguments) {
  12:          int numberOfImages = Integer.parseInt(arguments[0]);
  13:          
  14:          MinivisFactory factory = MinivisFactory.getInstance();
  15:          try {
  16:              factory.discover();
  17:              TypedNetAddress[] addresses = factory.getKnownDevices();
  18:              String mac = addresses.length > 0 ? addresses[0].getEntryName() : "00-50-C2-1D-7E-AB";
  19:              DummyConsumer callbackConsumer = new DummyConsumer();
  20:              Proxy camera = factory.getCamera(mac, callbackConsumer);
  21:              try {
  22:                  camera.connect();
  23:                  try {
  24:                      takeSnapshots(camera, numberOfImages, debug);
  25:                  }            
  26:                  finally {
  27:                      camera.disconnect();
  28:                  }
  29:              }
  30:              finally {
  31:                  camera.release();
  32:              }
  33:          }
  34:          catch(Exception e) {
  35:              e.printStackTrace();
  36:          }
  37:      }
  38:      private static void takeSnapshots(Proxy camera, int numberOfImages, boolean debug) throws IOException, MDriverException, Exception {
  39:          camera.setMode(4); //0 LIVE 1 RECORD 2 TRIGGERED 3 DRAM 4 LOWLIGHT
  40:          camera.trigger();
  41:          
  42:          //missing code here: wait for trigger to finish
  43:          
  44:          MFrameInfo frameInfo = new MFrameInfo();
  45:          for(int i = 0; i < numberOfImages; ++i) {
  46:              byte[] frameBytes = camera.getFrame(-1, -1  , frameInfo);
  47:          }
  48:      }
  49:  }

Aquí el código C++:


   1:   
   2:  #include "stdafx.h"
   3:  #include <iostream>
   4:   
   5:   
   6:  int _tmain(int argc, _TCHAR* argv[])
   7:  {
   8:      HMODULE hMod = LoadLibrary("minivis.dll");
   9:      typedef long (*MinivisFactory_Create)();
  10:      typedef long (*GetCamera)(long, char*, void*);
  11:      MinivisFactory_Create n_MinivisFactory_Create = (MinivisFactory_Create)GetProcAdress("_Java_com_artho_visart_plugins_minivis_internal_jni_MinivisFactory_n_1MinivisFactory_1Create@8");
  12:      GetCamera n_GetCamera = (GetCamera)GetProcAdress("_Java_com_artho_visart_plugins_minivis_internal_jni_MinivisFactory_n_1GetCamera@24");
  13:      
  14:      
  15:      long adapterPtr = n_MinivisFactory_Create();
  16:      void* consumer = NULL;
  17:      long pointer = n_GetCamera(adapterPtr, "00-50-C2-1D-7E-AB", consumer);
  18:      
  19:      std::cout << "Camera pointer:" << pointer << std::endl;
  20:      std::cout << "Press any key to continue..." << std::endl;
  21:      char character;
  22:      std::cin >> character;
  23:      return 0;
  24:  }