nginx_rtmp_module 之 ngx_rtmp_mp4_module 的mp4源码分析

一:整体代码函数预览

static ngx_int_t
ngx_rtmp_mp4_postconfiguration(ngx_conf_t *cf)
{
    ngx_rtmp_play_main_conf_t      *pmcf;
    ngx_rtmp_play_fmt_t           **pfmt, *fmt;
    pmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_play_module);
    pfmt = ngx_array_push(&pmcf->fmts);
    if (pfmt == NULL) {
        return NGX_ERROR;
    }
    fmt = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_play_fmt_t));
    if (fmt == NULL) {
        return NGX_ERROR;
    }
    *pfmt = fmt;
    ngx_str_set(&fmt->name, "mp4-format");
    ngx_str_set(&fmt->pfx, "mp4:");
    ngx_str_set(&fmt->sfx, ".mp4");
    fmt->init  = ngx_rtmp_mp4_init;  // 初始化
    fmt->done  = ngx_rtmp_mp4_done;  // 完成
    fmt->seek  = ngx_rtmp_mp4_seek;  // seek
    fmt->start = ngx_rtmp_mp4_start; // 开始
    fmt->stop  = ngx_rtmp_mp4_stop;  // 结束
    fmt->send  = ngx_rtmp_mp4_send;  // 发送数据
    return NGX_OK;
}

二:数据结构定义

定义

标准定义

nginx 数据结构定义

stsc 【Sample To Chunk Box】

type 4 字节已经偏移过,

box_size 4 字节已经偏移过

typedef struct {

    uint32_t                            first_chunk;

    uint32_t                            samples_per_chunk;

    uint32_t                            sample_descrption_index;

} ngx_rtmp_mp4_chunk_entry_t;

typedef struct {

    uint32_t                            version_flags;

    uint32_t                            entry_count;

    ngx_rtmp_mp4_chunk_entry_t          entries[0];

} ngx_rtmp_mp4_chunks_t;

stts 【Decoding Time to Sample Box】

type 4 字节已经偏移过,

box_size 4 字节已经偏移过

typedef struct {

    uint32_t                            sample_count;

    uint32_t                            sample_delta;

} ngx_rtmp_mp4_time_entry_t;

typedef struct {

    uint32_t                            version_flags;

    uint32_t                            entry_count;

    ngx_rtmp_mp4_time_entry_t           entries[0];

} ngx_rtmp_mp4_times_t;

ctts 【Composition Time to Sample Box】

type 4 字节已经偏移过,

box_size 4 字节已经偏移过

typedef struct {

    uint32_t                            sample_count;

    uint32_t                            sample_offset;

} ngx_rtmp_mp4_delay_entry_t;

typedef struct {

    uint32_t                            version_flags;

    uint32_t                            entry_count;

    ngx_rtmp_mp4_delay_entry_t          entries[0];

} ngx_rtmp_mp4_delays_t;

stss 【Sync Sample Box】

type 4 字节已经偏移过,

box_size 4 字节已经偏移过

typedef struct {

    uint32_t                            version_flags;

    uint32_t                            entry_count;

    uint32_t                            entries[0];

} ngx_rtmp_mp4_keys_t;

stsz 【Sample Size Boxes】

type 4 字节已经偏移过,

box_size 4 字节已经偏移过

typedef struct {

    uint32_t                            version_flags; 4 字节

    uint32_t                            sample_size;

    uint32_t                            sample_count;

    uint32_t                            entries[0];

} ngx_rtmp_mp4_sizes_t;

stco 【Chunk Offset Box】

co64

type 4 字节已经偏移过,

box_size 4 字节已经偏移过

typedef struct {

    uint32_t                            version_flags; 4 字节固定

    uint32_t                            entry_count;

    uint32_t                            entries[0];

} ngx_rtmp_mp4_offsets_t;

typedef struct {

    uint32_t                            version_flags;

    uint32_t                            entry_count;

    uint64_t                            entries[0];

} ngx_rtmp_mp4_offsets64_t;

三:数据结构解析

static ngx_rtmp_mp4_box_t                       ngx_rtmp_mp4_boxes[] = {
    { ngx_rtmp_mp4_make_tag('t','r','a','k'),   ngx_rtmp_mp4_parse_trak   },
    { ngx_rtmp_mp4_make_tag('m','d','i','a'),   ngx_rtmp_mp4_parse        },
    { ngx_rtmp_mp4_make_tag('m','d','h','d'),   ngx_rtmp_mp4_parse_mdhd   },
    { ngx_rtmp_mp4_make_tag('h','d','l','r'),   ngx_rtmp_mp4_parse_hdlr   },
    { ngx_rtmp_mp4_make_tag('m','i','n','f'),   ngx_rtmp_mp4_parse        },
    { ngx_rtmp_mp4_make_tag('s','t','b','l'),   ngx_rtmp_mp4_parse        },
    { ngx_rtmp_mp4_make_tag('s','t','s','d'),   ngx_rtmp_mp4_parse_stsd   },
    { ngx_rtmp_mp4_make_tag('s','t','s','c'),   ngx_rtmp_mp4_parse_stsc   },  // 记录了每个chunk中包含多少sample
    { ngx_rtmp_mp4_make_tag('s','t','t','s'),   ngx_rtmp_mp4_parse_stts   },  // sample 解码时间的压缩表
    { ngx_rtmp_mp4_make_tag('c','t','t','s'),   ngx_rtmp_mp4_parse_ctts   },  // 帧解码到渲染的时间差值,通常用在B帧的场景,sample的CTS与DTS的时间差的压缩表
    { ngx_rtmp_mp4_make_tag('s','t','s','s'),   ngx_rtmp_mp4_parse_stss   },  // 关键帧映射表
    { ngx_rtmp_mp4_make_tag('s','t','s','z'),   ngx_rtmp_mp4_parse_stsz   },  // 每帧数据的大小
    { ngx_rtmp_mp4_make_tag('s','t','z','2'),   ngx_rtmp_mp4_parse_stz2   },
    { ngx_rtmp_mp4_make_tag('s','t','c','o'),   ngx_rtmp_mp4_parse_stco   },  // 记录了chunk对应的offset
    { ngx_rtmp_mp4_make_tag('c','o','6','4'),   ngx_rtmp_mp4_parse_co64   },
    { ngx_rtmp_mp4_make_tag('a','v','c','1'),   ngx_rtmp_mp4_parse_avc1   },
    { ngx_rtmp_mp4_make_tag('a','v','c','C'),   ngx_rtmp_mp4_parse_avcC   },   // sps pps 解析
    { ngx_rtmp_mp4_make_tag('m','p','4','a'),   ngx_rtmp_mp4_parse_mp4a   },
    { ngx_rtmp_mp4_make_tag('m','p','4','v'),   ngx_rtmp_mp4_parse_mp4v   },
    { ngx_rtmp_mp4_make_tag('e','s','d','s'),   ngx_rtmp_mp4_parse_esds   },
    { ngx_rtmp_mp4_make_tag('.','m','p','3'),   ngx_rtmp_mp4_parse_mp3    },
    { ngx_rtmp_mp4_make_tag('n','m','o','s'),   ngx_rtmp_mp4_parse_nmos   },
    { ngx_rtmp_mp4_make_tag('s','p','e','x'),   ngx_rtmp_mp4_parse_spex   },
    { ngx_rtmp_mp4_make_tag('w','a','v','e'),   ngx_rtmp_mp4_parse        }
};

ngx_rtmp_mp4_parse_stsc   记录了每个chunk中包含多少sample

上图数据解析

00 00 00 28(size) --> 40 字节总大小包含的自己在内的所有数据:

00 00 00 28(size) 73 74 73 63(stsc) 00 00 00 00(version_flags) 00 00 00 02(entry_count) 00 00 00 01(first_chunk) 00 00 00 02(samples_per_chunk) 00 00 00 01(sample_descrption_index) 00 00 00 02(first_chunk) 00 00 00 01(samples_per_chunk) 00 00 00 01(sample_descrption_index)

static ngx_int_t
ngx_rtmp_mp4_parse_stsc(ngx_rtmp_session_t *s, u_char *pos, u_char *last)
{
    ngx_rtmp_mp4_ctx_t         *ctx;
    ngx_rtmp_mp4_track_t       *t;
    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module);
    t = ctx->track;
    if (t == NULL) {
        return NGX_OK;
    }
    t->chunks = (ngx_rtmp_mp4_chunks_t *) pos;  // 内存数据上面的解析
    if (pos + sizeof(*t->chunks) + ngx_rtmp_r32(t->chunks->entry_count) *
                                   sizeof(t->chunks->entries[0])
        <= last)// 内存越界判断
    {
        return NGX_OK;
    }
    t->chunks = NULL;
    return NGX_ERROR;
}

ngx_rtmp_mp4_parse_stco  记录了chunk对应的offset

00 00 03 D4(980 长度) 73 74 63 6F (stco)00 00 00 00(version_flags) 00 00 00 F1( entry_count 241 ) 00 00 00 30( entrie) 00 00 10 4D ( entrie)00 00 20 1C( entrie) 00 00 29 38( entrie) 00 00 49 2D( entrie) 00 00 70 C9( entrie) 00 00 B1 C1 ( entrie) ...............

static ngx_int_t
ngx_rtmp_mp4_parse_stco(ngx_rtmp_session_t *s, u_char *pos, u_char *last)
{
    ngx_rtmp_mp4_ctx_t         *ctx;
    ngx_rtmp_mp4_track_t       *t;
    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module);
    t = ctx->track;
    if (t == NULL) {
        return NGX_OK;
    }
    t->offsets = (ngx_rtmp_mp4_offsets_t *) pos; // 数据结构的赋值
    if (pos + sizeof(*t->offsets) + ngx_rtmp_r32(t->offsets->entry_count) *
                                    sizeof(t->offsets->entries[0])
        <= last) // 内存越界判断
    {
        return NGX_OK;
    }
    t->offsets = NULL;
    return NGX_ERROR;
}

其他数据结构的解析与上面的流程基本一样

四:数据处理流程

五:play 流程代码分析

ffplay -analyzeduration 1000000 "rtmp://10.90.103.5/vod/0001.mp4

1> ngx_rtmp_mp4_init 初始化变量解析 mp4 moov box 的数据结构

2> ngx_rtmp_mp4_seek (ngx_rtmp_session_t *s, ngx_file_t *f, ngx_uint_t timestamp), timestamp = 0 ,seek 时间戳为零标识从头开始播放。ngx_rtmp_mp4_seek_track 有两个轨道,音频轨和视频轨,分别进行seek

static ngx_int_t
ngx_rtmp_mp4_seek_track(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t,
                        ngx_int_t timestamp)
{
    ngx_rtmp_mp4_cursor_t          *cr;
    cr = &t->cursor;
    ngx_memzero(cr, sizeof(*cr));
    if (ngx_rtmp_mp4_seek_time(s, t, ngx_rtmp_mp4_from_rtmp_timestamp(  // ngx_rtmp_mp4_from_rtmp_timestamp 时间戳的
                          t, timestamp)) != NGX_OK ||
        ngx_rtmp_mp4_seek_key(s, t)   != NGX_OK ||
        ngx_rtmp_mp4_seek_chunk(s, t) != NGX_OK ||
        ngx_rtmp_mp4_seek_size(s, t)  != NGX_OK ||
        ngx_rtmp_mp4_seek_delay(s, t) != NGX_OK)
    {
        return NGX_ERROR;
    }
    cr->valid = 1;
    return NGX_OK;
} 

// 时间戳的转换函数,mp4 文件到rtmp
// rtmp_mp4 存放的是timescale 时间戳 通常是 12288
// rtmp 时间刻度通常是 1000 
static ngx_inline uint32_t
ngx_rtmp_mp4_to_rtmp_timestamp(ngx_rtmp_mp4_track_t *t, uint64_t ts) // 给定一个时间值 从rtmp_mp4 12288 刻度转换到 1000
{
    return (uint32_t) (ts * 1000 / t->time_scale); 
}
static ngx_inline uint32_t
ngx_rtmp_mp4_from_rtmp_timestamp(ngx_rtmp_mp4_track_t *t, uint32_t ts) // 给定一个时间值 从rtmp 1000 刻度转换到12288
{
    return (uint64_t) ts * t->time_scale / 1000;
}

ngx_rtmp_mp4_seek_time :

stts:

typedef struct {

    uint32_t                            sample_count;

    uint32_t                            sample_delta;

} ngx_rtmp_mp4_time_entry_t;

typedef struct {

    uint32_t                            version_flags;

    uint32_t                            entry_count;

    ngx_rtmp_mp4_time_entry_t           entries[0];

} ngx_rtmp_mp4_times_t;

static ngx_int_t
ngx_rtmp_mp4_seek_time(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t,
                       uint32_t timestamp)
{
    ngx_rtmp_mp4_cursor_t      *cr;
    ngx_rtmp_mp4_time_entry_t  *te;
    uint32_t                    dt;
    if (t->times == NULL) {
        return NGX_ERROR;
    }
    cr = &t->cursor; // 对应音频或者视频轨道的游标器
    te = t->times->entries; // times --> ngx_rtmp_mp4_times_t
    // sample_counts	6,1,5,1,5,1,1,1,1,1
    // sample_deltas	1024,3040,1024,3088,1024,1008,1024,1032,1024,1048     
    // 整个循环就是处理,遍历sample_counts个sample_deltas值与 参数 timestamp比大小. 1. 小于记录 cr->timestamp += dt,
    while (cr->time_pos < ngx_rtmp_r32(t->times->entry_count)) {
        dt = ngx_rtmp_r32(te->sample_delta) * ngx_rtmp_r32(te->sample_count); // 242 * 1024 = 22757376 
        // cr->timestamp + dt >= timestamp ,在这个范围说明找到
        if (cr->timestamp + dt >= timestamp) {
            if (te->sample_delta == 0) {
                return NGX_ERROR;
            }
            cr->time_count = (timestamp - cr->timestamp) /
                             ngx_rtmp_r32(te->sample_delta);                   // sample_delta 相同,cr->time_count 等于当前时间戳距离这个entry开始时间错的插值,的第几个delta
            cr->timestamp += ngx_rtmp_r32(te->sample_delta) * cr->time_count;  // seek 的时间戳,就是把 cr->timestamp 修改当前的时间戳
            cr->pos += cr->time_count;                                         // 记录当前seek 对应时间戳具体的位置
            break;
        }
        // 没找到,
        cr->timestamp += dt;                       // 记录值进行累加,访问下一个
        cr->pos += ngx_rtmp_r32(te->sample_count); // 记录pos 的位置
        cr->time_pos++;                            // 记录已经访问第几个entry
        te++;                                      // te++ 下一个entry
    }
    if (cr->time_pos >= ngx_rtmp_r32(t->times->entry_count)) {
        return  NGX_ERROR;
    }
    return NGX_OK;
}

ngx_rtmp_mp4_seek_key

stss

typedef struct {

    uint32_t                            version_flags;

    uint32_t                            entry_count; // 个数

    uint32_t                            entries[0]; // pos

} ngx_rtmp_mp4_keys_t;

static ngx_int_t
ngx_rtmp_mp4_seek_key(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t)
{
    ngx_rtmp_mp4_cursor_t      *cr;
    uint32_t                   *ke;
    ngx_int_t                   dpos;
    cr = &t->cursor;       // 对应音频或者视频轨道的游标器
    if (t->keys == NULL) {
        return NGX_OK;
    }
    // cr->key_pos 初始值为0 ,t->keys->entry_count 关键帧的个数
    while (cr->key_pos < ngx_rtmp_r32(t->keys->entry_count)) {
        // 遍历每一个entry得到帧号,与当前pos位置进行对比,找到当前位置的下一个关键帧
        if (ngx_rtmp_r32(t->keys->entries[cr->key_pos]) > cr->pos) {    // cr->pos 上面seek 后具体位置
            break;
        }
        cr->key_pos++;
    }
    if (cr->key_pos >= ngx_rtmp_r32(t->keys->entry_count)) {
        return NGX_OK;
    }
    ke = &t->keys->entries[cr->key_pos];                                // 
    dpos = ngx_rtmp_r32(*ke) - cr->pos - 1;                             // 计算距离下一个关键帧的差值
    cr->key = 1;
    /* TODO: range version needed */
    for (; dpos > 0; --dpos) {
        ngx_rtmp_mp4_next_time(s, t);
    }
    return NGX_OK;
}

ngx_rtmp_mp4_seek_chunk

stsc

typedef struct {

    uint32_t                            first_chunk;

    uint32_t                            samples_per_chunk;

    uint32_t                            sample_descrption_index;

} ngx_rtmp_mp4_chunk_entry_t;

typedef struct {

    uint32_t                            version_flags;

    uint32_t                            entry_count;

    ngx_rtmp_mp4_chunk_entry_t          entries[0];

} ngx_rtmp_mp4_chunks_t;

static ngx_int_t
ngx_rtmp_mp4_seek_chunk(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t)
{
    ngx_rtmp_mp4_cursor_t          *cr;
    ngx_rtmp_mp4_chunk_entry_t     *ce, *nce;
    ngx_uint_t                      pos, dpos, dchunk;
    cr = &t->cursor;                                              // 对应音频或者视频轨道的游标器
    if (t->chunks == NULL || t->chunks->entry_count == 0) {
        cr->chunk = 1;
        return NGX_OK;
    }
    ce = t->chunks->entries;                                      // entry 实体结构  
    pos = 0;
    // cr->chunk_pos 初始值是0 
    while (cr->chunk_pos + 1 < ngx_rtmp_r32(t->chunks->entry_count)) {
        nce = ce + 1;                                             // 下一个entry
        dpos = (ngx_rtmp_r32(nce->first_chunk) -
                ngx_rtmp_r32(ce->first_chunk)) *
                ngx_rtmp_r32(ce->samples_per_chunk);              // 距离下一个chunk总共有多少点,(2-1) * 2 = 2 帧
        if (pos + dpos > cr->pos) {                               // 判断当前位置是不是在这个chunk 中
            break;
        }
        pos += dpos;                                              // pos 从头遍历累加 
        ce++;                                                     // 访问下一个entry
        cr->chunk_pos++;                                          // chunk_pos ++,记录游标当前在第几个chunk位置 
    }
    if (ce->samples_per_chunk == 0) {
        return NGX_ERROR;
    }
    dchunk = (cr->pos - pos) / ngx_rtmp_r32(ce->samples_per_chunk); // seek 之后的当前pos 位置,与找到chunk的pos差值,相同连续的chunk 可能不记录写进去。
    // 比如:  first_chunk   1,2,3,4,5,6,12,13
    //  samples_per_chunk   1,4,2,4,2,4,3, 4      ---> 1 + 4 + 2 + 4 + 2 + 4 = 17 , 18 --> 38 之间都是; 这个时候会有一个 dchunk差值
    
    cr->chunk = ngx_rtmp_r32(ce->first_chunk) + dchunk;             // 得到当前游标chunk的值,真实对应chunk 位置。
    cr->chunk_pos = (ngx_uint_t) (ce - t->chunks->entries);         // cr->chunk_pos 记录的是存储的真实解析位置
    cr->chunk_count = (ngx_uint_t) (cr->pos - pos - dchunk *
                                    ngx_rtmp_r32(ce->samples_per_chunk)); // 剩余是 chunk 偏移的变量帧
    return ngx_rtmp_mp4_update_offset(s, t);
}

static ngx_int_t
ngx_rtmp_mp4_update_offset(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t)
{
    ngx_rtmp_mp4_cursor_t          *cr;
    ngx_uint_t                      chunk;
    cr = &t->cursor;
    if (cr->chunk < 1) {
        return NGX_ERROR;
    }
    chunk = cr->chunk - 1; // 数组访问 -1  
    if (t->offsets) {
        if (chunk >= ngx_rtmp_r32(t->offsets->entry_count)) {
            return NGX_ERROR;
        }
        cr->offset = (off_t) ngx_rtmp_r32(t->offsets->entries[chunk]); // 取出第几个chunk偏移大小进行赋值
        cr->size = 0;
        return NGX_OK;
    }
    if (t->offsets64) {
        if (chunk >= ngx_rtmp_r32(t->offsets64->entry_count)) {
            return NGX_ERROR;
        }
        cr->offset = (off_t) ngx_rtmp_r64(t->offsets64->entries[chunk]);
        cr->size = 0;
        return NGX_OK;
    }
    return NGX_ERROR;
}

ngx_rtmp_mp4_seek_size

stsz

typedef struct {

    uint32_t                            version_flags; 4 字节

    uint32_t                            sample_size;

    uint32_t                            sample_count;

    uint32_t                            entries[0];

} ngx_rtmp_mp4_sizes_t;

static ngx_int_t
ngx_rtmp_mp4_seek_size(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t)
{
    ngx_rtmp_mp4_cursor_t      *cr;
    ngx_uint_t                  pos;
    cr = &t->cursor;                                             // 当前游标
    if (cr->chunk_count > cr->pos) {                             // 》 ??
        return NGX_ERROR;
    }
    if (t->sizes) {
        if (t->sizes->sample_size) {
            cr->size = ngx_rtmp_r32(t->sizes->sample_size);
            cr->offset += cr->size * cr->chunk_count;
            return NGX_OK;
        }
        if (cr->pos >= ngx_rtmp_r32(t->sizes->sample_count)) {
            return NGX_ERROR;
        }
        for (pos = 1; pos <= cr->chunk_count; ++pos) {           // chunk_count ??
            cr->offset += ngx_rtmp_r32(t->sizes->entries[cr->pos - pos]); 
        }
        cr->size_pos = cr->pos;                                   // seek 时间戳当前pos
        cr->size = ngx_rtmp_r32(t->sizes->entries[cr->size_pos]); // 获取当前pos对应帧数据大小
        return NGX_OK;
    }
    if (t->sizes2) {
        if (cr->size_pos >= ngx_rtmp_r32(t->sizes2->sample_count)) {
            return NGX_ERROR;
        }
        cr->size_pos = cr->pos;
        return NGX_OK;
    }
    return NGX_ERROR;
}

ngx_rtmp_mp4_seek_delay

ctts

typedef struct {

    uint32_t                            sample_count;

    uint32_t                            sample_offset;

} ngx_rtmp_mp4_delay_entry_t;

typedef struct {

    uint32_t                            version_flags;

    uint32_t                            entry_count;

    ngx_rtmp_mp4_delay_entry_t          entries[0];

} ngx_rtmp_mp4_delays_t;

static ngx_int_t
ngx_rtmp_mp4_seek_delay(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t)
{
    ngx_rtmp_mp4_cursor_t      *cr;
    ngx_rtmp_mp4_delay_entry_t *de;
    uint32_t                    pos, dpos;
    cr = &t->cursor;
    if (t->delays == NULL) {
        return NGX_OK;
    }
    pos = 0;
    de = t->delays->entries;
    while (cr->delay_pos < ngx_rtmp_r32(t->delays->entry_count)) { // 遍历所有entry
        dpos = ngx_rtmp_r32(de->sample_count);                     // 2 表示:sample的个数
        if (pos + dpos > cr->pos) {                                // 0 + 2 > 0 , 当前pos 帧在其中
            cr->delay_count = cr->pos - pos;                       // 差值 count
            cr->delay = ngx_rtmp_r32(de->sample_offset);           // 偏移大小
            break;
        }
        cr->delay_pos++; // 记录entry访问的位置
        pos += dpos;     // pos 累加计数
        de++;            // 下一个entry
    }
    if (cr->delay_pos >= ngx_rtmp_r32(t->delays->entry_count)) {
        return NGX_OK;
    }
    return NGX_OK;
}

3> ngx_rtmp_mp4_next play 播放一直取下一帧数据

static ngx_int_t
ngx_rtmp_mp4_next(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t)
{
    if (ngx_rtmp_mp4_next_time(s, t)  != NGX_OK ||
        ngx_rtmp_mp4_next_key(s, t)   != NGX_OK ||
        ngx_rtmp_mp4_next_chunk(s, t) != NGX_OK ||
        ngx_rtmp_mp4_next_size(s, t)  != NGX_OK ||
        ngx_rtmp_mp4_next_delay(s, t) != NGX_OK)
    {
        t->cursor.valid = 0;
        return NGX_ERROR;
    }
    t->cursor.valid = 1;
    return NGX_OK;
}

六:代码调试

使用进行查看变量的值: ngx_rtmp_r32(t->delays->entry_count)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/251949.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

极兔速递物流查询,用表格导出单号的每一条物流信息

批量查询极兔速递单号的物流信息&#xff0c;并以表格的形式导出单号的每一条物流信息。 所需工具&#xff1a; 一个【快递批量查询高手】软件 极兔速递单号若干 操作步骤&#xff1a; 步骤1&#xff1a;运行【快递批量查询高手】软件&#xff0c;第一次使用的朋友记得先注册…

HPV治疗期间如何预防重复感染?谭巍主任讲述具体方法

众所周知&#xff0c;人乳头瘤病毒(HPV)是一种常见的性传播疾病&#xff0c;感染后可能会引起生殖器疣、宫颈癌等疾病。在治疗期间&#xff0c;预防重复感染非常重要。今日将介绍一些预防HPV重复感染的方法。 1. 杜绝不洁性行为 在治疗期间&#xff0c;患者应该避免与感染HPV…

CT成像技术—20231210

本文要说的是扇束重排&#xff0c;对于扇束及锥束直接重建公式&#xff0c;可以看我做的PDF https://github.com/leslielee619/CTRec/blob/main/重建公式.pdf 在说重排之前&#xff0c;我还想对那个文件内容补充两点&#xff1a; 1、FDK算法或Feldkamp算法&#xff0c;出自Fel…

linux下开放端口的方法

为了辅助我们查看端口状态&#xff0c;本文采用nmap扫描端口 目标机&#xff1a;192.168.241.1&#xff0c;本文的目的是开启22端口 我们可以根据端口状态&#xff08;filtered&#xff09;看出&#xff0c;端口处于过滤状态&#xff0c;即防火墙过滤了该端口 PS&#xff1a;…

PCB设计规则中的经验公式_笔记

PCB设计规则中的经验公式 规则1 - 临界长度规则2 - 信号带宽与上升时间规则3- 时钟信号带宽规则4-信号传输速度规则5- 集肤 (效应) 深度规则6 - 50Ω传输线电容规则7 - 50Ω传输线电感规则8 - 回流路径电感规则9 - 地弹噪声规则10- 串行传输比特率与信号带宽规则11- PCB走线直流…

JAVA序列化(创建可复用的 Java 对象)

JAVA 序列化(创建可复用的 Java 对象) 保存(持久化)对象及其状态到内存或者磁盘 Java 平台允许我们在内存中创建可复用的 Java 对象&#xff0c;但一般情况下&#xff0c;只有当 JVM 处于运行时&#xff0c;这些对象才可能存在&#xff0c;即&#xff0c;这些对象的生命周期不…

Linux---文本搜索命令

1. grep命令的使用 命令说明grep文本搜索 grep命令效果图: 2. grep命令选项的使用 命令选项说明-i忽略大小写-n显示匹配行号-v显示不包含匹配文本的所有行 -i命令选项效果图: -n命令选项效果图: -v命令选项效果图: 3. grep命令结合正则表达式的使用 正则表达式说明^以指…

STM32_HAL库—IWDG看门狗

一、CubeMX设置 1、晶振配置&#xff08;72M&#xff09; 2、数据配置 超时时间 Tout prv / LSI * rlv (s) 其中prv是预分频器寄存器的值&#xff0c;rlv是重装载寄存器的值&#xff0c;而LSI值默认是40kHz&#xff0c;如下所示。 3、代码实现 int main(){while(1){HAL_IW…

gitlab ci pages

参考文章 gitlab pages是什么 一个可以利用gitlab的域名和项目部署自己静态网站的机制 开启 到gitlab的如下页面 通过gitlab.ci部署项目的静态网站 # build ruby 1/3: # stage: build # script: # - echo "ruby1"# build ruby 2/3: # stage: build …

docker入门小结

docker是什么&#xff1f;它有什么优势&#xff1f; 快速获取开箱即用的程序 docker使得所有的应用传输就像我们日常通过聊天工具文件传输一样&#xff0c;发送方将程序传输到超级码头而接收方也只需通过超级码头进行获取即可&#xff0c;就像一只鲸鱼拖着货物来回运输一样。…

pl_vio线特征·part II

pl_vio线特征part II 0.引言4.线段残差对位姿的导数4.1.直线的观测模型和误差4.2.误差雅克比推导4.3.误差雅可比求导简洁版(不含imu坐标系转换)4.4.相关代码 0.引言 pl_vio线特征part I 现在CSDN有字数限制了&#xff0c;被迫拆分为两篇文章。 4.线段残差对位姿的导数 这一小…

盛最多水的容器

给定一个长度为 n 的整数列表 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。返回容器可以储存的最大水量。 说明&#xff1a;你不能倾斜容器。 示例1&…

ARM I2C通信

1.概念 I2C总线是PHLIPS公司在八十年代初推出的一种串行的半双工同步总线&#xff0c;主要用于连接整体电路2.IIC总线硬件连接 1.IIC总线支持多主机多从机&#xff0c;但是在实际开发过程中&#xff0c;大多数采用单主机多从机模式 2.挂接到IIC总线上&#xff0c;每个从机设备都…

一键批量改名,将西班牙语文件轻松转换为中文!

你是否曾经遇到过需要将大量西班牙语文件批量转换为中文文件的问题&#xff1f;这可能会让你感到头疼和繁琐。但是&#xff0c;现在有了我们的批量改名工具&#xff0c;你可以轻松解决这个问题&#xff01; 首先&#xff0c;进入文件批量改名高手的主页面&#xff0c;并在板块…

dockerfile,Docker镜像的创建

dockerfile&#xff1a;创建镜像&#xff0c;创建自定义的镜像。包括配置文件&#xff0c;挂载点&#xff0c;对外暴露的端口。设置环境变量。 docker的创建镜像的方式&#xff1a; 1、基于已有镜像进行创建。根据官方提供的镜像源&#xff0c;创建镜像&#xff0c;然后拉起容…

一问掌握SpringBoot常见注解,后无压力。

文章目录 一、&#x1f50e; SpringBoot常用注解大全&#x1f341;&#x1f341; 01. RequestMapping 注解&#x1f341; 1.1. RequestMapping 是什么&#xff1f;&#x1f341; 1.2. RequestMapping 特点有哪些&#xff1f;&#x1f341; 1.3. RequestMapping 作用是什么&…

GZ015 机器人系统集成应用技术样题6-学生赛

2023年全国职业院校技能大赛 高职组“机器人系统集成应用技术”赛项 竞赛任务书&#xff08;学生赛&#xff09; 样题6 选手须知&#xff1a; 本任务书共 25页&#xff0c;如出现任务书缺页、字迹不清等问题&#xff0c;请及时向裁判示意&#xff0c;并进行任务书的更换。参赛队…

【C++11特性篇】一文助小白轻松理解 C++中的【左值&左值引用】【右值&右值引用】

前言 大家好吖&#xff0c;欢迎来到 YY 滴C系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; 目录 一.【左值&#xff06;左值引用】&…

josef约瑟 时间继电器 DS-23/C AC220V 10S柜内板前接线

系列型号&#xff1a; DS-21时间继电器 &#xff1b;DS-22时间继电器&#xff1b; DS-23时间继电器&#xff1b;DS-24时间继电器&#xff1b; DS-21C时间继电器&#xff1b;DS-22C时间继电器&#xff1b; DS-23C时间继电器&#xff1b; DS-25时间继电器&#xff1b;DS-26…

C语言实现Hoare版快速排序(递归版)

Hoare版 快速排序是由Hoare发明的&#xff0c;所以我们先来讲创始人的想法。我们直接切入主题&#xff0c;Hoare版快速排序的思想是将一个值设定为key&#xff0c;这个值不一定是第一个&#xff0c;如果你选其它的值作为你的key&#xff0c;那么你的思路也就要转换一下&#xf…