mjpg-streamer结构

refer to
http://www.friendlyarm.net/forum/topic/279?lang=en
http://code.google.com/p/mjpg-streamer-mini2440/
http://code.google.com/p/mjpg-streamer-mini2440/source/checkout

下载mjpg_streamer源码并观察源码结构如下
[root@localhost tmp]# svn checkout http://mjpg-streamer-mini2440.googlecode.com/svn/trunk/ mjpg-streamer-mini2440-read-only
[root@localhost tmp]# cd mjpg-streamer-mini2440-read-only/
[root@localhost mjpg-streamer-mini2440-read-only]# pwd
/tmp/mjpg-streamer-mini2440-read-only

[root@localhost mjpg-streamer-mini2440-read-only]# tree
.


|-- CHANGELOG
|-- LICENSE
|-- Makefile
|-- README


|-- mjpg-streamer-mini2440.kdev4
|-- mjpg_streamer.c
|-- mjpg_streamer.h


|-- plugins
| |-- input.h


| |-- input_control
| | |-- Makefile
| | |-- dynctrl.c
| | |-- dynctrl.h
| | |-- input_uvc.c
| | |-- uvc_compat.h
| | `-- uvcvideo.h


| |-- input_file
| | |-- Makefile
| | `-- input_file.c


| |-- input_gspcav1
| | |-- Makefile
| | |-- encoder.c
| | |-- encoder.h
| | |-- huffman.c
| | |-- huffman.h
| | |-- input_gspcav1.c
| | |-- jconfig.h
| | |-- jdatatype.h
| | |-- marker.c
| | |-- marker.h
| | |-- quant.c
| | |-- quant.h
| | |-- readme.spcacat
| | |-- spcaframe.h
| | |-- spcav4l.c
| | |-- spcav4l.h
| | |-- utils.c
| | `-- utils.h


| |-- input_s3c2410
| | |-- Makefile
| | |-- input_s3c2410.c
| | |-- readme.s3c2410
| | |-- s3c2410.c
| | |-- s3c2410.h
| | |-- utils.c
| | `-- utils.h


| |-- input_testpicture
| | |-- Makefile
| | |-- input_testpicture.c
| | |-- pictures
| | | |-- 160x120_1.jpg
| | | |-- 160x120_2.jpg
| | | |-- 320x240_1.jpg
| | | |-- 320x240_2.jpg
| | | |-- 640x480_1.jpg
| | | |-- 640x480_2.jpg
| | | |-- 960x720_1.jpg
| | | `-- 960x720_2.jpg
| | `-- testpictures.h


| |-- input_uvc
| | |-- Makefile
| | |-- dynctrl.c
| | |-- dynctrl.h
| | |-- huffman.h
| | |-- input_uvc.c
| | |-- jpeg_utils.c
| | |-- jpeg_utils.h
| | |-- uvc_compat.h
| | |-- uvcvideo.h
| | |-- v4l2uvc.c
| | `-- v4l2uvc.h


| |-- output.h


| |-- output_autofocus
| | |-- Makefile
| | |-- output_autofocus.c
| | |-- processJPEG_onlyCenter.c
| | `-- processJPEG_onlyCenter.h


| |-- output_file
| | |-- Makefile
| | `-- output_file.c


| `-- output_http
| |-- Makefile
| |-- httpd.c
| |-- httpd.h
| `-- output_http.c


|-- simplified_jpeg_encoder.c
|-- simplified_jpeg_encoder.h


|-- start_s3c2410.sh
|-- start_uvc.sh
|-- start_uvc_yuv.sh


|-- utils.c
|-- utils.h


`-- www
|-- LICENSE.txt
|-- bodybg.gif
|-- cambozola.jar
|-- control.htm
|-- example.jpg
|-- favicon.ico
|-- favicon.png
|-- fix.css
|-- functions.js
|-- index.html
|-- java.html
|-- java_control.html
|-- java_simple.html
|-- javascript.html
|-- javascript_motiondetection.html
|-- javascript_simple.html
|-- sidebarbg.gif
|-- static.html
|-- static_simple.html
|-- stream.html
|-- stream_simple.html
|-- style.css
`-- videolan.html

12 directories, 104 files
[root@localhost mjpg-streamer-mini2440-read-only]#

其中
主目录下
|-- start_s3c2410.sh
|-- start_uvc.sh
|-- start_uvc_yuv.sh
这几个脚本用于执行mjpg_streamer并传递参数给main


|-- simplified_jpeg_encoder.c
|-- simplified_jpeg_encoder.h
用于对input模块从驱动中取得图像的数据进行编码


mjpg_streamer.h,定义了一个重要的全局结构体globals
mjpg_streamer.c,main函数所在文件,此文件分析启动命令并初始化,并定义静态全局变量 static globals global;

plugins/目录下是一些插件,以so文件形式提供,在装载时动态链接。其中
plugins/input_file
plugins/input_gspcav1
plugins/input_s3c2410目录,读取cmos摄像头驱动中的数据,保存到全局变量global
plugins/input_testpicture
plugins/input_uvc目录,读取usb摄像头驱动中的数据,保存到全局变量global


plugins/output_autofocus
plugins/output_file目录,用于将图像数据保存到文件
plugins/output_http目录,用于监视外部端口(默认8080)的socket请求,将图像数据通过socket发出。即是一个小型web服务器,支持同时发起的xx个链接。


www目录下是几个网页

**************************************************************************************************

对源码试着编译一下---在板子上先插入cmos摄像头,型号0v9650

先修改mjpg_streamer.h,添加DEBUG宏,以便出错时可以知道错在哪里
#define DEBUG
再修改Makefile,指定编译器,并指定生成input_s3c2410.so插件
CC = arm-linux-gcc
PLUGINS += input_s3c2410.so
执行
make
make package
此时会生成mjpg-streamer-mini2440-bin.tar.gz,考到板子上解压后进入它的目录

执行
./start_s3c2410.sh
结果如下
MJPG Streamer Version.: 2.0
 DBG(input_s3c2410.c, input_init(), 95): argv[0]=S3C2410 embedded camera
 DBG(input_s3c2410.c, input_init(), 169): initializing s3c2410 device
 i: Using V4L2 device.: /dev/video0
 i: Desired Resolution: 640 x 512
 i: Grayscale mode: off
 DBG(s3c2410.c, init_s3c2410(), 63): Opening device
ERROR opening V4L interface

即打开video0时出现问题,修改start_s3c2410.sh ,在-i参数里指定设备文件为/dev/camera,如下
export LD_LIBRARY_PATH="$(pwd)"
./mjpg_streamer -o "output_http.so -w ./www" -i "input_s3c2410.so -d /dev/camera"

再次执行./start_s3c2410.sh
结果如下
MJPG Streamer Version.: 2.0
 DBG(input_s3c2410.c, input_init(), 95): argv[0]=S3C2410 embedded camera
 DBG(input_s3c2410.c, input_init(), 95): argv[1]=-d
 DBG(input_s3c2410.c, input_init(), 95): argv[2]=/dev/camera
 DBG(input_s3c2410.c, input_init(), 124): case d
 DBG(input_s3c2410.c, input_init(), 169): initializing s3c2410 device
 i: Using V4L2 device.: /dev/camera
 i: Desired Resolution: 640 x 512
 i: Grayscale mode: off
 DBG(s3c2410.c, init_s3c2410(), 63): Opening device
Error opening device /dev/camera: unable to query device.
 i: init_s3c2410 failed

即初始化时出现问题,不能查询设备。
根据提示找到源码s3c2410.c line 63附近

DBG("Opening device\n");//line 63
if ((vd->fd = open( vd->videodevice, O_RDWR)) == -1)
exit_fatal ("ERROR opening V4L interface");


memset(&vd->cap, 0, sizeof(struct v4l2_capability));
err = ioctl(vd->fd, VIDIOC_QUERYCAP, &vd->cap);
if (err < 0) {
fprintf(stderr, "Error opening device %s: unable to query device.\n", vd->videodevice);
return err;
}

可知unable to query device.是由于设备不支持ioctl方法而导致的。
很奇怪哦,怎么友善给的编译好的mjpg-streamer-mini2440-bin-r6.tar.gz放在miroc2440上就可以直接使用,而下载的源码再编译就不可以了呢。。。
发现友善的编译好的是r6版本的,而用svn checkout http://mjpg-streamer-mini2440.googlecode.com/svn/trunk/ mjpg-streamer-mini2440-read-only下载到的是最新版本,而在最新版本中加入了ioctl方法对摄像头操作。所以要下一个r6版的mjpg-streamer。不必全下,只把r6版的s3c2410.c下载下来替换掉最新版里的s3c2410.c即可。
http://code.google.com/p/mjpg-streamer-mini2440/source/browse/trunk/plugins/input_s3c2410/s3c2410.c?r=6

再次按照上面所讲编译后下到板子上,执行start_s3c2410.sh,success!如下

[root@FriendlyARM bin6]# ./start_s3c2410.sh
MJPG Streamer Version.: 2.0
DBG(input_s3c2410.c, input_init(), 95): argv[0]=S3C2410 embedded camera
DBG(input_s3c2410.c, input_init(), 95): argv[1]=-d
DBG(input_s3c2410.c, input_init(), 95): argv[2]=/dev/camera
DBG(input_s3c2410.c, input_init(), 124): case d
DBG(input_s3c2410.c, input_init(), 169): initializing s3c2410 device
i: Using V4L2 device.: /dev/camera
i: Desired Resolution: 640 x 512
i: Grayscale mode: off
DBG(s3c2410.c, init_s3c2410(), 45): Opening device
DBG(s3c2410.c, init_s3c2410(), 50): Allocating input buffers
Allocated
DBG(output_http.c, output_init(), 85): output #00
DBG(output_http.c, output_init(), 118): argv[0]=HTTP output plugin
DBG(output_http.c, output_init(), 118): argv[1]=-w
DBG(output_http.c, output_init(), 118): argv[2]=./www
DBG(output_http.c, output_init(), 176): case 6,7
o: www-folder-path...: ./www/
o: HTTP TCP port.....: 8080
o: username:password.: disabled
o: commands..........: enabled
DBG(mjpg_streamer.c, main(), 374): starting input plugin
……

我稍微修改过的mjpg-streamer源码
http://download.csdn.net/detail/songqqnew/3841684
**************************************************************************************************先看几个结构体


typedef struct _globals globals;
struct _globals {
int stop; //控制读写线程的读写动作进行与否


pthread_mutex_t db; //互斥量,用于读和写线程的同步
pthread_cond_t db_update;


unsigned char *buf; //全局数据区域,图像数据
int size; //图像数据大小


input in; //输入模块


output out[MAX_OUTPUT_PLUGINS];//输出模块

#define MAX_OUTPUT_PLUGINS 10,可知最多能同时将图像数据输出到10个地方,比如到file,到http
int outcnt; //输出数目,即记录命令参数中有几个 -o

};




typedef struct _input input;
struct _input {
char *plugin; //so文件名,比如-i "input_s3c2410.so -d&nbsp; /dev/camera -r 320x240",则plugin="input_s3c2410.so"
void *handle; //so句柄
input_parameter param; //参数

int (*init)(input_parameter *);//初始化函数指针
int (*stop)(void); //打开函数指针
int (*run)(void); //执行函数指针
int (*cmd)(in_cmd_type, int); //命令函数指针
};


typedef struct _input_parameter input_parameter;
struct _input_parameter {
char *parameter_string; //比如-i "input_s3c2410.so -d&nbsp; /dev/camera -r 320x240",则parameter_string="-d&nbsp; /dev/camera -r 320x240"
struct _globals *global; //回指全局结构体
};



typedef struct _output output;
struct _output {
char *plugin; //比如 -o "output_http.so -w ./www",则plugin="output_http.so"
void *handle; //so句柄
output_parameter param; //参数

int (*init)(output_parameter *);
int (*stop)(int);
int (*run)(int);
int (*cmd)(int, out_cmd_type, int);
};


typedef struct _output_parameter output_parameter;
struct _output_parameter {
int id; //记录这是第几个输出(-o)
char *parameter_string; //比如 -o "output_http.so -w ./www",则parameter_string="-w ./www"
struct _globals *global; //回指全局结构体
};

//mjpg-streamer.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <linux/videodev.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <getopt.h>
#include <pthread.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <syslog.h>

#include "utils.h"
#include "mjpg_streamer.h"


static globals global;


void help(char *progname)
{
fprintf(stderr, "-----------------------------------------------------------------------\n");
fprintf(stderr, "Usage: %s\n" \
" -i | --input \"<input-plugin.so> [parameters]\"\n" \
" -o | --output \"<output-plugin.so> [parameters]\"\n" \
" [-h | --help ]........: display this help\n" \
" [-v | --version ].....: display version information\n" \
" [-b | --background]...: fork to the background, daemon mode\n", progname);
fprintf(stderr, "-----------------------------------------------------------------------\n");
fprintf(stderr, "Example #1:\n" \
" To open an UVC webcam \"/dev/video1\" and stream it via HTTP:\n" \
" %s -i \"input_uvc.so -d /dev/video1\" -o \"output_http.so\"\n", progname);
fprintf(stderr, "-----------------------------------------------------------------------\n");
fprintf(stderr, "Example #2:\n" \
" To open an UVC webcam and stream via HTTP port 8090:\n" \
" %s -i \"input_uvc.so\" -o \"output_http.so -p 8090\"\n", progname);
fprintf(stderr, "-----------------------------------------------------------------------\n");
fprintf(stderr, "Example #3:\n" \
" To get help for a certain input plugin:\n" \
" %s -i \"input_uvc.so --help\"\n", progname);
fprintf(stderr, "-----------------------------------------------------------------------\n");
fprintf(stderr, "In case the modules (=plugins) can not be found:\n" \
" * Set the default search path for the modules with:\n" \
" export LD_LIBRARY_PATH=/path/to/plugins,\n" \
" * or put the plugins into the \"/lib/\" or \"/usr/lib\" folder,\n" \
" * or instead of just providing the plugin file name, use a complete\n" \
" path and filename:\n" \
" %s -i \"/path/to/modules/input_uvc.so\"\n", progname);
fprintf(stderr, "-----------------------------------------------------------------------\n");
}


void signal_handler(int sig)
{
int i;


LOG("setting signal to stop\n");
global.stop = 1;
usleep(1000*1000);


LOG("force cancelation of threads and cleanup ressources\n");
global.in.stop();
for(i=0; i<global.outcnt; i++) {
global.out[i].stop(global.out[i].param.id);
}
usleep(1000*1000);


dlclose(&global.in.handle);
for(i=0; i<global.outcnt; i++) {

dlclose(global.out[i].handle);
}
DBG("all plugin handles closed\n");

pthread_cond_destroy(&global.db_update);
pthread_mutex_destroy(&global.db);

LOG("done\n");

closelog();
exit(0);
return;
}


int control(int command, char *details) {

switch(command) {
case CONTROL_CMD_RECONFIGURE_INPUT:
printf("will reload input plugin: %s\n", details);
break;
default:
LOG("unknown control command received\n");
}
return 0;
}



int main(int argc, char *argv[])
{

//默认参数;
char *input = "input_uvc.so --resolution 640x480 --fps 5 --device /dev/video0";
char *output[MAX_OUTPUT_PLUGINS];
int daemon=0, i;
size_t tmp=0;

output[0] = "output_http.so --port 8080";
global.outcnt = 0;

global.control = control;


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);


if (c == -1) break;


if(c=='?'){ help(argv[0]); return 0; }

switch (option_index) {

case 0:
case 1:
help(argv[0]);
return 0;
break;


case 2:
case 3:
input = strdup(optarg);
break;


case 4:
case 5:
output[global.outcnt++] = strdup(optarg);//每遇到一个-o选项outcnt就+1,,所以outcnt代表输出的个数
break;


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;


case 8:
case 9:
daemon=1;
break;

default:
help(argv[0]);
return 0;
}
}

openlog("MJPG-streamer ", LOG_PID|LOG_CONS, LOG_USER);
//openlog("MJPG-streamer ", LOG_PID|LOG_CONS|LOG_PERROR, LOG_USER);
syslog(LOG_INFO, "starting application");


if ( daemon ) {
LOG("enabling daemon mode");
daemon_mode();
}




global.stop = 0;
global.buf = NULL;
global.size = 0;
global.in.plugin = NULL;


if( pthread_mutex_init(&global.db, NULL) != 0 ) {
LOG("could not initialize mutex variable\n");
closelog();
exit(EXIT_FAILURE);
}
if( pthread_cond_init(&global.db_update, NULL) != 0 ) {
LOG("could not initialize condition variable\n");
closelog();
exit(EXIT_FAILURE);
}


signal(SIGPIPE, SIG_IGN);


if (signal(SIGINT, signal_handler) == SIG_ERR) {
LOG("could not register signal handler\n");
closelog();
exit(EXIT_FAILURE);
}


LOG("MJPG Streamer Version.: %s\n", SOURCE_VERSION);


if ( global.outcnt == 0 ) {

global.outcnt = 1;
}




tmp = (size_t)(strchr(input, ' ')-input);


global.in.plugin = (tmp > 0)?strndup(input, tmp):strdup(input);
&nbsp; //比如./mjpg_streamer -o "output_http.so -w ./www" -i "input_s3c2410.so -d&nbsp; /dev/camera -r 320x240"
&nbsp; //则char* input="input_s3c2410.so -d&nbsp; /dev/camera -r 320x240"
&nbsp; //则char* global.in.plugin ="input_s3c2410.so"

global.in.handle = dlopen(global.in.plugin, RTLD_LAZY);
&nbsp; //以RTLD_LAZY方式打开input_s3c2410.so,装载进内存
&nbsp; //RTLD_LAZY延迟绑定
&nbsp; //可见各个so都是以插件形式装载进内存
&nbsp; //一般需要动态链接器去装载
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");
//查找符号,在刚装载的模块比如input_s3c2410.so内存单元范围内中查找符号input_init,其虚拟地址已经确定
//找到,返回地址,赋给结构体global的成员结构体in的成员init函数指针
//即用input_init去填充in结构体的函数指针init
if ( global.in.init == NULL ) {
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}

global.in.stop = dlsym(global.in.handle, "input_stop");
//查找符号,在刚装载的模块比如input_s3c2410.so内存单元范围内中查找符号input_stop,
if ( global.in.stop == NULL ) {
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}

global.in.run = dlsym(global.in.handle, "input_run");
//查找符号,在刚装载的模块比如input_s3c2410.so内存单元范围内中查找符号input_run,
if ( global.in.run == NULL ) {
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}


global.in.cmd = dlsym(global.in.handle, "input_cmd");
//查找符号,在刚装载的模块比如input_s3c2410.so内存单元范围内中查找符号input_cmd,

global.in.param.parameter_string = strchr(input, ' ');
&nbsp; //比如./mjpg_streamer -o "output_http.so -w ./www" -i "input_s3c2410.so -d /dev/camera -r 320x240"
&nbsp; //则char*&nbsp; input="input_s3c2410.so -d&nbsp; /dev/camera -r 320x240"
&nbsp; //则char*&nbsp; global.in.plugin ="input_s3c2410.so"
&nbsp; //则char*&nbsp; global.in.param.parameter_string ="-d /dev/camera -r 320x240"

&nbsp; global.in.param.global = &global;

if ( global.in.init(&global.in.param) ) {
LOG("input_init() return value signals to exit");
closelog();
exit(0);
}


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);
&nbsp; //比如./mjpg_streamer -o "output_http.so -w ./www" -i "input_s3c2410.so -d&nbsp; /dev/camera -r 320x240"
&nbsp; //则char*&nbsp; output[0]="output_http.so -w ./www"
&nbsp; //则char*&nbsp; global.out[0].plugin ="output_http.so"

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);
}
global.out[i].run = dlsym(global.out[i].handle, "output_run");
if ( global.out[i].run == NULL ) {
LOG("%s\n", dlerror());
exit(EXIT_FAILURE);
}

global.out[i].cmd = dlsym(global.out[i].handle, "output_cmd");

global.out[i].param.parameter_string = strchr(output[i], ' ');
&nbsp; //比如./mjpg_streamer -o "output_http.so -w ./www" -i "input_s3c2410.so -d&nbsp; /dev/camera -r 320x240"
&nbsp; //则char*&nbsp; output[0]="output_http.so -w ./www"
&nbsp; //则char*&nbsp; global.out[0].plugin ="output_http.so"
&nbsp; //则char*&nbsp; global.out[0].param.parameter_string ="-w ./www"

global.out[i].param.global = &global;
global.out[i].param.id = i;
//i用于记录-o的索引,表示第几个输出(-o)

if ( global.out[i].init(&global.out[i].param) ) {
LOG("output_init() return value signals to exit");
closelog();
exit(0);
}
}




DBG("starting input plugin\n");
syslog(LOG_INFO, "starting input plugin");
if ( global.in.run() ) {
LOG("can not run input plugin\n");
closelog();
return 1;
}



DBG("starting %d output plugin(s)\n", global.outcnt);
for(i=0; i<global.outcnt; i++) {
syslog(LOG_INFO, "starting output plugin: %s (ID: d)", global.out[i].plugin, global.out[i].param.id);
global.out[i].run(global.out[i].param.id);
传递的是id(表示第几个-o)
}


pause();

return 0;
}

可知mjpg-streamer.c的作用主要是接受并分析启动参数,然后去加载起读写作用的部分--插件即那几个so.并
从输入插件中寻找以下符号
input_init()
input_stop()
input_run()
input_cmd()
从输出插件中寻找以下符号
output_init()
output_stop()
output_run()
output_cmd()
分别赋给globals的对应函数指针
然后执行
input_init(),output_init(),input_run(),output_run(),跳到对应代码里面。见下文。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值