正在显示
10 个修改的文件
包含
215 行增加
和
27 行删除
| @@ -501,7 +501,8 @@ Supported operating systems and hardware: | @@ -501,7 +501,8 @@ Supported operating systems and hardware: | ||
| 501 | * 2013-10-17, Created.<br/> | 501 | * 2013-10-17, Created.<br/> |
| 502 | 502 | ||
| 503 | ## History | 503 | ## History |
| 504 | -* v2.0, 2015-01-02, hotfix [#211](https://github.com/winlinvip/simple-rtmp-server/issues/211), support security allow/deny publish/play all/ip. 2.0.86 | 504 | +* v2.0, 2015-01-03, fix [#179](https://github.com/winlinvip/simple-rtmp-server/issues/179), dvr support custom filepath by variables. 2.0.87 |
| 505 | +* v2.0, 2015-01-02, fix [#211](https://github.com/winlinvip/simple-rtmp-server/issues/211), support security allow/deny publish/play all/ip. 2.0.86 | ||
| 505 | * v2.0, 2015-01-02, hotfix [#207](https://github.com/winlinvip/simple-rtmp-server/issues/207), trim the last 0 of log. 2.0.85 | 506 | * v2.0, 2015-01-02, hotfix [#207](https://github.com/winlinvip/simple-rtmp-server/issues/207), trim the last 0 of log. 2.0.85 |
| 506 | * v2.0, 2014-01-02, fix [#158](https://github.com/winlinvip/simple-rtmp-server/issues/158), http-callback check http status code ok(200). 2.0.84 | 507 | * v2.0, 2014-01-02, fix [#158](https://github.com/winlinvip/simple-rtmp-server/issues/158), http-callback check http status code ok(200). 2.0.84 |
| 507 | * v2.0, 2015-01-02, hotfix [#216](https://github.com/winlinvip/simple-rtmp-server/issues/216), http-callback post in application/json content-type. 2.0.83 | 508 | * v2.0, 2015-01-02, hotfix [#216](https://github.com/winlinvip/simple-rtmp-server/issues/216), http-callback post in application/json content-type. 2.0.83 |
trunk/conf/dvr.path.conf
0 → 100644
| 1 | +# the config for srs to dvr in custom path. | ||
| 2 | +# @see https://github.com/winlinvip/simple-rtmp-server/wiki/v2_CN_DVR#custom-path | ||
| 3 | +# @see https://github.com/winlinvip/simple-rtmp-server/wiki/v2_EN_DVR#custom-path | ||
| 4 | +# @see full.conf for detail config. | ||
| 5 | + | ||
| 6 | +listen 1935; | ||
| 7 | +max_connections 1000; | ||
| 8 | +vhost __defaultVhost__ { | ||
| 9 | + dvr { | ||
| 10 | + enabled on; | ||
| 11 | + dvr_path ./objs/nginx/html/[app]/[stream]/[2006]/[01]/[02]/[15].[04].[05].[999].flv; | ||
| 12 | + dvr_plan segment; | ||
| 13 | + dvr_duration 30; | ||
| 14 | + dvr_wait_keyframe on; | ||
| 15 | + } | ||
| 16 | +} |
trunk/conf/full.conf
100644 → 100755
| @@ -236,15 +236,38 @@ vhost dvr.srs.com { | @@ -236,15 +236,38 @@ vhost dvr.srs.com { | ||
| 236 | # default: off | 236 | # default: off |
| 237 | enabled on; | 237 | enabled on; |
| 238 | # the dvr output path. | 238 | # the dvr output path. |
| 239 | - # the app dir is auto created under the dvr_path. | ||
| 240 | - # for example, for rtmp stream: | ||
| 241 | - # rtmp://127.0.0.1/live/livestream | ||
| 242 | - # http://127.0.0.1/live/livestream.m3u8 | ||
| 243 | - # where dvr_path is /dvr, srs will create the following files: | ||
| 244 | - # /dvr/live the app dir for all streams. | ||
| 245 | - # /dvr/live/livestream.{time}.flv the dvr flv file. | ||
| 246 | - # @remark, the time use system timestamp in ms, user can use http callback to rename it. | ||
| 247 | - # in a word, the dvr_path is for vhost. | 239 | + # we supports some variables to generate the filename. |
| 240 | + # [vhost], the vhost of stream. | ||
| 241 | + # [app], the app of stream. | ||
| 242 | + # [stream], the stream name of stream. | ||
| 243 | + # [2006], replace this const to current year. | ||
| 244 | + # [01], replace this const to current month. | ||
| 245 | + # [02], replace this const to current date. | ||
| 246 | + # [15], replace this const to current hour. | ||
| 247 | + # [04], repleace this const to current minute. | ||
| 248 | + # [05], repleace this const to current second. | ||
| 249 | + # [999], repleace this const to current millisecond. | ||
| 250 | + # [timestamp],replace this const to current UNIX timestamp in ms. | ||
| 251 | + # @remark we use golang time format "2006-01-02 15:04:05.999" | ||
| 252 | + # for example, for url rtmp://ossrs.net/live/livestream and time 2015-01-03 10:57:30.776 | ||
| 253 | + # 1. No variables, the rule of SRS1.0(auto add [stream].[timestamp].flv as filename): | ||
| 254 | + # dvr_path ./objs/nginx/html; | ||
| 255 | + # => | ||
| 256 | + # dvr_path ./objs/nginx/html/live/livestream.1420254068776.flv; | ||
| 257 | + # 2. Use stream and date as dir name, time as filename: | ||
| 258 | + # dvr_path /data/[vhost]/[app]/[stream]/[2006]/[01]/[02]/[15].[04].[05].[999].flv; | ||
| 259 | + # => | ||
| 260 | + # dvr_path /data/ossrs.net/live/livestream/2015/01/03/10.57.30.776.flv; | ||
| 261 | + # 3. Use stream and year/month as dir name, date and time as filename: | ||
| 262 | + # dvr_path /data/[vhost]/[app]/[stream]/[2006]/[01]/[02]-[15].[04].[05].[999].flv; | ||
| 263 | + # => | ||
| 264 | + # dvr_path /data/ossrs.net/live/livestream/2015/01/03-10.57.30.776.flv; | ||
| 265 | + # 4. Use vhost/app and year/month as dir name, stream/date/time as filename: | ||
| 266 | + # dvr_path /data/[vhost]/[app]/[2006]/[01]/[stream]-[02]-[15].[04].[05].[999].flv; | ||
| 267 | + # => | ||
| 268 | + # dvr_path /data/ossrs.net/live/2015/01/livestream-03-10.57.30.776.flv; | ||
| 269 | + # @see https://github.com/winlinvip/simple-rtmp-server/wiki/v2_CN_DVR#custom-path | ||
| 270 | + # @see https://github.com/winlinvip/simple-rtmp-server/wiki/v2_EN_DVR#custom-path | ||
| 248 | # default: ./objs/nginx/html | 271 | # default: ./objs/nginx/html |
| 249 | dvr_path ./objs/nginx/html; | 272 | dvr_path ./objs/nginx/html; |
| 250 | # the dvr plan. canbe: | 273 | # the dvr plan. canbe: |
| @@ -27,6 +27,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | @@ -27,6 +27,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| 27 | 27 | ||
| 28 | #include <fcntl.h> | 28 | #include <fcntl.h> |
| 29 | #include <sstream> | 29 | #include <sstream> |
| 30 | +#include <sys/time.h> | ||
| 30 | using namespace std; | 31 | using namespace std; |
| 31 | 32 | ||
| 32 | #include <srs_app_config.hpp> | 33 | #include <srs_app_config.hpp> |
| @@ -136,14 +137,97 @@ int SrsDvrPlan::open_new_segment() | @@ -136,14 +137,97 @@ int SrsDvrPlan::open_new_segment() | ||
| 136 | 137 | ||
| 137 | SrsRequest* req = _req; | 138 | SrsRequest* req = _req; |
| 138 | 139 | ||
| 139 | - // new flv file | ||
| 140 | - std::stringstream path; | 140 | + // the path in config, for example, |
| 141 | + // /data/[vhost]/[app]/[stream]/[2006]/[01]/[02]/[15].[04].[05].[999].flv | ||
| 142 | + std::string path_config = _srs_config->get_dvr_path(req->vhost); | ||
| 141 | 143 | ||
| 142 | - path << _srs_config->get_dvr_path(req->vhost) | ||
| 143 | - << "/" << req->app << "/" | ||
| 144 | - << req->stream << "." << srs_get_system_time_ms() << ".flv"; | 144 | + // add [stream].[timestamp].flv as filename for dir |
| 145 | + if (path_config.find(".flv") != path_config.length() - 4) { | ||
| 146 | + path_config += "/[stream].[timestamp].flv"; | ||
| 147 | + } | ||
| 148 | + | ||
| 149 | + // the flv file path | ||
| 150 | + std::string path = path_config; | ||
| 151 | + | ||
| 152 | + // variable [vhost] | ||
| 153 | + path = srs_string_replace(path, "[vhost]", req->vhost); | ||
| 154 | + // variable [app] | ||
| 155 | + path = srs_string_replace(path, "[app]", req->app); | ||
| 156 | + // variable [stream] | ||
| 157 | + path = srs_string_replace(path, "[stream]", req->stream); | ||
| 158 | + | ||
| 159 | + // date and time substitude | ||
| 160 | + // clock time | ||
| 161 | + timeval tv; | ||
| 162 | + if (gettimeofday(&tv, NULL) == -1) { | ||
| 163 | + return ERROR_SYSTEM_TIME; | ||
| 164 | + } | ||
| 165 | + | ||
| 166 | + // to calendar time | ||
| 167 | + struct tm* tm; | ||
| 168 | + if ((tm = localtime(&tv.tv_sec)) == NULL) { | ||
| 169 | + return ERROR_SYSTEM_TIME; | ||
| 170 | + } | ||
| 171 | + | ||
| 172 | + // the buffer to format the date and time. | ||
| 173 | + char buf[64]; | ||
| 174 | + | ||
| 175 | + // [2006], replace with current year. | ||
| 176 | + if (true) { | ||
| 177 | + snprintf(buf, sizeof(buf), "%d", 1900 + tm->tm_year); | ||
| 178 | + path = srs_string_replace(path, "[2006]", buf); | ||
| 179 | + } | ||
| 180 | + // [2006], replace with current year. | ||
| 181 | + if (true) { | ||
| 182 | + snprintf(buf, sizeof(buf), "%d", 1900 + tm->tm_year); | ||
| 183 | + path = srs_string_replace(path, "[2006]", buf); | ||
| 184 | + } | ||
| 185 | + // [01], replace this const to current month. | ||
| 186 | + if (true) { | ||
| 187 | + snprintf(buf, sizeof(buf), "%d", 1 + tm->tm_mon); | ||
| 188 | + path = srs_string_replace(path, "[01]", buf); | ||
| 189 | + } | ||
| 190 | + // [02], replace this const to current date. | ||
| 191 | + if (true) { | ||
| 192 | + snprintf(buf, sizeof(buf), "%d", tm->tm_mday); | ||
| 193 | + path = srs_string_replace(path, "[02]", buf); | ||
| 194 | + } | ||
| 195 | + // [15], replace this const to current hour. | ||
| 196 | + if (true) { | ||
| 197 | + snprintf(buf, sizeof(buf), "%d", tm->tm_hour); | ||
| 198 | + path = srs_string_replace(path, "[15]", buf); | ||
| 199 | + } | ||
| 200 | + // [04], repleace this const to current minute. | ||
| 201 | + if (true) { | ||
| 202 | + snprintf(buf, sizeof(buf), "%d", tm->tm_min); | ||
| 203 | + path = srs_string_replace(path, "[04]", buf); | ||
| 204 | + } | ||
| 205 | + // [05], repleace this const to current second. | ||
| 206 | + if (true) { | ||
| 207 | + snprintf(buf, sizeof(buf), "%d", tm->tm_sec); | ||
| 208 | + path = srs_string_replace(path, "[05]", buf); | ||
| 209 | + } | ||
| 210 | + // [999], repleace this const to current millisecond. | ||
| 211 | + if (true) { | ||
| 212 | + snprintf(buf, sizeof(buf), "%03d", (int)(tv.tv_usec / 1000)); | ||
| 213 | + path = srs_string_replace(path, "[999]", buf); | ||
| 214 | + } | ||
| 215 | + // [timestamp],replace this const to current UNIX timestamp in ms. | ||
| 216 | + if (true) { | ||
| 217 | + int64_t now_us = ((int64_t)tv.tv_sec) * 1000 * 1000 + (int64_t)tv.tv_usec; | ||
| 218 | + snprintf(buf, sizeof(buf), "%"PRId64, now_us / 1000); | ||
| 219 | + path = srs_string_replace(path, "[timestamp]", buf); | ||
| 220 | + } | ||
| 221 | + | ||
| 222 | + // create dir first. | ||
| 223 | + std::string dir = path.substr(0, path.rfind("/")); | ||
| 224 | + if ((ret = srs_create_dir_recursively(dir)) != ERROR_SUCCESS) { | ||
| 225 | + srs_error("create dir=%s failed. ret=%d", dir.c_str(), ret); | ||
| 226 | + return ret; | ||
| 227 | + } | ||
| 228 | + srs_info("create dir=%s ok", dir.c_str()); | ||
| 145 | 229 | ||
| 146 | - if ((ret = flv_open(req->get_stream_url(), path.str())) != ERROR_SUCCESS) { | 230 | + if ((ret = flv_open(req->get_stream_url(), path)) != ERROR_SUCCESS) { |
| 147 | return ret; | 231 | return ret; |
| 148 | } | 232 | } |
| 149 | dvr_enabled = true; | 233 | dvr_enabled = true; |
| @@ -951,16 +951,12 @@ int SrsHlsMuxer::create_dir() | @@ -951,16 +951,12 @@ int SrsHlsMuxer::create_dir() | ||
| 951 | app_dir += app; | 951 | app_dir += app; |
| 952 | 952 | ||
| 953 | // TODO: cleanup the dir when startup. | 953 | // TODO: cleanup the dir when startup. |
| 954 | - | ||
| 955 | - mode_t mode = S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IWGRP|S_IXGRP|S_IROTH|S_IXOTH; | ||
| 956 | - if (::mkdir(app_dir.c_str(), mode) < 0) { | ||
| 957 | - if (errno != EEXIST) { | ||
| 958 | - ret = ERROR_HLS_CREATE_DIR; | ||
| 959 | - srs_error("create app dir %s failed. ret=%d", app_dir.c_str(), ret); | ||
| 960 | - return ret; | ||
| 961 | - } | 954 | + |
| 955 | + if ((ret = srs_create_dir_recursively(app_dir)) != ERROR_SUCCESS) { | ||
| 956 | + srs_error("create app dir %s failed. ret=%d", app_dir.c_str(), ret); | ||
| 957 | + return ret; | ||
| 962 | } | 958 | } |
| 963 | - srs_info("create app dir %s success.", app_dir.c_str()); | 959 | + srs_info("create app dir %s ok", app_dir.c_str()); |
| 964 | 960 | ||
| 965 | return ret; | 961 | return ret; |
| 966 | } | 962 | } |
| @@ -98,6 +98,9 @@ int SrsSecurity::allow_check(SrsConfDirective* rules, SrsRtmpConnType type, std: | @@ -98,6 +98,9 @@ int SrsSecurity::allow_check(SrsConfDirective* rules, SrsRtmpConnType type, std: | ||
| 98 | break; | 98 | break; |
| 99 | } | 99 | } |
| 100 | break; | 100 | break; |
| 101 | + case SrsRtmpConnUnknown: | ||
| 102 | + default: | ||
| 103 | + break; | ||
| 101 | } | 104 | } |
| 102 | 105 | ||
| 103 | // when matched, donot search more. | 106 | // when matched, donot search more. |
| @@ -140,6 +143,9 @@ int SrsSecurity::deny_check(SrsConfDirective* rules, SrsRtmpConnType type, std:: | @@ -140,6 +143,9 @@ int SrsSecurity::deny_check(SrsConfDirective* rules, SrsRtmpConnType type, std:: | ||
| 140 | break; | 143 | break; |
| 141 | } | 144 | } |
| 142 | break; | 145 | break; |
| 146 | + case SrsRtmpConnUnknown: | ||
| 147 | + default: | ||
| 148 | + break; | ||
| 143 | } | 149 | } |
| 144 | 150 | ||
| 145 | // when matched, donot search more. | 151 | // when matched, donot search more. |
| @@ -31,7 +31,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | @@ -31,7 +31,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| 31 | // current release version | 31 | // current release version |
| 32 | #define VERSION_MAJOR 2 | 32 | #define VERSION_MAJOR 2 |
| 33 | #define VERSION_MINOR 0 | 33 | #define VERSION_MINOR 0 |
| 34 | -#define VERSION_REVISION 86 | 34 | +#define VERSION_REVISION 87 |
| 35 | // server info. | 35 | // server info. |
| 36 | #define RTMP_SIG_SRS_KEY "SRS" | 36 | #define RTMP_SIG_SRS_KEY "SRS" |
| 37 | #define RTMP_SIG_SRS_ROLE "origin/edge server" | 37 | #define RTMP_SIG_SRS_ROLE "origin/edge server" |
| @@ -93,6 +93,9 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | @@ -93,6 +93,9 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| 93 | #define ERROR_SYSTEM_SECURITY 1052 | 93 | #define ERROR_SYSTEM_SECURITY 1052 |
| 94 | #define ERROR_SYSTEM_SECURITY_DENY 1053 | 94 | #define ERROR_SYSTEM_SECURITY_DENY 1053 |
| 95 | #define ERROR_SYSTEM_SECURITY_ALLOW 1054 | 95 | #define ERROR_SYSTEM_SECURITY_ALLOW 1054 |
| 96 | +#define ERROR_SYSTEM_TIME 1055 | ||
| 97 | +#define ERROR_SYSTEM_DIR_EXISTS 1056 | ||
| 98 | +#define ERROR_SYSTEM_CREATE_DIR 1057 | ||
| 96 | 99 | ||
| 97 | /////////////////////////////////////////////////////// | 100 | /////////////////////////////////////////////////////// |
| 98 | // RTMP protocol error. | 101 | // RTMP protocol error. |
| @@ -152,7 +155,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | @@ -152,7 +155,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| 152 | /////////////////////////////////////////////////////// | 155 | /////////////////////////////////////////////////////// |
| 153 | #define ERROR_HLS_METADATA 3000 | 156 | #define ERROR_HLS_METADATA 3000 |
| 154 | #define ERROR_HLS_DECODE_ERROR 3001 | 157 | #define ERROR_HLS_DECODE_ERROR 3001 |
| 155 | -#define ERROR_HLS_CREATE_DIR 3002 | 158 | +//#define ERROR_HLS_CREATE_DIR 3002 |
| 156 | #define ERROR_HLS_OPEN_FAILED 3003 | 159 | #define ERROR_HLS_OPEN_FAILED 3003 |
| 157 | #define ERROR_HLS_WRITE_FAILED 3004 | 160 | #define ERROR_HLS_WRITE_FAILED 3004 |
| 158 | #define ERROR_HLS_AAC_FRAME_LENGTH 3005 | 161 | #define ERROR_HLS_AAC_FRAME_LENGTH 3005 |
| @@ -32,10 +32,13 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | @@ -32,10 +32,13 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
| 32 | #endif | 32 | #endif |
| 33 | 33 | ||
| 34 | #include <string.h> | 34 | #include <string.h> |
| 35 | +#include <sys/stat.h> | ||
| 36 | +#include <fcntl.h> | ||
| 35 | 37 | ||
| 36 | using namespace std; | 38 | using namespace std; |
| 37 | 39 | ||
| 38 | #include <srs_kernel_log.hpp> | 40 | #include <srs_kernel_log.hpp> |
| 41 | +#include <srs_kernel_error.hpp> | ||
| 39 | 42 | ||
| 40 | // this value must: | 43 | // this value must: |
| 41 | // equals to (SRS_SYS_CYCLE_INTERVAL*SRS_SYS_TIME_RESOLUTION_MS_TIMES)*1000 | 44 | // equals to (SRS_SYS_CYCLE_INTERVAL*SRS_SYS_TIME_RESOLUTION_MS_TIMES)*1000 |
| @@ -222,3 +225,56 @@ bool srs_string_ends_with(string str, string flag) | @@ -222,3 +225,56 @@ bool srs_string_ends_with(string str, string flag) | ||
| 222 | return str.rfind(flag) == str.length() - flag.length(); | 225 | return str.rfind(flag) == str.length() - flag.length(); |
| 223 | } | 226 | } |
| 224 | 227 | ||
| 228 | +int __srs_create_dir_recursively(string dir) | ||
| 229 | +{ | ||
| 230 | + int ret = ERROR_SUCCESS; | ||
| 231 | + | ||
| 232 | + struct stat st; | ||
| 233 | + | ||
| 234 | + // stat current dir, if exists, return error. | ||
| 235 | + if (stat(dir.c_str(), &st) == 0) { | ||
| 236 | + return ERROR_SYSTEM_DIR_EXISTS; | ||
| 237 | + } | ||
| 238 | + | ||
| 239 | + // create parent first. | ||
| 240 | + size_t pos; | ||
| 241 | + if ((pos = dir.rfind("/")) != std::string::npos) { | ||
| 242 | + std::string parent = dir.substr(0, pos); | ||
| 243 | + ret = __srs_create_dir_recursively(parent); | ||
| 244 | + // return for error. | ||
| 245 | + if (ret != ERROR_SUCCESS && ret != ERROR_SYSTEM_DIR_EXISTS) { | ||
| 246 | + return ret; | ||
| 247 | + } | ||
| 248 | + // parent exists, set to ok. | ||
| 249 | + ret = ERROR_SUCCESS; | ||
| 250 | + } | ||
| 251 | + | ||
| 252 | + // create curren dir. | ||
| 253 | + mode_t mode = S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IWGRP|S_IXGRP|S_IROTH|S_IXOTH; | ||
| 254 | + if (::mkdir(dir.c_str(), mode) < 0) { | ||
| 255 | + if (errno == EEXIST) { | ||
| 256 | + return ERROR_SYSTEM_DIR_EXISTS; | ||
| 257 | + } | ||
| 258 | + | ||
| 259 | + ret = ERROR_SYSTEM_CREATE_DIR; | ||
| 260 | + srs_error("create dir %s failed. ret=%d", dir.c_str(), ret); | ||
| 261 | + return ret; | ||
| 262 | + } | ||
| 263 | + srs_info("create dir %s success.", dir.c_str()); | ||
| 264 | + | ||
| 265 | + return ret; | ||
| 266 | +} | ||
| 267 | + | ||
| 268 | +int srs_create_dir_recursively(string dir) | ||
| 269 | +{ | ||
| 270 | + int ret = ERROR_SUCCESS; | ||
| 271 | + | ||
| 272 | + ret = __srs_create_dir_recursively(dir); | ||
| 273 | + | ||
| 274 | + if (ret == ERROR_SYSTEM_DIR_EXISTS) { | ||
| 275 | + return ERROR_SUCCESS; | ||
| 276 | + } | ||
| 277 | + | ||
| 278 | + return ret; | ||
| 279 | +} | ||
| 280 | + |
| @@ -59,5 +59,8 @@ extern std::string srs_string_remove(std::string str, std::string remove_chars); | @@ -59,5 +59,8 @@ extern std::string srs_string_remove(std::string str, std::string remove_chars); | ||
| 59 | // whether string end with | 59 | // whether string end with |
| 60 | extern bool srs_string_ends_with(std::string str, std::string flag); | 60 | extern bool srs_string_ends_with(std::string str, std::string flag); |
| 61 | 61 | ||
| 62 | +// create dir recursively | ||
| 63 | +extern int srs_create_dir_recursively(std::string dir); | ||
| 64 | + | ||
| 62 | #endif | 65 | #endif |
| 63 | 66 |
-
请 注册 或 登录 后发表评论