mjpg-streamer專案原始碼分析 2
阿新 • • 發佈:2019-01-26
mjpg-streamer專案原始碼分析
2013-09-09 10:41:05| 分類: GT2440 |舉報|字號 訂閱
mjpg-streamer專案原始碼分析
2012-11-06 10:04 397人閱讀 評論(2) 收藏 舉報
前一段時間自己買了個開發板(GT2440的),可是我沒有夠相應的買cmos攝像頭,可是又想做下國嵌的usb視訊採集和傳輸的哪個專案,沒辦法,只好網上找找相關的專案,最終發現了mjpg-streamer這個開源專案。看了blog們的文章,有種激動,於是自己問同學借了個usb攝像頭,試了試,挺好使的,而且處理速度上也挺好的,就開始想了解這個專案是怎麼工作了(研究了好幾天哦)。
下面是我個人在這學習mjpg-streamer時的一些看法,不足之處,還請大家多多指出:
首先,mjpg-streamer目錄由以下目錄組成:
.deps
doc
mjpeg-client //這個目錄包含我們使用的客服端
mjpg-streamer //這個是usb攝像頭採集和傳輸的目錄
mjpg-streamer-experiment
udp-client
uvc-streamer.
這裡我們只分析下mjpg-streamer目錄,下面看下上面的mjpg-streamer目錄的組成:
plugins目錄:主要是一些usb攝像頭的資料採集和傳輸的功能子函式
scripts目錄貌似沒啥用
www目錄主要是在使用瀏覽器瀏覽時,可以增加html介面上一些功能。
mjpg-streamer.c資料夾,主要實現命令引數的解析及呼叫相關執行緒執行功能子函式
下面我們看下mjpg-streamer.c中的原始碼:
int main(int argc, char *argv[])
{
char *input = "input_uvc.so --resolution 640x480 --fps 5 --device /dev/video0";
char *output[MAX_OUTPUT_PLUGINS];//10
int daemon=0, i;
size_t tmp=0;
output[0] = "output_http.so --port 8080";
global.outcnt = 0;
global.control = control;
/* parameter parsing */
while(1) {
int option_index = 0, c=0;
static struct option long_options[] = \
{
{"h", no_argument, 0, 0},
{"help", no_argument, 0, 0},
{"i", required_argument, 0, 0},
{"input", required_argument, 0, 0},
{"o", required_argument, 0, 0},
{"output", required_argument, 0, 0},
{"v", no_argument, 0, 0},
{"version", no_argument, 0, 0},
{"b", no_argument, 0, 0},
{"background", no_argument, 0, 0},
{0, 0, 0, 0}
};
c = getopt_long_only(argc, argv, "", long_options, &option_index);
這裡主要遇到一個功能函式getopt_long_only(),該函式功能主要是解析命令列選項,也就是將*input中的-h -i -o -v -b的引數解析出來,該函式具體如何使用,可參照getopt_long_only.
switch (option_index) {
/* h, help */
case 0:
case 1:
help(argv[0]);
return 0;
break;
/* i, input */
case 2:
case 3:
input = strdup(optarg);
break;
/* o, output */
case 4:
case 5:
output[global.outcnt++] = strdup(optarg);
break;
/* v, version */
case 6:
case 7:
printf("MJPG Streamer Version: %s\n" \
"Compilation Date.....: %s\n" \
"Compilation Time.....: %s\n", SOURCE_VERSION, __DATE__, __TIME__);
return 0;
break;
/* b, background */
case 8:
case 9:
daemon=1;
break;
default:
help(argv[0]);
return 0;
}
}
option_index儲存的是上面的long_options中的座標值,strdup(optarg)用來獲取相應的標記後的引數,例如當main函式中傳入的命令為./mjpg_streamer -i "./input_uvc.so"時,
程式將執行到case3:中,然後將input="./input_uvc.so".
至此,前面就幫我們將輸入的mjpg-streamer命令讀解析好了。
下面開始呼叫相應的函式:
/* open input plugin */
tmp = (size_t)(strchr(input, ' ')-input);
global.in.plugin = (tmp > 0)?strndup(input, tmp):strdup(input);
global.in.handle = dlopen(global.in.plugin, RTLD_LAZY);
if ( !global.in.handle ) {
LOG("ERROR: could not find input plugin\n");
LOG(" Perhaps you want to adjust the search path with:\n");
LOG(" # export LD_LIBRARY_PATH=/path/to/plugin/folder\n");
LOG(" dlopen: %s\n", dlerror() );
closelog();
exit(EXIT_FAILURE);
}
global.in.init = dlsym(global.in.handle, "input_init");
if ( global.in.init == NULL ) {
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
global.in.stop = dlsym(global.in.handle, "input_stop");
if ( global.in.stop == NULL ) {
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
global.in.run = dlsym(global.in.handle, "input_run");
if ( global.in.run == NULL ) {
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
dlopen()函式負責開啟*.so外掛,dlsym()負責呼叫外掛中的相關函式。目前意思已經很明顯了,當輸入./mjpg_streamer -i "./input_uvc.so"時,系統將會呼叫input_uvc.so中的input_init函式,對輸入裝置進行初始化。我們開啟plugins\input_uvc\input_uvc.c看下是否有input_init()函式,確實存在,這說明我前面的理解沒錯。
既然輸入已經初始化了,那輸出呢?不急,我們繼續看下下面的程式碼:
/* open output plugin */
for (i=0; i<global.outcnt; i++) {
tmp = (size_t)(strchr(output[i], ' ')-output[i]);
global.out[i].plugin = (tmp > 0)?strndup(output[i], tmp):strdup(output[i]);
global.out[i].handle = dlopen(global.out[i].plugin, RTLD_LAZY);
if ( !global.out[i].handle ) {
LOG("ERROR: could not find output plugin %s\n", global.out[i].plugin);
LOG(" Perhaps you want to adjust the search path with:\n");
LOG(" # export LD_LIBRARY_PATH=/path/to/plugin/folder\n");
LOG(" dlopen: %s\n", dlerror() );
closelog();
exit(EXIT_FAILURE);
}
global.out[i].init = dlsym(global.out[i].handle, "output_init");
if ( global.out[i].init == NULL ) {
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
global.out[i].stop = dlsym(global.out[i].handle, "output_stop");
if ( global.out[i].stop == NULL ) {
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
if ( global.out[i].stop == NULL ) {
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
global.out[i].run = dlsym(global.out[i].handle, "output_run");
if ( global.out[i].run == NULL ) {
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
這段程式碼就不分析了,相信大家已經很明白了。
嗯,輸入和輸出裝置都準備,下面該幹啥了,不用說肯定幹活了嗎。俗話說“養軍千日用在一時”:
if ( global.in.run() ) {
LOG("can not run input plugin\n");
closelog();
return 1;
}
for(i=0; i<global.outcnt; i++) {
syslog(LOG_INFO, "starting output plugin: %s (ID: %02d)", global.out[i].plugin, global.out[i].param.id);
global.out[i].run(global.out[i].param.id);
}
mjpg-streamer就這樣結束了,貌似還真不過癮,下面我們到input_uvc.c和output_http.c檔案看下:
input_uvc.c(plugins\input_uvc\)
int input_init(input_parameter *param)
這個函式貌似挺長的,其實程式碼也就只是實現對usb攝像頭的格式、幀、請求buf,佇列buf等的一些設定,主要實現也是在函式init_videoIn(videoIn, dev, width, height, fps, format, 1) 中,有興趣的話,大家可以去看看。
int input_stop(void) {
DBG("will cancel input thread\n");
pthread_cancel(cam);
return 0;
}
輸入停止,貌似更加簡單,只要將執行緒取下cancel就ok,那麼run肯定是crete threads了。
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;
}
猜對了,可惜沒分加。下面那些程式碼基本也沒用到,這就不細說了。
output_http.c(plugins\output_http\)
剛開始也肯定是對其初始化,然後stop,最後run。其實我挺喜歡這個專案的編寫者,處處為咱們考慮。
int output_init(output_parameter *param) {
servers[param->id].id = param->id;
servers[param->id].pglobal = param->global;
servers[param->id].conf.port = port;
servers[param->id].conf.credentials = credentials;
servers[param->id].conf.www_folder = www_folder;
servers[param->id].conf.nocommands = nocommands;
}
int output_stop(int id) {
DBG("will cancel server thread #%02d\n", id);
pthread_cancel(servers[id].threadID);
return 0;
}
int output_run(int id) {
DBG("launching server thread #%02d\n", id);
/* create thread and pass context to thread function */
pthread_create(&(servers[id].threadID), NULL, server_thread, &(servers[id]));
pthread_detach(servers[id].threadID);
return 0;
}
我想這個專案分析的差不多了,至於一些小的方面,我不依依細說了,最後送大家一句古語“ 師傅領進門,修行靠個人”,並贈上這個專案的框架圖。
2013-09-09 10:41:05| 分類: GT2440 |舉報|字號 訂閱
mjpg-streamer專案原始碼分析
2012-11-06 10:04 397人閱讀 評論(2) 收藏 舉報
前一段時間自己買了個開發板(GT2440的),可是我沒有夠相應的買cmos攝像頭,可是又想做下國嵌的usb視訊採集和傳輸的哪個專案,沒辦法,只好網上找找相關的專案,最終發現了mjpg-streamer這個開源專案。看了blog們的文章,有種激動,於是自己問同學借了個usb攝像頭,試了試,挺好使的,而且處理速度上也挺好的,就開始想了解這個專案是怎麼工作了(研究了好幾天哦)。
下面是我個人在這學習mjpg-streamer時的一些看法,不足之處,還請大家多多指出:
首先,mjpg-streamer目錄由以下目錄組成:
.deps
doc
mjpeg-client //這個目錄包含我們使用的客服端
mjpg-streamer //這個是usb攝像頭採集和傳輸的目錄
mjpg-streamer-experiment
udp-client
uvc-streamer.
這裡我們只分析下mjpg-streamer目錄,下面看下上面的mjpg-streamer目錄的組成:
plugins目錄:主要是一些usb攝像頭的資料採集和傳輸的功能子函式
scripts目錄貌似沒啥用
www目錄主要是在使用瀏覽器瀏覽時,可以增加html介面上一些功能。
mjpg-streamer.c資料夾,主要實現命令引數的解析及呼叫相關執行緒執行功能子函式
下面我們看下mjpg-streamer.c中的原始碼:
int main(int argc, char *argv[])
{
char *input = "input_uvc.so --resolution 640x480 --fps 5 --device /dev/video0";
char *output[MAX_OUTPUT_PLUGINS];//10
int daemon=0, i;
size_t tmp=0;
output[0] = "output_http.so --port 8080";
global.outcnt = 0;
global.control = control;
/* parameter parsing */
while(1) {
int option_index = 0, c=0;
static struct option long_options[] = \
{
{"h", no_argument, 0, 0},
{"help", no_argument, 0, 0},
{"i", required_argument, 0, 0},
{"input", required_argument, 0, 0},
{"o", required_argument, 0, 0},
{"output", required_argument, 0, 0},
{"v", no_argument, 0, 0},
{"version", no_argument, 0, 0},
{"b", no_argument, 0, 0},
{"background", no_argument, 0, 0},
{0, 0, 0, 0}
};
c = getopt_long_only(argc, argv, "", long_options, &option_index);
這裡主要遇到一個功能函式getopt_long_only(),該函式功能主要是解析命令列選項,也就是將*input中的-h -i -o -v -b的引數解析出來,該函式具體如何使用,可參照getopt_long_only.
switch (option_index) {
/* h, help */
case 0:
case 1:
help(argv[0]);
return 0;
break;
/* i, input */
case 2:
case 3:
input = strdup(optarg);
break;
/* o, output */
case 4:
case 5:
output[global.outcnt++] = strdup(optarg);
break;
/* v, version */
case 6:
case 7:
printf("MJPG Streamer Version: %s\n" \
"Compilation Date.....: %s\n" \
"Compilation Time.....: %s\n", SOURCE_VERSION, __DATE__, __TIME__);
return 0;
break;
/* b, background */
case 8:
case 9:
daemon=1;
break;
default:
help(argv[0]);
return 0;
}
}
option_index儲存的是上面的long_options中的座標值,strdup(optarg)用來獲取相應的標記後的引數,例如當main函式中傳入的命令為./mjpg_streamer -i "./input_uvc.so"時,
程式將執行到case3:中,然後將input="./input_uvc.so".
至此,前面就幫我們將輸入的mjpg-streamer命令讀解析好了。
下面開始呼叫相應的函式:
/* open input plugin */
tmp = (size_t)(strchr(input, ' ')-input);
global.in.plugin = (tmp > 0)?strndup(input, tmp):strdup(input);
global.in.handle = dlopen(global.in.plugin, RTLD_LAZY);
if ( !global.in.handle ) {
LOG("ERROR: could not find input plugin\n");
LOG(" Perhaps you want to adjust the search path with:\n");
LOG(" # export LD_LIBRARY_PATH=/path/to/plugin/folder\n");
LOG(" dlopen: %s\n", dlerror() );
closelog();
exit(EXIT_FAILURE);
}
global.in.init = dlsym(global.in.handle, "input_init");
if ( global.in.init == NULL ) {
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
global.in.stop = dlsym(global.in.handle, "input_stop");
if ( global.in.stop == NULL ) {
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
global.in.run = dlsym(global.in.handle, "input_run");
if ( global.in.run == NULL ) {
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
dlopen()函式負責開啟*.so外掛,dlsym()負責呼叫外掛中的相關函式。目前意思已經很明顯了,當輸入./mjpg_streamer -i "./input_uvc.so"時,系統將會呼叫input_uvc.so中的input_init函式,對輸入裝置進行初始化。我們開啟plugins\input_uvc\input_uvc.c看下是否有input_init()函式,確實存在,這說明我前面的理解沒錯。
既然輸入已經初始化了,那輸出呢?不急,我們繼續看下下面的程式碼:
/* open output plugin */
for (i=0; i<global.outcnt; i++) {
tmp = (size_t)(strchr(output[i], ' ')-output[i]);
global.out[i].plugin = (tmp > 0)?strndup(output[i], tmp):strdup(output[i]);
global.out[i].handle = dlopen(global.out[i].plugin, RTLD_LAZY);
if ( !global.out[i].handle ) {
LOG("ERROR: could not find output plugin %s\n", global.out[i].plugin);
LOG(" Perhaps you want to adjust the search path with:\n");
LOG(" # export LD_LIBRARY_PATH=/path/to/plugin/folder\n");
LOG(" dlopen: %s\n", dlerror() );
closelog();
exit(EXIT_FAILURE);
}
global.out[i].init = dlsym(global.out[i].handle, "output_init");
if ( global.out[i].init == NULL ) {
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
global.out[i].stop = dlsym(global.out[i].handle, "output_stop");
if ( global.out[i].stop == NULL ) {
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
if ( global.out[i].stop == NULL ) {
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
global.out[i].run = dlsym(global.out[i].handle, "output_run");
if ( global.out[i].run == NULL ) {
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}
這段程式碼就不分析了,相信大家已經很明白了。
嗯,輸入和輸出裝置都準備,下面該幹啥了,不用說肯定幹活了嗎。俗話說“養軍千日用在一時”:
if ( global.in.run() ) {
LOG("can not run input plugin\n");
closelog();
return 1;
}
for(i=0; i<global.outcnt; i++) {
syslog(LOG_INFO, "starting output plugin: %s (ID: %02d)", global.out[i].plugin, global.out[i].param.id);
global.out[i].run(global.out[i].param.id);
}
mjpg-streamer就這樣結束了,貌似還真不過癮,下面我們到input_uvc.c和output_http.c檔案看下:
input_uvc.c(plugins\input_uvc\)
int input_init(input_parameter *param)
這個函式貌似挺長的,其實程式碼也就只是實現對usb攝像頭的格式、幀、請求buf,佇列buf等的一些設定,主要實現也是在函式init_videoIn(videoIn, dev, width, height, fps, format, 1) 中,有興趣的話,大家可以去看看。
int input_stop(void) {
DBG("will cancel input thread\n");
pthread_cancel(cam);
return 0;
}
輸入停止,貌似更加簡單,只要將執行緒取下cancel就ok,那麼run肯定是crete threads了。
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;
}
猜對了,可惜沒分加。下面那些程式碼基本也沒用到,這就不細說了。
output_http.c(plugins\output_http\)
剛開始也肯定是對其初始化,然後stop,最後run。其實我挺喜歡這個專案的編寫者,處處為咱們考慮。
int output_init(output_parameter *param) {
servers[param->id].id = param->id;
servers[param->id].pglobal = param->global;
servers[param->id].conf.port = port;
servers[param->id].conf.credentials = credentials;
servers[param->id].conf.www_folder = www_folder;
servers[param->id].conf.nocommands = nocommands;
}
int output_stop(int id) {
DBG("will cancel server thread #%02d\n", id);
pthread_cancel(servers[id].threadID);
return 0;
}
int output_run(int id) {
DBG("launching server thread #%02d\n", id);
/* create thread and pass context to thread function */
pthread_create(&(servers[id].threadID), NULL, server_thread, &(servers[id]));
pthread_detach(servers[id].threadID);
return 0;
}
我想這個專案分析的差不多了,至於一些小的方面,我不依依細說了,最後送大家一句古語“ 師傅領進門,修行靠個人”,並贈上這個專案的框架圖。