1
2 /*
3 * Copyright (C) Roman Arutyunyan
4 */
5
6
7 #include <ngx_config.h>
8 #include <ngx_core.h>
9 #include "ngx_rtmp_play_module.h"
10 #include "ngx_rtmp_codec_module.h"
11 #include "ngx_rtmp_streams.h"
12
13
14 static ngx_int_t ngx_rtmp_flv_postconfiguration(ngx_conf_t *cf);
15 static void ngx_rtmp_flv_read_meta(ngx_rtmp_session_t *s, ngx_file_t *f);
16 static ngx_int_t ngx_rtmp_flv_timestamp_to_offset(ngx_rtmp_session_t *s,
17 ngx_file_t *f, ngx_int_t timestamp);
18 static ngx_int_t ngx_rtmp_flv_init(ngx_rtmp_session_t *s, ngx_file_t *f,
19 ngx_int_t aindex, ngx_int_t vindex);
20 static ngx_int_t ngx_rtmp_flv_start(ngx_rtmp_session_t *s, ngx_file_t *f);
21 static ngx_int_t ngx_rtmp_flv_seek(ngx_rtmp_session_t *s, ngx_file_t *f,
22 ngx_uint_t offset);
23 static ngx_int_t ngx_rtmp_flv_stop(ngx_rtmp_session_t *s, ngx_file_t *f);
24 static ngx_int_t ngx_rtmp_flv_send(ngx_rtmp_session_t *s, ngx_file_t *f,
25 ngx_uint_t *ts);
26
27
28 typedef struct {
29 ngx_uint_t nelts;
30 ngx_uint_t offset;
31 } ngx_rtmp_flv_index_t;
32
33
34 typedef struct {
35 ngx_int_t offset;
36 ngx_int_t start_timestamp;
37 ngx_event_t write_evt;
38 uint32_t last_audio;
39 uint32_t last_video;
40 ngx_uint_t msg_mask;
41 uint32_t epoch;
42
43 unsigned meta_read:1;
44 ngx_rtmp_flv_index_t filepositions;
45 ngx_rtmp_flv_index_t times;
46 } ngx_rtmp_flv_ctx_t;
47
48
49 #define NGX_RTMP_FLV_BUFFER (1024*1024)
50 #define NGX_RTMP_FLV_BUFLEN_ADDON 1000
51 #define NGX_RTMP_FLV_TAG_HEADER 11
52 #define NGX_RTMP_FLV_DATA_OFFSET 13
53
54
55 static u_char ngx_rtmp_flv_buffer[
56 NGX_RTMP_FLV_BUFFER];
57 static u_char ngx_rtmp_flv_header[
58 NGX_RTMP_FLV_TAG_HEADER];
59
60
61 static ngx_rtmp_module_t ngx_rtmp_flv_module_ctx = {
62 NULL, /* preconfiguration */
63 ngx_rtmp_flv_postconfiguration, /* postconfiguration */
64 NULL, /* create main configuration */
65 NULL, /* init main configuration */
66 NULL, /* create server configuration */
67 NULL, /* merge server configuration */
68 NULL, /* create app configuration */
69 NULL /* merge app configuration */
70 };
71
72
73 ngx_module_t ngx_rtmp_flv_module = {
74 NGX_MODULE_V1,
75 &ngx_rtmp_flv_module_ctx, /* module context */
76 NULL, /* module directives */
77 NGX_RTMP_MODULE, /* module type */
78 NULL, /* init master */
79 NULL, /* init module */
80 NULL, /* init process */
81 NULL, /* init thread */
82 NULL, /* exit thread */
83 NULL, /* exit process */
84 NULL, /* exit master */
85 NGX_MODULE_V1_PADDING
86 };
87
88
89 static ngx_int_t
ngx_rtmp_flv_fill_index(ngx_rtmp_amf_ctx_t * ctx,ngx_rtmp_flv_index_t * idx)90 ngx_rtmp_flv_fill_index(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_flv_index_t *idx)
91 {
92 uint32_t nelts;
93 ngx_buf_t *b;
94
95 /* we have AMF array pointed by context;
96 * need to extract its size (4 bytes) &
97 * save offset of actual array data */
98
99 b = ctx->link->buf;
100
101 if (b->last - b->pos < (ngx_int_t) ctx->offset + 4) {
102 return NGX_ERROR;
103 }
104
105 ngx_rtmp_rmemcpy(&nelts, b->pos + ctx->offset, 4);
106
107 idx->nelts = nelts;
108 idx->offset = ctx->offset + 4;
109
110 return NGX_OK;
111 }
112
113
114 static ngx_int_t
ngx_rtmp_flv_init_index(ngx_rtmp_session_t * s,ngx_chain_t * in)115 ngx_rtmp_flv_init_index(ngx_rtmp_session_t *s, ngx_chain_t *in)
116 {
117 ngx_rtmp_flv_ctx_t *ctx;
118
119 static ngx_rtmp_amf_ctx_t filepositions_ctx;
120 static ngx_rtmp_amf_ctx_t times_ctx;
121
122 static ngx_rtmp_amf_elt_t in_keyframes[] = {
123
124 { NGX_RTMP_AMF_ARRAY | NGX_RTMP_AMF_CONTEXT,
125 ngx_string("filepositions"),
126 &filepositions_ctx, 0 },
127
128 { NGX_RTMP_AMF_ARRAY | NGX_RTMP_AMF_CONTEXT,
129 ngx_string("times"),
130 ×_ctx, 0 }
131 };
132
133 static ngx_rtmp_amf_elt_t in_inf[] = {
134
135 { NGX_RTMP_AMF_OBJECT,
136 ngx_string("keyframes"),
137 in_keyframes, sizeof(in_keyframes) }
138 };
139
140 static ngx_rtmp_amf_elt_t in_elts[] = {
141
142 { NGX_RTMP_AMF_STRING,
143 ngx_null_string,
144 NULL, 0 },
145
146 { NGX_RTMP_AMF_OBJECT,
147 ngx_null_string,
148 in_inf, sizeof(in_inf) },
149 };
150
151 ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
152
153 if (ctx == NULL || in == NULL) {
154 return NGX_OK;
155 }
156
157 ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
158 "flv: init index");
159
160 ngx_memzero(&filepositions_ctx, sizeof(filepositions_ctx));
161 ngx_memzero(×_ctx, sizeof(times_ctx));
162
163 if (ngx_rtmp_receive_amf(s, in, in_elts,
164 sizeof(in_elts) / sizeof(in_elts[0])))
165 {
166 ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
167 "flv: init index error");
168 return NGX_OK;
169 }
170
171 if (filepositions_ctx.link && ngx_rtmp_flv_fill_index(&filepositions_ctx,
172 &ctx->filepositions)
173 != NGX_OK)
174 {
175 ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
176 "flv: failed to init filepositions");
177 return NGX_ERROR;
178 }
179
180 ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
181 "flv: filepositions nelts=%ui offset=%ui",
182 ctx->filepositions.nelts, ctx->filepositions.offset);
183
184 if (times_ctx.link && ngx_rtmp_flv_fill_index(×_ctx,
185 &ctx->times)
186 != NGX_OK)
187 {
188 ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
189 "flv: failed to init times");
190 return NGX_ERROR;
191 }
192
193 ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
194 "flv: times nelts=%ui offset=%ui",
195 ctx->times.nelts, ctx->times.offset);
196
197 return NGX_OK;
198 }
199
200
201 static double
ngx_rtmp_flv_index_value(void * src)202 ngx_rtmp_flv_index_value(void *src)
203 {
204 double v;
205
206 ngx_rtmp_rmemcpy(&v, src, 8);
207
208 return v;
209 }
210
211
212 static ngx_int_t
ngx_rtmp_flv_timestamp_to_offset(ngx_rtmp_session_t * s,ngx_file_t * f,ngx_int_t timestamp)213 ngx_rtmp_flv_timestamp_to_offset(ngx_rtmp_session_t *s, ngx_file_t *f,
214 ngx_int_t timestamp)
215 {
216 ngx_rtmp_flv_ctx_t *ctx;
217 ssize_t n, size;
218 ngx_uint_t offset, index, ret, nelts;
219 double v;
220
221 ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
222
223 if (ctx == NULL) {
224 goto rewind;
225 }
226
227 ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
228 "flv: lookup index start timestamp=%i",
229 timestamp);
230
231 if (ctx->meta_read == 0) {
232 ngx_rtmp_flv_read_meta(s, f);
233 ctx->meta_read = 1;
234 }
235
236 if (timestamp <= 0 || ctx->filepositions.nelts == 0
237 || ctx->times.nelts == 0)
238 {
239 goto rewind;
240 }
241
242 /* read index table from file given offset */
243 offset = NGX_RTMP_FLV_DATA_OFFSET + NGX_RTMP_FLV_TAG_HEADER +
244 ctx->times.offset;
245
246 /* index should fit in the buffer */
247 nelts = ngx_min(ctx->times.nelts, sizeof(ngx_rtmp_flv_buffer) / 9);
248 size = nelts * 9;
249
250 n = ngx_read_file(f, ngx_rtmp_flv_buffer, size, offset);
251
252 if (n != size) {
253 ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
254 "flv: could not read times index");
255 goto rewind;
256 }
257
258 /*TODO: implement binary search */
259 ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
260 "flv: lookup times nelts=%ui", nelts);
261
262 for (index = 0; index < nelts - 1; ++index) {
263 v = ngx_rtmp_flv_index_value(ngx_rtmp_flv_buffer +
264 index * 9 + 1) * 1000;
265
266 ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
267 "flv: lookup times index=%ui value=%ui",
268 index, (ngx_uint_t) v);
269
270 if (timestamp < v) {
271 break;
272 }
273 }
274
275 if (index >= ctx->filepositions.nelts) {
276 ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
277 "flv: index out of bounds: %ui>=%ui",
278 index, ctx->filepositions.nelts);
279 goto rewind;
280 }
281
282 /* take value from filepositions */
283 offset = NGX_RTMP_FLV_DATA_OFFSET + NGX_RTMP_FLV_TAG_HEADER +
284 ctx->filepositions.offset + index * 9;
285
286 n = ngx_read_file(f, ngx_rtmp_flv_buffer, 8, offset + 1);
287
288 if (n != 8) {
289 ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
290 "flv: could not read filepositions index");
291 goto rewind;
292 }
293
294 ret = (ngx_uint_t) ngx_rtmp_flv_index_value(ngx_rtmp_flv_buffer);
295
296 ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
297 "flv: lookup index timestamp=%i offset=%ui",
298 timestamp, ret);
299
300 return ret;
301
302 rewind:
303 ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
304 "flv: lookup index timestamp=%i offset=begin",
305 timestamp);
306
307 return NGX_RTMP_FLV_DATA_OFFSET;
308 }
309
310
311 static void
ngx_rtmp_flv_read_meta(ngx_rtmp_session_t * s,ngx_file_t * f)312 ngx_rtmp_flv_read_meta(ngx_rtmp_session_t *s, ngx_file_t *f)
313 {
314 ngx_rtmp_flv_ctx_t *ctx;
315 ssize_t n;
316 ngx_rtmp_header_t h;
317 ngx_chain_t *out, in;
318 ngx_buf_t in_buf;
319 ngx_rtmp_core_srv_conf_t *cscf;
320 uint32_t size;
321
322 cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
323
324 ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
325
326 if (ctx == NULL) {
327 return;
328 }
329
330 ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
331 "flv: read meta");
332
333 /* read tag header */
334 n = ngx_read_file(f, ngx_rtmp_flv_header, sizeof(ngx_rtmp_flv_header),
335 NGX_RTMP_FLV_DATA_OFFSET);
336
337 if (n != sizeof(ngx_rtmp_flv_header)) {
338 ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
339 "flv: could not read metadata tag header");
340 return;
341 }
342
343 if (ngx_rtmp_flv_header[0] != NGX_RTMP_MSG_AMF_META) {
344 ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
345 "flv: first tag is not metadata, giving up");
346 return;
347 }
348
349 ngx_memzero(&h, sizeof(h));
350
351 h.type = NGX_RTMP_MSG_AMF_META;
352 h.msid = NGX_RTMP_MSID;
353 h.csid = NGX_RTMP_CSID_AMF;
354
355 size = 0;
356 ngx_rtmp_rmemcpy(&size, ngx_rtmp_flv_header + 1, 3);
357
358 ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
359 "flv: metadata size=%D", size);
360
361 if (size > sizeof(ngx_rtmp_flv_buffer)) {
362 ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
363 "flv: too big metadata");
364 return;
365 }
366
367 /* read metadata */
368 n = ngx_read_file(f, ngx_rtmp_flv_buffer, size,
369 sizeof(ngx_rtmp_flv_header) +
370 NGX_RTMP_FLV_DATA_OFFSET);
371
372 if (n != (ssize_t) size) {
373 ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
374 "flv: could not read metadata");
375 return;
376 }
377
378 /* prepare input chain */
379 ngx_memzero(&in, sizeof(in));
380 ngx_memzero(&in_buf, sizeof(in_buf));
381
382 in.buf = &in_buf;
383 in_buf.pos = ngx_rtmp_flv_buffer;
384 in_buf.last = ngx_rtmp_flv_buffer + size;
385
386 ngx_rtmp_flv_init_index(s, &in);
387
388 /* output chain */
389 out = ngx_rtmp_append_shared_bufs(cscf, NULL, &in);
390
391 ngx_rtmp_prepare_message(s, &h, NULL, out);
392 ngx_rtmp_send_message(s, out, 0);
393 ngx_rtmp_free_shared_chain(cscf, out);
394 }
395
396
397 static ngx_int_t
ngx_rtmp_flv_send(ngx_rtmp_session_t * s,ngx_file_t * f,ngx_uint_t * ts)398 ngx_rtmp_flv_send(ngx_rtmp_session_t *s, ngx_file_t *f, ngx_uint_t *ts)
399 {
400 ngx_rtmp_flv_ctx_t *ctx;
401 uint32_t last_timestamp;
402 ngx_rtmp_header_t h, lh;
403 ngx_rtmp_core_srv_conf_t *cscf;
404 ngx_chain_t *out, in;
405 ngx_buf_t in_buf;
406 ngx_int_t rc;
407 ssize_t n;
408 uint32_t buflen, end_timestamp, size;
409
410 cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
411
412 ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
413
414 if (ctx == NULL) {
415 return NGX_ERROR;
416 }
417
418 if (ctx->offset == -1) {
419 ctx->offset = ngx_rtmp_flv_timestamp_to_offset(s, f,
420 ctx->start_timestamp);
421 ctx->start_timestamp = -1; /* set later from actual timestamp */
422 }
423
424 ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
425 "flv: read tag at offset=%i", ctx->offset);
426
427 /* read tag header */
428 n = ngx_read_file(f, ngx_rtmp_flv_header,
429 sizeof(ngx_rtmp_flv_header), ctx->offset);
430
431 if (n != sizeof(ngx_rtmp_flv_header)) {
432 ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
433 "flv: could not read flv tag header");
434 return NGX_DONE;
435 }
436
437 /* parse header fields */
438 ngx_memzero(&h, sizeof(h));
439
440 h.msid = NGX_RTMP_MSID;
441 h.type = ngx_rtmp_flv_header[0];
442
443 size = 0;
444
445 ngx_rtmp_rmemcpy(&size, ngx_rtmp_flv_header + 1, 3);
446 ngx_rtmp_rmemcpy(&h.timestamp, ngx_rtmp_flv_header + 4, 3);
447
448 ((u_char *) &h.timestamp)[3] = ngx_rtmp_flv_header[7];
449
450 ctx->offset += (sizeof(ngx_rtmp_flv_header) + size + 4);
451
452 last_timestamp = 0;
453
454 switch (h.type) {
455
456 case NGX_RTMP_MSG_AUDIO:
457 h.csid = NGX_RTMP_CSID_AUDIO;
458 last_timestamp = ctx->last_audio;
459 ctx->last_audio = h.timestamp;
460 break;
461
462 case NGX_RTMP_MSG_VIDEO:
463 h.csid = NGX_RTMP_CSID_VIDEO;
464 last_timestamp = ctx->last_video;
465 ctx->last_video = h.timestamp;
466 break;
467
468 default:
469 return NGX_OK;
470 }
471
472 ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
473 "flv: read tag type=%i size=%uD timestamp=%uD "
474 "last_timestamp=%uD",
475 (ngx_int_t) h.type,size, h.timestamp, last_timestamp);
476
477 lh = h;
478 lh.timestamp = last_timestamp;
479
480 if (size > sizeof(ngx_rtmp_flv_buffer)) {
481 ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
482 "flv: too big message: %D>%uz", size,
483 sizeof(ngx_rtmp_flv_buffer));
484 goto next;
485 }
486
487 /* read tag body */
488 n = ngx_read_file(f, ngx_rtmp_flv_buffer, size,
489 ctx->offset - size - 4);
490
491 if (n != (ssize_t) size) {
492 ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
493 "flv: could not read flv tag");
494 return NGX_ERROR;
495 }
496
497 /* prepare input chain */
498 ngx_memzero(&in, sizeof(in));
499 ngx_memzero(&in_buf, sizeof(in_buf));
500
501 in.buf = &in_buf;
502 in_buf.pos = ngx_rtmp_flv_buffer;
503 in_buf.last = ngx_rtmp_flv_buffer + size;
504
505 /* output chain */
506 out = ngx_rtmp_append_shared_bufs(cscf, NULL, &in);
507
508 ngx_rtmp_prepare_message(s, &h, ctx->msg_mask & (1 << h.type) ?
509 &lh : NULL, out);
510 rc = ngx_rtmp_send_message(s, out, 0);
511 ngx_rtmp_free_shared_chain(cscf, out);
512
513 if (rc == NGX_AGAIN) {
514 return NGX_AGAIN;
515 }
516
517 if (rc != NGX_OK) {
518 return NGX_ERROR;
519 }
520
521 ctx->msg_mask |= (1 << h.type);
522
523 next:
524 if (ctx->start_timestamp == -1) {
525 ctx->start_timestamp = h.timestamp;
526 ctx->epoch = ngx_current_msec;
527
528 ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
529 "flv: start_timestamp=%i", ctx->start_timestamp);
530 return NGX_OK;
531 }
532
533 buflen = s->buflen + NGX_RTMP_FLV_BUFLEN_ADDON;
534
535 end_timestamp = (ngx_current_msec - ctx->epoch) +
536 ctx->start_timestamp + buflen;
537
538 ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
539 "flv: %s wait=%D timestamp=%D end_timestamp=%D bufen=%i",
540 h.timestamp > end_timestamp ? "schedule" : "advance",
541 h.timestamp > end_timestamp ? h.timestamp - end_timestamp : 0,
542 h.timestamp, end_timestamp, (ngx_int_t) buflen);
543
544 s->current_time = h.timestamp;
545
546 /* too much data sent; schedule timeout */
547 if (h.timestamp > end_timestamp) {
548 return h.timestamp - end_timestamp;
549 }
550
551 return NGX_OK;
552 }
553
554
555 static ngx_int_t
ngx_rtmp_flv_init(ngx_rtmp_session_t * s,ngx_file_t * f,ngx_int_t aindex,ngx_int_t vindex)556 ngx_rtmp_flv_init(ngx_rtmp_session_t *s, ngx_file_t *f, ngx_int_t aindex,
557 ngx_int_t vindex)
558 {
559 ngx_rtmp_flv_ctx_t *ctx;
560
561 ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
562
563 if (ctx == NULL) {
564 ctx = ngx_palloc(s->connection->pool, sizeof(ngx_rtmp_flv_ctx_t));
565
566 if (ctx == NULL) {
567 return NGX_ERROR;
568 }
569
570 ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_flv_module);
571 }
572
573 ngx_memzero(ctx, sizeof(*ctx));
574
575 return NGX_OK;
576 }
577
578
579 static ngx_int_t
ngx_rtmp_flv_start(ngx_rtmp_session_t * s,ngx_file_t * f)580 ngx_rtmp_flv_start(ngx_rtmp_session_t *s, ngx_file_t *f)
581 {
582 ngx_rtmp_flv_ctx_t *ctx;
583
584 ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
585
586 if (ctx == NULL) {
587 return NGX_OK;
588 }
589
590 ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
591 "flv: start");
592
593 ctx->offset = -1;
594 ctx->msg_mask = 0;
595
596 return NGX_OK;
597 }
598
599
600 static ngx_int_t
ngx_rtmp_flv_seek(ngx_rtmp_session_t * s,ngx_file_t * f,ngx_uint_t timestamp)601 ngx_rtmp_flv_seek(ngx_rtmp_session_t *s, ngx_file_t *f, ngx_uint_t timestamp)
602 {
603 ngx_rtmp_flv_ctx_t *ctx;
604
605 ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
606
607 if (ctx == NULL) {
608 return NGX_OK;
609 }
610
611 ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
612 "flv: seek timestamp=%ui", timestamp);
613
614 ctx->start_timestamp = timestamp;
615 ctx->epoch = ngx_current_msec;
616 ctx->offset = -1;
617 ctx->msg_mask = 0;
618
619 return NGX_OK;
620 }
621
622
623 static ngx_int_t
ngx_rtmp_flv_stop(ngx_rtmp_session_t * s,ngx_file_t * f)624 ngx_rtmp_flv_stop(ngx_rtmp_session_t *s, ngx_file_t *f)
625 {
626 ngx_rtmp_flv_ctx_t *ctx;
627
628 ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);
629
630 if (ctx == NULL) {
631 return NGX_OK;
632 }
633
634 ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
635 "flv: stop");
636
637 return NGX_OK;
638 }
639
640
641 static ngx_int_t
ngx_rtmp_flv_postconfiguration(ngx_conf_t * cf)642 ngx_rtmp_flv_postconfiguration(ngx_conf_t *cf)
643 {
644 ngx_rtmp_play_main_conf_t *pmcf;
645 ngx_rtmp_play_fmt_t **pfmt, *fmt;
646
647 pmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_play_module);
648
649 pfmt = ngx_array_push(&pmcf->fmts);
650
651 if (pfmt == NULL) {
652 return NGX_ERROR;
653 }
654
655 fmt = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_play_fmt_t));
656
657 if (fmt == NULL) {
658 return NGX_ERROR;
659 }
660
661 *pfmt = fmt;
662
663 ngx_str_set(&fmt->name, "flv-format");
664
665 ngx_str_null(&fmt->pfx); /* default fmt */
666 ngx_str_set(&fmt->sfx, ".flv");
667
668 fmt->init = ngx_rtmp_flv_init;
669 fmt->start = ngx_rtmp_flv_start;
670 fmt->seek = ngx_rtmp_flv_seek;
671 fmt->stop = ngx_rtmp_flv_stop;
672 fmt->send = ngx_rtmp_flv_send;
673
674 return NGX_OK;
675 }
676