static double ngx_rtmp_play_index_value(void *src) { double v; ngx_rtmp_rmemcpy(&v, src, 8); return v; }
static ngx_int_t ngx_rtmp_handshake_parse_challenge(ngx_rtmp_session_t *s, ngx_str_t *peer_key, ngx_str_t *key) { ngx_buf_t *b; u_char *p; ngx_int_t offs; b = s->hs_buf; if (*b->pos != '\x03') { ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, "handshake: unexpected RTMP version: %i", (ngx_int_t)*b->pos); return NGX_ERROR; } ++b->pos; s->peer_epoch = 0; ngx_rtmp_rmemcpy(&s->peer_epoch, b->pos, 4); p = b->pos + 4; ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "handshake: peer version=%i.%i.%i.%i epoch=%uD", (ngx_int_t)p[3], (ngx_int_t)p[2], (ngx_int_t)p[1], (ngx_int_t)p[0], (uint32_t)s->peer_epoch); if (*(uint32_t *)p == 0) { s->hs_old = 1; return NGX_OK; } offs = ngx_rtmp_find_digest(b, peer_key, 772, s->connection->log); if (offs == NGX_ERROR) { offs = ngx_rtmp_find_digest(b, peer_key, 8, s->connection->log); } if (offs == NGX_ERROR) { ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, "handshake: digest not found"); s->hs_old = 1; return NGX_OK; } ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "handshake: digest found at pos=%i", offs); b->pos += offs; b->last = b->pos + NGX_RTMP_HANDSHAKE_KEYLEN; /* 获得密文 */ s->hs_digest = ngx_palloc(s->connection->pool, NGX_RTMP_HANDSHAKE_KEYLEN); if (ngx_rtmp_make_digest(key, b, NULL, s->hs_digest, s->connection->log) != NGX_OK) { return NGX_ERROR; } return NGX_OK; }
static ngx_int_t ngx_rtmp_play_fill_index(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_play_index_t *idx) { uint32_t nelts; ngx_buf_t *b; /* we have AMF array pointed by context; * need to extract its size (4 bytes) & * save offset of actual array data */ b = ctx->link->buf; if (b->last - b->pos < (ngx_int_t) ctx->offset + 4) { return NGX_ERROR; } ngx_rtmp_rmemcpy(&nelts, b->pos + ctx->offset, 4); idx->nelts = nelts; idx->offset = ctx->offset + 4; return NGX_OK; }
static void ngx_rtmp_play_send(ngx_event_t *e) { ngx_rtmp_session_t *s; ngx_rtmp_play_ctx_t *ctx; uint32_t last_timestamp; ngx_rtmp_header_t h, lh; ngx_rtmp_core_srv_conf_t *cscf; ngx_chain_t *out, in; ngx_buf_t in_buf; ssize_t n; uint32_t buflen, end_timestamp, size; s = e->data; cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); if (ctx == NULL) { return; } if (ctx->offset == -1) { ctx->offset = ngx_rtmp_play_timestamp_to_offset(s, ctx->start_timestamp); ctx->start_timestamp = -1; /* set later from actual timestamp */ } ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "play: read tag at offset=%i", ctx->offset); /* read tag header */ n = ngx_read_file(&ctx->file, ngx_rtmp_play_header, sizeof(ngx_rtmp_play_header), ctx->offset); if (n != sizeof(ngx_rtmp_play_header)) { ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "play: could not read flv tag header"); ngx_rtmp_send_user_stream_eof(s, 1); return; } /* parse header fields */ ngx_memzero(&h, sizeof(h)); h.msid = NGX_RTMP_LIVE_MSID; h.type = ngx_rtmp_play_header[0]; size = 0; ngx_rtmp_rmemcpy(&size, ngx_rtmp_play_header + 1, 3); ngx_rtmp_rmemcpy(&h.timestamp, ngx_rtmp_play_header + 4, 3); ((u_char *) &h.timestamp)[3] = ngx_rtmp_play_header[7]; ctx->offset += (sizeof(ngx_rtmp_play_header) + size + 4); last_timestamp = 0; switch (h.type) { case NGX_RTMP_MSG_AUDIO: h.csid = NGX_RTMP_LIVE_CSID_AUDIO; last_timestamp = ctx->last_audio; ctx->last_audio = h.timestamp; break; case NGX_RTMP_MSG_VIDEO: h.csid = NGX_RTMP_LIVE_CSID_VIDEO; last_timestamp = ctx->last_video; ctx->last_video = h.timestamp; break; default: goto skip; } ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "play: read tag type=%i size=%uD timestamp=%uD " "last_timestamp=%uD", (ngx_int_t) h.type,size, h.timestamp, last_timestamp); lh = h; lh.timestamp = last_timestamp; if (size > sizeof(ngx_rtmp_play_buffer)) { ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "play: too big message: %D>%uz", size, sizeof(ngx_rtmp_play_buffer)); goto next; } /* read tag body */ n = ngx_read_file(&ctx->file, ngx_rtmp_play_buffer, size, ctx->offset - size - 4); if (n != (ssize_t) size) { ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "play: could not read flv tag"); return; } /* prepare input chain */ ngx_memzero(&in, sizeof(in)); ngx_memzero(&in_buf, sizeof(in_buf)); in.buf = &in_buf; in_buf.pos = ngx_rtmp_play_buffer; in_buf.last = ngx_rtmp_play_buffer + size; /* output chain */ out = ngx_rtmp_append_shared_bufs(cscf, NULL, &in); ngx_rtmp_prepare_message(s, &h, ctx->msg_mask & (1 << h.type) ? &lh : NULL, out); ngx_rtmp_send_message(s, out, 0); /* TODO: priority */ ngx_rtmp_free_shared_chain(cscf, out); ctx->msg_mask |= (1 << h.type); next: if (ctx->start_timestamp == -1) { ctx->start_timestamp = h.timestamp; ctx->epoch = ngx_current_msec; ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "play: start_timestamp=%i", ctx->start_timestamp); goto skip; } buflen = (s->buflen ? s->buflen : NGX_RTMP_PLAY_DEFAULT_BUFLEN); end_timestamp = (ngx_current_msec - ctx->epoch) + ctx->start_timestamp + buflen; ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "play: %s wait=%D timestamp=%D end_timestamp=%D bufen=%i", h.timestamp > end_timestamp ? "schedule" : "advance", h.timestamp > end_timestamp ? h.timestamp - end_timestamp : 0, h.timestamp, end_timestamp, (ngx_int_t) buflen); /* too much data sent; schedule timeout */ if (h.timestamp > end_timestamp) { ngx_add_timer(e, h.timestamp - end_timestamp); return; } skip: ngx_post_event(e, &ngx_posted_events); }
static void ngx_rtmp_play_read_meta(ngx_rtmp_session_t *s) { ngx_rtmp_play_ctx_t *ctx; ssize_t n; ngx_rtmp_header_t h; ngx_chain_t *out, in; ngx_buf_t in_buf; ngx_rtmp_core_srv_conf_t *cscf; uint32_t size; cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module); if (ctx == NULL) { return; } ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "play: read meta"); /* read tag header */ n = ngx_read_file(&ctx->file, ngx_rtmp_play_header, sizeof(ngx_rtmp_play_header), NGX_RTMP_PLAY_DATA_OFFSET); if (n != sizeof(ngx_rtmp_play_header)) { ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "play: could not read metadata tag header"); return; } if (ngx_rtmp_play_header[0] != NGX_RTMP_MSG_AMF_META) { ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "play: first tag is not metadata, giving up"); return; } ngx_memzero(&h, sizeof(h)); h.type = NGX_RTMP_MSG_AMF_META; h.msid = NGX_RTMP_LIVE_MSID; h.csid = NGX_RTMP_LIVE_CSID_META; size = 0; ngx_rtmp_rmemcpy(&size, ngx_rtmp_play_header + 1, 3); ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "play: metadata size=%D", size); if (size > sizeof(ngx_rtmp_play_buffer)) { ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "play: too big metadata"); return; } /* read metadata */ n = ngx_read_file(&ctx->file, ngx_rtmp_play_buffer, size, sizeof(ngx_rtmp_play_header) + NGX_RTMP_PLAY_DATA_OFFSET); if (n != (ssize_t) size) { ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "play: could not read metadata"); return; } /* prepare input chain */ ngx_memzero(&in, sizeof(in)); ngx_memzero(&in_buf, sizeof(in_buf)); in.buf = &in_buf; in_buf.pos = ngx_rtmp_play_buffer; in_buf.last = ngx_rtmp_play_buffer + size; ngx_rtmp_play_init_index(s, &in); /* output chain */ out = ngx_rtmp_append_shared_bufs(cscf, NULL, &in); ngx_rtmp_prepare_message(s, &h, NULL, out); ngx_rtmp_send_message(s, out, 0); ngx_rtmp_free_shared_chain(cscf, out); }
static ngx_int_t ngx_rtmp_flv_send(ngx_rtmp_session_t *s, ngx_file_t *f, ngx_uint_t *ts) { ngx_rtmp_flv_ctx_t *ctx; uint32_t last_timestamp; ngx_rtmp_header_t h, lh; ngx_rtmp_core_srv_conf_t *cscf; ngx_chain_t *out, in; ngx_buf_t in_buf; ngx_int_t rc; ssize_t n; uint32_t buflen, end_timestamp, size; cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module); if (ctx == NULL) { return NGX_ERROR; } if (ctx->offset == -1) { ctx->offset = ngx_rtmp_flv_timestamp_to_offset(s, f, ctx->start_timestamp); ctx->start_timestamp = -1; /* set later from actual timestamp */ } ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "flv: read tag at offset=%i", ctx->offset); /* read tag header */ n = ngx_read_file(f, ngx_rtmp_flv_header, sizeof(ngx_rtmp_flv_header), ctx->offset); if (n != sizeof(ngx_rtmp_flv_header)) { ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "flv: could not read flv tag header"); return NGX_DONE; } /* parse header fields */ ngx_memzero(&h, sizeof(h)); h.msid = NGX_RTMP_MSID; h.type = ngx_rtmp_flv_header[0]; size = 0; ngx_rtmp_rmemcpy(&size, ngx_rtmp_flv_header + 1, 3); ngx_rtmp_rmemcpy(&h.timestamp, ngx_rtmp_flv_header + 4, 3); ((u_char *) &h.timestamp)[3] = ngx_rtmp_flv_header[7]; ctx->offset += (sizeof(ngx_rtmp_flv_header) + size + 4); last_timestamp = 0; switch (h.type) { case NGX_RTMP_MSG_AUDIO: h.csid = NGX_RTMP_CSID_AUDIO; last_timestamp = ctx->last_audio; ctx->last_audio = h.timestamp; break; case NGX_RTMP_MSG_VIDEO: h.csid = NGX_RTMP_CSID_VIDEO; last_timestamp = ctx->last_video; ctx->last_video = h.timestamp; break; default: return NGX_OK; } ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "flv: read tag type=%i size=%uD timestamp=%uD " "last_timestamp=%uD", (ngx_int_t) h.type,size, h.timestamp, last_timestamp); lh = h; lh.timestamp = last_timestamp; if (size > sizeof(ngx_rtmp_flv_buffer)) { ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "flv: too big message: %D>%uz", size, sizeof(ngx_rtmp_flv_buffer)); goto next; } /* read tag body */ n = ngx_read_file(f, ngx_rtmp_flv_buffer, size, ctx->offset - size - 4); if (n != (ssize_t) size) { ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "flv: could not read flv tag"); return NGX_ERROR; } /* prepare input chain */ ngx_memzero(&in, sizeof(in)); ngx_memzero(&in_buf, sizeof(in_buf)); in.buf = &in_buf; in_buf.pos = ngx_rtmp_flv_buffer; in_buf.last = ngx_rtmp_flv_buffer + size; /* output chain */ out = ngx_rtmp_append_shared_bufs(cscf, NULL, &in); ngx_rtmp_prepare_message(s, &h, ctx->msg_mask & (1 << h.type) ? &lh : NULL, out); rc = ngx_rtmp_send_message(s, out, 0); ngx_rtmp_free_shared_chain(cscf, out); if (rc == NGX_AGAIN) { return NGX_AGAIN; } if (rc != NGX_OK) { return NGX_ERROR; } ctx->msg_mask |= (1 << h.type); next: if (ctx->start_timestamp == -1) { ctx->start_timestamp = h.timestamp; ctx->epoch = ngx_current_msec; ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "flv: start_timestamp=%i", ctx->start_timestamp); return NGX_OK; } buflen = s->buflen + NGX_RTMP_FLV_BUFLEN_ADDON; end_timestamp = (ngx_current_msec - ctx->epoch) + ctx->start_timestamp + buflen; ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "flv: %s wait=%D timestamp=%D end_timestamp=%D bufen=%i", h.timestamp > end_timestamp ? "schedule" : "advance", h.timestamp > end_timestamp ? h.timestamp - end_timestamp : 0, h.timestamp, end_timestamp, (ngx_int_t) buflen); s->current_time = h.timestamp; /* too much data sent; schedule timeout */ if (h.timestamp > end_timestamp) { return h.timestamp - end_timestamp; } return NGX_OK; }
static ngx_int_t ngx_rtmp_hls_video(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, ngx_chain_t *in) { ngx_rtmp_hls_app_conf_t *hacf; ngx_rtmp_hls_ctx_t *ctx; ngx_rtmp_codec_ctx_t *codec_ctx; AVPacket packet; u_char *p; uint8_t fmt, ftype, htype, llen; uint32_t len, rlen; ngx_buf_t out; static u_char buffer[NGX_RTMP_HLS_BUFSIZE]; hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); if (hacf == NULL || !hacf->hls || ctx == NULL || codec_ctx == NULL || h->mlen < 1) { return NGX_OK; } /* Only H264 is supported */ if (codec_ctx->video_codec_id != NGX_RTMP_VIDEO_H264) { return NGX_OK; } p = in->buf->pos; if (ngx_rtmp_hls_copy(s, &fmt, &p, 1, &in) != NGX_OK) { return NGX_ERROR; } /* 1: keyframe (IDR) * 2: inter frame * 3: disposable inter frame */ ftype = (fmt & 0xf0) >> 4; /* H264 HDR/PICT */ if (ngx_rtmp_hls_copy(s, &htype, &p, 1, &in) != NGX_OK) { return NGX_ERROR; } /* proceed only with PICT */ if (htype != 1) { return NGX_OK; } /* 3 bytes: decoder delay */ if (ngx_rtmp_hls_copy(s, NULL, &p, 3, &in) != NGX_OK) { return NGX_ERROR; } out.pos = buffer; out.last = buffer + sizeof(buffer) - FF_INPUT_BUFFER_PADDING_SIZE; /* keyframe? */ if (ftype == 1) { if (ngx_current_msec - ctx->frag_start > hacf->fraglen) { ngx_rtmp_hls_restart(s); } /* Prepend IDR frame with H264 header for random seeks */ if (ngx_rtmp_hls_append_avc_header(s, &out) != NGX_OK) { ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "hls: error appenging H264 header"); } } if (!ctx->opened || !ctx->video) { return NGX_OK; } if (ctx->nal_bytes == -1) { ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "hls: nal length size is unknown, " "waiting for IDR to parse header"); return NGX_OK; } ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "hls: parsing NALs"); while (in) { llen = ctx->nal_bytes; if (ngx_rtmp_hls_copy(s, &rlen, &p, llen, &in) != NGX_OK) { return NGX_OK; } len = 0; ngx_rtmp_rmemcpy(&len, &rlen, llen); ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "hls: NAL type=%i llen=%i len=%uD unit_type=%i", (ngx_int_t)ftype, (ngx_int_t)llen, len, (ngx_int_t)(*p & 0x1f)); /* AnnexB prefix */ if (out.last - out.pos < 4) { ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "hls: not enough buffer for AnnexB prefix"); return NGX_OK; } /* first AnnexB prefix is long (4 bytes) */ if (out.pos == buffer) { *out.pos++ = 0; } *out.pos++ = 0; *out.pos++ = 0; *out.pos++ = 1; /* NAL body */ if (out.last - out.pos < (ngx_int_t) len) { ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "hls: not enough buffer for NAL"); return NGX_OK; } if (ngx_rtmp_hls_copy(s, out.pos, &p, len, &in) != NGX_OK) { return NGX_ERROR; } out.pos += len; } av_init_packet(&packet); packet.dts = h->timestamp * 90; packet.pts = packet.dts; packet.stream_index = ctx->out_vstream; /* if (ftype == 1) { packet.flags |= AV_PKT_FLAG_KEY; }*/ packet.data = buffer; packet.size = out.pos - buffer; if (av_interleaved_write_frame(ctx->out_format, &packet) < 0) { ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "hls: av_interleaved_write_frame failed"); } return NGX_OK; }
static ngx_int_t ngx_rtmp_hls_append_avc_header(ngx_rtmp_session_t *s, ngx_buf_t *out) { ngx_rtmp_codec_ctx_t *codec_ctx; u_char *p; ngx_chain_t *in; ngx_rtmp_hls_ctx_t *ctx; int8_t nnals; uint16_t len, rlen; ngx_int_t n; ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module); if (ctx == NULL || codec_ctx == NULL) { return NGX_ERROR; } in = codec_ctx->avc_header; if (in == NULL) { return NGX_ERROR; } p = in->buf->pos; /* skip bytes: * - flv fmt * - H264 CONF/PICT (0x00) * - 0 * - 0 * - 0 * - version * - profile * - compatibility * - level */ if (ngx_rtmp_hls_copy(s, NULL, &p, 9, &in) != NGX_OK) { return NGX_ERROR; } /* NAL size length (1,2,4) */ if (ngx_rtmp_hls_copy(s, &ctx->nal_bytes, &p, 1, &in) != NGX_OK) { return NGX_ERROR; } ctx->nal_bytes &= 0x03; /* 2 lsb */ ++ctx->nal_bytes; ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "hls: NAL size bytes: %uz", ctx->nal_bytes); /* number of SPS NALs */ if (ngx_rtmp_hls_copy(s, &nnals, &p, 1, &in) != NGX_OK) { return NGX_ERROR; } nnals &= 0x1f; /* 5lsb */ ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "hls: SPS number: %uz", nnals); /* SPS */ for (n = 0; ; ++n) { for (; nnals; --nnals) { /* NAL length */ if (ngx_rtmp_hls_copy(s, &rlen, &p, 2, &in) != NGX_OK) { return NGX_ERROR; } ngx_rtmp_rmemcpy(&len, &rlen, 2); ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "hls: header NAL length: %uz", (size_t)len); /* AnnexB prefix */ if (out->last - out->pos < 4) { ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "hls: too small buffer for header NAL length"); return NGX_ERROR; } *out->pos++ = 0; *out->pos++ = 0; *out->pos++ = 0; *out->pos++ = 1; /* NAL body */ if (out->last - out->pos < len) { ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "hls: too small buffer for header NAL"); return NGX_ERROR; } if (ngx_rtmp_hls_copy(s, out->pos, &p, len, &in) != NGX_OK) { return NGX_ERROR; } out->pos += len; } if (n == 1) { break; } /* number of PPS NALs */ if (ngx_rtmp_hls_copy(s, &nnals, &p, 1, &in) != NGX_OK) { return NGX_ERROR; } ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "hls: PPS number: %uz", nnals); } return NGX_OK; }