ARToolKit 的simpleLite程序分析

什么都不说了,直接上源码!!!!

#include <stdio.h>
#include <stdlib.h>

#include <GL/glut.h>

#include <AR/config.h>
#include <AR/video.h>
#include <AR/param.h>  //显示参数arParamDisp()
#include <AR/ar.h>
#include <AR/gsub_lite.h>


// ============================================================================
//      constants
// ============================================================================
#define VIEW_SCALEFACTOR         0.025// 1.0 ARToolKit unit becomes 0.025 of my OpenGL units.范围因子,一个ARToolKit单位变成0.025个OpenGL单位。
#define VIEW_DISTANCE_MIN        0.1// Objects closer to the camera than this will not be displayed.目标与摄像机距离小于这个不显示
#define VIEW_DISTANCE_MAX10      100.0// Objects further away from the camera than this will not be displayed.大于这个不显示

//---------------------------------------------
//     globle
//---------------------------------------------
static ARParam  gARTCparam;//全局变量,用于储存正确的相机的参数,包括大小修改过参数
static int gPatt_id; //全局变量,存储标识的ID号

// OpenGL 初始化参数设置Preferences.
static int prefWindowed = TRUE;
static int prefWidth = 640;// Fullscreen mode width.全屏模式长宽
static int prefHeight = 480;// Fullscreen mode height.
static int prefDepth = 32;// Fullscreen mode bit depth.全屏模式字节深度
static int prefRefresh = 0;// Fullscreen mode refresh rate. Set to 0 to use default rate.刷新频率,设为0则使用默认频率。

static ARGL_CONTEXT_SETTINGS_REF gArglSettings = NULL;//保存AR的状态变量,可以显示,在debugReportMode函数中进行显示。

// Image acquisition.
static ARUint8   *gARTImage = NULL;      //获取的图像

// Marker detection.
static int     gARTThreshhold = 100;   //标识检测时的二值化阈值
static long    gCallCountMarkerDetect = 0;//
//
// Transformation matrix retrieval.转换矩阵检索
static double    gPatt_width     = 80.0;
static double    gPatt_centre[2] = {0.0, 0.0}; 
static double    gPatt_trans[3][4];
static int       gPatt_found = FALSE;//判断是否找到标识

//计算四方体是否旋转,以及旋转角度
static int   gDrawRotate = FALSE;
static float gDrawRotateAngle = 0;// For use in drawing.

//----------------------------------------------------------------
//      Function
//----------------------------------------------------------------
static int setupCamera(const char *cparam_name,char *vconf,ARParam *cparam);
static int setupMarker(const char *patt_name,int *patt_id);
static void init_opengl(char **argv);
static void debugReportMode(const ARGL_CONTEXT_SETTINGS_REF arglContextSettings);
static void Display(void);
static void Reshape(int w,int h);
static void Visibility(int visible);
static void Idle(void);
static void Keyboard(unsigned char key,int x,int y);
static void DrawCube(void);
static void DrawCubeUpdate(float timeDelta);
static void Quit(void);//退出程序,清除内存


int main(int argc,char** argv)
  {
  const char *cparam_name = "/home/zhu/downloads/ARToolKit/examples/simple/Data/camera_para.dat";

#ifdef _WIN32 
  char         *vconf = "Data\\WDM_camera_flipV.xml";
#else
  char         *vconf = "v4l2src device=/dev/video0 use-fixed-fps=false ! ffmpegcolorspace ! video/x-raw-rgb,bpp=24 ! identity name=artoolkit sync=true ! fakesink";
#endif
  const char *patt_name = "/home/zhu/downloads/ARToolKit/examples/simple/Data/patt.hiro";

  //--------------------------
  //初始化glut库,所有OpenGL函数首先要进行这一步
  //--------------------------
  glutInit(&argc,argv);

  //--------------------------
  //打开相机,导入相机参数,更改相机长宽大小,初始化相机参数,捕获图像。
  //--------------------------
   if(!setupCamera(cparam_name,vconf,&gARTCparam)){
      fprintf(stderr,"main():Unable to set up AR camera.\n");
      exit(-1);
  }

  //--------------------------
  //打开相机线程,与arVideoCapStop对应,合理运用可减少CPU负载
  //--------------------------
  if(arVideoCapStart() != 0){
      fprintf(stderr,"setupCamera(): Unable to begin data capture.\n");
      exit(-1);
  }

  //--------------------------------------------------
  //设置OpenGL环境,新建glut窗口,初始化AR环境。
  //--------------------------------------------------
  init_opengl(argv);    

  arUtilTimerReset();//在刚开始的时候即帧数为0的时候,复位定时器arUtilTimerReset,以便使用arUtilTimer得到从复位开始消耗的时间。

  //--------------------------
  //导入标识图,一个或多个
  //--------------------------
  if(!setupMarker(patt_name,&gPatt_id)){
      fprintf(stderr,"main():Unable to set up AR marker.\n");
      Quit();//到这里为止,初始化完成,若这里再出错,则耀清除内存了。
  }

  //注册GLUT事件处理回调
  //注意:Idle() 在Visibility中注册。
  //glutReshapeFunc与glutIdelFunc这些函数都不去直接处理显示,而是设置好相应的参数,让glutdisplayfunc来执行绘制。
  glutDisplayFunc(Display);//用于注册一个绘图函数, 这样操作系统在必要时刻就会对窗体进行重新绘制操作。类似于windows程序设计中处理WM_PAINT消息。具体来说呢,就是设置一个函数当需要进行画图时就调用这个函数。
  glutReshapeFunc(Reshape);//当你改变窗口大小时的回调(CallBack)函数 需要你自己编写的ReShape Function来注册
  glutVisibilityFunc(Visibility);//设置当前窗口的可视回调函数,这个函数设置当前窗口的可视回调函数,当窗口的可视性改变时,该窗口的可视回调函数被调用.只要窗口中的任何一个像素是可见的,或者他的任意一个子窗口中任意一个像素是可见的,GLUT则认为窗口是可见的.
  glutKeyboardFunc(Keyboard);//注册当前窗口的键盘回调函数

  glutMainLoop();//进入GLUT事件处理循环,让所有的与“事件”有关的函数调用无限循环。在一个GLUT程序中,这个例程被调用一次 。一旦被调用,这个程序将永远不会返回 。它将调用必要的任何已注册的回调。

  return (0);

}


//打开相机,导入相机参数,更改相机长宽大小,初始化相机参数,捕获图像
static int setupCamera(const char *cparam_name, char *vconf, ARParam *cparam)
{
  int xsize,ysize;//储存图像大小的值
  ARParam wparam;//储存相机参数

//--------------------------------------------
//导入相机参数,或者得到相机校正参数
//--------------------------------------------
  //打开视频路径
  if(arVideoOpen(vconf) < 0){
      fprintf(stderr,"setupCamera(): Unable to open connection to camera.\n");
      return (FALSE);
  }

  //得到当前视频图像的大小
  if(arVideoInqSize(&xsize,&ysize) < 0) return (FALSE);
  fprintf(stdout,"Camera image size (x,y) = (%d,%d)\n",xsize,ysize);//debug打印出图像大小值

  //导入相机参数,重新定义窗口大小
  if(arParamLoad(cparam_name,1,&wparam) < 0){
      fprintf(stderr,"setupCamera(): Error loading parameter file %s for camera.\n",cparam_name);//两种情况:文件未找到,文件结构不对
      return (FALSE);
  }

  arParamChangeSize(&wparam,xsize,ysize,cparam);
  fprintf(stdout,"*** Camera Parameter ***\n");
  arParamDisp(cparam);//显示参数值

  arInitCparam(cparam);//初始化相机参数

  return (TRUE);
}

//导入标识图像矩阵,一个或多个
static int setupMarker(const char *patt_name, int *patt_id)
{
   //-----------------------------------
   //导入标识图,一个或多个
   //-----------------------------------
   if((*patt_id = arLoadPatt(patt_name)) < 0){
      fprintf(stderr,"setupMarker():pattern load error!!\n");
      return (FALSE);
  }
   return (TRUE);
}

//初始化OpenGL显示环境
static void init_opengl(char** argv)
{
  char glutGamemode[32];
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);//设置初始显示模式:指定 RGBA 颜色模式的窗口,指定双缓存窗口,窗口使用深度缓存
  if(!prefWindowed){
      if(prefRefresh) 
        sprintf(glutGamemode,"%ix%i:%i@%i",prefWidth,prefHeight,prefDepth,prefRefresh);
      else 
        sprintf(glutGamemode,"%ix%i:%i",prefWidth,prefHeight,prefDepth);//GLUT的游戏模式是为开启高性能全屏渲染而设计的.告诉GLUT全屏模式的设置,设置包括屏幕分辨率,像素深度和刷新频率,640x512:32 @默认刷新频率.
      if(glutGameModeGet(GLUT_GAME_MODE_POSSIBLE))//检查给定的游戏模式是否有效,by zhuzhu
        glutEnterGameMode();//进入或至少可以尝试进入游戏模式.函数会准确的设置屏幕到指定配置,只要是可行的.
  }
   else{
      glutInitWindowSize(prefWidth,prefHeight);//若不进入游戏模式,就直接指定窗口大小,新建一个窗口
      glutCreateWindow(argv[0]);
  }

  //为当前OpenGL环境建立argl库
  //arglSetupForCurrentContext()是针对当前的OpenGL环境初始化Gsub_Lite库;返回一个ARGL_CONTEXT_SETTINGS_REF参数,这个参数就代表OpenGL在这个机器上的状态,包括纹理和显示列表,可传递,Gsub_Lite要跟踪这个参数,特别是在显示多个窗口时;使用结束后,可用arglCleanup()清除这个参数。
  if((gArglSettings = arglSetupForCurrentContext()) == NULL){
      fprintf(stderr,"main(): arglSetupForCurrentContext() returned error.\n");
      exit(-1);
  }

  debugReportMode(gArglSettings);//显示当前AR的系统变量状态。
  glEnable(GL_DEPTH_TEST);//使能深度测试

}

// Report state of ARToolKit global variables arFittingMode,
// arImageProcMode, arglDrawMode, arTemplateMatchingMode, arMatchingPCAMode.
// 报告这些变量的状态。显示这个变量的值。
static void debugReportMode(const ARGL_CONTEXT_SETTINGS_REF arglContextSettings)
 {
    // 1:arFittingMode:ARToolKit的合适的显示模式:对相机失真的校正模式,可以用纹理映射使能校正,可能的值:AR_FITTING_TO_INPUT:输入图像;AR_FITTING_TO_IDEAL:默认的补偿图像。
    if(arFittingMode == AR_FITTING_TO_INPUT){
        fprintf(stderr,"FittingMode(Z):INPUT IMAGE\n");
    } else{
        fprintf(stderr,"FittingMode(Z):COMPENSATED IMAGE\n");
    }

    // 2:定义标识图像分辨率的大小:如果图像整体被分析,这个变量的值:AR_IMAGE_PROC_IN_FULL:使用全分辨率图像;AR_IMAGE_PROC_IN_HALF:使用半分辨率图像,默认模式。
    if (arImageProcMode == AR_IMAGE_PROC_IN_FULL) {
        fprintf(stderr, "ProcMode (X)   : FULL IMAGE\n");
    } else {
        fprintf(stderr, "ProcMode (X)   : HALF IMAGE\n");
    } 

    // 3:定义绘制后台显示的配置模式:AR_DRAW_BY_GL_DRAW_PIXELS:使用GL_DRAW_PIXELS函数;AR_DRAW_BY_TEXTURE_MAPPING:使用四方格进行纹理映射模式;
    if (arglDrawModeGet(arglContextSettings) == AR_DRAW_BY_GL_DRAW_PIXELS) {
        fprintf(stderr, "DrawMode (C)   : GL_DRAW_PIXELS\n");
    } else if (arglTexmapModeGet(arglContextSettings) == AR_DRAW_TEXTURE_FULL_IMAGE) {
        fprintf(stderr, "DrawMode (C)   : TEXTURE MAPPING (FULL RESOLUTION)\n");
    } else {
        fprintf(stderr, "DrawMode (C)   : TEXTURE MAPPING (HALF RESOLUTION)\n");
    }             

    // 4:这个可能的值:AR_TEMPLATE_MATCHING_COLOR:颜色模板;AR_TEMPLATE_MATCHING_BW:BW模板?        
    if (arTemplateMatchingMode == AR_TEMPLATE_MATCHING_COLOR) {
        fprintf(stderr, "TemplateMatchingMode (M)   : Color Template\n");
    } else {                                          
        fprintf(stderr, "TemplateMatchingMode (M)   : BW Template\n");
    }

    // 5:这个可能的值:-AR_MATCHING_WITHOUT_PCA:没有PCA;-AR_MATCHING_WITH_PCA:有PCA,默认模式。               
    if (arMatchingPCAMode == AR_MATCHING_WITHOUT_PCA) {
        fprintf(stderr, "MatchingPCAMode (P)   : Without PCA\n");
    } else {
        fprintf(stderr, "MatchingPCAMode (P)   : With PCA\n");
    }

}

static void Display(void)
  {
  GLdouble p[16];
  GLdouble m[16];
  //第一步:清除屏幕,显示最新的后台缓冲帧
  glDrawBuffer(GL_BACK);//指定哪个颜色缓存将要被绘制;当像素颜色被写到帧缓存时,他们被写进由glDrawBuffer指定的颜色缓存区:GL_NONE没有缓存区要被写入;GL_FRONT_LEFT只有左前缓存区被写入;类似的都是这样。这里是后缓存区被绘制。
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);//为绘制新帧清除之前帧的颜色缓存和深度缓存。
  arglDispImage(gARTImage,&gARTCparam,1.0,gArglSettings);//zoom = 1.0.在后台缓存中显示相机图像作为背景,这个必须在渲染3D目标前调用。根据你的argDrawMode,argTexnapMode和内部图像的格式,调用的OpenGL函数是不同的。
  arVideoCapNext();//开启下一帧,这个函数应该每一帧调用一次,它可以允许视频驱动完成常规任务,给视频捕获器信号表示最近的视频帧已经处理完毕,视频驱动可以重新使用被原来帧占用的内存。最好的调用地方是在arglDispImage和argDispImage之后。在一些操作系统中相当于一个no-op,空指令。返回:成功为0,-1是视频驱动遇到错误。
  gARTImage = NULL;//当调用了arVideoCapNext(),此时图像数据不再有效。等待接收下一帧图像。这里以上是清除屏幕,绘制后台最新的图像帧!!

  //第二步:根据相机参数设置投影矩阵
  //投影变换:创建一个透视投影矩阵,传递给OpenGL设置模型视景矩阵。
  arglCameraFrustumRH(&gARTCparam,VIEW_DISTANCE_MIN,VIEW_DISTANCE_MAX10,p);//把相机参数转换成OpenGL投影矩阵16的数组,然后直接导入,生成的三维物体就会匹配真实的相机视场。
  glMatrixMode(GL_PROJECTION);
  glLoadMatrixd(p);
  glMatrixMode(GL_MODELVIEW);

  //视景变换:
  glLoadIdentity();//重置当前指定的矩阵为单位矩阵

  //目标随摄像机移动的光照和几何一致性应该在这里,必须在视景变换之前指明。

  //第三步:寻找标识,找到了之后就定位相机观察到的标识,在标识上放置系统原点
  if(gPatt_found){//这里是找到标识物了!!!!!!!
      //Calculate the camera position relative to the marker.
      //计算相机相对于标识的位置。
      //Replace VIEW_SCALEFACTOR with 1.0 to make one drawing unit equal to 1.0 ARToolKit units(mm为单位)
      arglCameraViewRH(gPatt_trans,m,VIEW_SCALEFACTOR);//创建一个视景矩阵,传递给OpenGL设置虚拟相机的视景变换。转换标识转换矩阵成OpenGL视景矩阵,这16个值就是真实相机的位置和方向,使用他们设置虚拟相机的位置,使三维目标准确的放置在物理标识上。
      glLoadMatrixd(m);//这里是设置虚拟相机的位置。

      //第四步:在标识上画出虚拟物体。每次执行这个绘制函数时,OpenGL系统的原点就在物理标识的中间,注意不要绘制-z部分的东西。
      //所有的三维目标相对于标识的光照和几何一致性在这里
      DrawCube();//绘制一个带颜色的正方体
  }
  //All 2D overlays go here.
  //none

  glutSwapBuffers();//设置双缓存显示。

}

//手动改变显示窗口大小时调用的程序,当窗口大小变化时,为了防止物体变形,这时要重设投影转换矩阵,设置视口转换矩阵,以及视图转换矩阵。
static void Reshape(int w,int h)
 {
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glViewport(0,0,(GLsizei) w,(GLsizei) h);//获得窗口的大小,负责把视景体截取的图像按照怎样的高和宽显示到屏幕上

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

//当窗口的可视化改变时,调用该函数,包括第一次可视化也要调用。每次循环至少调用一次Idle函数。
static void Visibility(int visible)
 {
   if(visible == GLUT_VISIBLE){
      glutIdleFunc(Idle);
  } else{
      glutIdleFunc(NULL);
  }
}


static void Idle(void)
 {
  static int ms_prev;
  int ms;
  float s_elapsed;
  ARUint8 *image;

  ARMarkerInfo  *marker_info;
  int marker_num;
  int j,k;

  //计算上次运行Idle函数到现在用了多长时间
  ms = glutGet(GLUT_ELAPSED_TIME);
  s_elapsed = (float)(ms - ms_prev) * 0.001;
  if(s_elapsed < 0.01f) return; //大于100HZ时不更新
  ms_prev = ms;

  //Update drawing.
  DrawCubeUpdate(s_elapsed);//本帧比上一帧四方体旋转角度;

  //捕获一帧图像
   if((image = arVideoGetImage()) != NULL){
      gARTImage = image;  //缓存这一帧图像

      gCallCountMarkerDetect++;//增加ARToolKit 帧频计数器

      //在图像帧中检测标识
       if(arDetectMarker(gARTImage,gARTThreshhold,&marker_info,&marker_num) < 0){
          exit(-1);
      }

      //从检测出的形似标识物的数组中按确信度匹配标识
      k = -1;
       for(j = 0;j < marker_num;j++){
           if(marker_info[j].id == gPatt_id){
              if(k == -1) k = j;//检测到的第一个标识
              else if(marker_info[j].cf > marker_info[k].cf) k = j;//找到最高可信度的那个
          }
      }

       if(k != -1){
          //得到标识物和真实相机的变换矩阵
          arGetTransMat(&(marker_info[k]),gPatt_centre,gPatt_width,gPatt_trans);
          gPatt_found = TRUE;
      } else{
          gPatt_found = FALSE;
      }

      glutPostRedisplay();//告诉glut,要显示的内容已经改变了。标记当前窗口需要重新绘制。在下一次循环中,display函数将被回调,重新绘制窗口。节省CPU资源,防止display函数被频繁调用,即使没有更新。

  }
}


static void Keyboard(unsigned char key, int x, int y)
 {
   int mode;
   switch (key) {
    case 0x1B:           // Quit.
    case 'Q':
    case 'q':
              Quit();
              break;
    case ' ':
              gDrawRotate = !gDrawRotate;//旋转立方体
              break;
    case 'C':
    case 'c'://绘制模式从DRAW_PIXELS到AR_DRAW_BY_TEXTURE_MAPPING和FULL_IMAGE 到HALF_IMAGE 再到PIXELS;绘制模式的变化,纹理映射的分辨率变化
              mode = arglDrawModeGet(gArglSettings);
               if (mode == AR_DRAW_BY_GL_DRAW_PIXELS) {                                                                              
                  arglDrawModeSet(gArglSettings, AR_DRAW_BY_TEXTURE_MAPPING);                    
                  arglTexmapModeSet(gArglSettings, AR_DRAW_TEXTURE_FULL_IMAGE);                                                            
              } else {
                  mode = arglTexmapModeGet(gArglSettings);
                  if (mode == AR_DRAW_TEXTURE_FULL_IMAGE)
                    arglTexmapModeSet(gArglSettings, AR_DRAW_TEXTURE_HALF_IMAGE);                    
                  else 
                    arglDrawModeSet(gArglSettings, AR_DRAW_BY_GL_DRAW_PIXELS);   
              }
              fprintf(stderr, "*** Camera - %f (frame/sec)\n", (double)gCallCountMarkerDetect/arUtilTimer());
              gCallCountMarkerDetect = 0;                                                                                                   
              arUtilTimerReset(); 
              debugReportMode(gArglSettings);   
              break;
    case 'D':
    case 'd':
              arDebug = !arDebug;//这是ARToolKit内部变量,激活debug模式,0不debug,1debug,默认0;                             
              break;
    case '?':
    case '/':
              printf("Keys:\n");
              printf(" q or [esc]    Quit demo.\n");
              printf("space          rorate the cube\n");      
              printf(" c             Change arglDrawMode and arglTexmapMode.\n");
              printf(" d             Activate / deactivate debug mode.\n");
              printf(" ? or /        Show this help.\n");
              printf("\nAdditionally, the ARVideo library supplied the following help text:\n");
              arVideoDispOption();//显示视频选项
              break;  
    default:
              break;

    }
}


// Something to look at, draw a rotating colour cube.
static void DrawCube(void)
  {
    // Colour cube data.
    static GLuint polyList = 0;
    float fSize = 0.5f;
    long f, i;  
    const GLfloat cube_vertices [8][3] = {
    {1.0, 1.0, 1.0}, {1.0, -1.0, 1.0}, {-1.0, -1.0, 1.0}, {-1.0, 1.0, 1.0},
    {1.0, 1.0, -1.0}, {1.0, -1.0, -1.0}, {-1.0, -1.0, -1.0}, {-1.0, 1.0, -1.0} };
    const GLfloat cube_vertex_colors [8][3] = {
    {1.0, 1.0, 1.0}, {1.0, 1.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 1.0, 1.0},
    {1.0, 0.0, 1.0}, {1.0, 0.0, 0.0}, {0.0, 0.0, 0.0}, {0.0, 0.0, 1.0} };
    GLint cube_num_faces = 6;
    const short cube_faces [6][4] = {
    {3, 2, 1, 0}, {2, 3, 7, 6}, {0, 1, 5, 4}, {3, 0, 4, 7}, {1, 2, 6, 5}, {4, 5, 6, 7} };

        if (!polyList) {
        polyList = glGenLists (1);//glGenLists()唯一的标识一个显示列表
        glNewList(polyList, GL_COMPILE);//用于对显示列表进行定界。第一个参数是一个整形索引值,由glGenLists()指定,第二个参数指只是编译。
        glBegin (GL_QUADS);//画四方体
        for (f = 0; f < cube_num_faces; f++)
                for (i = 0; i < 4; i++) {
                glColor3f (cube_vertex_colors[cube_faces[f][i]][0], cube_vertex_colors[cube_faces[f][i]][1], cube_vertex_colors[cube_faces[f][i]][2]);
                glVertex3f(cube_vertices[cube_faces[f][i]][0] * fSize, cube_vertices[cube_faces[f][i]][1] * fSize, cube_vertices[cube_faces[f][i]][2] * fSize);
            }
        glEnd ();
        glColor3f (0.0, 0.0, 0.0);
            for (f = 0; f < cube_num_faces; f++) {
            glBegin (GL_LINE_LOOP);
            for (i = 0; i < 4; i++)
                glVertex3f(cube_vertices[cube_faces[f][i]][0] * fSize, cube_vertices[cube_faces[f][i]][1] * fSize, cube_vertices[cube_faces[f][i]][2] * fSize);
            glEnd ();
        }
        glEndList ();
    }

    glPushMatrix(); // Save world coordinate system.//保存当前矩阵
    glTranslatef(0.0, 0.0, 0.5); // Place base of cube on marker surface.往z轴移动0.5
    glRotatef(gDrawRotateAngle, 0.0, 0.0, 1.0); // Rotate about z axis.根据参数进行选准
    glDisable(GL_LIGHTING); // Just use colours.//不使能光照。
    glCallList(polyList);   // Draw the cube.
    glPopMatrix();  // Restore world coordinate system.//弹出栈顶的矩阵

}

static void DrawCubeUpdate(float timeDelta)//每秒转动45度,按帧频计算每帧转动角度。
{
    if (gDrawRotate) {
        gDrawRotateAngle += timeDelta * 45.0f; // Rotate cube at 45 degrees per second.
        if (gDrawRotateAngle > 360.0f) gDrawRotateAngle -= 360.0f;
    }
}
//清除使用过的内存,释放资源。
static void Quit(void)
 {
  arglCleanup(gArglSettings);
  arVideoCapStop();
  arVideoClose();
  exit(0);
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值