这里写自定义目录标题
- 1. 为什么需要ngx_http_flv_module
- 2. 配置指令
- 3. 加载ngx_http_flv_module 模块
- 4. 源码分析
- 4.1 指令分析
- 4.2 ngx_http_flv_handler处理函数
- 5. 如何请求flv进行验证
- 6. 思考
1. 为什么需要ngx_http_flv_module
毋庸多说,就是为了提供在线的http flv流媒体播放服务。在若干年前,adobe flash player风靡的年代,可以说http flv是最流行的在线流媒体点播的解决方案,甚至很多直播也用http flv来实现,可以和RTMP实时流媒体协议的实时性相当。可是若干年过去了,随着H5的兴起,新的视频编码器的出现,adobe flash player已经被各大浏览器彻底抛弃了,虽然http flv也逐渐式微了,但是因为flv格式有着格式简单高效的特点,很容易进行流化处理,所以用http flv协议进行流媒体播放还一直生生不息。那么如何用nginx搭建一个http flv的流媒体服务器呢?这里就要用到ngx_http_flv_module了。
2. 配置指令
ngx_http_flv_module 的配置非常方便。只要在nginx.conf的location块中添加以下指令:
flv
即可开启flv流媒体模块。
3. 加载ngx_http_flv_module 模块
在configure的时候需要添加ngx_http_flv_module来将其编译进来,使用如下命令:
./configure --with-http_flv_module
然后在nginx.conf 中添加以下配置,如:
location / {flv;root html;
}
4. 源码分析
4.1 指令分析
static ngx_command_t ngx_http_flv_commands[] = {{ ngx_string("flv"),NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,ngx_http_flv,0,0,NULL },ngx_null_command
};
从以上代码知道,flv指令在location中使用,后面不带参数,并且用ngx_http_flv指令分析函数进行解析。
下面来看看ngx_http_flv函数,如下:
static char *
ngx_http_flv(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{ngx_http_core_loc_conf_t *clcf;clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);clcf->handler = ngx_http_flv_handler;return NGX_CONF_OK;
}
代码非常简单,就是在nginx 的http content phase阶段将处理回调函数挂进去,这里挂进去的钩子函数即ngx_http_flv_handler。将回调函数挂到content phase有两种方法,不过大都采用本模块使用的方法,另外也可以王ngx_htt_core_main_conf_t全局结构体的phases[NGX_HTTP_CONTENT_PHASE]动态数组添加回调函数来实现。
4.2 ngx_http_flv_handler处理函数
当用户请求的url匹配到nginx.conf中的某个开启了flv的location后,在content phase阶段,就会触发调用ngx_http_flv_handler函数。下面对该函数的实现过程进行详细解析:
static ngx_int_t
ngx_http_flv_handler(ngx_http_request_t *r)
{u_char *last;off_t start, len;size_t root;ngx_int_t rc;ngx_uint_t level, i;ngx_str_t path, value;ngx_log_t *log;ngx_buf_t *b;ngx_chain_t out[2];ngx_open_file_info_t of;ngx_http_core_loc_conf_t *clcf;if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {return NGX_HTTP_NOT_ALLOWED;}if (r->uri.data[r->uri.len - 1] == '/') {return NGX_DECLINED;}rc = ngx_http_discard_request_body(r);if (rc != NGX_OK) {return rc;}last = ngx_http_map_uri_to_path(r, &path, &root, 0);if (last == NULL) {return NGX_HTTP_INTERNAL_SERVER_ERROR;}log = r->connection->log;path.len = last - path.data;ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,"http flv filename: \"%V\"", &path);clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);ngx_memzero(&of, sizeof(ngx_open_file_info_t));of.read_ahead = clcf->read_ahead;of.directio = clcf->directio;of.valid = clcf->open_file_cache_valid;of.min_uses = clcf->open_file_cache_min_uses;of.errors = clcf->open_file_cache_errors;of.events = clcf->open_file_cache_events;if (ngx_http_set_disable_symlinks(r, clcf, &path, &of) != NGX_OK) {return NGX_HTTP_INTERNAL_SERVER_ERROR;}if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool)!= NGX_OK){ switch (of.err) {case 0:return NGX_HTTP_INTERNAL_SERVER_ERROR;case NGX_ENOENT:case NGX_ENOTDIR:case NGX_ENAMETOOLONG:level = NGX_LOG_ERR;rc = NGX_HTTP_NOT_FOUND;break;case NGX_EACCES:
#if (NGX_HAVE_OPENAT)case NGX_EMLINK:case NGX_ELOOP:
#endiflevel = NGX_LOG_ERR;rc = NGX_HTTP_FORBIDDEN;break;default:level = NGX_LOG_CRIT;rc = NGX_HTTP_INTERNAL_SERVER_ERROR;break;}if (rc != NGX_HTTP_NOT_FOUND || clcf->log_not_found) {ngx_log_error(level, log, of.err,"%s \"%s\" failed", of.failed, path.data);}return rc;}if (!of.is_file) {if (ngx_close_file(of.fd) == NGX_FILE_ERROR) {ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,ngx_close_file_n " \"%s\" failed", path.data);}return NGX_DECLINED;}r->root_tested = !r->error_page;start = 0;len = of.size;i = 1;if (r->args.len) {if (ngx_http_arg(r, (u_char *) "start", 5, &value) == NGX_OK) {start = ngx_atoof(value.data, value.len);if (start == NGX_ERROR || start >= len) {start = 0;}}}}log->action = "sending flv to client";r->headers_out.status = NGX_HTTP_OK;r->headers_out.content_length_n = len;r->headers_out.last_modified_time = of.mtime;if (ngx_http_set_etag(r) != NGX_OK) {return NGX_HTTP_INTERNAL_SERVER_ERROR;}if (ngx_http_set_content_type(r) != NGX_OK) {return NGX_HTTP_INTERNAL_SERVER_ERROR;}if (i == 0) { b = ngx_calloc_buf(r->pool);if (b == NULL) {return NGX_HTTP_INTERNAL_SERVER_ERROR;}b->pos = ngx_flv_header;b->last = ngx_flv_header + sizeof(ngx_flv_header) - 1;b->memory = 1;out[0].buf = b;out[0].next = &out[1]; }b = ngx_calloc_buf(r->pool);if (b == NULL) {return NGX_HTTP_INTERNAL_SERVER_ERROR;}b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t));if (b->file == NULL) {return NGX_HTTP_INTERNAL_SERVER_ERROR;}r->allow_ranges = 1;rc = ngx_http_send_header(r);if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {return rc;}b->file_pos = start;b->file_last = of.size;b->in_file = b->file_last ? 1: 0;b->last_buf = (r == r->main) ? 1 : 0; b->last_in_chain = 1; b->file->fd = of.fd; b->file->name = path; b->file->log = log;b->file->directio = of.is_directio;out[1].buf = b;out[1].next = NULL; return ngx_http_output_filter(r, &out[i]);
}
5. 如何请求flv进行验证
可以通过以下命令行,例如:
curl "http://127.0.0.1:8080/path/test.flv?start=12345" > /dev/null
当然,最好还是用支持flv流媒体播放的播放器,譬如vlc,ffplay来进行播放测试验证。
但是vlc和ffplay无法通过http flv来进行视频拖拽播放,必须实现一个能够支持http flv拖拽功能的播放器,是需要解析flv文件内容,根据metadata知道每一个关键帧的起始未知,然后在拖拽的时候自动对齐到最近的一个关键帧,然后发起一个新的带有start=xxx的http flv播放地址参数的请求进行播放。
6. 思考
其实FLV流媒体实现的代码还是非常简单的。如果希望能够像[nginx slice模块的使用和源码分析](https://editor.csdn.net/md/?articleId=136029381)中提到的那样,对FLV进行切片处理,来实现对cdn的缓存友好性,那怎么来做呢?由于FLV流媒体下载和普通的Range请求下载还是有一定的区别的,肯定需要进行特殊的处理。如果有时间的话我再来参数一下支持FLV切片回源的流媒体播放功能吧,敬请期待。