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

发布于:2024-03-29 ⋅ 阅读:(22) ⋅ 点赞:(0)

深入理解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就会返回错误。另外,因为请求的视频内容

本文含有隐藏内容,请 开通VIP 后查看