首页 > 系统相关 >深入理解nginx mp4流媒体模块[下]

深入理解nginx mp4流媒体模块[下]

时间:2024-03-29 16:33:22浏览次数:19  
标签:流媒体 mdat data nginx mp4 atom ngx size

深入理解nginx mp4流媒体模块[上]
深入理解nginx mp4流媒体模块[中]

  以下对各个mp4的加载过程依次进行分析。

1. 加载ftyp atom

  加载ftyp atom的逻辑由ngx_http_mp4_read_ftyp_atom函数来完成,其最主要的逻辑就是将文件中读取到的ftyp atom放到ngx_http_mp4_file_t上下文的名字为ftyp_atom的ngx_chain_t中,为后续mp4文件内容发送做好准备。其源码如下:

static ngx_int_t
ngx_http_mp4_read_ftyp_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
{
   
    u_char     *ftyp_atom;
    size_t      atom_size;
    ngx_buf_t  *atom;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 ftyp atom");

	/* ftyp atom限制不能超过1024字节 */
    if (atom_data_size > 1024
        || ngx_mp4_atom_data(mp4) + (size_t) atom_data_size > mp4->buffer_end)
    {
   
        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
                      "\"%s\" mp4 ftyp atom is too large:%uL",
                      mp4->file.name.data, atom_data_size);
        return NGX_ERROR;
    }

    /* ftyp在一个mp4中只能有1个 */
    if (mp4->ftyp_atom.buf) {
   
        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
                      "duplicate mp4 ftyp atom in \"%s\"", mp4->file.name.data);
        return NGX_ERROR;
    }

    /* 分配一个ftyp atom的存储空间 */
    atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size;

    ftyp_atom = ngx_palloc(mp4->request->pool, atom_size);
    if (ftyp_atom == NULL) {
   
        return NGX_ERROR;
    }

    /* 设置ftyp atom的大小和名字字段 */
    ngx_mp4_set_32value(ftyp_atom, atom_size);
    ngx_mp4_set_atom_name(ftyp_atom, 'f', 't', 'y', 'p');

    /* 
     * only moov atom content is guaranteed to be in mp4->buffer
     * during sending response, so ftyp atom content should be copied
     */
    ngx_memcpy(ftyp_atom + sizeof(ngx_mp4_atom_header_t),
               ngx_mp4_atom_data(mp4), (size_t) atom_data_size);
    
    /* 将刚才分配的ftyp的存储空间装入mp4->ftyp_atom_buf的ngx_but_t中 */
    atom = &mp4->ftyp_atom_buf;
    atom->temporary = 1;
    atom->pos = ftyp_atom;     
    atom->last = ftyp_atom + atom_size;

    /* 最后将mp4->ftyp_atom_buf装入mp4->ftyp_atom的ngx_chain_t中 */
    mp4->ftyp_atom.buf = atom;
    mp4->ftyp_size = atom_size;
    mp4->content_length = atom_size;

	/* 跳过ftyp atom准备进行后面的分析 */
    ngx_mp4_atom_next(mp4, atom_data_size);

    return NGX_OK;
}

2. 加载mdat atom
  mp4文件本身没有规定mdat和moov这两个atom到底哪个在前哪个在后,为了提升网络流媒体视频打开速度,一般是建议将moov放在mdat atom前面的,但是有时候还是会碰到mdat atom是放在moov atom前面的情况,ngx_http_mp4_module对这两种情况都能够支持。源码如下:

static ngx_int_t
ngx_http_mp4_read_mdat_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
{
   
    ngx_buf_t  *data;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 mdat atom");
	
	/* 一个mp4中只能有一个mdat atom */
    if (mp4->mdat_atom.buf) {
   
        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
                      "duplicate mp4 mdat atom in \"%s\"", mp4->file.name.data);
        return NGX_ERROR;
    }

	/* 设置mp4->mdat_data_buf并将其装入mp4->mdat_data的ngx_chain_t链中 
	 * 因为mdat往往都很大,nginx不需要将其都加载到内存中,而是采用ngx_buf_t支持的
	 * 文件类型的ngx_buf_t使用方法。
	 */
    data = &mp4->mdat_data_buf;
    data->file = &mp4->file;
    data->in_file = 1;
    data->last_buf = (mp4->request == mp4->request->main) ? 1 : 0;
    data->last_in_chain = 1;
    data->file_last = mp4->offset + atom_data_size;

    mp4->mdat_atom.buf = &mp4->mdat_atom_buf;
    mp4->mdat_atom.next = &mp4->mdat_data;
    mp4->mdat_data.buf = data;

    if (mp4->trak.nelts) {
   
        /* 这种情况是mdat在moov后面的理想情况,mdat atom后面就没有东西了,
           直接将文件读指针调整到最末尾
         */
        mp4->offset = mp4->end;

    } else {
   
	    /* 这种情况是mdat在moove前面,mdat后面还需要继续加载moov atom
	       所以按照atom_data_size跳过mdat atom,继续进行扫描
	    */
	    ngx_mp4_atom_next(mp4, atom_data_size);
    }

    return NGX_OK;
}

3. 加载moov atom
  moov atom的读取由ngx_http_mp4_read_moov_atom函数来完成,其主要逻辑是递归调用ngx_http_mp4_read来加载各个子atom。源码如下:

/*
 * Small excess buffer to process atoms after moov atom, mp4->buffer_start
 * will be set to this buffer part after moov atom processing.
 */
#define NGX_HTTP_MP4_MOOV_BUFFER_EXCESS  (4 * 1024)

static ngx_int_t
ngx_http_mp4_read_moov_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
{
   
    ngx_int_t             rc;
    ngx_uint_t            no_mdat;
    ngx_buf_t            *atom;
    ngx_http_mp4_conf_t  *conf;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 moov atom");
	
	/* no_mdat表示前面没有碰到过mdat atom */
    no_mdat = (mp4->mdat_atom.buf == NULL);

	/* 如果用户请求中指定了时间起始偏移为0,并且没有指定结束时间偏移,
	   那么就简化流程,返回整个文件,不需要进一步处理了
	*/
    if (no_mdat && mp4->start == 0 && mp4->length == 0) {
   
        /*
         * send original file if moov atom resides before
         * mdat atom and client requests integral file
         */
        return NGX_DECLINED;
    }

	/* 一个mp4文件中只能有一个moov atom */
    if (mp4->moov_atom.buf) {
   
        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
                      "duplicate mp4 moov atom in \"%s\"", mp4->file.name.data);
        return NGX_ERROR;
    }

    conf = ngx_http_get_module_loc_conf(mp4->request, ngx_http_mp4_module);

	/* 判断当前的moov atom的大小是否超过了配置文件中设置的最大尺寸,
	   所以在提供服务前需要对库里面的视频文件的moov大小做一个预判。
	 */
    if (atom_data_size > mp4->buffer_size) {
   

        if (atom_data_size > conf->max_buffer_size) {
   
            ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
                          "\"%s\" mp4 moov atom is too large:%uL, "
                          "you may want to increase mp4_max_buffer_size",
                          mp4->file.name.data, atom_data_size);
            return NGX_ERROR;
        }
        
		/* 如果只是超过了默认大小限制,那么重新分配缓冲区,
		   缓冲区的大小为moov atom的大小加上预读mdat的
		   NGX_HTTP_MP4_MOOV_BUFFER_EXCESS字节大小 */
		 */
        ngx_pfree(mp4->request->pool, mp4->buffer);
        mp4->buffer = NULL;
        mp4->buffer_pos = NULL;
        mp4->buffer_end = NULL;

        mp4->buffer_size = (size_t) atom_data_size
                         + NGX_HTTP_MP4_MOOV_BUFFER_EXCESS * no_mdat;
    }

	/* 确保将整个moov读取到内存缓冲区中 */
    if (ngx_http_mp4_read(mp4, (size_t) atom_data_size) != NGX_OK) {
   
        return NGX_ERROR;
    }

	/* 初始化设置mp4的trak数组,默认支持2个trak */
    mp4->trak.elts = &mp4->traks;
    mp4->trak.size = sizeof(ngx_http_mp4_trak_t);
    mp4->trak.nalloc = 2;
    mp4->trak.pool = mp4->request->pool;

	/* 将mp4->moov_atom_buf装载到mp4->moov_atom ngx_chain_t链中 */
    atom = &mp4->moov_atom_buf;
    atom->temporary = 1;
    atom->pos = mp4->moov_atom_header;
    atom->last = mp4->moov_atom_header + 8;

    mp4->moov_atom.buf = &mp4->moov_atom_buf;
	
	/* 递归调用ngx_http_mp4_read_atom,解析moov的子atom */
    rc = ngx_http_mp4_read_atom(mp4, ngx_http_mp4_moov_atoms, atom_data_size);

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 moov atom done");

    if (no_mdat) {
   
        /* 对于mdat在moov后面的情况
           重置缓冲区的指针,并且允许最大预读NGX_HTTP_MP4_MOOV_BUFFER_EXCESS
           的mdat数据
		 */
        mp4->buffer_start = mp4->buffer_pos;
        mp4->buffer_size = NGX_HTTP_MP4_MOOV_BUFFER_EXCESS;

        if (mp4->buffer_start + mp4->buffer_size > mp4->buffer_end) {
   
            mp4->buffer = NULL;
            mp4->buffer_pos = NULL;
            mp4->buffer_end = NULL;
        }

    } else {
   
        /* 对于mdat在前面的情况,读取到moov后,就可以结束了 */
        mp4->offset = mp4->end;
    }

    return rc;
}

4. 加载mvhd atom
   mvhd作为moov的子atom,其加载逻辑由ngx_http_mp4_read_mvhd_atom函数来提供,它会在mvhd atom中解析mp4文件的timescale和duration两个字段,其中timescale字段的含义是每个mp4中的时间戳的一个单位对应的是多少分之一秒时间。这里需要判断请求的视频起始时间是否已经超过了mp4的总时长,如果超过了那么肯定是不合法的请求,nginx就会返回错误。另外,因为请求的视频内容

标签:流媒体,mdat,data,nginx,mp4,atom,ngx,size
From: https://blog.csdn.net/bluestn/article/details/137148850

相关文章

  • python根据达芬奇场景分析保存的edl文件,智能裁切输出4K视频画面(不带声音)-自动找到MP
    使用前先将mp4对应的EDL文件命名为相同的名字,如:春天.mp4,春天.edl只处理持续时间大于5帧的画面importcv2importosimporttimeimportdatetimeimportshutilfrommoviepy.editorimportVideoFileClip#读取切分文件defreadQiFenWenJian(filename):withopen(......
  • 用docker创建nginx反向代理tcp流量
    有这样一个需求,需要反向代理一个tcp连接,我打算用nginx来做,比较简单的实现掉./conf/nginx.conf配置文件usernginx;worker_processesauto;error_log/var/log/nginx/error.lognotice;pid/var/run/nginx.pid;events{worker_connections1024;}......
  • 使用Nginx服务部署一个表白的网站
    表白网站源码链接:https://pan.baidu.com/s/1Y0xKhlCfThaQJkIQU4Zi9w?pwd=erlt 提取码:erlt附上一张样例,这些可DIY修改:下面开始部署吧!  一.注册云服务器,并在安全规则开放80端口设置可访问ip为任意IP即0:0:0:0.这里不在赘述,详情可访问博主上篇详解:CSDN二.配置yum源#配......
  • 云服务器ubuntu下nginx和php-fpm环境配置
    云服务器ubuntu下nginx和php-fpm环境配置1.首先更新源apt-getupdate2.安装nginx服务apt-getinstall-ynginx3.查看nginx状态,如果显示中有active(running),表示已经安装成功servicenginxstatus4.寻找可安装的php-fpm中间件apt-cachesearchphp-fpm5.安装可安装......
  • 云计算笔记03--配置yum源及下载nginx并上传项目至服务器(常用命令 lrzsz cat head tail
    配置yum源首先将系统自带的yum源进行备份cd/etc/yum.repos.d///进入到yum配置目录mkdirbackup//创建一个备份目录mv*.repobackup///将所有以.repo结尾的文件移动到备份目录中#阿里云的yum源网站:https://developer.aliyun.com/......
  • 使用 Nginx Proxy Manager反向代理开启SSL
    今天又看到有人在推荐NginxProxyManger(太长了,下文NPM指代),于是我也在局域网的机器内装了一个,发现确实简单好用完全的界面操作非常便捷而且支持使用CloudflareDNS验证申请Let’sEncrypt的通配符证书,也很方便设置访问限制。安装部署使用Docker安装是非常的简单首先创建nginx-p......
  • docker创建nginx
    操作系统Linuxversion5.15.0-86-generic(buildd@lcy02-amd64-086)(gcc(Ubuntu11.4.0-1ubuntu1~22.04)11.4.0,GNUld(GNUBinutilsforUbuntu)2.38)#96-UbuntuSMPWedSep2008:23:49UTC2023查找nginx1.24sudodockersearchnginx:1.24拉nginx1.24sudodock......
  • Nginx日志统计分析
    Nginx日志统计分析1.统计IP访问量(独立ip访问数量)​awk'{print$1}'access.log|sort-n|uniq|wc-l​2.查看某一时间段的IP访问量(4-5点)​grep"07/Apr/2017:0[4-5]"access.log|awk'{print$1}'|sort|uniq-c|sort-nr|wc-l​3.查看访问最频繁的前100个......
  • web、keepalived、lvs、nginx 面试常问解析
    web、keepalived、lvs、nginx面试常问解析1.nginx代码状态代表啥意思--(工作可以快熟定位故障)200:服务器正常响应301:资源永久重定向302:资源临时重定向403:访问请求被禁止404:服务器找不到客户端请求的资源500:服务器内部错误502:代理服务器从后端收到了一条伪响应;badgateway......
  • Nginx配置静态代理/静态资源映射时root与alias的区别,带前缀映射用alias
    场景Nginx搭建静态资源映射实现远程访问服务器上的图片资源:https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/117283572以上在配置静态资源映射时使用的如下配置     location/{           root  D:/pic_old/;           try_......