1. 类定义

1.1 SrsConfig 类

/**
* the config service provider.
* for the config supports reload, so never keep the reference cross st-thread,
* that is, never save the SrsConfDirective* get by any api of config,
* for it maybe free in the reload st-thread cycle.
* you can keep it before st-thread switch, or simply never keep it.
*/
class SrsConfig
{
// user command
private:
/**
* whether srs is run in dolphin mode.
* @see https://github.com/ossrs/srs-dolphin
*/
bool dolphin;
std::string dolphin_rtmp_port;
std::string dolphin_http_port;
/**
* whether show help and exit.
*/
bool show_help;
/**
* whether test config file and exit.
*/
bool test_conf;
/**
* whether show SRS version and exit.
*/
bool show_version;
// global env variables.
private:
/**
* the user parameters, the argc and argv.
* the argv is " ".join(argv), where argv is from main(argc, argv).
*/
std::string _argv;
/**
* current working directory.
*/
std::string _cwd;
// config section
private:
/**
* the last parsed config file.
* if reload, reload the config file.
*/
std::string config_file;
/**
* the directive root.
*/
SrsConfDirective* root;
// reload section
private:
/**
* the reload subscribers, when reload, callback all handlers.
*/
std::vector<ISrsReloadHandler*> subscribes;
public:
SrsConfig();
virtual ~SrsConfig();
// dolphin
public:
/**
* whether srs is in dolphin mode.
*/
virtual bool is_dolphin();
private:
virtual void set_config_directive(SrsConfDirective* parent, std::string dir, std::string value);
// reload
public:
/**
* for reload handler to register itself,
* when config service do the reload, callback the handler.
*/
virtual void subscribe(ISrsReloadHandler* handler);
/**
* for reload handler to unregister itself.
*/
virtual void unsubscribe(ISrsReloadHandler* handler);
/**
* reload the config file.
* @remark, user can test the config before reload it.
*/
virtual int reload();
private:
/**
* reload the vhost section of config.
*/
virtual int reload_vhost(SrsConfDirective* old_root);
protected:
/**
* reload from the config.
* @remark, use protected for the utest to override with mock.
*/
virtual int reload_conf(SrsConfig* conf);
private:
/**
* reload the http_api section of config.
*/
virtual int reload_http_api(SrsConfDirective* old_root);
/**
* reload the http_stream section of config.
*/
virtual int reload_http_stream(SrsConfDirective* old_root);
/**
* reload the transcode section of vhost of config.
*/
virtual int reload_transcode(SrsConfDirective* new_vhost, SrsConfDirective* old_vhost);
/**
* reload the ingest section of vhost of config.
*/
virtual int reload_ingest(SrsConfDirective* new_vhost, SrsConfDirective* old_vhost);
// parse options and file
public:
/**
* parse the cli, the main(argc,argv) function.
*/
virtual int parse_options(int argc, char** argv);
/**
* initialize the cwd for server,
* because we may change the workdir.
*/
virtual int initialize_cwd();
/**
* get the config file path.
*/
virtual std::string config();
private:
/**
* parse each argv.
*/
virtual int parse_argv(int& i, char** argv);
/**
* print help and exit.
*/
virtual void print_help(char** argv);
public:
/**
* parse the config file, which is specified by cli.
*/
virtual int parse_file(const char* filename);
/**
* check the parsed config.
*/
virtual int check_config();
protected:
/**
* parse config from the buffer.
* @param buffer, the config buffer, user must delete it.
* @remark, use protected for the utest to override with mock.
*/
virtual int parse_buffer(_srs_internal::SrsConfigBuffer* buffer);
// global env
public:
/**
* get the current work directory.
*/
virtual std::string cwd();
/**
* get the cli, the main(argc,argv), program start command.
*/
virtual std::string argv();
// global section
public:
/**
* get the directive root, corresponding to the config file.
* the root directive, no name and args, contains directives.
* all directive parsed can retrieve from root.
*/
virtual SrsConfDirective* get_root();
/**
* get the deamon config.
* if true, SRS will run in deamon mode, fork and fork to reap the
* grand-child process to init process.
*/
virtual bool get_deamon();
/**
* get the max connections limit of system.
* if exceed the max connection, SRS will disconnect the connection.
* @remark, linux will limit the connections of each process,
* for example, when you need SRS to service 10000+ connections,
* user must use "ulimit -HSn 10000" and config the max connections
* of SRS.
*/
virtual int get_max_connections();
/**
* get the listen port of SRS.
* user can specifies multiple listen ports,
* each args of directive is a listen port.
*/
virtual std::vector<std::string> get_listens();
/**
* get the pid file path.
* the pid file is used to save the pid of SRS,
* use file lock to prevent multiple SRS starting.
* @remark, if user need to run multiple SRS instance,
* for example, to start multiple SRS for multiple CPUs,
* user can use different pid file for each process.
*/
virtual std::string get_pid_file();
/**
* get pithy print pulse ms,
* for example, all rtmp connections only print one message
* every this interval in ms.
*/
virtual int get_pithy_print_ms();
/**
* whether use utc-time to format the time.
*/
virtual bool get_utc_time();
/**
* get the configed work dir.
* ignore if empty string.
*/
virtual std::string get_work_dir();
// whether use asprocess mode.
virtual bool get_asprocess();
// stream_caster section
public:
/**
* get all stream_caster in config file.
*/
virtual std::vector<SrsConfDirective*> get_stream_casters();
/**
* get whether the specified stream_caster is enabled.
*/
virtual bool get_stream_caster_enabled(SrsConfDirective* sc);
/**
* get the engine of stream_caster, the caster config.
*/
virtual std::string get_stream_caster_engine(SrsConfDirective* sc);
/**
* get the output rtmp url of stream_caster, the output config.
*/
virtual std::string get_stream_caster_output(SrsConfDirective* sc);
/**
* get the listen port of stream caster.
*/
virtual int get_stream_caster_listen(SrsConfDirective* sc);
/**
* get the min udp port for rtp of stream caster rtsp.
*/
virtual int get_stream_caster_rtp_port_min(SrsConfDirective* sc);
/**
* get the max udp port for rtp of stream caster rtsp.
*/
virtual int get_stream_caster_rtp_port_max(SrsConfDirective* sc);
// vhost specified section
public:
/**
* get the vhost directive by vhost name.
* @param vhost, the name of vhost to get.
*/
virtual SrsConfDirective* get_vhost(std::string vhost);
/**
* get all vhosts in config file.
*/
virtual void get_vhosts(std::vector<SrsConfDirective*>& vhosts);
/**
* whether vhost is enabled
* @param vhost, the vhost name.
* @return true when vhost is ok; otherwise, false.
*/
virtual bool get_vhost_enabled(std::string vhost);
/**
* whether vhost is enabled
* @param vhost, the vhost directive.
* @return true when vhost is ok; otherwise, false.
*/
virtual bool get_vhost_enabled(SrsConfDirective* vhost);
/**
* whether gop_cache is enabled of vhost.
* gop_cache used to cache last gop, for client to fast startup.
* @return true when gop_cache is ok; otherwise, false.
* @remark, default true.
*/
virtual bool get_gop_cache(std::string vhost);
/**
* whether debug_srs_upnode is enabled of vhost.
* debug_srs_upnode is very important feature for tracable log,
* but some server, for instance, flussonic donot support it.
* @see https://github.com/ossrs/srs/issues/160
* @return true when debug_srs_upnode is ok; otherwise, false.
* @remark, default true.
*/
virtual bool get_debug_srs_upnode(std::string vhost);
/**
* whether atc is enabled of vhost.
* atc always use encoder timestamp, SRS never adjust the time.
* @return true when atc is ok; otherwise, false.
* @remark, default false.
*/
virtual bool get_atc(std::string vhost);
/**
* whether atc_auto is enabled of vhost.
* atc_auto used to auto enable atc, when metadata specified the bravo_atc.
* @return true when atc_auto is ok; otherwise, false.
* @remark, default true.
*/
virtual bool get_atc_auto(std::string vhost);
/**
* get the time_jitter algorithm.
* @return the time_jitter algorithm, defined in SrsRtmpJitterAlgorithm.
* @remark, default full.
*/
virtual int get_time_jitter(std::string vhost);
/**
* whether use mix correct algorithm to ensure the timestamp
* monotonically increase.
*/
virtual bool get_mix_correct(std::string vhost);
/**
* get the cache queue length, in seconds.
* when exceed the queue length, drop packet util I frame.
* @remark, default 10.
*/
virtual double get_queue_length(std::string vhost);
/**
* get the refer antisuck directive.
* each args of directive is a refer config.
* when the client refer(pageUrl) not match the refer config,
* SRS will reject the connection.
* @remark, default NULL.
*/
virtual SrsConfDirective* get_refer(std::string vhost);
/**
* get the play refer, refer for play clients.
* @remark, default NULL.
*/
virtual SrsConfDirective* get_refer_play(std::string vhost);
/**
* get the publish refer, refer for publish clients.
* @remark, default NULL.
*/
virtual SrsConfDirective* get_refer_publish(std::string vhost);
/**
* get the chunk size of vhost.
* @param vhost, the vhost to get the chunk size. use global if not specified.
* empty string to get the global.
* @remark, default 60000.
*/
virtual int get_chunk_size(std::string vhost);
/**
* whether parse the sps when publish stream to SRS.
*/
virtual bool get_parse_sps(std::string vhost);
/**
* whether mr is enabled for vhost.
* @param vhost, the vhost to get the mr.
*/
virtual bool get_mr_enabled(std::string vhost);
/**
* get the mr sleep time in ms for vhost.
* @param vhost, the vhost to get the mr sleep time.
*/
// TODO: FIXME: add utest for mr config.
virtual int get_mr_sleep_ms(std::string vhost);
/**
* get the mw sleep time in ms for vhost.
* @param vhost, the vhost to get the mw sleep time.
*/
// TODO: FIXME: add utest for mw config.
virtual int get_mw_sleep_ms(std::string vhost);
/**
* whether min latency mode enabled.
* @param vhost, the vhost to get the min_latency.
*/
// TODO: FIXME: add utest for min_latency.
virtual bool get_realtime_enabled(std::string vhost);
/**
* whether enable tcp nodelay for all clients of vhost.
*/
virtual bool get_tcp_nodelay(std::string vhost);
/**
* the minimal send interval in ms.
*/
virtual double get_send_min_interval(std::string vhost);
/**
* whether reduce the sequence header.
*/
virtual bool get_reduce_sequence_header(std::string vhost);
/**
* the 1st packet timeout in ms for encoder.
*/
virtual int get_publish_1stpkt_timeout(std::string vhost);
/**
* the normal packet timeout in ms for encoder.
*/
virtual int get_publish_normal_timeout(std::string vhost);
private:
/**
* get the global chunk size.
*/
virtual int get_global_chunk_size();
// forward section
public:
/**
* get the forward directive of vhost.
*/
virtual SrsConfDirective* get_forward(std::string vhost);
// http_hooks section
private:
/**
* get the http_hooks directive of vhost.
*/
virtual SrsConfDirective* get_vhost_http_hooks(std::string vhost);
public:
/**
* whether vhost http-hooks enabled.
* @remark, if not enabled, donot callback all http hooks.
*/
virtual bool get_vhost_http_hooks_enabled(std::string vhost);
/**
* get the on_connect callbacks of vhost.
* @return the on_connect callback directive, the args is the url to callback.
*/
virtual SrsConfDirective* get_vhost_on_connect(std::string vhost);
/**
* get the on_close callbacks of vhost.
* @return the on_close callback directive, the args is the url to callback.
*/
virtual SrsConfDirective* get_vhost_on_close(std::string vhost);
/**
* get the on_publish callbacks of vhost.
* @return the on_publish callback directive, the args is the url to callback.
*/
virtual SrsConfDirective* get_vhost_on_publish(std::string vhost);
/**
* get the on_unpublish callbacks of vhost.
* @return the on_unpublish callback directive, the args is the url to callback.
*/
virtual SrsConfDirective* get_vhost_on_unpublish(std::string vhost);
/**
* get the on_play callbacks of vhost.
* @return the on_play callback directive, the args is the url to callback.
*/
virtual SrsConfDirective* get_vhost_on_play(std::string vhost);
/**
* get the on_stop callbacks of vhost.
* @return the on_stop callback directive, the args is the url to callback.
*/
virtual SrsConfDirective* get_vhost_on_stop(std::string vhost);
/**
* get the on_dvr callbacks of vhost.
* @return the on_dvr callback directive, the args is the url to callback.
*/
virtual SrsConfDirective* get_vhost_on_dvr(std::string vhost);
/**
* get the on_hls callbacks of vhost.
* @return the on_hls callback directive, the args is the url to callback.
*/
virtual SrsConfDirective* get_vhost_on_hls(std::string vhost);
/**
* get the on_hls_notify callbacks of vhost.
* @return the on_hls_notify callback directive, the args is the url to callback.
*/
virtual SrsConfDirective* get_vhost_on_hls_notify(std::string vhost);
// bwct(bandwidth check tool) section
public:
/**
* whether bw check enabled for vhost.
* if enabled, serve all clients with bandwidth check services.
* oterwise, serve all cleints with stream.
*/
virtual bool get_bw_check_enabled(std::string vhost);
/**
* the key of server, if client key mot match, reject.
*/
virtual std::string get_bw_check_key(std::string vhost);
/**
* the check interval, in ms.
* if the client request check in very short time(in the interval),
* SRS will reject client.
* @remark this is used to prevent the bandwidth check attack.
*/
virtual int get_bw_check_interval_ms(std::string vhost);
/**
* the max kbps that user can test,
* if exceed the kbps, server will slowdown the send-recv.
* @remark this is used to protect the service bandwidth.
*/
virtual int get_bw_check_limit_kbps(std::string vhost);
// vhost edge section
public:
/**
* whether vhost is edge mode.
* for edge, publish client will be proxyed to upnode,
* for edge, play client will share a connection to get stream from upnode.
*/
virtual bool get_vhost_is_edge(std::string vhost);
/**
* whether vhost is edge mode.
* for edge, publish client will be proxyed to upnode,
* for edge, play client will share a connection to get stream from upnode.
*/
virtual bool get_vhost_is_edge(SrsConfDirective* vhost);
/**
* get the origin config of edge,
* specifies the origin ip address, port.
*/
virtual SrsConfDirective* get_vhost_edge_origin(std::string vhost);
/**
* whether edge token tranverse is enabled,
* if true, edge will send connect origin to verfy the token of client.
* for example, we verify all clients on the origin FMS by server-side as,
* all clients connected to edge must be tranverse to origin to verify.
*/
virtual bool get_vhost_edge_token_traverse(std::string vhost);
/**
* get the transformed vhost for edge,
* @see https://github.com/ossrs/srs/issues/372
*/
virtual std::string get_vhost_edge_transform_vhost(std::string vhost);
// vhost security section
public:
/**
* whether the secrity of vhost enabled.
*/
virtual bool get_security_enabled(std::string vhost);
/**
* get the security rules.
*/
virtual SrsConfDirective* get_security_rules(std::string vhost);
// vhost transcode section
public:
/**
* get the transcode directive of vhost in specified scope.
* @param vhost, the vhost name to get the transcode directive.
* @param scope, the scope, empty to get all. for example, user can transcode
* the app scope stream, by config with app:
* transcode live {...}
* when the scope is "live", this directive is matched.
* the scope can be: empty for all, app, app/stream.
* @remark, please see the samples of full.conf, the app.transcode.srs.com
* and stream.transcode.srs.com.
*/
virtual SrsConfDirective* get_transcode(std::string vhost, std::string scope);
/**
* whether the transcode directive is enabled.
*/
virtual bool get_transcode_enabled(SrsConfDirective* transcode);
/**
* get the ffmpeg tool path of transcode.
*/
virtual std::string get_transcode_ffmpeg(SrsConfDirective* transcode);
/**
* get the engines of transcode.
*/
virtual std::vector<SrsConfDirective*> get_transcode_engines(SrsConfDirective* transcode);
/**
* whether the engine is enabled.
*/
virtual bool get_engine_enabled(SrsConfDirective* engine);
/**
* get the iformat of engine
*/
virtual std::string get_engine_iformat(SrsConfDirective* engine);
/**
* get the vfilter of engine,
* the video filter set before the vcodec of FFMPEG.
*/
virtual std::vector<std::string> get_engine_vfilter(SrsConfDirective* engine);
/**
* get the vcodec of engine,
* the codec of video, can be vn, copy or libx264
*/
virtual std::string get_engine_vcodec(SrsConfDirective* engine);
/**
* get the vbitrate of engine,
* the bitrate in kbps of video, for example, 800kbps
*/
virtual int get_engine_vbitrate(SrsConfDirective* engine);
/**
* get the vfps of engine.
* the video fps, for example, 25fps
*/
virtual double get_engine_vfps(SrsConfDirective* engine);
/**
* get the vwidth of engine,
* the video width, for example, 1024
*/
virtual int get_engine_vwidth(SrsConfDirective* engine);
/**
* get the vheight of engine,
* the video height, for example, 576
*/
virtual int get_engine_vheight(SrsConfDirective* engine);
/**
* get the vthreads of engine,
* the video transcode libx264 threads, for instance, 8
*/
virtual int get_engine_vthreads(SrsConfDirective* engine);
/**
* get the vprofile of engine,
* the libx264 profile, can be high,main,baseline
*/
virtual std::string get_engine_vprofile(SrsConfDirective* engine);
/**
* get the vpreset of engine,
* the libx264 preset, can be ultrafast,superfast,veryfast,faster,fast,medium,slow,slower,veryslow,placebo
*/
virtual std::string get_engine_vpreset(SrsConfDirective* engine);
/**
* get the additional video params.
*/
virtual std::vector<std::string> get_engine_vparams(SrsConfDirective* engine);
/**
* get the acodec of engine,
* the audio codec can be an, copy or libfdk_aac
*/
virtual std::string get_engine_acodec(SrsConfDirective* engine);
/**
* get the abitrate of engine,
* the audio bitrate in kbps, for instance, 64kbps.
*/
virtual int get_engine_abitrate(SrsConfDirective* engine);
/**
* get the asample_rate of engine,
* the audio sample_rate, for instance, 44100HZ
*/
virtual int get_engine_asample_rate(SrsConfDirective* engine);
/**
* get the achannels of engine,
* the audio channel, for instance, 1 for mono, 2 for stereo.
*/
virtual int get_engine_achannels(SrsConfDirective* engine);
/**
* get the aparams of engine,
* the audio additional params.
*/
virtual std::vector<std::string> get_engine_aparams(SrsConfDirective* engine);
/**
* get the oformat of engine
*/
virtual std::string get_engine_oformat(SrsConfDirective* engine);
/**
* get the output of engine, for example, rtmp://localhost/live/livestream,
* @remark, we will use some variable, for instance, [vhost] to substitude with vhost.
*/
virtual std::string get_engine_output(SrsConfDirective* engine);
// vhost ingest section
public:
/**
* get the ingest directives of vhost.
*/
virtual std::vector<SrsConfDirective*> get_ingesters(std::string vhost);
/**
* get specified ingest.
*/
virtual SrsConfDirective* get_ingest_by_id(std::string vhost, std::string ingest_id);
/**
* whether ingest is enalbed.
*/
virtual bool get_ingest_enabled(SrsConfDirective* ingest);
/**
* get the ingest ffmpeg tool
*/
virtual std::string get_ingest_ffmpeg(SrsConfDirective* ingest);
/**
* get the ingest input type, file or stream.
*/
virtual std::string get_ingest_input_type(SrsConfDirective* ingest);
/**
* get the ingest input url.
*/
virtual std::string get_ingest_input_url(SrsConfDirective* ingest);
// log section
public:
/**
* whether log to file.
*/
virtual bool get_log_tank_file();
/**
* get the log level.
*/
virtual std::string get_log_level();
/**
* get the log file path.
*/
virtual std::string get_log_file();
/**
* whether ffmpeg log enabled
*/
virtual bool get_ffmpeg_log_enabled();
/**
* the ffmpeg log dir.
* @remark, /dev/null to disable it.
*/
virtual std::string get_ffmpeg_log_dir();
// hls section
private:
/**
* get the hls directive of vhost.
*/
virtual SrsConfDirective* get_hls(std::string vhost);
public:
/**
* whether HLS is enabled.
*/
virtual bool get_hls_enabled(std::string vhost);
/**
* get the HLS m3u8 list ts segment entry prefix info.
*/
virtual std::string get_hls_entry_prefix(std::string vhost);
/**
* get the HLS ts/m3u8 file store path.
*/
virtual std::string get_hls_path(std::string vhost);
/**
* get the HLS m3u8 file path template.
*/
virtual std::string get_hls_m3u8_file(std::string vhost);
/**
* get the HLS ts file path template.
*/
virtual std::string get_hls_ts_file(std::string vhost);
/**
* whether enable the floor(timestamp/hls_fragment) for variable timestamp.
*/
virtual bool get_hls_ts_floor(std::string vhost);
/**
* get the hls fragment time, in seconds.
*/
virtual double get_hls_fragment(std::string vhost);
/**
* get the hls td(target duration) ratio.
*/
virtual double get_hls_td_ratio(std::string vhost);
/**
* get the hls aof(audio overflow) ratio.
*/
virtual double get_hls_aof_ratio(std::string vhost);
/**
* get the hls window time, in seconds.
* a window is a set of ts, the ts collection in m3u8.
* @remark SRS will delete the ts exceed the window.
*/
virtual double get_hls_window(std::string vhost);
/**
* get the hls hls_on_error config.
* the ignore will ignore error and disable hls.
* the disconnect will disconnect publish connection.
* @see https://github.com/ossrs/srs/issues/264
*/
virtual std::string get_hls_on_error(std::string vhost);
/**
* get the HLS default audio codec.
*/
virtual std::string get_hls_acodec(std::string vhost);
/**
* get the HLS default video codec.
*/
virtual std::string get_hls_vcodec(std::string vhost);
/**
* whether cleanup the old ts files.
*/
virtual bool get_hls_cleanup(std::string vhost);
/**
* the timeout to dispose the hls.
*/
virtual int get_hls_dispose(std::string vhost);
/**
* whether reap the ts when got keyframe.
*/
virtual bool get_hls_wait_keyframe(std::string vhost);
/**
* get the size of bytes to read from cdn network, for the on_hls_notify callback,
* that is, to read max bytes of the bytes from the callback, or timeout or error.
*/
virtual int get_vhost_hls_nb_notify(std::string vhost);
// hds section
private:
/**
* get the hds directive of vhost.
*/
virtual SrsConfDirective* get_hds(const std::string &vhost);
public:
/**
* whether HDS is enabled.
*/
virtual bool get_hds_enabled(const std::string &vhost);
/**
* get the HDS file store path.
*/
virtual std::string get_hds_path(const std::string &vhost);
/**
* get the hds fragment time, in seconds.
*/
virtual double get_hds_fragment(const std::string &vhost);
/**
* get the hds window time, in seconds.
* a window is a set of hds fragments.
*/
virtual double get_hds_window(const std::string &vhost); // dvr section
private:
/**
* get the dvr directive.
*/
virtual SrsConfDirective* get_dvr(std::string vhost);
public:
/**
* whether dvr is enabled.
*/
virtual bool get_dvr_enabled(std::string vhost);
/**
* get the dvr path, the flv file to save in.
*/
virtual std::string get_dvr_path(std::string vhost);
/**
* get the plan of dvr, how to reap the flv file.
*/
virtual std::string get_dvr_plan(std::string vhost);
/**
* get the duration of dvr flv.
*/
virtual int get_dvr_duration(std::string vhost);
/**
* whether wait keyframe to reap segment.
*/
virtual bool get_dvr_wait_keyframe(std::string vhost);
/**
* get the time_jitter algorithm for dvr.
*/
virtual int get_dvr_time_jitter(std::string vhost);
// http api section
private:
/**
* get the http api directive.
*/
virtual SrsConfDirective* get_http_api();
/**
* whether http api enabled
*/
virtual bool get_http_api_enabled(SrsConfDirective* conf);
public:
/**
* whether http api enabled.
*/
virtual bool get_http_api_enabled();
/**
* get the http api listen port.
*/
virtual std::string get_http_api_listen();
/**
* whether enable crossdomain for http api.
*/
virtual bool get_http_api_crossdomain();
// http stream section
private:
/**
* get the http stream directive.
*/
virtual SrsConfDirective* get_http_stream();
/**
* whether http stream enabled.
*/
virtual bool get_http_stream_enabled(SrsConfDirective* conf);
public:
/**
* whether http stream enabled.
*/
virtual bool get_http_stream_enabled();
/**
* get the http stream listen port.
*/
virtual std::string get_http_stream_listen();
/**
* get the http stream root dir.
*/
virtual std::string get_http_stream_dir();
public:
/**
* get whether vhost enabled http stream
*/
virtual bool get_vhost_http_enabled(std::string vhost);
/**
* get the http mount point for vhost.
* for example, http://vhost/live/livestream
*/
virtual std::string get_vhost_http_mount(std::string vhost);
/**
* get the http dir for vhost.
* the path on disk for mount root of http vhost.
*/
virtual std::string get_vhost_http_dir(std::string vhost);
// flv live streaming section
public:
/**
* get whether vhost enabled http flv live stream
*/
virtual bool get_vhost_http_remux_enabled(std::string vhost);
/**
* get the fast cache duration for http audio live stream.
*/
virtual double get_vhost_http_remux_fast_cache(std::string vhost);
/**
* get the http flv live stream mount point for vhost.
* used to generate the flv stream mount path.
*/
virtual std::string get_vhost_http_remux_mount(std::string vhost);
/**
* get whether the hstrs(http stream trigger rtmp source) enabled.
*/
virtual bool get_vhost_http_remux_hstrs(std::string vhost);
// http heartbeart section
private:
/**
* get the heartbeat directive.
*/
virtual SrsConfDirective* get_heartbeart();
public:
/**
* whether heartbeat enabled.
*/
virtual bool get_heartbeat_enabled();
/**
* get the heartbeat interval, in ms.
*/
virtual int64_t get_heartbeat_interval();
/**
* get the heartbeat report url.
*/
virtual std::string get_heartbeat_url();
/**
* get the device id of heartbeat, to report to server.
*/
virtual std::string get_heartbeat_device_id();
/**
* whether report with summaries of http api: /api/v1/summaries.
*/
virtual bool get_heartbeat_summaries();
// stats section
private:
/**
* get the stats directive.
*/
virtual SrsConfDirective* get_stats();
public:
/**
* get the network device index, used to retrieve the ip of device,
* for heartbeat to report to server, or to get the local ip.
* for example, 0 means the eth0 maybe.
*/
virtual int get_stats_network();
/**
* get the disk stat device name list.
* the device name configed in args of directive.
* @return the disk device name to stat. NULL if not configed.
*/
virtual SrsConfDirective* get_stats_disk_device();
};

1.2 SrsConfDirective 类

/**
* the config directive.
* the config file is a group of directives,
* all directive has name, args and child-directives.
* for example, the following config text:
vhost vhost.ossrs.net {
enabled on;
ingest livestream {
enabled on;
ffmpeg /bin/ffmpeg;
}
}
* will be parsed to:
* SrsConfDirective: name="vhost", arg0="vhost.ossrs.net", child-directives=[
* SrsConfDirective: name="enabled", arg0="on", child-directives=[]
* SrsConfDirective: name="ingest", arg0="livestream", child-directives=[
* SrsConfDirective: name="enabled", arg0="on", child-directives=[]
* SrsConfDirective: name="ffmpeg", arg0="/bin/ffmpeg", child-directives=[]
* ]
* ]
* @remark, allow empty directive, for example: "dir0 {}"
* @remark, don't allow empty name, for example: ";" or "{dir0 arg0;}
*/
class SrsConfDirective
{
public:
/**
* the line of config file in which the directive from
*/
int conf_line;
/**
* the name of directive, for example, the following config text:
* enabled on;
* will be parsed to a directive, its name is "enalbed"
*/
std::string name;
/**
* the args of directive, for example, the following config text:
* listen 1935 1936;
* will be parsed to a directive, its args is ["1935", "1936"].
*/
std::vector<std::string> args;
/**
* the child directives, for example, the following config text:
* vhost vhost.ossrs.net {
* enabled on;
* }
* will be parsed to a directive, its directives is a vector contains
* a directive, which is:
* name:"enalbed", args:["on"], directives:[]
*
* @remark, the directives can contains directives.
*/
std::vector<SrsConfDirective*> directives;
public:
SrsConfDirective();
virtual ~SrsConfDirective();
// args
public:
/**
* get the args0,1,2, if user want to get more args,
* directly use the args.at(index).
*/
virtual std::string arg0();
virtual std::string arg1();
virtual std::string arg2();
// directives
public:
/**
* get the directive by index.
* @remark, assert the index<directives.size().
*/
virtual SrsConfDirective* at(int index);
/**
* get the directive by name, return the first match.
*/
virtual SrsConfDirective* get(std::string _name);
/**
* get the directive by name and its arg0, return the first match.
*/
virtual SrsConfDirective* get(std::string _name, std::string _arg0);
// help utilities
public:
/**
* whether current directive is vhost.
*/
virtual bool is_vhost();
/**
* whether current directive is stream_caster.
*/
virtual bool is_stream_caster();
// parse utilities
public:
/**
* parse config directive from file buffer.
*/
virtual int parse(_srs_internal::SrsConfigBuffer* buffer);
// private parse.
private:
/**
* the directive parsing type.
*/
enum SrsDirectiveType {
/**
* the root directives, parsing file.
*/
parse_file,
/**
* for each direcitve, parsing text block.
*/
parse_block
};
/**
* parse the conf from buffer. the work flow:
* 1. read a token(directive args and a ret flag),
* 2. initialize the directive by args, args[0] is name, args[1-N] is args of directive,
* 3. if ret flag indicates there are child-directives, read_conf(directive, block) recursively.
*/
virtual int parse_conf(_srs_internal::SrsConfigBuffer* buffer, SrsDirectiveType type);
/**
* read a token from buffer.
* a token, is the directive args and a flag indicates whether has child-directives.
* @param args, the output directive args, the first is the directive name, left is the args.
* @param line_start, the actual start line of directive.
* @return, an error code indicates error or has child-directives.
*/
virtual int read_token(_srs_internal::SrsConfigBuffer* buffer, std::vector<std::string>& args, int& line_start);
};

1.3 SrsConfigBuffer

namespace _srs_internal
{
class SrsConfigBuffer;
} namespace _srs_internal
{
/**
* the buffer of config content.
*/
class SrsConfigBuffer
{
protected:
// last available position.
char* last;
// end of buffer.
char* end;
// start of buffer.
char* start;
public:
// current consumed position.
char* pos;
// current parsed line.
int line;
public:
SrsConfigBuffer();
virtual ~SrsConfigBuffer();
public:
/**
* fullfill the buffer with content of file specified by filename.
*/
virtual int fullfill(const char* filename);
/**
* whether buffer is empty.
*/
virtual bool empty();
};
};

2. 函数实现

2.1 SrsConfig::parse_options

// see: ngx_get_options
int SrsConfig::parse_options(int argc, char** argv)
{
int ret = ERROR_SUCCESS; // argv: 将 argv 中的数据拷贝到 _argv 中,多个字符串之间以 " " 间隔
for (int i = 0; i < argc; i++) {
_argv.append(argv[i]); if (i < argc - 1) {
_argv.append(" ");
}
} // config: 解析通过命令行传入的参数
show_help = true;
for (int i = 1; i < argc; i++) {
if ((ret = parse_argv(i, argv)) != ERROR_SUCCESS) {
return ret;
}
} if (show_help) {
print_help(argv);
exit(0);
} if (show_version) {
fprintf(stderr, "%s\n", RTMP_SIG_SRS_VERSION);
exit(0);
} // first hello message.
srs_trace(_srs_version); if (config_file.empty()) {
ret = ERROR_SYSTEM_CONFIG_INVALID;
srs_error("config file not specified, see help: %s -h, ret=%d", argv[0], ret);
return ret;
} // 解析配置文件
ret = parse_file(config_file.c_str()); if (test_conf) {
// the parse_file never check the config,
// we check it when user requires check config file.
if (ret == ERROR_SUCCESS) {
ret = check_ocnfig();
} if (ret == ERROR_SUCCESS) {
srs_trace("config file is ok");
exit(0);
} else {
srs_error("config file is invalid");
exit(ret);
}
} ////////////////////////////////////////////////////////////////////////
// check log name and level
////////////////////////////////////////////////////////////////////////
if (true) {
/* 从 root 指令中获取"srs_log_file"指令指定的log文件路径,若没有找到,
* 则使用默认log文件: "./objs/srs.log" */
std::string log_filename = this->get_log_file();
if (get_log_tank_file() && log_filename.empty()) {
ret = ERROR_SYSTEM_CONFIG_INVALID;
srs_error("must specifies the file to write log to. ret=%d", ret);
return ret;
}
// 将日志保存到文件中
if (get_log_tank_file()) {
srs_trace("write log to file %s", log_filename.c_str());
srs_trace("you can: tailf %s", log_filename.c_str());
srs_trace("@see: %s", SRS_WIKI_URL_LOG);
} else { // 否则,将日志打印到 console 中
srs_trace("write log to console");
}
}
}

2.2 SrsConfig::parse_argv

int SrsConfig::parse_argv(int& i, char** argv)
{
int ret = ERROR_SUCCESS; char* p = argv[i]; if (*p++ != '-') {
show_help = true;
return ret;
} while (*p) {
switch (*p++) {
case '?':
case 'h':
show_help = true;
break;
case 't':
show_help = false;
test_conf = true;
break;
case 'p':
dolphin = true;
if (*p) {
dolphin_rtmp_port = p;
continue;
}
if (argv[++i]) {
dolphin_rtmp_port = argv[i];
continue;
}
ret = ERROR_SYSTEM_CONFIG_INVALID;
srs_error("option \"-p\" requires params, ret=%d", ret);
return ret;
case 'x':
dolphin = true;
if (*p) {
dolphin_http_port = p;
continue;
}
if (argv[++i]) {
dolphin_http_port = argv[i];
continue;
}
ret = ERROR_SYSTEM_CONFIG_INVALID;
srs_error("option \"-x\" requires params, ret=%d", ret);
return ret;
case 'v':
case 'V':
show_help = false;
show_version = true;
break;
case 'c':
show_help = false;
if (*p) {
config_file = p;
continue;
}
if (argv[++i]) {
config_file = argv[i];
continue;
}
ret = ERROR_SYSTEM_CONFIG_INVALID;
srs_error("option \"-c\" requires parameter, ret=%d", ret);
return ret;
default:
ret = ERROR_SYSTEM_CONFIG_INVALID;
srs_error("invalid option: \"%c\", see help: %s -h, ret=%d", *(p - 1), argv[0], ret);
return ret;
}
} return ret;
}

2.3 SrsConfig::parse_file

int SrsConfig::parse_file(const char *filename)
{
int ret = ERROR_SUCCESS; config_file = filename; if (config_file.empty()) {
return ERROR_SYSTEM_CONFIG_INVALID;
} // 构造一个 SrsConfigBuffer 类的变量,用于存放配置文件的内容
SrsConfigBuffer buffer; // 将配置文件的内容填充到 buffer 缓存中
if ((ret = buffer.fullfill(config_file.c_str())) != ERROR_SUCCESS) {
return ret;
} // 解析 buffer 中的配置项
return parse_buffer(&buffer);
}

2.4 namespace _srs_internal

namespace _srs_internal
{
SrsConfigBuffer::SrsConfigBuffer()
{
line = 1; pos = last = start = NULL;
end = start;
} SrsConfigBuffer::~SrsConfigBuffer()
{
srs_freepa(start);
} int SrsConfigBuffer::fullfill(const char *filename)
{
int ret = ERROR_SUCCESS; SrsFileReader reader; // open file reader.
if ((ret = reader.open(filename)) != ERROR_SUCCESS) {
srs_error("open conf file error. ret=%d", ret);
return ret;
} // read all.
int filesize = (int)reader.filesize(); // create buffer
srs_freepa(start);
pos = last = start = new char[filesize];
end = start + filesize; // read total content from file.
ssize_t nread = 0;
if ((ret = reader.read(start, filesize, &nread)) != ERROR_SUCCESS) {
srs_error("read file read error. expect %d, actual %d bytes, ret=%d",
filesize, nread, ret);
return ret;
} return ret;
} bool SrsConfigBuffer::empty()
{
return pos >= end;
}
}

2.5 SrsConfig::parse_buffer

int SrsConfig::parse_buffer(SrsConfigBuffer* buffer)
{
int ret = ERROR_SUCCESS; /* 解析 buffer 中的配置,解析出来的配置值保存在 root 指令中,
* root 构建了一个指令树,保存着配置文件中所有指令 */
if ((ret = root->parse(buffer)) != ERROR_SUCCESS) {
return ret;
} // mock by dolphin mode.
// for the dolphin will start srs with specified params.
if (dolphin) {
// for RTMP.
set_config_directive(root, "listen", dolphin_rtmp_port); // for HTTP.
set_config_directive(root, "http_server", "");
SrsConfDirective* http_server = root->get("http_server");
set_config_directive(http_server, "enabled", "on");
set_config_directive(http_server, "listen", dolphin_http_port); // others.
set_config_directive(root, "daemon", "off");
set_config_directive(root, "srs_log_tank", "console");
} return ret;
}

2.5.1 SrsConfig::set_config_directive

void SrsConfig::set_config_directive(SrsConfDirective* parent, string dir, string value)
{
// 尝试从 parent 中获取该 dir 指令
SrsConfDirective* d = parent->get(dir); // 若 parent 指令中没有该 dir 子指令,则创建一个新指令
if (!d) {
d = new SrsConfDirective();
if (!dir.empty()) {
d->name = dir;
}
parent->directive.push_back(d);
} // 清空该指令的参数
d->args.clear(); if (!value.empty) {
d->args.push_back(value);
}
}

2.6 SrsConfDirective::parse

int SrsConfDirective::parse(SrsConfigBuffer* buffer)
{
// parse_file 表示当前开始解析配置文件
return parse_conf(buffer, parse_file);
}

2.7 SrsConfDirective::parse_conf

// see: ngx_conf_parse
int SrsConfDirective::parse_conf(SrsConfigBuffer* buffer, SrsDirectiveType type)
{
int ret = ERROR_SUCCESS; while (true) {
std::vector<string> args;
int line_start = 0;
// 读取一行内容
ret = read_token(buffer, args, line_start); /**
* ret maybe:
* ERROR_SYSTEM_CONFIG_INVALID error.
* ERROR_SYSTEM_CONFIG_DIRECTIVE directive terminated by ';' found
* ERROR_SYSTEM_CONFIG_BLOCK_START token terminated by '{' found
* ERROR_SYSTEM_CONFIG_BLOCK_END the '}' found
* ERROR_SYSTEM_CONFIG_EOF the config file is done
*/
if (ret == ERROR_SYSTEM_CONFIG_INVALID) {
return ret;
}
if (ret == ERROR_SYSTEM_CONFIG_BLOCK_END) {
if (type != parse_block) {
srs_error("line: %d: unexpected \"}\", ret=%d", buffer->line, ret);
return ret;
}
return ERROR_SUCCESS;
}
if (ret == ERROR_SYSTEM_CONFIG_EOF) {
if (type == parse_block) {
srs_error("line %d: unexpected end of file, expecting \"}\", ret=%d", conf_line, ret);
return ret;
}
return ERROR_SUCCESS;
} // 若没有读取到任何数据,则表示出错了
if (args.empty()) {
ret = ERROR_SYSTEM_CONFIG_INVALID;
srs_error("line %d: empty directive. ret=%d", conf_line, ret);
return ret;
} // build directive tree.
SrsConfDirective* directive = new SrsConfDirective(); directive->conf_line = line_start;
directive->name = args[0];
// 将 args[0] 从 args vector 容器中删除
args.erase(args.begin());
/* 将 args 中的全部数据和 directive->args 中的数据进行交换,
* 即相等于将 args 中的数据拷贝给 directive->args */
directive->args.swap(args); // 将该新解析的指令放入到 vector 类的 directives 容器中
directives.push_back(directive); // 若为一个配置块的开始,则递归调用 parse_conf
if (ret == ERROR_SYSTEM_CONFIG_BLOCK_START) {
if ((ret = directive->parse_conf(buffer, parse_block) != ERROR_SUCCESS) {
return ret;
}
}
} return ret;
}

2.8 SrsConfDirective::read_token

// see: ngx_conf_read_token
int SrsConfDirective::read_token(SrsConfigBuffer* buffer, vector<string>& args, int& line_start)
{
int ret = ERROR_SUCCESS; char* pstart = buffer->pos; // 若为 true,表示当前行是注释
bool sharp_comment = false; // 若为 true,表示当前字符是 双引号 "
bool d_quoted = false;
// 若为 true,表示当前字符是 单引号 '
bool s_quoted = false; // 若为 true,表示下一个的字符必须是空格
bool need_space = false;
// 若为 true,表示当前字符是最后一个空格,即下一个字符不是空格了
bool last_space = true; while (true) {
if (buffer->empty()) {
ret = ERROR_SYSTEM_CONFIG_EOF; if (!args.empty() || !last_space) {
srs_error("line %d: unexpected end of file, expecting ; or \"}\"", buffer->line);
return ERROR_SYSTEM_CONFIG_INVALID;
}
srs_trace("config parse complete"); return ret;
} char ch = *buffer->pos++; // 若当前字符为 换行符,则表示当前行结束
if (ch == SRS_LF) {
buffer->line++;
sharp_comment = false;
} // 表示遇到注释了
if (sharp_comment) {
continue;
} if (need_space) {
if (is_common_space(ch)) {
last_space = true;
need_space = false;
continue;
}
if (ch == ';') {
return ERROR_SYSTEM_CONFIG_DIRECTIVE;
}
if (ch == '{') {
return ERROR_SYSTEM_CONFIG_BLOCK_START;
}
srs_error("line %d: unexpected '%c'", buffer->line, ch);
return ERROR_SYSTEM_CONFIG_INVALID;
} // last charecter is space.
if (last_space) {
// 若当前字符是空格,则继续取下一个字符
if (is_common_space(ch)) {
continue;
}
pstart = buffer->pos - 1;
switch (ch) {
case ';':
if (args.size() == 0) {
srs_error("line %d: unexpected ';'", buffer->line);
return ERROR_SYSTEM_CONFIG_INVALID;
}
return ERROR_SYSTEM_CONFIG_DIRECTIVE;
case '{':
if (args.size() == 0) {
srs_error("line %d: unexpected '{'", buffer->line);
return ERROR_SYSTEM_CONFIG_INVALID;
}
return ERROR_SYSTEM_CONFIG_BLOCK_START;
case '}':
if (args.size() != 0) {
srs_error("line %d: unexpected '}'", buffer->line);
return ERROR_SYSTEM_CONFIG_INVALID;
}
return ERROR_SYSTEM_CONFIG_BLOCK_END;
case '#':
sharp_comment = 1;
continue;
case '"':
pstart++;
d_quoted = true;
last_space = 0;
continue;
case '\'':
pstart++;
s_quoted = true;
last_space = 0;
continue;
default:
last_space = 0;
continue;
}
} else {
// last charecter is not space
if (line_start == 0) {
line_start = buffer->line;
} bool found = false;
if (d_quoted) {
if (ch == '"') {
d_quoted = false;
need_space = true;
found = true;
}
} else if (s_quoted) {
if (ch == '\'') {
s_quoted = false;
need_space = true;
found = true;
}
// 若当前字符是空格 或 ; 或 {,则置位 found
} else if (is_common_space(ch) || ch == ';' || ch == '{') {
last_space = true;
found = 1;
} if (found) {
int len = (int)(buffer->pos - pstart);
char* aword = new char[len];
memcpy(aword, pstart, len);
aword[len - 1] = 0; // 将其放入到 vector 类型的 args 容器中
string word_str = aword;
if (!word_str.empty()) {
args.push_back(word_str);
}
srs_freepa(aword); if (ch == ';') {
return ERROR_SYSTEM_CONFIG_DIRECTIVE;
}
if (ch == '{') {
return ERROR_SYSTEM_CONFIG_BLOCK_START;
}
}
}
} return ret;
}

2.9 SrsConfig::subscribe

void SrsConfig::subscribe(ISrsReloadHandler* handler)
{
std::vector<ISrsReloadHandler*>::iterator it; /* 在 subscribes 容器中查找到与 handler 等值的第一个元素的迭代器,
* 若遍历完都没有找到,则将该 handler 放入到 subscribes 容器的末尾 */
it = std::find(subscribes.begin(), subscribes.end(), handler);
if (it != subscribes.end()) {
return;
} subscribes.push_back(handler);
}

SRS之SrsConfig类的更多相关文章

  1. Java类的继承与多态特性-入门笔记

    相信对于继承和多态的概念性我就不在怎么解释啦!不管你是.Net还是Java面向对象编程都是比不缺少一堂课~~Net如此Java亦也有同样的思想成分包含其中. 继承,多态,封装是Java面向对象的3大特 ...

  2. 图书馆管理系统 SRS文档

    图书馆管理系统 SRS文档 编写人:魏晓 日期:2015年05月27日 1介绍 1.1编写目的 图书管理系统需求规格说明书是为了让系统的涉众就该系统的需求达成一致认可,明确该系统的需求,为后续的开发工 ...

  3. lucene原理及源码解析--核心类

    马云说:大家还没搞清PC时代的时候,移动互联网来了,还没搞清移动互联网的时候,大数据时代来了. 然而,我看到的是:在PC时代搞PC的,移动互联网时代搞移动互联网的,大数据时代搞大数据的,都是同一伙儿人 ...

  4. Java 控制类的引用类型,合理使用内存

    Java提供了 java.lang.ref包,该包下的类均与垃圾回收机制相关 先介绍Java对象的集中引用类型 1.强引用 强引用是最常见的,创建对象就是强引用,如 String a = new St ...

  5. 开源流媒体服务器SRS学习笔记(1) - 安装、推流、拉流

    SRS(Simple RTMP Server)  是国人写的一款非常优秀的开源流媒体服务器软件,可用于直播/录播/视频客服等多种场景,其定位是运营级的互联网直播服务器集群. 一.安装 官网提供了3种安 ...

  6. 五子棋游戏SRS文档

        SRS技术文档,包括对SRS的解释说明.SRS描述规范.软件需求规格说明书(SRS,Software Requirement Specification)是为了软件开发系统而编写的,主要用来描 ...

  7. java8-12-Optional类

      Optional类  java.util.Optional   是一个容器类 避免空指针  NPE  能够快速定位空指针   常用方法: Optional.of(T t) : 创建一个 Optio ...

  8. SRS之SrsHlsCache::reap_segment详解

    1. 是否可切片的检测 首先在调用 SrsHlsCache::reap_segment 函数进行切片时,针对音频或视频,都会有一个函数来进行检测当前片的时长是否符合所要求的时长. 对于音频,会调用 S ...

  9. SRS之SrsHls::on_audio详解

    1. SrsHls::on_audio 将音频数据封装到 ts 文件中. /* * mux the audio packet to ts. * @param shared_audio, directl ...

随机推荐

  1. eclipse设置字体

  2. 【科创人上海行】Tinyfool郝培强 :始于不惑的微服务式创业,背负身心病痛贴地飞行

    Intro 科创人,分享技术创业者的成长与创业经验. 何谓创业?勇于面对未知.肩负重任的人生抉择. 何谓经验?沟沟坎坎.有笑有泪,各有其宝贵. 科创人的价值支点是"人",我们不以市 ...

  3. BootStrape基础使用

    官网:www.bootcss.com 一. 全局css样式 栅格系统 栅格系统用于通过一系列的行(row)与列(column)的组合来创建页面布局 <!DOCTYPE html> < ...

  4. golang 环境变量讲解

    以下配置以MAC 下配置为例,但其他环境下大同小异. GOROOT就是go的安装路径在~/.bash_profile中添加下面语句: GOROOT=/usr/local/go export GOROO ...

  5. 第四篇.python的基础

    目录 第四篇.python基础01 1. 变量 2. 常量 3. python变量内存管理 4. 变量的三个特征 5. 花式赋值 6. 注释 7. 数据类型基础 8. 数字类型 9. 字符串类型 10 ...

  6. Python3简易接口自动化测试框架设计与实现(上)

    目录 1.开发环境 2.用到的模块 3.框架设计 3.1.流程 3.2.项目结构 5.日志打印 6.接口请求类封装 接口开发请参考:使用Django开发简单接口:文章增删改查 1.开发环境 操作系统: ...

  7. WiFi 6是否适合个人用户

    5G已经被热炒得家喻户晓,但与其对标的WiFi新技术标准——Wi-Fi 6却可能有着更好的经济和技术前景,WiFi 6也称为802.11ax,比过去的Wi-Fi技术更好,速度更快能跟5G对标,连接性更 ...

  8. Linux工具之top

    top命令详解: 第一行:10:01:23----当前系统时间   126days,14:29------系统已经运行了126天14小时29分钟(在这期间没有重启过)   2users------当前 ...

  9. java-消息队列相关-activeMQ

    ,1,如何防止activeMQ崩溃导致消息丢失呢? 第一点,首先消息需要使用持久化消息,服务挂掉,重启服务后消息依然可以消费,不会丢失: 第二点,ActiveMQ采用主从模式搭建集群,比如搭建3台主从 ...

  10. 小程序UI设计(9)-文字排版

    小程序中一般很少大段落的文字,基本是4.5个字的主题.一行标题.一行摘要.两行描述.显示时超出部分用省略号代替.下面结合工具使用介绍一下文字排版的方法.先看效果图.  工具中属性设置如下图:两行文字属 ...