一、前言
本文中的程序適用於Ubuntu或者ARM linux平台上外接USB攝像頭,將攝像頭插入USB口后在/dev目錄下會出現名為video*的設備。需要注意的是,電腦自帶的USB設備也可以接入Ubuntu系統中,並且在/dev目錄下也會出現名為video*的設備,但是本文的例程不適用於電腦自帶的攝像頭。
二、代碼
1 /** 2 * filename: camera.c 3 * author: Suzkfly 4 * date: 2021-08-15 5 * platform: S3C2416或Ubuntu 6 * 程序運行成功后會在當前目錄下生成pic.jpg文件,如果在Ubuntu上運行,需要超級用權限。 7 */ 8 #include <stdio.h> 9 #include <stdlib.h> 10 #include <string.h> 11 #include <getopt.h> 12 #include <fcntl.h> 13 #include <unistd.h> 14 #include <errno.h> 15 #include <malloc.h> 16 #include <sys/stat.h> 17 #include <sys/types.h> 18 #include <sys/time.h> 19 #include <sys/mman.h> 20 #include <sys/ioctl.h> 21 #include <asm/types.h> 22 #include <linux/videodev2.h> 23 #include <time.h> 24 25 /** <\brief 定義攝像頭設備路徑 */ 26 #define CAMERA_DEV "/dev/video0" 27 28 /** <\brief 定義清零數據的宏 */ 29 #define CLEAR(x) memset (&(x), 0, sizeof (x)) 30 31 /** <\brief 定義保存攝像頭數據的結構體 */ 32 struct buffer { 33 void * start; /* 起始地址 */ 34 size_t length; /* 數據長度 */ 35 }; 36 37 #define BUFFER_CNT 4 /* 緩沖幀的個數 */ 38 39 /** 40 * \brief 初始化v4l2 41 * 42 * \param[in] p_dev_name:攝像頭所處路徑 43 * \param[out] p_fd:得到的攝像頭設備文件描述符 44 * \param[out] pp_buffers:得到的緩沖幀的地址,注意這是一個二級指針,該變量保存的數據是一個地址 45 * 46 * \retval 成功返回0,失敗返回-1 47 */ 48 int v4l2_init(const char *p_dev_name, int *p_fd, struct buffer **pp_buffers) 49 { 50 int fd = -1; 51 struct v4l2_capability cap; 52 struct v4l2_format fmt; 53 enum v4l2_buf_type type; 54 struct v4l2_requestbuffers req; 55 time_t t; 56 struct tm *ptm; 57 unsigned int i; 58 struct v4l2_buffer buf; //驅動中的一幀 59 60 /* 打開攝像頭設備 */ 61 if ((fd = open (p_dev_name, O_RDWR | O_NONBLOCK, 0)) < 0) { 62 perror("fail to open\n"); 63 return -1; 64 } 65 66 /* ioctl是一個強大的函數,它的功能取決於傳入的第2個參數,這里第2個參數傳入 67 VIDIOC_QUERYCAP表示獲取設備屬性,獲取到的屬性會保存在第3個參數中,第3個 68 參數是一個struct v4l2_capability類型的結構體指針,struct v4l2_capability 69 結構定義如下: 70 struct v4l2_capability 71 { 72 u8 driver[16]; // 驅動名字 73 u8 card[32]; // 設備名字 74 u8 bus_info[32]; // 設備在系統中的位置 75 u32 version; // 驅動版本號 76 u32 capabilities; // 設備支持的操作 77 u32 reserved[4]; // 保留字段 78 }; 79 80 capabilities是能力的意思,其中支持的能力如下所示: 81 #V4L2_CAP_VIDEO_CAPTURE 0x00000001 //是視頻采集設備 82 #V4L2_CAP_VIDEO_OUTPUT 0x00000002 //是視頻輸出設備 83 #V4L2_CAP_VIDEO_OVERLAY 0x00000004 //可以做視頻疊加 84 #V4L2_CAP_VBI_CAPTURE 0x00000010 //是一個原始的 VBI 捕獲設備 85 #V4L2_CAP_VBI_OUTPUT 0x00000020 //是一個原始的 VBI 輸出設備 86 #V4L2_CAP_SLICED_VBI_CAPTURE 0x00000040 //是一個切片(sliced)的 VBI 捕獲設備 87 #V4L2_CAP_SLICED_VBI_OUTPUT 0x00000080 //是一個切片(sliced)的 VBI 輸出設備 88 #V4L2_CAP_RDS_CAPTURE 0x00000100 //RDS數據采集 89 #V4L2_CAP_VIDEO_OUTPUT_OVERLAY 0x00000200 //可以做視頻輸出疊加 90 #V4L2_CAP_HW_FREQ_SEEK 0x00000400 //可以做硬件尋頻 91 92 #V4L2_CAP_TUNER 0x00010000 //有一個協調器 93 #V4L2_CAP_AUDIO 0x00020000 //支持音頻 94 #V4L2_CAP_RADIO 0x00040000 //是無線電設備 95 96 #V4L2_CAP_READWRITE 0x01000000 //讀/寫系統調用 97 #V4L2_CAP_ASYNCIO 0x02000000 // 異步 I/O 98 #V4L2_CAP_STREAMING 0x04000000 // streaming I/O ioctls 99 */ 100 101 /* 獲取設備參數 */ 102 if (0 != ioctl(fd, VIDIOC_QUERYCAP, &cap)) { 103 perror("fail to ioctl\n"); 104 return -1; 105 } 106 #if 0 107 printf("cap.driver = %s\n", cap.driver); 108 printf("cap.card = %s\n", cap.card); 109 printf("cap.bus_info = %s\n", cap.bus_info); 110 printf("cap.version = %u\n", cap.version); 111 printf("cap.capabilities = %#08x\n", cap.capabilities); 112 #endif 113 /* struct v4l2_format結構定義如下: 114 struct v4l2_format { 115 enum v4l2_buf_type type; 116 union { 117 struct v4l2_pix_format pix; // V4L2_BUF_TYPE_VIDEO_CAPTURE 118 struct v4l2_window win; // V4L2_BUF_TYPE_VIDEO_OVERLAY 119 struct v4l2_vbi_format vbi; // V4L2_BUF_TYPE_VBI_CAPTURE 120 struct v4l2_sliced_vbi_format sliced; // V4L2_BUF_TYPE_SLICED_VBI_CAPTURE 121 __u8 raw_data[200]; // user-defined 122 } fmt; 123 }; 124 可以看出struct v4l2_format結構中有1個枚舉類型的type和一個共用體fmt,數據保存 125 在fmt中,fmt共用體中具體使用哪個結構由type的值決定,本程序中需要設置圖像格式, 126 因此type的值設為V4L2_BUF_TYPE_VIDEO_CAPTURE,使用fmt中的pix成員。 127 struct v4l2_pix_format結構定義如下: 128 struct v4l2_pix_format { 129 __u32 width; //圖像寬度 130 __u32 height; //圖像高度 131 __u32 pixelformat; //像素格式 132 enum v4l2_field field; //場格式,見下文 133 __u32 bytesperline; //表明緩沖區中有多少字節用於表示圖像中一行像素的所有像素值。 134 //由於一個像素可能有多個字節表示,所以 bytesperline 可能是字段 width 值的若干倍 135 __u32 sizeimage; //圖像大小 136 enum v4l2_colorspace colorspace; //色彩空間,其中V4L2_COLORSPACE_JPEG = 7 137 __u32 priv; //私有數據,取決於像素格式 138 }; 139 pixelformat表示像素格式,可以設置的格式有很多,比如 140 #V4L2_PIX_FMT_RGB565 141 #V4L2_PIX_FMT_RGB24 142 #V4L2_PIX_FMT_YUV565 143 #V4L2_PIX_FMT_JPEG 144 #V4L2_PIX_FMT_MPEG 145 等。 146 147 enum v4l2_field { 148 V4L2_FIELD_ANY = 0, //驅動程序可以選擇無、頂部、底部、隔行掃描... 149 V4L2_FIELD_NONE = 1, //此設備沒有場 150 V4L2_FIELD_TOP = 2, //只有頂場 151 V4L2_FIELD_BOTTOM = 3, //只有底場 152 V4L2_FIELD_INTERLACED = 4, //兩個場交錯 153 V4L2_FIELD_SEQ_TB = 5, //兩個場順序合並到一個緩沖區中,從上到下順序 154 V4L2_FIELD_SEQ_BT = 6, //同上,加自上而下的順序 155 V4L2_FIELD_ALTERNATE = 7, //兩個場交替進入單獨的緩沖區 156 V4L2_FIELD_INTERLACED_TB = 8, //兩個場交錯,頂場在前,頂場先傳輸 157 V4L2_FIELD_INTERLACED_BT = 9, //兩個場交錯,前場先傳輸,后場先傳輸 158 }; */ 159 160 /* 設置圖像格式 */ 161 CLEAR(fmt); 162 fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 163 fmt.fmt.pix.width = 800; 164 fmt.fmt.pix.height = 600; 165 fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_JPEG; 166 fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; 167 if (0 != ioctl(fd, VIDIOC_S_FMT, &fmt)) { 168 perror("fail to ioctl\n"); 169 return -1; 170 } 171 172 /* 設置結果可能與寫入的數值不一樣,可以打印出來看一下 */ 173 #if 0 174 printf("fmt.type = %d\n", fmt.type); 175 printf("fmt.fmt.pix.width = %d\n", fmt.fmt.pix.width); 176 printf("fmt.fmt.pix.height = %d\n", fmt.fmt.pix.height); 177 printf("fmt.fmt.pix.pixelformat = %c%c%c%c\n", 178 (fmt.fmt.pix.pixelformat >> 0) & 0xFF, 179 (fmt.fmt.pix.pixelformat >> 8) & 0xFF, 180 (fmt.fmt.pix.pixelformat >> 16) & 0xFF, 181 (fmt.fmt.pix.pixelformat >> 24) & 0xFF 182 ); 183 printf("fmt.fmt.pix.field = %d\n", fmt.fmt.pix.field); 184 printf("fmt.fmt.pix.bytesperline = %d\n", fmt.fmt.pix.bytesperline); 185 printf("fmt.fmt.pix.sizeimage = %d\n", fmt.fmt.pix.sizeimage); 186 printf("fmt.fmt.pix.colorspace = %d\n", fmt.fmt.pix.colorspace); 187 printf("fmt.fmt.pix.priv = %d\n", fmt.fmt.pix.priv); 188 printf("V4L2_BUF_TYPE_VIDEO_CAPTURE = %d\n", V4L2_BUF_TYPE_VIDEO_CAPTURE); 189 #endif 190 191 //file_length = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height; //計算圖片大小 192 193 /* struct v4l2_requestbuffers結構定義如下: 194 struct v4l2_requestbuffers { 195 __u32 count; // 緩沖區內緩沖幀的數目 196 enum v4l2_buf_type type; // 緩沖幀數據格式 197 enum v4l2_memory memory; // 區別是內存映射還是用戶指針方式, 198 V4L2_MEMORY_MMAP 為內存映射, V4L2_MEMORY_USERPTR 為用戶指針 199 __u32 reserved[2]; 200 }; */ 201 202 /* 向設備申請緩沖區 */ 203 CLEAR (req); 204 req.count = BUFFER_CNT; /* \todo 為什么要申請4個緩沖幀 */ 205 req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 206 req.memory = V4L2_MEMORY_MMAP; 207 ioctl(fd, VIDIOC_REQBUFS, &req); 208 209 if (req.count < 2) { 210 printf("Insufficient buffer memory\n"); /* 緩沖幀數量不夠 */ 211 } 212 213 /* 內存中建立對應空間 */ 214 *pp_buffers = (struct buffer *)calloc(req.count, sizeof(struct buffer)); 215 if (*pp_buffers == NULL) { 216 perror("calloc failed\n"); 217 return -1; 218 } 219 220 /* 得到緩沖幀的起始地址和長度 */ 221 for (i = 0; i < req.count; i++) { 222 /* struct v4l2_buffer { 223 __u32 index; //緩存編號 224 enum v4l2_buf_type type; //視頻捕獲模式 225 __u32 bytesused; //緩存已使用空間大小 226 __u32 flags; //緩存當前狀態,可取下列值 227 #V4L2_BUF_FLAG_MAPPED 0x0001 //當前緩存已經映射 228 #V4L2_BUF_FLAG_QUEUED 0x0002 //緩存可以采集數據 229 #V4L2_BUF_FLAG_DONE 0x0004 //緩存可以提取數據 230 #V4L2_BUF_FLAG_KEYFRAME 0x0008 //Image is a keyframe (I-frame) 231 #V4L2_BUF_FLAG_PFRAME 0x0010 //Image is a P-frame 232 #V4L2_BUF_FLAG_BFRAME 0x0020 //Image is a B-frame 233 #V4L2_BUF_FLAG_TIMECODE 0x0100 //timecode field is valid 234 #V4L2_BUF_FLAG_INPUT 0x0200 //input field is valid 235 enum v4l2_field field; 236 struct timeval timestamp; //獲取第一個字節時的系統時間 237 struct v4l2_timecode timecode; 238 __u32 sequence; //隊列中的序號 239 240 // memory location 241 enum v4l2_memory memory; //IO 方式,被應用程序設置 242 union { 243 __u32 offset; //緩沖幀地址偏移量,只對MMAP 有效 244 unsigned long userptr; 245 } m; 246 __u32 length; //緩沖幀長度 247 __u32 input; 248 __u32 reserved; 249 }; 250 */ 251 252 /* 獲取緩沖幀的地址,長度 */ 253 CLEAR (buf); 254 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 255 buf.memory = V4L2_MEMORY_MMAP; 256 buf.index = i; 257 if (-1 == ioctl (fd, VIDIOC_QUERYBUF, &buf)) { 258 printf ("VIDIOC_QUERYBUF error\n"); 259 } 260 261 (*pp_buffers)[i].length = buf.length; 262 /* 通過mmap建立映射關系 */ 263 (*pp_buffers)[i].start = mmap (NULL, 264 buf.length, 265 PROT_READ | PROT_WRITE, 266 MAP_SHARED, 267 fd, 268 buf.m.offset); /* 被映射內容的偏移量 */ 269 if (MAP_FAILED == (*pp_buffers)[i].start) { /* MAP_FAILED其實是((void *) -1),mmap失敗時返回 */ 270 printf ("mmap failed\n"); 271 } 272 } 273 274 for (i = 0; i < req.count; i++) { 275 CLEAR (buf); 276 277 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 278 buf.memory = V4L2_MEMORY_MMAP; 279 buf.index = i; 280 281 /* 把幀放入隊列 */ 282 if (0 != ioctl (fd, VIDIOC_QBUF, &buf)) { 283 printf ("VIDIOC_QBUF failed\n"); 284 } 285 } 286 287 /* 開始捕捉圖像數據 */ 288 type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 289 if (-1 == ioctl (fd, VIDIOC_STREAMON, &type)) { 290 printf ("VIDIOC_STREAMON failed\n"); 291 } 292 293 *p_fd = fd; 294 295 return 0; 296 } 297 298 /** 299 * \brief v4l2去除初始化 300 * 301 * \param[in] camera_fd:打開的攝像頭設備文件描述符 302 * \param[in] p_buffers:緩沖幀地址 303 * 304 * \retval 成功返回0,失敗返回-1 305 */ 306 void v4l2_deinit(int camera_fd, struct buffer *p_buffers) 307 { 308 int i; 309 310 /* 解除映射關系 */ 311 for (i = 0; i < BUFFER_CNT; i++) { 312 if (-1 == munmap (p_buffers[i].start, p_buffers[i].length)) { 313 printf ("munmap error\n"); 314 } 315 } 316 317 /* 釋放申請的內存 */ 318 free(p_buffers); 319 320 /* 關閉文件 */ 321 close(camera_fd); 322 } 323 324 /** 325 * \brief 讀取一幀數據 326 * 327 * \param[in] camera_fd:打開攝像頭設備得到的文件描述符 328 * \param[in] p_buffers:緩沖幀地址 329 * \param[in] filename:保存的jpg圖片的路徑 330 * 331 * \retval 成功返回0,失敗返回-1 332 */ 333 static int __read_frame (int camera_fd, struct buffer *p_buffers, char *filename) 334 { 335 FILE *file_fd = NULL; 336 struct v4l2_buffer buf; 337 unsigned int i; 338 339 /* 以"w+"方式打開文件,如果文件不存在則會創建文件,但前提是文件所在目錄對於 340 其他用戶有寫權限,可以用umask命令查看和修改掩碼 */ 341 if((file_fd = fopen(filename, "w+")) == NULL) { 342 perror("fail to fopen\n"); 343 return -1; 344 } 345 346 /* 從隊列中取出幀 */ 347 CLEAR(buf); 348 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 349 buf.memory = V4L2_MEMORY_MMAP; 350 ioctl(camera_fd, VIDIOC_DQBUF, &buf); 351 352 fwrite(p_buffers[buf.index].start, p_buffers[buf.index].length, 1, file_fd); //將其寫入文件中 353 354 ioctl(camera_fd, VIDIOC_QBUF, &buf); /* 把幀放入隊列 */ 355 356 fclose(file_fd); 357 358 return 0; 359 } 360 361 /** 362 * \brief 得到一張jpg圖片 363 * 364 * \param[in] camera_fd:打開攝像頭設備得到的文件描述符 365 * \param[in] p_buffers:緩沖幀地址 366 * \param[in] filename:保存的jpg圖片的路徑 367 * 368 * \retval 成功返回0,失敗返回-1 369 */ 370 int camera_jpg (int camera_fd, struct buffer *p_buffers, char *filename) 371 { 372 unsigned int i; 373 fd_set fds; 374 struct timeval tv; 375 int r; 376 377 /* 設定超時時間 */ 378 tv.tv_sec = 2; 379 tv.tv_usec = 0; 380 381 while (1) { //這一段涉及到異步IO 382 FD_ZERO (&fds); //將指定的文件描述符集清空 383 FD_SET (camera_fd, &fds); //在文件描述符集合中增加一個新的文件描述符 384 385 /* select函數執行成功返回集合內包含的文件描述符數量,失敗返回-1,超時返回0 */ 386 r = select (camera_fd + 1, &fds, NULL, NULL, &tv);//判斷是否可讀(即攝像頭是否准備好),tv是定時 387 388 if (-1 == r) { 389 if (EINTR == errno) { /* 系統調用被中斷 */ 390 continue; 391 } 392 printf ("select err\n"); 393 return -1; 394 } else if (0 == r) { /* 超時 */ 395 fprintf (stderr, "select timeout\n"); 396 return -1; 397 } 398 399 /* 如果可讀,__read_frame()函數,並跳出循環 */ 400 return __read_frame(camera_fd, p_buffers, filename); 401 } 402 403 return 0; 404 } 405 406 #if 1 407 /* 將攝像頭拍到的照片保存成pic.jpg文件 */ 408 int main(int argc, const char *argv[]) 409 { 410 int camera_fd = 0; /* 攝像頭設備的文件描述符 */ 411 struct buffer * p_buffers = NULL; /* 用於保存緩沖幀地址和長度 */ 412 413 if (0 != v4l2_init(CAMERA_DEV, &camera_fd, &p_buffers)) { 414 perror("v4l2_init failed\n"); 415 return 0; 416 } 417 camera_jpg(camera_fd, p_buffers, "pic.jpg"); 418 419 v4l2_deinit(camera_fd, p_buffers); 420 421 return 0; 422 } 423 424 #else 425 /* 將攝像頭拍攝的照片實時顯示出來,需要將jpg.c一同編譯,要加-ljpeg選項 */ 426 extern int framebuffer_init (void); 427 extern int show_jpg(unsigned int x, unsigned int y, const char *name); 428 429 int main(int argc, const char *argv[]) 430 { 431 int camera_fd = 0; /* 攝像頭設備的文件描述符 */ 432 struct buffer * p_buffers = NULL; /* 用於保存緩沖幀地址和長度 */ 433 434 framebuffer_init(); 435 if (0 != v4l2_init(CAMERA_DEV, &camera_fd, &p_buffers)) { 436 perror("v4l2_init failed\n"); 437 return 0; 438 } 439 440 while (1) { 441 camera_jpg(camera_fd, p_buffers, "pic.jpg"); 442 show_jpg(0, 0, "pic.jpg"); 443 } 444 445 v4l2_deinit(camera_fd, p_buffers); 446 447 return 0; 448 } 449 #endif
程序運行后,在當前路徑下會出現名為pic.jpg的文件,這就是用攝像頭拍攝到的照片。如果使用代碼最后面的main函數,可以將拍攝到的照片直接在屏幕上顯示出來,但是要支持jpg圖片的顯示。通過framebuffer顯示jpg圖片可以參考我這篇博客:framebuffer顯示jpg圖片。但是用這種方式顯示出來的圖片是一卡一卡的,應該是處理器性能不夠強大,再者,攝像頭本來就支持視頻顯示的,本文中的例程是取出攝像頭拍攝到的畫面,保存成jpg文件,再將圖片顯示出來,這個過程做了很多多余的事情,因此顯示出來是一卡一卡的。
三、問題解答
如果使用nfs掛載運行該程序,那么很容易出現下面兩個問題。
1. 運行程序時,如果pic.jpg不存在,那么在調用fopen打開pic.jpg時會打開失敗,並且報錯“Permission denied”。
既然報“Permission denied”,那顯然就是權限問題。(有些人可能會覺得是文件不存在導致的,但是本程序使用fopen打開文件,傳入的標志是“w+”,這個標志在文件不存在時會自己創建文件,而且如果是文件不存在,那么會直接報“No such file or directory”,而不是“Permission denied”。)
要解決這個問題,有2種方法
1)在Ubuntu上用touch命令手動創建pic.jpg文件。
(因為nfs目錄下的文件是Ubuntu的,因此只能在Ubutnu上創建,在開發板終端沒有權限創建文件)。創建完成之后可以用ls -l命令查看文件權限,此時pic.jpg的權限為“rw-rw-r--”,為什么是這個權限呢,首先,文件權限和umask有關,可以在終端上直接輸入umask命令,可以看到終端打印出來“0002”,這表示其他用戶是沒有寫權限的。另外使用touch命令創建的文件都是沒有執行權限的,因此創建出來的文件權限為“rw-rw-r--”。所以創建出來的文件還需要使用chmod 0666 pic.jpg命令,讓其他用戶有寫權限。這樣再在開發板端運行程序就沒問題了。但是這種方法缺點很明顯,就是如果改變了文件名,那么又要進行一遍這樣的操作,或者如果需要一次性拍很多張照片,這種方法就不適用了。
2)通過程序自動創建文件。
前面已經說了,如果pic.jpg文件是不存在的,那么程序運行時報沒有權限的錯誤,既然是創建文件時需要權限,就應該看看希望被創建的文件所處的文件夾有沒有寫權限,如果沒有的話,使用chmod加入寫權限。之后運行程序,發現文件可以自動創建。
但是這又帶來一個新的問題,就是文件的所有者變成了nobody,組變成了nogroup,如下圖。
![]()
暫時不知道這種情況會帶來什么后果,但是這個是可以解決的,解決辦法如下:
在Ubuntu終端中,輸入sudu vi /etc/exports
文件的最后一行寫的是nfs共享目錄的路徑也權限等信息,在其中加入一項:no_root_squash,修改過后如下圖:
![]()
注意各項中間用逗號分隔,且沒有空格。保存文件,使用命令:sudo /etc/init.d/nfs-kernel-server restart重啟nfs服務。
之后再在開發板上運行程序,這時創建出來的文件的用戶和組都變成了root。
2. 程序運行一段時間就死了,經過統計,問題會有下面幾種:
1)程序運行時報錯:

然后程序自動退出,但系統沒有死掉,並且可以再次用a.out運行程序,但程序運行一段時間后還是會死掉。
2)程序運行時報錯,如下圖:

並且系統死掉,需要重啟,但問題又來了,重啟也不一定成功,可能需要多次斷電上電才能成功重啟。
最氣的是,重燒系統之后這個問題就沒再出現了。
雖然問題沒再出現了,但是沒有找到出現問題的根本原因和100%能解決問題的方法。在這里先作幾點猜測:
1. 網絡連接問題。既然在開發板上運行系統不會死機,而使用nfs掛載就會死機,那有理由懷疑和網絡連接有關,但是這肯定不是問題發生的根本原因,只是由某個問題導致了兩種不同的現象;
2. 存儲器問題。可能是Nand Flash出問題,導致內核下載進去時出現了某些錯誤內容。這樣推測的理由是出現問題之后系統變得難以啟動,而且重燒系統之后這個問題就不再出現了。
