nginx flv模块的使用和源码分析

这里写自定义目录标题

    • [1. 为什么需要ngx_http_flv_module](#1. 为什么需要ngx_http_flv_module)
    • [2. 配置指令](#2. 配置指令)
    • [3. 加载ngx_http_flv_module 模块](#3. 加载ngx_http_flv_module 模块)
    • [4. 源码分析](#4. 源码分析)
      • [4.1 指令分析](#4.1 指令分析)
      • [4.2 ngx_http_flv_handler处理函数](#4.2 ngx_http_flv_handler处理函数)
    • [5. 如何请求flv进行验证](#5. 如何请求flv进行验证)
    • [6. 思考](#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块中添加以下指令:
c 复制代码
flv
复制代码
即可开启flv流媒体模块。

3. 加载ngx_http_flv_module 模块

复制代码
	在configure的时候需要添加ngx_http_flv_module来将其编译进来,使用如下命令:
bash 复制代码
 ./configure --with-http_flv_module
复制代码
    然后在nginx.conf 中添加以下配置,如:
bash 复制代码
location / {
    flv;
    root html;
}

4. 源码分析

4.1 指令分析

c 复制代码
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函数,如下:
c 复制代码
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函数。
    下面对该函数的实现过程进行详细解析:
c 复制代码
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;
    /* 仅支持HTTP GET/HEAD方法,否则返回http 405错误码 */
    if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
        return NGX_HTTP_NOT_ALLOWED;
    }
	/* 请求地址不能以/结尾,意味着没有指定特定的文件,则返回NGX_DECLINED,
	   即跳过本模块进行处理 */
    if (r->uri.data[r->uri.len - 1] == '/') {
        return NGX_DECLINED;
    }
	/* 因为是流媒体服务,没有需要处理HTTP请求的body部分,所以需要丢弃请求的body内容 */
    rc = ngx_http_discard_request_body(r);

    if (rc != NGX_OK) {
        return rc;
    }
	/* 将请求的url地址映射到本地硬盘上的文件路径, 
	   其中path为返回值:即映射出来的本地硬盘上的文件路径
	   其中root为返回值:即当前location对应的根目录的字符串长度
	   返回值是指向path字符串的结尾符\0的指针 */
    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);
	/* 以下准备打开flv文件 */
    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;
    }
	/* 在nginx的文件缓冲区中打开flv文件 */
    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:
#endif

            level = 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;
    }
	/* 如果打开的不是文件,那么久关闭文件句柄,并返回NGX_DECLINED */
    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;
	/* 根据请求url中的start来获取客户端实际想要的视频内容开始的文件偏移量 */
    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;
            }
			/* 如果不是从头开始请求,那么响应的内容长度按照以下代码进行计算
            if (start) {
                len = sizeof(ngx_flv_header) - 1 + len - start;
                i = 0;	/* i = 0 表示不是响应完整内容 */
            }
        }
    }

    log->action = "sending flv to client";
	/* 响应的状态设置为200, Content-Length设置为将要响应的文件的长度 */
    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = len;
    r->headers_out.last_modified_time = of.mtime;
	/* 设置当前文件的http etag header值 */
    if (ngx_http_set_etag(r) != NGX_OK) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
	/* 设置文件的Content-Type类型 */
    if (ngx_http_set_content_type(r) != NGX_OK) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
	/* 不是响应完整文件内容的情况,需要自己构建一个flv header放入响应缓冲区中 */
    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];		/* 将flv头部缓存连接到flv body缓存,构成一个响应缓存链 */
    }

	/* 接下去分配一个文件类型的ngx_buf_t缓冲区 */
    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;
    }
	/* 设置允许ngx_http_range_filter_module处理Range请求 */
    r->allow_ranges = 1;
	/* 发送http响应头 */
    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;
	/* 设置ngx_buf_t的in_file标记,表示是文件缓存 */
    b->in_file = b->file_last ? 1: 0;
    b->last_buf = (r == r->main) ? 1 : 0;  /* 如果是主请求,设置当前ngx_buf_t是最后一个buf */
    b->last_in_chain = 1;		/* 设置为当前ngx_buf_t是响应缓存链中的最后一个 */

    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;			/* 设置next为NULL, 表示当前是最后一个ngx_buf_t */
	/* 通知nginx框架发送flv内容 */
    return ngx_http_output_filter(r, &out[i]);
}

5. 如何请求flv进行验证

复制代码
可以通过以下命令行,例如:
Bash 复制代码
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切片回源的流媒体播放功能吧,敬请期待。
相关推荐
05大叔25 分钟前
网络基础知识 域名,JSON格式,AI基础
运维·服务器·网络
安当加密28 分钟前
无需改 PAM!轻量级 RADIUS + ASP身份认证系统 实现 Linux 登录双因子认证
linux·运维·服务器
dashizhi201529 分钟前
服务器共享禁止保存到本地磁盘、共享文件禁止另存为本地磁盘、移动硬盘等
运维·网络·stm32·安全·电脑
卷福同学1 小时前
【养虾日记】QClaw操作浏览器自动化发文
运维·人工智能·程序人生·自动化
woho7788992 小时前
不同网段IP的网络打印机,打印、扫描设置
运维·服务器·网络
耗子会飞2 小时前
小白学习固定VM虚拟机的centos服务器的IP
运维·服务器·centos
门豪杰3 小时前
Ubuntu下安装Claude Code
linux·运维·ubuntu·claude·claude code
新新学长搞科研3 小时前
第五届电子、集成电路与通信技术国际学术会议(EICCT 2026)
运维·人工智能·自动化·集成测试·信号处理·集成学习·电气自动化
桌面运维家3 小时前
Windows/Linux双启动:BIOS/UEFI多配置桌面创建指南
linux·运维·windows
無法複制3 小时前
debian安装Postgresql-14.x
运维·postgresql·debian