1892 lines
52 KiB
C
1892 lines
52 KiB
C
|
|
/*
|
|
* Copyright (C) Pingo (cczjp89@gmail.com)
|
|
*/
|
|
|
|
#include <ngx_config.h>
|
|
#include <ngx_core.h>
|
|
#include <ngx_event.h>
|
|
#include "ngx_rtmp.h"
|
|
#include "ngx_rtmp_cmd_module.h"
|
|
#include "ngx_rtmp_live_module.h"
|
|
#include "ngx_rbuf.h"
|
|
#include "ngx_mpegts_live_module.h"
|
|
#include "ngx_mpegts_gop_module.h"
|
|
#include "ngx_rtmp_codec_module.h"
|
|
|
|
ngx_mpegts_video_pt ngx_mpegts_video;
|
|
ngx_mpegts_audio_pt ngx_mpegts_audio;
|
|
|
|
static ngx_rtmp_publish_pt next_publish;
|
|
static ngx_rtmp_play_pt next_play;
|
|
static ngx_rtmp_close_stream_pt next_close_stream;
|
|
|
|
#define NGX_MPEGTS_BUF_SIZE 1316
|
|
#define NGX_RTMP_MPEG_BUFSIZE 1024*1024
|
|
|
|
#define TS_AUDIO_TYPE_AAC 0
|
|
#define TS_AUDIO_TYPE_MP3 1
|
|
|
|
#define TS_VIDEO_TYPE_H264 0
|
|
#define TS_VIDEO_TYPE_H265 1
|
|
|
|
typedef struct ngx_mpegts_live_app_conf_s {
|
|
ngx_pool_t *pool;
|
|
size_t audio_buffer_size;
|
|
ngx_msec_t sync;
|
|
ngx_msec_t audio_delay;
|
|
size_t out_queue;
|
|
ngx_mpegts_live_ctx_t *players;
|
|
u_char packet_buffer[NGX_RTMP_MPEG_BUFSIZE];
|
|
} ngx_mpegts_live_app_conf_t;
|
|
|
|
typedef struct ngx_mpegts_live_avc_codec_s {
|
|
ngx_rtmp_frame_t *avc_header;
|
|
ngx_uint_t video_codec_id;
|
|
ngx_uint_t avc_nal_bytes;
|
|
} ngx_mpegts_live_avc_codec_t;
|
|
|
|
typedef struct ngx_mpegts_live_aac_codec_s {
|
|
ngx_rtmp_frame_t *aac_header;
|
|
uint64_t sample_rate;
|
|
} ngx_mpegts_live_aac_codec_t;
|
|
|
|
struct ngx_mpegts_live_ctx_s {
|
|
|
|
ngx_mpegts_live_ctx_t *next;
|
|
ngx_rtmp_session_t *session;
|
|
ngx_live_stream_t *stream;
|
|
|
|
/* mpegts-module config */
|
|
size_t audio_buffer_size;
|
|
ngx_msec_t sync;
|
|
ngx_msec_t audio_delay;
|
|
size_t out_queue;
|
|
|
|
/* pat pmt frame*/
|
|
ngx_mpegts_frame_t *patpmt;
|
|
|
|
/* video packet */
|
|
ngx_mpegts_live_avc_codec_t *avc_codec;
|
|
ngx_uint_t video_cc;
|
|
|
|
/* audio packet */
|
|
ngx_mpegts_live_aac_codec_t *aac_codec;
|
|
ngx_uint_t audio_cc;
|
|
uint64_t aframe_pts;
|
|
ngx_uint_t aframe_num;
|
|
ngx_msec_t aframe_base;
|
|
ngx_buf_t *aframe;
|
|
};
|
|
|
|
/* 700 ms PCR delay */
|
|
#define NGX_RTMP_MEGPTS_DELAY 63000
|
|
|
|
static void *
|
|
ngx_mpegts_live_create_app_conf(ngx_conf_t *cf);
|
|
static char *
|
|
ngx_mpegts_live_merge_app_conf(ngx_conf_t *cf, void *parent, void *child);
|
|
static ngx_int_t
|
|
ngx_mpegts_live_postconfiguration(ngx_conf_t *cf);
|
|
|
|
static ngx_command_t ngx_mpegts_live_commands[] = {
|
|
|
|
{ ngx_string("mpegts_audio_buffer_size"),
|
|
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
|
|
ngx_conf_set_size_slot,
|
|
NGX_RTMP_APP_CONF_OFFSET,
|
|
offsetof(ngx_mpegts_live_app_conf_t, audio_buffer_size),
|
|
NULL },
|
|
|
|
{ ngx_string("mpegts_sync"),
|
|
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
|
|
ngx_conf_set_msec_slot,
|
|
NGX_RTMP_APP_CONF_OFFSET,
|
|
offsetof(ngx_mpegts_live_app_conf_t, sync),
|
|
NULL },
|
|
|
|
{ ngx_string("mpegts_audio_delay"),
|
|
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
|
|
ngx_conf_set_msec_slot,
|
|
NGX_RTMP_APP_CONF_OFFSET,
|
|
offsetof(ngx_mpegts_live_app_conf_t, audio_delay),
|
|
NULL },
|
|
|
|
{ ngx_string("mpegts_out_queue"),
|
|
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
|
|
ngx_conf_set_size_slot,
|
|
NGX_RTMP_APP_CONF_OFFSET,
|
|
offsetof(ngx_mpegts_live_app_conf_t, out_queue),
|
|
NULL },
|
|
|
|
ngx_null_command
|
|
};
|
|
|
|
|
|
static ngx_rtmp_module_t ngx_mpegts_live_ctx = {
|
|
NULL, /* preconfiguration */
|
|
ngx_mpegts_live_postconfiguration, /* postconfiguration */
|
|
NULL, /* create main configuration */
|
|
NULL, /* init main configuration */
|
|
NULL, /* create server configuration */
|
|
NULL, /* merge server configuration */
|
|
ngx_mpegts_live_create_app_conf, /* create app configuration */
|
|
ngx_mpegts_live_merge_app_conf /* merge app configuration */
|
|
};
|
|
|
|
|
|
ngx_module_t ngx_mpegts_live_module = {
|
|
NGX_MODULE_V1,
|
|
&ngx_mpegts_live_ctx, /* module context */
|
|
ngx_mpegts_live_commands, /* module directives */
|
|
NGX_RTMP_MODULE, /* module type */
|
|
NULL, /* init master */
|
|
NULL, /* init module */
|
|
NULL, /* init process */
|
|
NULL, /* init thread */
|
|
NULL, /* exit thread */
|
|
NULL, /* exit process */
|
|
NULL, /* exit master */
|
|
NGX_MODULE_V1_PADDING
|
|
};
|
|
|
|
|
|
static void *
|
|
ngx_mpegts_live_create_app_conf(ngx_conf_t *cf)
|
|
{
|
|
ngx_mpegts_live_app_conf_t *macf;
|
|
|
|
macf = ngx_pcalloc(cf->pool, sizeof(ngx_mpegts_live_app_conf_t));
|
|
if (!macf) {
|
|
return NULL;
|
|
}
|
|
|
|
macf->audio_buffer_size = NGX_CONF_UNSET;
|
|
macf->sync = NGX_CONF_UNSET_MSEC;
|
|
macf->audio_delay = NGX_CONF_UNSET_MSEC;
|
|
macf->out_queue = NGX_CONF_UNSET;
|
|
|
|
return macf;
|
|
}
|
|
|
|
|
|
static char *
|
|
ngx_mpegts_live_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)
|
|
{
|
|
ngx_mpegts_live_app_conf_t *prev = parent;
|
|
ngx_mpegts_live_app_conf_t *conf = child;
|
|
|
|
ngx_conf_merge_size_value(conf->audio_buffer_size, prev->audio_buffer_size,
|
|
NGX_RTMP_MPEG_BUFSIZE);
|
|
ngx_conf_merge_msec_value(conf->sync, prev->sync, 2);
|
|
ngx_conf_merge_msec_value(conf->audio_delay, prev->audio_delay, 300);
|
|
ngx_conf_merge_size_value(conf->out_queue, prev->out_queue, 4096);
|
|
conf->pool = ngx_create_pool(4096, &cf->cycle->new_log);
|
|
if (!conf->pool) {
|
|
return NGX_CONF_ERROR;
|
|
}
|
|
|
|
return NGX_CONF_OK;
|
|
}
|
|
|
|
static u_char *
|
|
ngx_mpegts_live_write_pcr(u_char *p, uint64_t pcr)
|
|
{
|
|
*p++ = (u_char) (pcr >> 25);
|
|
*p++ = (u_char) (pcr >> 17);
|
|
*p++ = (u_char) (pcr >> 9);
|
|
*p++ = (u_char) (pcr >> 1);
|
|
*p++ = (u_char) (pcr << 7 | 0x7e);
|
|
*p++ = 0;
|
|
|
|
return p;
|
|
}
|
|
|
|
static u_char *
|
|
ngx_mpegts_live_write_pts(u_char *p, ngx_uint_t fb, uint64_t pts)
|
|
{
|
|
ngx_uint_t val;
|
|
|
|
val = fb << 4 | (((pts >> 30) & 0x07) << 1) | 1;
|
|
*p++ = (u_char) val;
|
|
|
|
val = (((pts >> 15) & 0x7fff) << 1) | 1;
|
|
*p++ = (u_char) (val >> 8);
|
|
*p++ = (u_char) val;
|
|
|
|
val = (((pts) & 0x7fff) << 1) | 1;
|
|
*p++ = (u_char) (val >> 8);
|
|
*p++ = (u_char) val;
|
|
|
|
return p;
|
|
}
|
|
|
|
ngx_int_t
|
|
ngx_mpegts_live_shared_append_chain(ngx_mpegts_frame_t *f, ngx_buf_t *b,
|
|
ngx_flag_t mandatory)
|
|
{
|
|
ngx_uint_t pes_size, header_size, body_size, in_size, stuff_size, flags;
|
|
u_char *packet, *p, *base;
|
|
ngx_int_t first;
|
|
ngx_chain_t *cl, **ll;
|
|
uint64_t pcr;
|
|
|
|
for (ll = &f->chain; (*ll) && (*ll)->next; ll = &(*ll)->next);
|
|
cl = *ll;
|
|
|
|
if ((b == NULL || b->pos == b->last) && mandatory) {
|
|
*ll = ngx_get_chainbuf(NGX_MPEGTS_BUF_SIZE, 1);
|
|
(*ll)->buf->flush = 1;
|
|
return NGX_OK;
|
|
}
|
|
|
|
first = 1;
|
|
|
|
while (b->pos < b->last) {
|
|
if ((*ll) && (*ll)->buf->end - (*ll)->buf->last < 188) {
|
|
ll = &(*ll)->next;
|
|
cl = *ll;
|
|
}
|
|
|
|
if (*ll == NULL) {
|
|
*ll = ngx_get_chainbuf(NGX_MPEGTS_BUF_SIZE, 1);
|
|
cl = *ll;
|
|
cl->buf->flush = 1;
|
|
}
|
|
|
|
packet = cl->buf->last;
|
|
p = packet;
|
|
|
|
f->cc++;
|
|
|
|
*p++ = 0x47;
|
|
*p++ = (u_char) (f->pid >> 8);
|
|
|
|
if (first) {
|
|
p[-1] |= 0x40;
|
|
}
|
|
|
|
*p++ = (u_char) f->pid;
|
|
*p++ = 0x10 | (f->cc & 0x0f); /* payload */
|
|
|
|
if (first) {
|
|
|
|
if (f->key) {
|
|
packet[3] |= 0x20; /* adaptation */
|
|
|
|
*p++ = 7; /* size */
|
|
*p++ = 0x50; /* random access + PCR */
|
|
if (f->dts < NGX_RTMP_MEGPTS_DELAY) {
|
|
pcr = 0;
|
|
} else {
|
|
pcr = f->dts;
|
|
}
|
|
p = ngx_mpegts_live_write_pcr(p, pcr);
|
|
}
|
|
|
|
/* PES header */
|
|
|
|
*p++ = 0x00;
|
|
*p++ = 0x00;
|
|
*p++ = 0x01;
|
|
*p++ = (u_char) f->sid;
|
|
|
|
header_size = 5;
|
|
flags = 0x80; /* PTS */
|
|
|
|
if (f->dts != f->pts) {
|
|
header_size += 5;
|
|
flags |= 0x40; /* DTS */
|
|
}
|
|
|
|
pes_size = (b->last - b->pos) + header_size + 3;
|
|
if (pes_size > 0xffff) {
|
|
pes_size = 0;
|
|
}
|
|
|
|
*p++ = (u_char) (pes_size >> 8);
|
|
*p++ = (u_char) pes_size;
|
|
*p++ = 0x80; /* H222 */
|
|
*p++ = (u_char) flags;
|
|
*p++ = (u_char) header_size;
|
|
|
|
p = ngx_mpegts_live_write_pts(p, flags >> 6, f->pts +
|
|
NGX_RTMP_MEGPTS_DELAY);
|
|
|
|
if (f->dts != f->pts) {
|
|
p = ngx_mpegts_live_write_pts(p, 1, f->dts +
|
|
NGX_RTMP_MEGPTS_DELAY);
|
|
}
|
|
|
|
first = 0;
|
|
}
|
|
|
|
body_size = (ngx_uint_t) (packet + 188 - p);
|
|
in_size = (ngx_uint_t) (b->last - b->pos);
|
|
|
|
if (body_size <= in_size) {
|
|
ngx_memcpy(p, b->pos, body_size);
|
|
b->pos += body_size;
|
|
|
|
} else {
|
|
stuff_size = (body_size - in_size);
|
|
|
|
if (packet[3] & 0x20) {
|
|
|
|
/* has adaptation */
|
|
|
|
base = &packet[5] + packet[4];
|
|
p = ngx_movemem(base + stuff_size, base, p - base);
|
|
ngx_memset(base, 0xff, stuff_size);
|
|
packet[4] += (u_char) stuff_size;
|
|
|
|
} else {
|
|
|
|
/* no adaptation */
|
|
|
|
packet[3] |= 0x20;
|
|
p = ngx_movemem(&packet[4] + stuff_size, &packet[4],
|
|
p - &packet[4]);
|
|
|
|
packet[4] = (u_char) (stuff_size - 1);
|
|
if (stuff_size >= 2) {
|
|
packet[5] = 0;
|
|
ngx_memset(&packet[6], 0xff, stuff_size - 2);
|
|
}
|
|
}
|
|
|
|
ngx_memcpy(p, b->pos, in_size);
|
|
b->pos = b->last;
|
|
}
|
|
|
|
cl->buf->last += 188;
|
|
f->length += 188;
|
|
}
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_mpegts_live_copy(ngx_rtmp_session_t *s, void *dst, u_char **src, size_t n,
|
|
ngx_chain_t **in)
|
|
{
|
|
u_char *last;
|
|
size_t pn;
|
|
|
|
if (*in == NULL) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
for ( ;; ) {
|
|
last = (*in)->buf->last;
|
|
|
|
if ((size_t)(last - *src) >= n) {
|
|
if (dst) {
|
|
ngx_memcpy(dst, *src, n);
|
|
}
|
|
|
|
*src += n;
|
|
|
|
while (*in && *src == (*in)->buf->last) {
|
|
*in = (*in)->next;
|
|
if (*in) {
|
|
*src = (*in)->buf->pos;
|
|
}
|
|
}
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
pn = last - *src;
|
|
|
|
if (dst) {
|
|
ngx_memcpy(dst, *src, pn);
|
|
dst = (u_char *)dst + pn;
|
|
}
|
|
|
|
n -= pn;
|
|
*in = (*in)->next;
|
|
|
|
if (*in == NULL) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"rtmp-mpegts: mpegts_copy| failed to read %uz byte(s)", n);
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
*src = (*in)->buf->pos;
|
|
}
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_mpegts_live_append_aud(ngx_rtmp_session_t *s, ngx_buf_t *out)
|
|
{
|
|
static u_char aud_nal[] = { 0x00, 0x00, 0x00, 0x01, 0x09, 0xf0 };
|
|
|
|
if (out->last + sizeof(aud_nal) > out->end) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
out->last = ngx_cpymem(out->last, aud_nal, sizeof(aud_nal));
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_mpegts_live_append_sps_pps(ngx_rtmp_session_t *s, ngx_buf_t *out)
|
|
{
|
|
u_char *p;
|
|
ngx_chain_t *in;
|
|
ngx_mpegts_live_ctx_t *ctx;
|
|
int8_t nnals;
|
|
uint16_t len, rlen;
|
|
ngx_int_t n;
|
|
|
|
ctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_live_module);
|
|
|
|
if (ctx == NULL || ctx->avc_codec == NULL) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
in = ctx->avc_codec->avc_header->chain;
|
|
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
|
|
* - nal bytes
|
|
*/
|
|
|
|
if (ngx_mpegts_live_copy(s, NULL, &p, 10, &in) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
/* number of SPS NALs */
|
|
if (ngx_mpegts_live_copy(s, &nnals, &p, 1, &in) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
nnals &= 0x1f; /* 5lsb */
|
|
|
|
ngx_log_error(NGX_LOG_DEBUG, s->log, 0,
|
|
"rtmp-mpegts: append_sps_pps| SPS number: %uz", nnals);
|
|
|
|
/* SPS */
|
|
for (n = 0; ; ++n) {
|
|
for (; nnals; --nnals) {
|
|
|
|
/* NAL length */
|
|
if (ngx_mpegts_live_copy(s, &rlen, &p, 2, &in) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
ngx_rtmp_rmemcpy(&len, &rlen, 2);
|
|
|
|
ngx_log_error(NGX_LOG_DEBUG, s->log, 0,
|
|
"rtmp-mpegts: append_sps_pps| header NAL length: %uz", (size_t) len);
|
|
|
|
/* AnnexB prefix */
|
|
if (out->end - out->last < 4) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"rtmp-mpegts: append_sps_pps| too small buffer for header NAL size");
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
*out->last++ = 0;
|
|
*out->last++ = 0;
|
|
*out->last++ = 0;
|
|
*out->last++ = 1;
|
|
|
|
/* NAL body */
|
|
if (out->end - out->last < len) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"rtmp-mpegts: append_sps_pps| too small buffer for header NAL");
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
if (ngx_mpegts_live_copy(s, out->last, &p, len, &in) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
out->last += len;
|
|
}
|
|
|
|
if (n == 1) {
|
|
break;
|
|
}
|
|
|
|
/* number of PPS NALs */
|
|
if (ngx_mpegts_live_copy(s, &nnals, &p, 1, &in) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
ngx_log_error(NGX_LOG_DEBUG, s->log, 0,
|
|
"rtmp-mpegts: append_sps_pps| PPS number: %uz", nnals);
|
|
}
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_mpegts_live_init_aac_codec(ngx_rtmp_session_t *s)
|
|
{
|
|
ngx_mpegts_live_ctx_t *ctx;
|
|
ngx_rtmp_codec_ctx_t *codec_ctx;
|
|
|
|
ctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_live_module);
|
|
codec_ctx = ngx_rtmp_get_module_ctx(s->live_stream->publish_ctx->session, ngx_rtmp_codec_module);
|
|
|
|
if (ctx->aac_codec) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
if (codec_ctx == NULL || codec_ctx->aac_header == NULL) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"rtmp-mpegts: aac_codec| codec ctx %p, aac_header is null", codec_ctx);
|
|
return NGX_AGAIN;
|
|
}
|
|
|
|
ctx->aac_codec = ngx_pcalloc(s->pool, sizeof(ngx_mpegts_live_aac_codec_t));
|
|
if (ctx->aac_codec == NULL) {
|
|
ngx_log_error(NGX_LOG_DEBUG, s->log, 0,
|
|
"rtmp-mpegts: aac_codec| alloc mpegts aac_codec failed");
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
ctx->aac_codec->aac_header = ngx_rtmp_shared_alloc_frame(1024,
|
|
codec_ctx->aac_header->chain, 0);
|
|
|
|
ctx->aac_codec->sample_rate = codec_ctx->sample_rate;
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_mpegts_live_init_avc_codec(ngx_rtmp_session_t *s)
|
|
{
|
|
ngx_mpegts_live_ctx_t *ctx;
|
|
ngx_rtmp_codec_ctx_t *codec_ctx;
|
|
|
|
ctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_live_module);
|
|
codec_ctx = ngx_rtmp_get_module_ctx(s->live_stream->publish_ctx->session, ngx_rtmp_codec_module);
|
|
|
|
if (ctx->avc_codec) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
if (codec_ctx == NULL || codec_ctx->avc_header == NULL) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"rtmp-mpegts: avc_codec| codec ctx %p, avc_header is null", codec_ctx);
|
|
return NGX_AGAIN;
|
|
}
|
|
|
|
ctx->avc_codec = ngx_pcalloc(s->pool, sizeof(ngx_mpegts_live_avc_codec_t));
|
|
if (ctx->avc_codec == NULL) {
|
|
ngx_log_error(NGX_LOG_DEBUG, s->log, 0,
|
|
"rtmp-mpegts: avc_codec| alloc mpegts avc_codec failed");
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
ctx->avc_codec->video_codec_id = codec_ctx->video_codec_id;
|
|
ctx->avc_codec->avc_nal_bytes = codec_ctx->avc_nal_bytes;
|
|
ctx->avc_codec->avc_header = ngx_rtmp_shared_alloc_frame(1024,
|
|
codec_ctx->avc_header->chain, 0);
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
/*
|
|
static void
|
|
ngx_mpegts_live_append_out_chain(ngx_chain_t **header, ngx_mpegts_frame_t *frame)
|
|
{
|
|
ngx_chain_t **ll;
|
|
ngx_chain_t *tail;
|
|
|
|
ngx_rtmp_shared_acquire_frame(frame);
|
|
tail = frame->chain;
|
|
|
|
for (ll = header; *ll; ll = &((*ll)->next)) {
|
|
(*ll)->buf->flush = 1;
|
|
}
|
|
|
|
*ll = tail;
|
|
|
|
for (; *ll; ll = &((*ll)->next)) {
|
|
(*ll)->buf->flush = 1;
|
|
if (!(*ll)->next) {
|
|
(*ll)->buf->last_in_chain = 1;
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
|
|
static ngx_int_t
|
|
ngx_mpegts_live_flush_audio(ngx_rtmp_session_t *s)
|
|
{
|
|
ngx_mpegts_live_ctx_t *ctx;
|
|
ngx_mpegts_frame_t *frame;
|
|
ngx_int_t rc;
|
|
ngx_buf_t *b;
|
|
|
|
ctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_live_module);
|
|
|
|
if (ctx == NULL) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
b = ctx->aframe;
|
|
|
|
if (b == NULL || b->pos == b->last) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
frame = ngx_rtmp_shared_alloc_mpegts_frame(NULL, 0);
|
|
|
|
frame->dts = ctx->aframe_pts;
|
|
frame->pts = ctx->aframe_pts;
|
|
frame->cc = ctx->audio_cc;
|
|
frame->pid = 0x101;
|
|
frame->sid = 0xc0;
|
|
frame->type = NGX_MPEGTS_MSG_AUDIO;
|
|
|
|
ngx_log_error(NGX_LOG_DEBUG, s->log, 0,
|
|
"rtmp-mpegts: flush_audio| pts=%uL", frame->pts);
|
|
|
|
rc = ngx_mpegts_live_shared_append_chain(frame, b, 1);
|
|
|
|
if (rc != NGX_OK) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"rtmp-mpegts: flush_audio| flush failed");
|
|
} else {
|
|
ctx->audio_cc = frame->cc;
|
|
ngx_mpegts_live_audio_filter(s, frame);
|
|
}
|
|
|
|
ngx_rtmp_shared_free_mpegts_frame(frame);
|
|
|
|
b->pos = b->last = b->start;
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_mpegts_live_append_hevc_vps_sps_pps(ngx_rtmp_session_t *s, ngx_buf_t *out)
|
|
{
|
|
ngx_mpegts_live_ctx_t *ctx;
|
|
u_char *p;
|
|
ngx_chain_t *in;
|
|
ngx_uint_t rnal_unit_len, nal_unit_len, i, j,
|
|
num_arrays, nal_unit_type, rnum_nalus,
|
|
num_nalus;
|
|
|
|
ctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_live_module);
|
|
|
|
if (ctx == NULL || ctx->avc_codec == NULL) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
in = ctx->avc_codec->avc_header->chain;
|
|
if (in == NULL) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
p = in->buf->pos;
|
|
/* 6 bytes
|
|
* FrameType 4 bits
|
|
* CodecID 4 bits
|
|
* AVCPacketType 1 byte
|
|
* CompositionTime 3 bytes
|
|
* HEVCDecoderConfigurationRecord
|
|
* configurationVersion 1 byte
|
|
*/
|
|
|
|
/* 20 bytes
|
|
* HEVCDecoderConfigurationRecord
|
|
* general_profile_space 2 bits
|
|
* general_tier_flag 1 bit
|
|
* general_profile_idc 5 bits
|
|
* general_profile_compatibility_flags 4 bytes
|
|
* general_constraint_indicator_flags 6 bytes
|
|
* general_level_idc 1 byte
|
|
* min_spatial_segmentation_idc 4 bits reserved + 12 bits
|
|
* parallelismType 6 bits reserved + 2 bits
|
|
* chroma_format_idc 6 bits reserved + 2 bits
|
|
* bit_depth_luma_minus8 5 bits reserved + 3 bits
|
|
* bit_depth_chroma_minus8 5 bits reserved + 3 bits
|
|
* avgFrameRate 2 bytes
|
|
*/
|
|
|
|
/* 1 bytes
|
|
* HEVCDecoderConfigurationRecord
|
|
* constantFrameRate 2 bits
|
|
* numTemporalLayers 3 bits
|
|
* temporalIdNested 1 bit
|
|
* lengthSizeMinusOne 2 bits
|
|
*/
|
|
|
|
if (ngx_mpegts_live_copy(s, NULL, &p, 27, &in) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
/* 1 byte
|
|
* HEVCDecoderConfigurationRecord
|
|
* numOfArrays 1 byte
|
|
*/
|
|
num_arrays = 0;
|
|
if (ngx_mpegts_live_copy(s, &num_arrays, &p, 1, &in) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
for (i = 0; i < num_arrays; ++i) {
|
|
/*
|
|
* array_completeness 1 bit
|
|
* reserved 1 bit
|
|
* NAL_unit_type 6 bits
|
|
* numNalus 2 bytes
|
|
*/
|
|
if (ngx_mpegts_live_copy(s, &nal_unit_type, &p, 1, &in) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
nal_unit_type &= 0x3f;
|
|
|
|
if (ngx_mpegts_live_copy(s, &rnum_nalus, &p, 2, &in) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
num_nalus = 0;
|
|
ngx_rtmp_rmemcpy(&num_nalus, &rnum_nalus, 2);
|
|
|
|
for (j = 0; j < num_nalus; ++j) {
|
|
/*
|
|
* nalUnitLength 2 bytes
|
|
*/
|
|
if (ngx_mpegts_live_copy(s, &rnal_unit_len, &p, 2, &in) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
nal_unit_len = 0;
|
|
ngx_rtmp_rmemcpy(&nal_unit_len, &rnal_unit_len, 2);
|
|
if (out->end - out->last < 4) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"hls: too small buffer for header NAL size");
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
*out->last++ = 0;
|
|
*out->last++ = 0;
|
|
*out->last++ = 0;
|
|
*out->last++ = 1;
|
|
|
|
if (out->end - out->last < (ngx_int_t)nal_unit_len) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"hls: too small buffer for header NAL");
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
if (ngx_mpegts_live_copy(s, out->last, &p, nal_unit_len, &in) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
out->last += nal_unit_len;
|
|
}
|
|
}
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
/* set h265 aud first, now is null*/
|
|
static ngx_int_t
|
|
ngx_mpegts_live_append_hevc_aud(ngx_rtmp_session_t *s, ngx_buf_t *out)
|
|
{
|
|
static u_char aud_nal[] = { 0x00, 0x00, 0x00, 0x01, 0x46, 0x01, 0x50 };
|
|
|
|
if (out->last + sizeof(aud_nal) > out->end) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
out->last = ngx_cpymem(out->last, aud_nal, sizeof(aud_nal));
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_mpegts_live_h265_handler(ngx_rtmp_session_t *s, ngx_rtmp_frame_t *f)
|
|
{
|
|
ngx_mpegts_live_ctx_t *ctx;
|
|
ngx_mpegts_live_app_conf_t *macf;
|
|
u_char *p;
|
|
uint8_t fmt, ftype, htype, nal_type, src_nal_type;
|
|
uint32_t len, rlen;
|
|
ngx_buf_t out;
|
|
uint32_t cts;
|
|
ngx_mpegts_frame_t *frame;
|
|
ngx_uint_t nal_bytes;
|
|
ngx_int_t aud_sent, sps_pps_sent;
|
|
u_char *buffer;
|
|
ngx_rtmp_header_t *h;
|
|
ngx_chain_t *in;
|
|
ngx_int_t rc;
|
|
ngx_rtmp_core_app_conf_t *cacf;
|
|
|
|
cacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_core_module);
|
|
|
|
h = &f->hdr;
|
|
in = f->chain;
|
|
|
|
macf = ngx_rtmp_get_module_app_conf(s, ngx_mpegts_live_module);
|
|
buffer = macf->packet_buffer;
|
|
|
|
ctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_live_module);
|
|
if (ctx == NULL || h->mlen < 1) {
|
|
ngx_log_error(NGX_LOG_DEBUG, s->log, 0,
|
|
"rtmp-mpegts: h265_handler| "
|
|
"resource error, mpegts_ctx=%p, h->mlen=%d",
|
|
ctx, h->mlen);
|
|
return NGX_OK;
|
|
}
|
|
|
|
if (ctx->avc_codec == NULL) {
|
|
rc = ngx_mpegts_live_init_avc_codec(s);
|
|
if (rc == NGX_ERROR) {
|
|
ngx_log_error(NGX_LOG_DEBUG, s->log, 0,
|
|
"rtmp-mpegts: h265_handler| init avc_codec failed");
|
|
return NGX_OK;
|
|
} else if (rc == NGX_AGAIN) {
|
|
return NGX_OK;
|
|
}
|
|
}
|
|
|
|
/* H265 is supported */
|
|
if (ctx->avc_codec->video_codec_id != cacf->hevc_codec)
|
|
{
|
|
return NGX_OK;
|
|
}
|
|
|
|
p = in->buf->pos;
|
|
if (ngx_mpegts_live_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; // 0x17/0x27/...
|
|
|
|
/* H264 HDR/PICT */
|
|
|
|
if (ngx_mpegts_live_copy(s, &htype, &p, 1, &in) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
/* proceed only with PICT */
|
|
|
|
if (htype != 1) { /*0:AVC sequence header,1:AVC NALU 2:AVC end of sequence*/
|
|
return NGX_OK;
|
|
}
|
|
|
|
/* 3 bytes: decoder delay */
|
|
|
|
if (ngx_mpegts_live_copy(s, &cts, &p, 3, &in) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
/* convert big end to little end */
|
|
cts = ((cts & 0x00FF0000) >> 16) | ((cts & 0x000000FF) << 16) |
|
|
(cts & 0x0000FF00);
|
|
|
|
ngx_memzero(&out, sizeof(out));
|
|
|
|
out.start = buffer;
|
|
out.end = buffer + NGX_RTMP_MPEG_BUFSIZE;
|
|
out.pos = out.start;
|
|
out.last = out.pos;
|
|
|
|
nal_bytes = ctx->avc_codec->avc_nal_bytes;
|
|
aud_sent = 0;
|
|
sps_pps_sent = 0;
|
|
ngx_int_t vps_copy = 0;
|
|
ngx_int_t sps_copy = 0;
|
|
ngx_int_t pps_copy = 0;
|
|
|
|
while (in) {
|
|
if (ngx_mpegts_live_copy(s, &rlen, &p, nal_bytes, &in) != NGX_OK) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
len = 0;
|
|
ngx_rtmp_rmemcpy(&len, &rlen, nal_bytes);
|
|
|
|
if (len == 0) {
|
|
continue;
|
|
}
|
|
|
|
if (ngx_mpegts_live_copy(s, &src_nal_type, &p, 1, &in) != NGX_OK) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
nal_type = (src_nal_type & 0x7e) >> 1;
|
|
|
|
ngx_log_error(NGX_LOG_DEBUG, s->log, 0,
|
|
"rtmp-mpegts: h265_handler| h265 NAL type=%ui, len=%uD",
|
|
(ngx_uint_t) nal_type, len);
|
|
|
|
/* h264 format of rtmp_flv contains NAL header Prefix "00 00 00 01" */
|
|
if (0 == nal_type) {
|
|
u_char nal_header[4] = {0};
|
|
if (ngx_mpegts_live_copy(s, nal_header, &p, 3, &in) != NGX_OK) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
if (0 != ngx_strcmp(nal_header, "\0\0\1")) {
|
|
ngx_log_error(NGX_LOG_DEBUG, s->log, 0,
|
|
"mpegts module: h265 hander| "
|
|
"is not h265 NAL header [00 00 00 01]");
|
|
p -= 3;
|
|
goto NAL_TRAIL_N;
|
|
return NGX_OK;
|
|
}
|
|
|
|
if (ngx_mpegts_live_copy(s, &src_nal_type, &p, 1, &in) != NGX_OK) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
nal_type = (src_nal_type & 0x7e) >> 1;
|
|
if (0 == nal_type) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"mpegts module: h265 hander| h265 NAL type reparse error");
|
|
return NGX_OK;
|
|
}
|
|
|
|
#define HEVC_NAL_AUD_LENGTH 0
|
|
if (out.end - out.last < (ngx_int_t) (len + HEVC_NAL_AUD_LENGTH)) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"mpegts module: h265 hander| not enough buffer for NAL");
|
|
return NGX_OK;
|
|
}
|
|
#if 1
|
|
if (ngx_mpegts_live_append_hevc_aud(s, &out) != NGX_OK) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"mpegts module: h265 hander| error appending AUD NAL");
|
|
}
|
|
#endif
|
|
/* back to 00 00 01 nal_type*/
|
|
p = p - 4;
|
|
if (ngx_mpegts_live_copy(s, out.last, &p, len - 1, &in) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
out.last += (len - 1);
|
|
break;
|
|
}
|
|
|
|
NAL_TRAIL_N:
|
|
|
|
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->log, 0,
|
|
"mpegts module: h265 hander| h265 NAL type=%ui, len=%uD",
|
|
(ngx_uint_t) nal_type, len);
|
|
|
|
/*
|
|
* NAL_VPS 32
|
|
* NAL_SPS 33
|
|
* NAL_PPS 34
|
|
* NAL_AUD 35
|
|
* NAL_SEI_PREFIX 39
|
|
* NAL_SEI_SUFFIX 40
|
|
*/
|
|
if ((nal_type >= 32 && nal_type <= 35)
|
|
|| nal_type == 39 || nal_type == 40)
|
|
{
|
|
if (out.end - out.last < (5 + len -1)) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"hls: not enough buffer for AnnexB prefix");
|
|
return NGX_OK;
|
|
}
|
|
if (32 == nal_type) {
|
|
++vps_copy;
|
|
if(!aud_sent){
|
|
if (ngx_mpegts_live_append_hevc_aud(s, &out) != NGX_OK) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"hls: error appending AUD NAL");
|
|
}
|
|
aud_sent = 1;
|
|
}
|
|
} else if (33 == nal_type) {
|
|
++sps_copy;
|
|
} else if (34 == nal_type) {
|
|
++pps_copy;
|
|
} else if (35 == nal_type) {
|
|
aud_sent = 1;
|
|
}
|
|
|
|
*out.last++ = 0;
|
|
*out.last++ = 0;
|
|
*out.last++ = 0;
|
|
*out.last++ = 1;
|
|
*out.last++ = src_nal_type;
|
|
if (ngx_mpegts_live_copy(s, out.last, &p, len - 1, &in) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
out.last += (len - 1);
|
|
continue;
|
|
}
|
|
|
|
if (vps_copy > 0 && sps_copy > 0 && pps_copy > 0) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"mpegts vps_copy %i, sps_copy %i, pps_copy %i\n",
|
|
vps_copy, sps_copy, pps_copy);
|
|
sps_pps_sent = 1;
|
|
}
|
|
|
|
if (!aud_sent) {
|
|
if (35 == nal_type) {
|
|
aud_sent = 1;
|
|
} else if (!sps_pps_sent) {
|
|
if (ngx_mpegts_live_append_hevc_vps_sps_pps(s, &out) != NGX_OK)
|
|
{
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"hls: error appending AUD NAL");
|
|
}
|
|
aud_sent = 1;
|
|
}
|
|
}
|
|
|
|
if (IS_IRAP(nal_type)) {
|
|
if (!sps_pps_sent) {
|
|
if (ngx_mpegts_live_append_hevc_vps_sps_pps(s, &out) != NGX_OK)
|
|
{
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"hls: error appenging VPS/SPS/PPS NALs");
|
|
}
|
|
sps_pps_sent = 1;
|
|
}
|
|
}
|
|
|
|
/* AnnexB prefix */
|
|
if (out.end - out.last < 5) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"hls: not enough buffer for AnnexB prefix");
|
|
return NGX_OK;
|
|
}
|
|
|
|
/* first AnnexB prefix is long (4 bytes) */
|
|
|
|
if (out.last == out.pos) {
|
|
*out.last++ = 0;
|
|
}
|
|
|
|
*out.last++ = 0;
|
|
*out.last++ = 0;
|
|
*out.last++ = 1;
|
|
*out.last++ = src_nal_type;
|
|
|
|
/* NAL body */
|
|
if (out.end - out.last < (ngx_int_t) len) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"hls: not enough buffer for NAL");
|
|
return NGX_OK;
|
|
}
|
|
|
|
if (ngx_mpegts_live_copy(s, out.last, &p, len - 1, &in) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
out.last += (len - 1);
|
|
}
|
|
|
|
frame = ngx_rtmp_shared_alloc_mpegts_frame(NULL, 0);
|
|
if (frame == NULL) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"rtmp-mpegts: h265_handler| "
|
|
"memory error, alloc mpegts frame failed");
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
/* continuity counter */
|
|
frame->cc = ctx->video_cc;
|
|
frame->dts = (uint64_t) h->timestamp * 90;
|
|
/* pts = dts + composition time */
|
|
frame->pts = frame->dts + cts * 90;
|
|
/* program id */
|
|
frame->pid = 0x100;
|
|
/* stream id, video range from 0xe0 to 0xef */
|
|
frame->sid = 0xe0;
|
|
frame->key = (ftype == 1);
|
|
frame->type = NGX_MPEGTS_MSG_VIDEO;
|
|
|
|
/*
|
|
* start new fragment if
|
|
* - we have video key frame AND
|
|
* - we have audio buffered or have no audio at all or stream is closed
|
|
*/
|
|
if (ctx->aframe && ctx->aframe->last > ctx->aframe->pos &&
|
|
ctx->aframe_pts + (uint64_t) ctx->audio_delay * 90 < frame->dts)
|
|
{
|
|
ngx_mpegts_live_flush_audio(s);
|
|
}
|
|
|
|
ngx_log_error(NGX_LOG_DEBUG, s->log, 0,
|
|
"rtmp-mpegts: h265_handler| video pts=%uL, dts=%uL",
|
|
frame->pts, frame->dts);
|
|
|
|
if (ngx_mpegts_live_shared_append_chain(frame, &out, 1) != NGX_OK) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"rtmp-mpegts: h264_handler| video frame failed");
|
|
} else {
|
|
ctx->video_cc = frame->cc;
|
|
ngx_mpegts_live_video_filter(s, frame);
|
|
}
|
|
|
|
ngx_rtmp_shared_free_mpegts_frame(frame);
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_mpegts_live_h264_handler(ngx_rtmp_session_t *s, ngx_rtmp_frame_t *f)
|
|
{
|
|
ngx_rtmp_header_t *h;
|
|
ngx_chain_t *in;
|
|
ngx_mpegts_live_ctx_t *ctx;
|
|
u_char *p;
|
|
uint8_t fmt, ftype, htype, nal_type, src_nal_type;
|
|
uint32_t len, rlen;
|
|
ngx_buf_t out;
|
|
uint32_t cts;
|
|
ngx_mpegts_frame_t *frame;
|
|
ngx_uint_t nal_bytes;
|
|
ngx_int_t aud_sent, sps_pps_sent;
|
|
u_char *buffer;
|
|
ngx_mpegts_live_app_conf_t *macf;
|
|
ngx_int_t rc;
|
|
|
|
h = &f->hdr;
|
|
in = f->chain;
|
|
|
|
macf = ngx_rtmp_get_module_app_conf(s, ngx_mpegts_live_module);
|
|
buffer = macf->packet_buffer;
|
|
|
|
ctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_live_module);
|
|
if (ctx == NULL || h->mlen < 1) {
|
|
ngx_log_error(NGX_LOG_DEBUG, s->log, 0,
|
|
"mpegts-mux: h264_handler| "
|
|
"resource error, mpegts_ctx=%p, h->mlen=%d",
|
|
ctx, h->mlen);
|
|
return NGX_OK;
|
|
}
|
|
|
|
if (ctx->avc_codec == NULL) {
|
|
rc = ngx_mpegts_live_init_avc_codec(s);
|
|
if (rc == NGX_ERROR) {
|
|
ngx_log_error(NGX_LOG_DEBUG, s->log, 0,
|
|
"mpegts-mux: h264_handler| init avc_codec failed");
|
|
return NGX_OK;
|
|
} else if (rc == NGX_AGAIN) {
|
|
return NGX_OK;
|
|
}
|
|
}
|
|
|
|
/* H264 is supported */
|
|
if (ctx->avc_codec->video_codec_id != NGX_RTMP_VIDEO_H264) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
p = in->buf->pos;
|
|
if (ngx_mpegts_live_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_mpegts_live_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_mpegts_live_copy(s, &cts, &p, 3, &in) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
cts = ((cts & 0x00FF0000) >> 16) | ((cts & 0x000000FF) << 16) |
|
|
(cts & 0x0000FF00);
|
|
|
|
ngx_memzero(&out, sizeof(out));
|
|
|
|
out.start = buffer;
|
|
out.end = buffer + NGX_RTMP_MPEG_BUFSIZE;
|
|
out.pos = out.start;
|
|
out.last = out.pos;
|
|
|
|
nal_bytes = ctx->avc_codec->avc_nal_bytes;
|
|
aud_sent = 0;
|
|
sps_pps_sent = 0;
|
|
|
|
while (in) {
|
|
if (ngx_mpegts_live_copy(s, &rlen, &p, nal_bytes, &in) != NGX_OK) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
len = 0;
|
|
ngx_rtmp_rmemcpy(&len, &rlen, nal_bytes);
|
|
|
|
if (len == 0) {
|
|
continue;
|
|
}
|
|
|
|
if (ngx_mpegts_live_copy(s, &src_nal_type, &p, 1, &in) != NGX_OK) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
nal_type = src_nal_type & 0x1f;
|
|
|
|
ngx_log_error(NGX_LOG_DEBUG, s->log, 0,
|
|
"mpegts-mux: h264_handler| h264 NAL type=%ui, len=%uD",
|
|
(ngx_uint_t) nal_type, len);
|
|
|
|
if (nal_type >= 7 && nal_type <= 9) {
|
|
if (ngx_mpegts_live_copy(s, NULL, &p, len - 1, &in) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (!aud_sent) {
|
|
switch (nal_type) {
|
|
case 1:
|
|
case 5:
|
|
case 6:
|
|
if (ngx_mpegts_live_append_aud(s, &out) != NGX_OK) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"mpegts-mux: h264_handler| error appending AUD NAL");
|
|
}
|
|
aud_sent = 1;
|
|
break;
|
|
|
|
case 9:
|
|
aud_sent = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch (nal_type) {
|
|
case 1:
|
|
sps_pps_sent = 0;
|
|
break;
|
|
case 5:
|
|
if (sps_pps_sent) {
|
|
break;
|
|
}
|
|
if (ngx_mpegts_live_append_sps_pps(s, &out) != NGX_OK) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"mpegts-mux: h264_handler| error appenging SPS/PPS NALs");
|
|
}
|
|
sps_pps_sent = 1;
|
|
break;
|
|
}
|
|
|
|
/* AnnexB prefix */
|
|
|
|
if (out.end - out.last < 5) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"mpegts-mux: h264_handler| not enough buffer for AnnexB prefix");
|
|
return NGX_OK;
|
|
}
|
|
|
|
/* first AnnexB prefix is long (4 bytes) */
|
|
|
|
if (out.last == out.pos) {
|
|
*out.last++ = 0;
|
|
}
|
|
|
|
*out.last++ = 0;
|
|
*out.last++ = 0;
|
|
*out.last++ = 1;
|
|
*out.last++ = src_nal_type;
|
|
|
|
/* NAL body */
|
|
|
|
if (out.end - out.last < (ngx_int_t) len) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"mpegts-mux: h264_handler| not enough buffer for NAL");
|
|
return NGX_OK;
|
|
}
|
|
|
|
if (ngx_mpegts_live_copy(s, out.last, &p, len - 1, &in) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
out.last += (len - 1);
|
|
}
|
|
|
|
frame = ngx_rtmp_shared_alloc_mpegts_frame(NULL, 0);
|
|
if (frame == NULL) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"mpegts-mux: h264_handler| "
|
|
"memory error, alloc mpegts frame failed");
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
frame->cc = ctx->video_cc;
|
|
frame->dts = (uint64_t) h->timestamp * 90;
|
|
frame->pts = frame->dts + (cts * 90);
|
|
frame->pid = 0x100;
|
|
frame->sid = 0xe0;
|
|
frame->key = (ftype == 1);
|
|
frame->type = NGX_MPEGTS_MSG_VIDEO;
|
|
|
|
if (ctx->aframe && ctx->aframe->last > ctx->aframe->pos &&
|
|
ctx->aframe_pts + (uint64_t) ctx->audio_delay * 90 < frame->dts)
|
|
{
|
|
ngx_mpegts_live_flush_audio(s);
|
|
}
|
|
|
|
ngx_log_error(NGX_LOG_DEBUG, s->log, 0,
|
|
"mpegts-mux: h264_handler| video pts=%uL, dts=%uL",
|
|
frame->pts/90, frame->dts/90);
|
|
|
|
if (ngx_mpegts_live_shared_append_chain(frame, &out, 1) != NGX_OK) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"mpegts-mux: h264_handler| video frame failed");
|
|
} else {
|
|
ctx->video_cc = frame->cc;
|
|
ngx_mpegts_live_video_filter(s, frame);
|
|
}
|
|
|
|
ngx_rtmp_shared_free_mpegts_frame(frame);
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_mpegts_live_parse_aac_header(ngx_rtmp_session_t *s, ngx_uint_t *objtype,
|
|
ngx_uint_t *srindex, ngx_uint_t *chconf)
|
|
{
|
|
ngx_mpegts_live_ctx_t *ctx;
|
|
ngx_chain_t *cl;
|
|
u_char *p, b0, b1;
|
|
|
|
ctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_live_module);
|
|
|
|
cl = ctx->aac_codec->aac_header->chain;
|
|
|
|
p = cl->buf->pos;
|
|
|
|
if (ngx_mpegts_live_copy(s, NULL, &p, 2, &cl) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
if (ngx_mpegts_live_copy(s, &b0, &p, 1, &cl) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
if (ngx_mpegts_live_copy(s, &b1, &p, 1, &cl) != NGX_OK) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
*objtype = b0 >> 3;
|
|
if (*objtype == 0 || *objtype == 0x1f) {
|
|
ngx_log_error(NGX_LOG_DEBUG, s->log, 0,
|
|
"rtmp-mpegts: parse_aac_header| "
|
|
"unsupported adts object type:%ui", *objtype);
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
if (*objtype > 4) {
|
|
|
|
/*
|
|
* Mark all extended profiles as LC
|
|
* to make Android as happy as possible.
|
|
*/
|
|
|
|
*objtype = 2;
|
|
}
|
|
|
|
*srindex = ((b0 << 1) & 0x0f) | ((b1 & 0x80) >> 7);
|
|
if (*srindex == 0x0f) {
|
|
ngx_log_error(NGX_LOG_DEBUG, s->log, 0,
|
|
"rtmp-mpegts: parse_aac_header| "
|
|
"unsupported adts sample rate:%ui", *srindex);
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
*chconf = (b1 >> 3) & 0x0f;
|
|
|
|
ngx_log_error(NGX_LOG_DEBUG, s->log, 0,
|
|
"rtmp-mpegts: parse_aac_header| "
|
|
"aac object_type:%ui, sample_rate_index:%ui, "
|
|
"channel_config:%ui", *objtype, *srindex, *chconf);
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
|
|
static ngx_int_t
|
|
ngx_mpegts_live_aac_handler(ngx_rtmp_session_t *s, ngx_rtmp_frame_t *f)
|
|
{
|
|
ngx_rtmp_header_t *h;
|
|
ngx_chain_t *in;
|
|
ngx_mpegts_live_ctx_t *ctx;
|
|
uint64_t pts, est_pts;
|
|
int64_t dpts;
|
|
size_t bsize;
|
|
ngx_buf_t *b;
|
|
u_char *p;
|
|
ngx_uint_t objtype, srindex, chconf, size;
|
|
ngx_int_t rc;
|
|
|
|
h = &f->hdr;
|
|
in = f->chain;
|
|
|
|
ctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_live_module);
|
|
|
|
if (ctx == NULL || h->mlen < 2) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
if (ctx->aac_codec == NULL) {
|
|
rc = ngx_mpegts_live_init_aac_codec(s);
|
|
if (rc == NGX_ERROR) {
|
|
ngx_log_error(NGX_LOG_DEBUG, s->log, 0,
|
|
"rtmp-mpegts: aac_handler| init aac_codec failed");
|
|
return NGX_OK;
|
|
} else if(rc == NGX_AGAIN) {
|
|
return NGX_OK;
|
|
}
|
|
}
|
|
|
|
if (ngx_rtmp_is_codec_header(in)) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
b = ctx->aframe;
|
|
|
|
if (b == NULL) {
|
|
|
|
b = ngx_pcalloc(s->pool, sizeof(ngx_buf_t));
|
|
if (b == NULL) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
ctx->aframe = b;
|
|
|
|
b->start = ngx_palloc(s->pool, ctx->audio_buffer_size);
|
|
if (b->start == NULL) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
b->end = b->start + ctx->audio_buffer_size;
|
|
b->pos = b->last = b->start;
|
|
}
|
|
|
|
size = h->mlen - 2 + 7;
|
|
pts = (uint64_t) h->timestamp * 90;
|
|
|
|
if (b->start + size > b->end) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
if (b->last > b->pos &&
|
|
ctx->aframe_pts + (uint64_t) ctx->audio_delay * 90 / 2 < pts)
|
|
{
|
|
ngx_mpegts_live_flush_audio(s);
|
|
}
|
|
|
|
if (b->last + size > b->end) {
|
|
ngx_mpegts_live_flush_audio(s);
|
|
}
|
|
|
|
ngx_log_error(NGX_LOG_DEBUG, s->log, 0,
|
|
"rtmp-mpegts: aac_handler| audio pts=%uL", pts);
|
|
|
|
if (b->last + 7 > b->end) {
|
|
ngx_log_error(NGX_LOG_DEBUG, s->log, 0,
|
|
"rtmp-mpegts: aac_handler| not enough buffer for audio header");
|
|
return NGX_OK;
|
|
}
|
|
|
|
p = b->last;
|
|
b->last += 5;
|
|
|
|
/* copy payload */
|
|
|
|
for (; in && b->last < b->end; in = in->next) {
|
|
|
|
bsize = in->buf->last - in->buf->pos;
|
|
if (b->last + bsize > b->end) {
|
|
bsize = b->end - b->last;
|
|
}
|
|
|
|
b->last = ngx_cpymem(b->last, in->buf->pos, bsize);
|
|
}
|
|
|
|
/* make up ADTS header */
|
|
|
|
if (ngx_mpegts_live_parse_aac_header(s, &objtype, &srindex, &chconf)
|
|
!= NGX_OK)
|
|
{
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"rtmp-mpegts: aac_handler| aac header error");
|
|
return NGX_OK;
|
|
}
|
|
|
|
/* we have 5 free bytes + 2 bytes of RTMP frame header */
|
|
|
|
p[0] = 0xff;
|
|
p[1] = 0xf1;
|
|
p[2] = (u_char) (((objtype - 1) << 6) | (srindex << 2) |
|
|
((chconf & 0x04) >> 2));
|
|
p[3] = (u_char) (((chconf & 0x03) << 6) | ((size >> 11) & 0x03));
|
|
p[4] = (u_char) (size >> 3);
|
|
p[5] = (u_char) ((size << 5) | 0x1f);
|
|
p[6] = 0xfc;
|
|
|
|
if (p != b->start) {
|
|
ctx->aframe_num++;
|
|
return NGX_OK;
|
|
}
|
|
|
|
ctx->aframe_pts = pts;
|
|
|
|
if (!ctx->sync || ctx->aac_codec->sample_rate == 0) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
/* align audio frames */
|
|
|
|
/* TODO: We assume here AAC frame size is 1024
|
|
* Need to handle AAC frames with frame size of 960 */
|
|
|
|
est_pts = ctx->aframe_base + ctx->aframe_num * 90000 * 1024 /
|
|
ctx->aac_codec->sample_rate;
|
|
dpts = (int64_t) (est_pts - pts);
|
|
|
|
ngx_log_error(NGX_LOG_DEBUG, s->log, 0,
|
|
"rtmp-mpegts: aac_handler| audio sync dpts=%L (%.5fs)",
|
|
dpts, dpts / 90000.);
|
|
|
|
if (dpts <= (int64_t) ctx->sync * 90 &&
|
|
dpts >= (int64_t) ctx->sync * -90)
|
|
{
|
|
ctx->aframe_num++;
|
|
ctx->aframe_pts = est_pts;
|
|
return NGX_OK;
|
|
}
|
|
|
|
ctx->aframe_base = pts;
|
|
ctx->aframe_num = 1;
|
|
|
|
ngx_log_error(NGX_LOG_DEBUG, s->log, 0,
|
|
"rtmp-mpegts: aac_handler| audio sync gap dpts=%L (%.5fs)",
|
|
dpts, dpts / 90000.);
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
static ngx_int_t
|
|
ngx_mpegts_live_av(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
|
|
ngx_chain_t *in)
|
|
{
|
|
ngx_mpegts_live_ctx_t *ctx;
|
|
ngx_rtmp_frame_t frame;
|
|
ngx_rtmp_codec_ctx_t *codec_ctx;
|
|
ngx_rtmp_core_app_conf_t *cacf;
|
|
|
|
cacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_core_module);
|
|
|
|
codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
|
|
|
|
ctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_live_module);
|
|
if (ctx == NULL || codec_ctx == NULL || codec_ctx->avc_header == NULL) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
/* Only H264 and H265 is supported */
|
|
if (codec_ctx->video_codec_id != NGX_RTMP_VIDEO_H264 &&
|
|
codec_ctx->video_codec_id != cacf->hevc_codec)
|
|
{
|
|
return NGX_OK;
|
|
}
|
|
|
|
if (s->pause) {
|
|
return NGX_OK;
|
|
}
|
|
|
|
ngx_memzero(&frame, sizeof(frame));
|
|
|
|
frame.hdr = *h;
|
|
frame.chain = in;
|
|
|
|
ngx_log_error(NGX_LOG_DEBUG, s->log, 0,
|
|
"mpegts-mux: av| pts[%uL] type [%d] key[%d]",
|
|
frame.hdr.timestamp, frame.hdr.type, frame.keyframe);
|
|
|
|
switch (frame.hdr.type) {
|
|
case NGX_RTMP_MSG_AUDIO:
|
|
// only aac, for now
|
|
ngx_mpegts_live_aac_handler(s, &frame);
|
|
break;
|
|
|
|
case NGX_RTMP_MSG_VIDEO:
|
|
/* h264 h265 */
|
|
if (codec_ctx->video_codec_id == NGX_RTMP_VIDEO_H264) {
|
|
ngx_mpegts_live_h264_handler(s, &frame);
|
|
} else if (codec_ctx->video_codec_id == cacf->hevc_codec) {
|
|
ngx_mpegts_live_h265_handler(s, &frame);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0,
|
|
"rtmp-mpegts: av| unknown frame-type=%d", frame.hdr.type);
|
|
break;
|
|
}
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
ngx_int_t
|
|
ngx_mpegts_live_video_filter(ngx_rtmp_session_t *s, ngx_mpegts_frame_t *frame)
|
|
{
|
|
return ngx_mpegts_video(s, frame);
|
|
}
|
|
|
|
ngx_int_t
|
|
ngx_mpegts_live_audio_filter(ngx_rtmp_session_t *s, ngx_mpegts_frame_t *frame)
|
|
{
|
|
return ngx_mpegts_audio(s, frame);
|
|
}
|
|
|
|
static ngx_int_t
|
|
ngx_mpegts_live_avframe(ngx_rtmp_session_t *s, ngx_mpegts_frame_t *frame)
|
|
{
|
|
ngx_mpegts_live_ctx_t *cctx;
|
|
ngx_rtmp_session_t *ss;
|
|
|
|
for (cctx = s->live_stream->mpegts_ctx; cctx; cctx = cctx->next) {
|
|
ss = cctx->session;
|
|
ngx_mpegts_gop_send(s, ss);
|
|
|
|
if (!s->connection->write->active) {
|
|
ngx_post_event(s->connection->write, &ngx_posted_events);
|
|
}
|
|
}
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
static ngx_int_t
|
|
ngx_mpegts_live_ctx_init(ngx_rtmp_session_t *s)
|
|
{
|
|
ngx_mpegts_live_app_conf_t *macf;
|
|
ngx_mpegts_live_ctx_t *ctx;
|
|
|
|
macf = ngx_rtmp_get_module_app_conf(s, ngx_mpegts_live_module);
|
|
if (macf == NULL) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"rtmp-mpegts: ctx_init| get app conf failed");
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
ctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_live_module);
|
|
if (ctx == NULL) {
|
|
ctx = ngx_pcalloc(s->pool, sizeof(ngx_mpegts_live_ctx_t));
|
|
if (ctx == NULL) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"rtmp-mpegts: ctx_init| pcalloc ctx failed");
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
ngx_rtmp_set_ctx(s, ctx, ngx_mpegts_live_module);
|
|
ctx->session = s;
|
|
}
|
|
|
|
ctx->sync = macf->sync;
|
|
ctx->audio_buffer_size = macf->audio_buffer_size;
|
|
ctx->audio_delay = macf->audio_delay;
|
|
ctx->out_queue = macf->out_queue;
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
static ngx_int_t
|
|
ngx_mpegts_live_join(ngx_rtmp_session_t *s, u_char *name, unsigned publisher)
|
|
{
|
|
ngx_mpegts_live_ctx_t *ctx;
|
|
ngx_live_stream_t *st;
|
|
ngx_rtmp_live_app_conf_t *lacf;
|
|
|
|
lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module);
|
|
if (lacf == NULL) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
ctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_live_module);
|
|
if (ctx && ctx->stream) {
|
|
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0,
|
|
"mpegts-live: join| already joined");
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
if (ctx == NULL) {
|
|
ctx = ngx_pcalloc(s->pool, sizeof(ngx_mpegts_live_ctx_t));
|
|
if (ctx == NULL) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"mpegts-live: join| pcalloc ctx failed");
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
ngx_rtmp_set_ctx(s, ctx, ngx_mpegts_live_module);
|
|
}
|
|
|
|
ngx_memzero(ctx, sizeof(*ctx));
|
|
|
|
ctx->session = s;
|
|
|
|
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0,
|
|
"mpegts-live: join| join '%s'", name);
|
|
|
|
st = s->live_stream;
|
|
|
|
if (!(publisher || st->publish_ctx || lacf->idle_streams)) {
|
|
ngx_log_error(NGX_LOG_ERR, s->log, 0,
|
|
"mpegts-live: join| stream not found");
|
|
|
|
s->status = 404;
|
|
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
ctx->stream = st;
|
|
ctx->next = st->mpegts_ctx;
|
|
|
|
st->mpegts_ctx = ctx;
|
|
|
|
return NGX_OK;
|
|
}
|
|
|
|
static ngx_int_t
|
|
ngx_mpegts_live_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v)
|
|
{
|
|
ngx_mpegts_live_ctx_init(s);
|
|
|
|
return next_publish(s, v);
|
|
}
|
|
|
|
static ngx_int_t
|
|
ngx_mpegts_live_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v)
|
|
{
|
|
if (s->live_type != NGX_MPEGTS_LIVE) {
|
|
goto next;
|
|
}
|
|
|
|
if (ngx_mpegts_live_join(s, v->name, 0) == NGX_ERROR) {
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
next:
|
|
return next_play(s, v);
|
|
}
|
|
|
|
static ngx_int_t
|
|
ngx_mpegts_live_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v)
|
|
{
|
|
ngx_mpegts_live_ctx_t *ctx, **cctx;
|
|
|
|
ctx = ngx_rtmp_get_module_ctx(s, ngx_mpegts_live_module);
|
|
if (ctx == NULL) {
|
|
goto next;
|
|
}
|
|
|
|
if (ctx->stream == NULL) {
|
|
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->log, 0,
|
|
"mpegts-live: close_stream| not joined");
|
|
goto next;
|
|
}
|
|
|
|
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0,
|
|
"mpegts-live: close_stream| leave '%s'", ctx->stream->name);
|
|
|
|
for (cctx = &ctx->stream->mpegts_ctx; *cctx; cctx = &(*cctx)->next) {
|
|
if (*cctx == ctx) {
|
|
*cctx = ctx->next;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ctx->stream->mpegts_ctx) {
|
|
ctx->stream = NULL;
|
|
goto next;
|
|
}
|
|
|
|
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->log, 0,
|
|
"mpegts-live: close_stream| delete empty stream '%s'",
|
|
ctx->stream->name);
|
|
|
|
ctx->stream = NULL;
|
|
|
|
next:
|
|
return next_close_stream(s, v);
|
|
}
|
|
|
|
static ngx_int_t
|
|
ngx_mpegts_live_postconfiguration(ngx_conf_t *cf)
|
|
{
|
|
ngx_rtmp_core_main_conf_t *cmcf;
|
|
ngx_rtmp_handler_pt *h;
|
|
|
|
cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module);
|
|
|
|
/* register raw event handlers */
|
|
|
|
h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AUDIO]);
|
|
*h = ngx_mpegts_live_av;
|
|
|
|
h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_VIDEO]);
|
|
*h = ngx_mpegts_live_av;
|
|
|
|
next_publish = ngx_rtmp_publish;
|
|
ngx_rtmp_publish = ngx_mpegts_live_publish;
|
|
|
|
next_play = ngx_rtmp_play;
|
|
ngx_rtmp_play = ngx_mpegts_live_play;
|
|
|
|
next_close_stream = ngx_rtmp_close_stream;
|
|
ngx_rtmp_close_stream = ngx_mpegts_live_close_stream;
|
|
|
|
ngx_mpegts_video = ngx_mpegts_live_avframe;
|
|
ngx_mpegts_audio = ngx_mpegts_live_avframe;
|
|
|
|
return NGX_OK;
|
|
}
|
|
|