1. 程式人生 > >MJPG-streamer原始碼分析-輸入部分

MJPG-streamer原始碼分析-輸入部分

MJPG-streamer僅可以建立一個輸入通道,對一個相機進行採集(如需多個,需另行修改)。在進行相機採集開始前,需要對其進行初始化,包括採集過程的全域性變數定義和記憶體開闢。執行過程則是將相機採集得到的資料,根據從相機對映到記憶體的地址上,按照不同的格式要求(YUV或MJPEG)進行操作,包括格式轉換和資料複製,最後影象資料被複制到指定的全域性資料緩衝區。

====================================輸入通道初始化過程=======================================

input_init()函式的初始化過程概括如下:

1、【定義和初始化變數】

包括width=640, height=480, fps=5, format=V4L2_PIX_FMT_MJPEG,char *argv[MAX_ARGUMENTS]={NULL}, *dev = "/dev/video0",

2、【初始化互斥鎖】

pthread_mutex_init(&controls_mutex, NULL)

3、【解析引數】

將傳入的單串引數param->parameter_string轉換為字串陣列,存放在argv陣列中,呼叫c = getopt_long_only(argc, argv, "", long_options, &option_index);通過option_index和switch將對應引數存放在目標物件上,包括dev,width,height,fps,format,gquality,dynctrls,led或列印help,具體可看原始碼。這個過程跟主函式中對引數的解析過程類似,只是在主函式中,主要是把輸入引數分別分配到全域性變數的輸入引數和輸出引數上,而在初始化函式中,引數解析也是類似的,只是把引數解析成對應物件,如width, height, fps, format等引數,

4、【設定引數input_uvc.c全域性引數】

其中,在input_uvc.c檔案中也定義了一個全域性變數global的物件指標,並將其指向了主函式檔案中的全域性物件,這樣,在input_uvc.c檔案中的採集執行緒處理函式void *cam_thread( void *arg )可以直接通過該指標直接呼叫。
pglobal = param->global;
videoIn = malloc(sizeof(struct vdIn));
memset(videoIn, 0, sizeof(struct vdIn));

5、【開啟視訊裝置並準備資料結構】

init_videoIn(videoIn, dev, width, height, fps, format, 1)

定義videoIn的結構體如下,其中videoIn用於存放輸入的所有引數,包括影象大小、幀率、格式、緩衝區、相機引數等,

struct vdIn {
    int fd;
    char *videodevice;
    char *status;
    char *pictName;
    struct v4l2_capability cap;
    struct v4l2_format fmt;
    struct v4l2_buffer buf;
    struct v4l2_requestbuffers rb;
    void *mem[NB_BUFFER];
    unsigned char *tmpbuffer;
    unsigned char *framebuffer;
    int isstreaming;
    int grabmethod;
    int width;
    int height;
    int fps;
    int formatIn;
    int formatOut;
    int framesizeIn;
    int signalquit;
    int toggleAvi;
    int getPict;
    int rawFrameCapture;
    /* raw frame capture */
    unsigned int fileCounter;
    /* raw frame stream capture */
    unsigned int rfsFramesWritten;
    unsigned int rfsBytesWritten;
    /* raw stream capture */
    FILE *captureFile;
    unsigned int framesWritten;
    unsigned int bytesWritten;
    int framecount;
    int recordstart;
    int recordtime;
};

裝置開啟函式init_videoIn():主要是對vdIn定義的物件(即在input_init()函式傳入的videoIn引數,該引數在初始化檔案中是全域性狀態)進行賦值初始化,並呼叫init_v4l2()函式初始化裝置,通過返回的相機引數,由於相機返回的影象格式可能是YUYV,也可能是MJPEG格式,兩種格式在處理上有所不同,因此不同格式下資料需要複製到vd物件上不同的緩衝區,對應後續不同影象複製處理函式,因此需要根據格式不同,對對應的緩衝區的記憶體進行分配(也就是說,只有一種格式緩衝區會被分配記憶體)。
int init_videoIn(struct vdIn *vd, char *device, int width, int height, int fps, int format, int grabmethod)
{
  if (vd == NULL || device == NULL)
    return -1;
  if (width == 0 || height == 0)
    return -1;
  if (grabmethod < 0 || grabmethod > 1)
    grabmethod = 1;		//mmap by default;
  vd->videodevice = NULL;
  vd->status = NULL;
  vd->pictName = NULL;
  vd->videodevice = (char *) calloc (1, 16 * sizeof (char));
  vd->status = (char *) calloc (1, 100 * sizeof (char));
  vd->pictName = (char *) calloc (1, 80 * sizeof (char));
  //函式說明:最多從源串中拷貝n-1個字元到目標串中,然後再在後面加一個0。所以如果目標串的大小為n 的話,將不會溢位。
  //函式返回值:若成功則返回欲寫入的字串長度,若出錯則返回負值。
  snprintf (vd->videodevice, 12, "%s", device);
  vd->toggleAvi = 0;
  vd->getPict = 0;
  vd->signalquit = 1;
  vd->width = width;
  vd->height = height;
  vd->fps = fps;
  vd->formatIn = format;
  vd->grabmethod = grabmethod;
  //初始化裝置
  if (init_v4l2 (vd) < 0) {
    fprintf (stderr, " Init v4L2 failed !! exit fatal \n");
    goto error;;
  }
  /* alloc a temp buffer to reconstruct the pict */
  vd->framesizeIn = (vd->width * vd->height << 1);
  switch (vd->formatIn) {
  case V4L2_PIX_FMT_MJPEG:
    vd->tmpbuffer = (unsigned char *) calloc(1, (size_t) vd->framesizeIn);
    if (!vd->tmpbuffer)
      goto error;
    vd->framebuffer =
        (unsigned char *) calloc(1, (size_t) vd->width * (vd->height + 8) * 2);
    break;
  case V4L2_PIX_FMT_YUYV:
    vd->framebuffer =
        (unsigned char *) calloc(1, (size_t) vd->framesizeIn);
    break;
  default:
    fprintf(stderr, " should never arrive exit fatal !!\n");
    goto error;
    break;
  }
  if (!vd->framebuffer)
    goto error;
  return 0;
error:
  free(vd->videodevice);
  free(vd->status);
  free(vd->pictName);
  close(vd->fd);
  return -1;
}

呼叫init_v4l2()函式,主要是對相機引數的初始化。尤其重要部分是,把相機採集後得到資料的緩衝區對映到記憶體上
static int init_v4l2(struct vdIn *vd)
{
  int i;
  int ret = 0;
  //開啟裝置,vd->videodevice在前面經過引數解析後得到
  if ((vd->fd = open(vd->videodevice, O_RDWR)) == -1) {
    perror("ERROR opening V4L interface");
    return -1;
  }

  memset(&vd->cap, 0, sizeof(struct v4l2_capability));
  //裝置驅動程式中對裝置的I/O通道進行管理的函式,成功返回0,失敗返回-1;
  ret = ioctl(vd->fd, VIDIOC_QUERYCAP, &vd->cap);
  if (ret < 0) {
    fprintf(stderr, "Error opening device %s: unable to query device.\n", vd->videodevice);
    goto fatal;
  }

  if ((vd->cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0) {
    fprintf(stderr, "Error opening device %s: video capture not supported.\n",
           vd->videodevice);
    goto fatal;;
  }

  if (vd->grabmethod) {
    if (!(vd->cap.capabilities & V4L2_CAP_STREAMING)) {
      fprintf(stderr, "%s does not support streaming i/o\n", vd->videodevice);
      goto fatal;
    }
  } else {
    if (!(vd->cap.capabilities & V4L2_CAP_READWRITE)) {
      fprintf(stderr, "%s does not support read i/o\n", vd->videodevice);
      goto fatal;
    }
  }

  //裝置輸入引數
  memset(&vd->fmt, 0, sizeof(struct v4l2_format));
  vd->fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  vd->fmt.fmt.pix.width = vd->width;
  vd->fmt.fmt.pix.height = vd->height;
  vd->fmt.fmt.pix.pixelformat = vd->formatIn;
  vd->fmt.fmt.pix.field = V4L2_FIELD_ANY;
  ret = ioctl(vd->fd, VIDIOC_S_FMT, &vd->fmt);
  if (ret < 0) {
    perror("Unable to set format");
    goto fatal;
  }

  if ((vd->fmt.fmt.pix.width != vd->width) ||
      (vd->fmt.fmt.pix.height != vd->height)) {
    fprintf(stderr, " format asked unavailable get width %d height %d \n", vd->fmt.fmt.pix.width, vd->fmt.fmt.pix.height);
    vd->width = vd->fmt.fmt.pix.width;
    vd->height = vd->fmt.fmt.pix.height;
    /*
     * look the format is not part of the deal ???
     */
    // vd->formatIn = vd->fmt.fmt.pix.pixelformat;
  }

   //設定幀率
  struct v4l2_streamparm *setfps;
  setfps = (struct v4l2_streamparm *) calloc(1, sizeof(struct v4l2_streamparm));
  memset(setfps, 0, sizeof(struct v4l2_streamparm));
  setfps->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  setfps->parm.capture.timeperframe.numerator = 1;
  setfps->parm.capture.timeperframe.denominator = vd->fps;
  ret = ioctl(vd->fd, VIDIOC_S_PARM, setfps);


   //設定緩衝區引數
  memset(&vd->rb, 0, sizeof(struct v4l2_requestbuffers));
  vd->rb.count = NB_BUFFER;
  vd->rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  vd->rb.memory = V4L2_MEMORY_MMAP;

  ret = ioctl(vd->fd, VIDIOC_REQBUFS, &vd->rb);
  if (ret < 0) {
    perror("Unable to allocate buffers");
    goto fatal;
  }

  /*
   * 重要部分,MMAP緩衝區對映,把攝像頭採集的資料存放地址對映到記憶體上,並將對應的記憶體地址記錄在vd->mem[i]上,
   * 這樣子在後續的複製採集資料時可以直接操作該記憶體地址
   */
  for (i = 0; i < NB_BUFFER; i++) {
    memset(&vd->buf, 0, sizeof(struct v4l2_buffer));
    vd->buf.index = i;
    vd->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    vd->buf.memory = V4L2_MEMORY_MMAP;
    ret = ioctl(vd->fd, VIDIOC_QUERYBUF, &vd->buf);
    if (ret < 0) {
      perror("Unable to query buffer");
      goto fatal;
    }

    if (debug)
      fprintf(stderr, "length: %u offset: %u\n", vd->buf.length, vd->buf.m.offset);

    vd->mem[i] = mmap(0 /* start anywhere */ ,
                      vd->buf.length, PROT_READ, MAP_SHARED, vd->fd,
                      vd->buf.m.offset);
    if (vd->mem[i] == MAP_FAILED) {
      perror("Unable to map buffer");
      goto fatal;
    }
    if (debug)
      fprintf(stderr, "Buffer mapped at address %p.\n", vd->mem[i]);
  }

  /*
   * 對緩衝區的佇列,將空閒的記憶體加入可捕獲視訊的佇列中,相機會將視訊資料存放在該記憶體上
   */
  for (i = 0; i < NB_BUFFER; ++i) {
    memset(&vd->buf, 0, sizeof(struct v4l2_buffer));
    vd->buf.index = i;
    vd->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    vd->buf.memory = V4L2_MEMORY_MMAP;
    ret = ioctl(vd->fd, VIDIOC_QBUF, &vd->buf);
    if (ret < 0) {
      perror("Unable to queue buffer");
      goto fatal;;
    }
  }
  return 0;
fatal:
  return -1;

}

上述函式設計到通過ioctl命令與驅動程式進行互動,其中常見ioctl命令有以下幾種,對應的功能在後面註釋,關於引數更多的解釋,可以參考文章:ioctl命令
VIDIOC_QUERYCAP     /* 獲取裝置支援的操作 */  
VIDIOC_G_FMT        /* 獲取設定支援的視訊格式 */  
VIDIOC_S_FMT        /* 設定捕獲視訊的格式 */  
VIDIOC_REQBUFS      /* 向驅動提出申請記憶體的請求 */  
VIDIOC_QUERYBUF     /* 向驅動查詢申請到的記憶體 */  
VIDIOC_QBUF         /* 將空閒的記憶體加入可捕獲視訊的佇列 */  
VIDIOC_DQBUF        /* 將已經捕獲好視訊的記憶體拉出已捕獲視訊的佇列 */  
VIDIOC_STREAMON     /* 開啟視訊流 */  
VIDIOC_STREAMOFF    /* 關閉視訊流 */  
VIDIOC_QUERYCTRL    /* 查詢驅動是否支援該命令 */  
VIDIOC_G_CTRL       /* 獲取當前命令值 */  
VIDIOC_S_CTRL       /* 設定新的命令值 */  
VIDIOC_G_TUNER      /* 獲取調諧器資訊 */  
VIDIOC_S_TUNER      /* 設定調諧器資訊 */  
VIDIOC_G_FREQUENCY  /* 獲取調諧器頻率 */  
VIDIOC_S_FREQUENCY  /* 設定調諧器頻率 */  
VIDIOC_DQEVENT   //出佇列處理命令

經過了輸入通道的初始化之後,完成了相機引數設定、記憶體對映、格式判定和對應格式緩衝區記憶體分配,並啟動第一次相機的採集,但採集得到的影象資料並未複製出來,因此此時主函式中的全域性變數global還未為其緩衝區分配記憶體。
==========================================輸入通道執行過程===============================================

主函式通過呼叫int input_run(void)函式啟動相機採集,原始碼如下。可以看到,在執行run時,程式先為全域性變數的buff分配記憶體,併為相機建立一個執行緒,呼叫執行緒處理函式cam_thread()。接著使用pthread_detach(),線上程退出時自動釋放執行緒分配的資源。

pthread_detach()的意義在於:建立一個執行緒預設的狀態是joinable,如果一個執行緒結束執行但沒有被join,則它的狀態類似於程序中的Zombie Process,即還有一部分資源沒有被回收(退出狀態碼)。所以建立執行緒者應該呼叫pthread_join來等待執行緒執行結束,並可得到執行緒的退出代 碼,回收其資源(類似於wait,waitpid) 。但是呼叫pthread_join(pthread_id)後,如果該執行緒沒有執行結束,呼叫者會被阻塞。利用pthread_detach(),這將該子執行緒的狀態設定為detached,則該執行緒執行結束後會自動釋放所有資源。

int input_run(void) {
  pglobal->buf = malloc(videoIn->framesizeIn);
  if (pglobal->buf == NULL) {
    fprintf(stderr, "could not allocate memory\n");
    exit(EXIT_FAILURE);
  }

  pthread_create(&cam, 0, cam_thread, NULL);
  pthread_detach(cam);

  return 0;
}

對應執行緒處理函式cam_thread(),主要工作是獲取一幀影象,並將其複製到全域性緩衝區中,即上述函式所分配的全域性buf位置上。獲取影象是通過呼叫nvcGrab(vedioIn)函式,之後根據不同的格式,進行格式轉換和複製到全域性緩衝區的過程。

其中,當格式是YUYV時,執行pglobal->size = compress_yuyv_to_jpeg(videoIn, pglobal->buf, videoIn->framesizeIn, gquality);,並將所複製的內容大小寫進pglobal->size。這個過程需要注意的是,由於YUV格式轉換時,需要更多的CPU時間處理,故而會影響效能,應儘量避免使用YUV格式;

當採集格式是MJPEG格式,直接複製到記憶體中,呼叫pglobal->size = memcpy_picture(pglobal->buf, videoIn->tmpbuffer, videoIn->buf.bytesused);並將所複製的內容大小寫進pglobal->size。

void *cam_thread( void *arg ) {
  /* set cleanup handler to cleanup allocated ressources */
  pthread_cleanup_push(cam_cleanup, NULL);

  //pglobal->stop在主程式中初始化為0,當又ctrl+C按下後,呼叫signal_handler()函式清理,將其置1。
  //所以在呼叫run函式時,進入該迴圈體會不斷的採集資料並將其複製到全域性變數的緩衝區上,直到被置1才退出
  while( !pglobal->stop ) {

    /* grab a frame */
    if( uvcGrab(videoIn) < 0 ) {
      IPRINT("Error grabbing frames\n");
      exit(EXIT_FAILURE);
    }
  
    DBG("received frame of size: %d\n", videoIn->buf.bytesused);

    /*
     * Workaround for broken, corrupted frames:
     * Under low light conditions corrupted frames may get captured.
     * The good thing is such frames are quite small compared to the regular pictures.
     * For example a VGA (640x480) webcam picture is normally >= 8kByte large,
     * corrupted frames are smaller.
     */
    if ( videoIn->buf.bytesused < minimum_size ) {
      DBG("dropping too small frame, assuming it as broken\n");
      continue;
    }

    /* copy JPG picture to global buffer */
    //將影象複製到全域性前,必須鎖定互斥鎖,保證對該緩衝區的操作只有當前執行緒,避免衝突
   pthread_mutex_lock( &pglobal->db );

    /*
     * If capturing in YUV mode convert to JPEG now.
     * This compression requires many CPU cycles, so try to avoid YUV format.
     * Getting JPEGs straight from the webcam, is one of the major advantages of
     * Linux-UVC compatible devices.
     */
    if (videoIn->formatIn == V4L2_PIX_FMT_YUYV) {
      DBG("compressing frame\n");
      pglobal->size = compress_yuyv_to_jpeg(videoIn, pglobal->buf, videoIn->framesizeIn, gquality);
    }
    else {
      DBG("copying frame\n");
      pglobal->size = memcpy_picture(pglobal->buf, videoIn->tmpbuffer, videoIn->buf.bytesused);
    }

#if 0
    /* motion detection can be done just by comparing the picture size, but it is not very accurate!! */
    if ( (prev_size - global->size)*(prev_size - global->size) > 4*1024*1024 ) {
        DBG("motion detected (delta: %d kB)\n", (prev_size - global->size) / 1024);
    }
    prev_size = global->size;
#endif

   //pthread_cond_broadcast(),喚醒所有阻塞在某個條件變數上的執行緒,這些執行緒被喚醒後將再次競爭相應的互斥鎖
    pthread_cond_broadcast(&pglobal->db_update);
    pthread_mutex_unlock( &pglobal->db );

    DBG("waiting for next frame\n");

    /* only use usleep if the fps is below 5, otherwise the overhead is too long */
    if ( videoIn->fps < 5 ) {
      usleep(1000*1000/videoIn->fps);
    }
  }

  DBG("leaving input thread, calling cleanup function now\n");
  pthread_cleanup_pop(1);

  return NULL;
}

在nvcGrab(vedioIn)函式中,先執行ioctl命令DQBUF,通知驅動程式獲取已存放有影象資料的快取,該快取經過初始化已經對映到記憶體中,故而後續的操作只需要操作對應記憶體的即可(對映到記憶體上的起始地址已經儲存在vd->mem[i]中)。根據不同的視訊格式要求,把視訊資料複製到vd變數上不同的位置。之後再執行ioctl命令QBUF,向驅動傳遞應用程式已經處理完快取,把對應的快取加入到空閒捕獲佇列。之後退出本函式。而在cam_thread()函式中,只要pglobal->stop不為1,則不斷採集並將資料存放到指定位置。

其中,當採集格式是MJPEG時,資料被複制到vd->tmpbuffer;採集格式是YUYV時,資料被複制到vd->framebuffer

int uvcGrab(struct vdIn *vd)
{
#define HEADERFRAME1 0xaf
  int ret;

  if (!vd->isstreaming)
    if (video_enable(vd))
      goto err;

  memset(&vd->buf, 0, sizeof(struct v4l2_buffer));
  vd->buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  vd->buf.memory = V4L2_MEMORY_MMAP;

  ret = ioctl(vd->fd, VIDIOC_DQBUF, &vd->buf);
  if (ret < 0) {
    perror("Unable to dequeue buffer");
    goto err;
  }

  switch (vd->formatIn) {
    case V4L2_PIX_FMT_MJPEG:
      if (vd->buf.bytesused <= HEADERFRAME1) {    /* Prevent crash
                                                  * on empty image */
        fprintf(stderr, "Ignoring empty buffer ...\n");
        return 0;
      }

      memcpy(vd->tmpbuffer, vd->mem[vd->buf.index], vd->buf.bytesused);

      if (debug)
        fprintf(stderr, "bytes in used %d \n", vd->buf.bytesused);
      break;

    case V4L2_PIX_FMT_YUYV:
      if (vd->buf.bytesused > vd->framesizeIn)
        memcpy (vd->framebuffer, vd->mem[vd->buf.index], (size_t) vd->framesizeIn);
      else
        memcpy (vd->framebuffer, vd->mem[vd->buf.index], (size_t) vd->buf.bytesused);
      break;

    default:
      goto err;
    break;
  }

  ret = ioctl(vd->fd, VIDIOC_QBUF, &vd->buf);
  if (ret < 0) {
    perror("Unable to requeue buffer");
    goto err;
  }

  return 0;

err:
  vd->signalquit = 0;
  return -1;
}

小結:執行緒執行函式中的迴圈體執行過程|:先執行影象採集,再鎖定互斥鎖,根據不同格式,從不同的位置複製影象資料到指定全域性快取中。當格式指定為YUYV時,進行格式轉換後再執行復制。最後廣播條件變數和解鎖互斥鎖,通知其他執行緒有新資料更新。

相關推薦

MJPG-streamer原始碼分析-輸入部分

MJPG-streamer僅可以建立一個輸入通道,對一個相機進行採集(如需多個,需另行修改)。在進行相機採集開始前,需要對其進行初始化,包括採集過程的全域性變數定義和記憶體開闢。執行過程則是將相機採集得到的資料,根據從相機對映到記憶體的地址上,按照不同的格式要求(YUV或M

MJPG-streamer原始碼分析-輸出部分

MJPG-streamer可以建立多個輸出,簡單的說,根據主函式中輸入的引數解析的結果,確定輸出通道的個數,至少保證有一個輸出通道在程式執行時存在。從引數解析結果確定每個輸出通道的引數,並以這些引數為每個通道建立傳送執行緒。在每個傳送執行緒上,不斷偵聽是否有連線請求。每當有連

MJPG-Streamer原始碼分析(一)

-------------------------------------------------------------------------------------------------- 另一片篇推薦的博文:http://www.armbbs.net/foru

hadoop(2.7.3) 原始碼分析--RPC部分

hadoop(2.7.3) 原始碼分析–RPC部分 序列化 hadoop 自帶了Writable序列化方法,可序列化的物件需實現 Writable 介面。 Hadoop common下org.apache.hadoop.io 大量的可序列化物件,他們都

MJPG-streamer原始碼簡析

  MJPG-streamer主體上是由main函式和輸入外掛、輸出外掛組成。   軟體執行的流程是先對攝像頭進行初始化然後設定基本的輸入輸出引數,接著從攝像頭中獲取資料放到全域性記憶體中,然後通知輸出函式來取出,接著輸出。   攝像頭的初始化由結構體vdIn來進行

無線視訊監控Mjpg-streamer輸出分析

/******************************************************************************* #                                                                       

mjpg-streamer專案原始碼分析 2

mjpg-streamer專案原始碼分析   2013-09-09 10:41:05|  分類: GT2440 |舉報|字號 訂閱  mjpg-streamer專案原始碼分析 2012-11-06 10:04 397人閱讀 評論(2) 收藏 舉報         前一段時間

mjpg-streamer專案原始碼分析[轉載]

前一段時間自己買了個開發板(GT2440的),可是我沒有夠相應的買cmos攝像頭,可是又想做下國嵌的usb視訊採集和傳輸的那個專案。沒辦法,只好網上找找相關的專案,最終發現了mjpg-streamer這個開源專案。看了blog們的文章,有種激動,於是自己問同學借了個usb攝

SNMP原始碼分析之(一)配置檔案部分

snmpd.conf想必不陌生。在程序啟動過程中會去讀取配置檔案中各個配置。其中幾個引數需要先知道是幹什麼的:   token:配置檔案的每行的開頭,例如 group MyROGroup v1 readSec 這行token的引數是group。  

String 部分原始碼分析

String 無引數建構函式 /** * 底層儲存字串的目標位元組陣列, * Jdk 8 之前都是字元陣列 private final char[] value; */ @Stable private final byte[] value;

《2.uboot和系統移植-第6部分-2.6.uboot原始碼分析2-啟動第二階段》

《2.uboot和系統移植-第6部分-2.6.uboot原始碼分析2-啟動第二階段》 第一部分、章節目錄 2.6.1.start_armboot函式簡介 2.6.2.start_armboot解析1 2.6.3.記憶體使用排布 2.6.4.start_armboot解析2 2.6.5.s

《2.uboot和系統移植-第5部分-2.5.uboot原始碼分析1-啟動第一階段》

《2.uboot和系統移植-第5部分-2.5.uboot原始碼分析1-啟動第一階段》 第一部分、章節目錄 2.5.1.start.S引入 2.5.2.start.S解析1 2.5.3.start.S解析2 2.5.4.start.S解析3 2.5.5.start.S解析4 2.5.6.s

php中parse_url函式的原始碼分析(scheme部分)

前言 看師傅們的文章時發現,parse_url出現的次數較多,單純parse_url解析漏洞的考題也有很多,在此研究一下原始碼(太菜了看不懂,待日後再補充Orz) 原始碼 在ext/standard/url.c檔案中 PHPAPI php_url *php_url_parse_ex(char const

JSK1.8 String類部分原始碼分析

本文基於JDK1.8中的String類,看原始碼時無意發現String類中幾個比較有意思的地方,特此記錄下。 String類的兩個重要屬性,final的字元陣列和int的hash值,還有序列化相關的兩個欄位,這裡不寫; final char value[]初始化

Vue provide/inject 部分原始碼分析 實現響應式資料更新

下面是我自己曾經遇到 一個問題,直接以自己QA的形式來寫吧 官網給出例項,說本身是不支援資料響應式的, 但是可以傳入響應式資料,那麼provide,inject就可以實現響應式。我這裡理解應該沒錯哈,有不對的地方請指出。 我自己寫的demo,做了如下更改 parent 頁面: export def

subsampling-scale-image-view部分載入bitmap原始碼分析(一)

subsampling-scale-image-view原始碼分析背景介紹使用原始碼分析總結參考 背景 對於安卓開發人員,最頭疼的問題就是記憶體問題了,而記憶體問題又當屬bitmap最頭疼,雖然說現在市面上已經有越來越多成熟的圖片載入框架,像Fresco,Gli

django 的請求處理部分----WSGIHandler 原始碼分析 django1.5.5

轉自:Django的請求處理部分 從這裡拉開django框架的帷幕 主要涉及的類:django.core.handlers.base.BaseHandler,django.core.handlers.wsgi.WSGIHandler,前者為後者的父類。 django.http.req

co_routine.cpp/.h/inner.h(第三部分 : 協程的執行)—— libco原始碼分析、學習筆記

由於本原始碼蠻長的,所以按照功能劃分模組來分析,分為若干部分,詳見二級目錄↑ 三、協程的執行 void co_yield_env( stCoRoutineEnv_t *env );//將當前執行的env從協程棧中出棧並將執行權交給父協程。 void co_y

co_routine.cpp/.h/inner.h(第四部分:定時器和事件迴圈)—— libco原始碼分析、學習筆記

由於本原始碼蠻長的,所以按照功能劃分模組來分析,分為若干部分,詳見二級目錄↑ 定時器和事件迴圈 libco管理定時事件便是使用時間輪這種資料結構 定時器前驅知識本篇只稍微提一下,具體知識請參考《Linux高效能伺服器程式設計 遊雙 著》第11章,或其他方式學

Masonry 部分原始碼分析

 尋找最近的公共父檢視 - (instancetype)mas_closestCommonSuperview:(MAS_VIEW *)view { MAS_VIEW *closestCommonSuperview = nil; MAS_VIEW *secondView