1 /*******************************************************************************
2 ngx_http_h264_streaming_module.c
3
4 mod_h264_streaming - An Nginx module for streaming Quicktime/MPEG4 files.
5
6 Copyright (C) 2008-2009 CodeShop B.V.
7
8 Licensing
9 The Streaming Module is licened under a Creative Commons License. It
10 allows you to use, modify and redistribute the module, but only for
11 *noncommercial* purposes. For corporate use, please apply for a
12 commercial license.
13
14 Creative Commons License:
15 http://creativecommons.org/licenses/by-nc-sa/3.0/
16
17 Commercial License for H264 Streaming Module:
18 http://h264.code-shop.com/trac/wiki/Mod-H264-Streaming-License-Version2
19
20 Commercial License for Smooth Streaming Module:
21 http://smoothstreaming.code-shop.com/trac/wiki/Mod-Smooth-Streaming-License
22 ******************************************************************************/
23
24 #include <nginx.h>
25 #include <ngx_config.h>
26 #include <ngx_core.h>
27 #include <ngx_http.h>
28 #include "mp4_io.h"
29 #include "mp4_process.c"
30 #include "moov.h"
31 #include "output_bucket.h"
32 #ifdef BUILDING_H264_STREAMING
33 #include "output_mp4.h"
34 #define X_MOD_STREAMING_KEY X_MOD_H264_STREAMING_KEY
35 #define X_MOD_STREAMING_VERSION X_MOD_H264_STREAMING_VERSION
36 #endif
37 #ifdef BUILDING_SMOOTH_STREAMING
38 #include "ism_reader.h"
39 #include "output_ismv.h"
40 #define X_MOD_STREAMING_KEY X_MOD_SMOOTH_STREAMING_KEY
41 #define X_MOD_STREAMING_VERSION X_MOD_SMOOTH_STREAMING_VERSION
42 #endif
43 #ifdef BUILDING_FLV_STREAMING
44 #include "output_flv.h"
45 #endif
46
47 #if 0
48 /* Mod-H264-Streaming configuration
49
50 server {
51 listen 82;
52 server_name localhost;
53
54 location ~ \.mp4$ {
55 root /var/www;
56 mp4;
57 }
58 }
59
60 */
61
62 /* Mod-Smooth-Streaming configuration
63
64 server {
65 listen 82;
66 server_name localhost;
67
68 rewrite ^(.*/)?(.*)\.([is])sm/[Mm]anifest$ $1$2.$3sm/$2.ismc last;
69 rewrite ^(.*/)?(.*)\.([is])sm/QualityLevels\(([0-9]+)\)/Fragments\((.*)=([0-9]+)\)(.*)$ $1$2.$3sm/$2.ism?bitrate=$4&$5=$6 last;
70
71 location ~ \.ism$ {
72 root /var/www;
73 ism;
74 }
75 }
76 */
77 #endif
78
79 static char* ngx_streaming(ngx_conf_t* cf, ngx_command_t* cmd, void* conf);
80
81 static ngx_command_t ngx_streaming_commands[] =
82 {
83 {
84 #ifdef BUILDING_H264_STREAMING
85 ngx_string("mp4"),
86 #endif
87 #ifdef BUILDING_SMOOTH_STREAMING
88 ngx_string("ism"),
89 #endif
90 NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
91 ngx_streaming,
92 0,
93 0,
94 NULL
95 },
96 ngx_null_command
97 };
98
99 static ngx_http_module_t ngx_streaming_module_ctx =
100 {
101 NULL, /* preconfiguration */
102 NULL, /* postconfiguration */
103
104 NULL, /* create main configuration */
105 NULL, /* init main configuration */
106
107 NULL, /* create server configuration */
108 NULL, /* merge server configuration */
109
110 NULL, /* create location configuration */
111 NULL /* merge location configuration */
112 };
113
114 #ifdef BUILDING_H264_STREAMING
115 ngx_module_t ngx_http_h264_streaming_module =
116 #endif
117 #ifdef BUILDING_SMOOTH_STREAMING
118 ngx_module_t ngx_http_smooth_streaming_module =
119 #endif
120 {
121 NGX_MODULE_V1,
122 &ngx_streaming_module_ctx, /* module context */
123 ngx_streaming_commands, /* module directives */
124 NGX_HTTP_MODULE, /* module type */
125 NULL, /* init master */
126 NULL, /* init module */
127 NULL, /* init process */
128 NULL, /* init thread */
129 NULL, /* exit thread */
130 NULL, /* exit process */
131 NULL, /* exit master */
132 NGX_MODULE_V1_PADDING
133 };
134
ngx_streaming_handler(ngx_http_request_t * r)135 static ngx_int_t ngx_streaming_handler(ngx_http_request_t *r)
136 {
137 u_char *last;
138 size_t root;
139 ngx_int_t rc;
140 ngx_uint_t level;
141 ngx_str_t path;
142 char filename[256];
143 ngx_log_t *log;
144 ngx_open_file_info_t of;
145 ngx_http_core_loc_conf_t *clcf;
146
147 if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD)))
148 {
149 return NGX_HTTP_NOT_ALLOWED;
150 }
151
152 if (r->uri.data[r->uri.len - 1] == '/')
153 {
154 return NGX_DECLINED;
155 }
156
157 /* TODO: Win32 */
158
159 rc = ngx_http_discard_request_body(r);
160
161 if(rc != NGX_OK)
162 {
163 return rc;
164 }
165
166 mp4_split_options_t* options = mp4_split_options_init();
167 if(r->args.len && !mp4_split_options_set(options, (const char*)r->args.data, r->args.len))
168 {
169 mp4_split_options_exit(options);
170 return NGX_DECLINED;
171 }
172
173 last = ngx_http_map_uri_to_path(r, &path, &root, 0);
174 if (last == NULL)
175 {
176 return NGX_HTTP_INTERNAL_SERVER_ERROR;
177 }
178
179 log = r->connection->log;
180
181 path.len = last - path.data;
182
183 ngx_cpystrn((u_char*)filename, path.data, sizeof(filename) / sizeof(char) - 1);
184 filename[sizeof(filename) / sizeof(char) - 1] = '\0';
185
186 #ifdef BUILDING_SMOOTH_STREAMING
187 // if it is a fragment request then we read the server manifest file
188 // and based on the bitrate and track type we set the filename and track id
189 if(ends_with(filename, ".ism"))
190 {
191 if(options->output_format != OUTPUT_FORMAT_TS)
192 {
193 ism_t* ism = ism_init(filename);
194
195 if(ism == NULL)
196 {
197 return NGX_HTTP_NOT_FOUND;
198 }
199
200 const char* src;
201 if(!ism_get_source(ism, options->fragment_bitrate, options->fragment_type,
202 &src, &options->fragment_track_id))
203 {
204 return NGX_HTTP_NOT_FOUND;
205 }
206
207 char* dir_end = strrchr(filename, '/');
208 dir_end = dir_end == NULL ? filename : (dir_end + 1);
209 strcpy(dir_end, src);
210
211 ism_exit(ism);
212 }
213 }
214 #endif
215
216 path.len = strlen(filename);
217 path.data = ngx_pnalloc(r->pool, path.len + 1);
218 memcpy(path.data, filename, path.len);
219 // ngx_open_and_stat_file in ngx_open_cached_file expects the name to be zero-terminated.
220 path.data[path.len] = '\0';
221
222 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
223 "http mp4 filename: \"%V\"", &path);
224
225 clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
226
227 ngx_memzero(&of, sizeof(ngx_open_file_info_t));
228
229 #if nginx_version >= 8018
230 of.read_ahead = clcf->read_ahead;
231 #endif
232 of.directio = clcf->directio;
233 of.valid = clcf->open_file_cache_valid;
234 of.min_uses = clcf->open_file_cache_min_uses;
235 of.errors = clcf->open_file_cache_errors;
236 of.events = clcf->open_file_cache_events;
237
238 if(ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool) != NGX_OK)
239 {
240 switch (of.err)
241 {
242 case 0:
243 return NGX_HTTP_INTERNAL_SERVER_ERROR;
244 case NGX_ENOENT:
245 case NGX_ENOTDIR:
246 case NGX_ENAMETOOLONG:
247 level = NGX_LOG_ERR;
248 rc = NGX_HTTP_NOT_FOUND;
249 break;
250 case NGX_EACCES:
251 level = NGX_LOG_ERR;
252 rc = NGX_HTTP_FORBIDDEN;
253 break;
254 default:
255 level = NGX_LOG_CRIT;
256 rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
257 break;
258 }
259
260 if (rc != NGX_HTTP_NOT_FOUND || clcf->log_not_found)
261 {
262 #if nginx_version >= 8018
263 ngx_log_error(level, log, of.err,
264 "%s \"%s\" failed", of.failed, path.data);
265 #else
266 ngx_log_error(level, log, of.err,
267 ngx_open_file_n "%s \"%s\" failed", path.data);
268 #endif
269 }
270
271 return rc;
272 }
273
274 if (!of.is_file)
275 {
276 if(ngx_close_file(of.fd) == NGX_FILE_ERROR)
277 {
278 ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
279 ngx_close_file_n " \"%s\" failed", path.data);
280 }
281 return NGX_DECLINED;
282 }
283
284 // ??
285 r->root_tested = !r->error_page;
286
287 {
288 struct bucket_t* buckets = 0;
289 int verbose = 0;
290 int http_status =
291 mp4_process((const char*)path.data, of.size, verbose, &buckets, options);
292
293 mp4_split_options_exit(options);
294
295 if(http_status != 200)
296 {
297 if(buckets)
298 {
299 buckets_exit(buckets);
300 }
301
302 return http_status;
303 }
304
305 r->headers_out.content_type.len = sizeof("video/mp4") - 1;
306 r->headers_out.content_type.data = (u_char*)"video/mp4";
307
308 ngx_chain_t* out = 0;
309 ngx_chain_t** chain = &out;
310 uint64_t content_length = 0;
311
312 {
313 struct bucket_t* bucket = buckets;
314 if(bucket)
315 {
316 do
317 {
318 ngx_buf_t* b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
319 *chain = ngx_pcalloc(r->pool, sizeof(ngx_chain_t));
320 if(b == NULL || *chain == NULL)
321 {
322 return NGX_HTTP_INTERNAL_SERVER_ERROR;
323 }
324
325 switch(bucket->type_)
326 {
327 case BUCKET_TYPE_MEMORY:
328 b->pos = ngx_pcalloc(r->pool, bucket->size_);
329 if(b->pos == NULL)
330 {
331 return NGX_HTTP_INTERNAL_SERVER_ERROR;
332 }
333 b->last = b->pos + bucket->size_;
334 b->memory = 1;
335 memcpy(b->pos, bucket->buf_, bucket->size_);
336 break;
337 case BUCKET_TYPE_FILE:
338 b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t));
339 if(b->file == NULL)
340 {
341 return NGX_HTTP_INTERNAL_SERVER_ERROR;
342 }
343 b->file->fd = of.fd;
344 b->file->name = path;
345 b->file->log = log;
346 b->file_pos = bucket->offset_;
347 b->file_last = b->file_pos + bucket->size_;
348 b->in_file = b->file_last ? 1: 0;
349 break;
350 }
351
352 b->last_buf = bucket->next_ == buckets ? 1 : 0;
353 b->last_in_chain = bucket->next_ == buckets ? 1 : 0;
354
355 (*chain)->buf = b;
356 (*chain)->next = NULL;
357 chain = &(*chain)->next;
358
359 content_length += bucket->size_;
360 bucket = bucket->next_;
361 } while(bucket != buckets);
362 buckets_exit(buckets);
363 }
364 }
365
366 log->action = "sending mp4 to client";
367
368 r->headers_out.status = NGX_HTTP_OK;
369 r->headers_out.content_length_n = content_length;
370 r->headers_out.last_modified_time = of.mtime;
371
372 // Nginx properly handles range requests, even when we're sending chunks
373 // from both memory (the metadata) and part file (the movie data).
374 r->allow_ranges = 1;
375
376 // if(ngx_http_set_content_type(r) != NGX_OK)
377 // {
378 // return NGX_HTTP_INTERNAL_SERVER_ERROR;
379 // }
380
381 ngx_table_elt_t* h = ngx_list_push(&r->headers_out.headers);
382 if(h == NULL)
383 {
384 return NGX_HTTP_INTERNAL_SERVER_ERROR;
385 }
386 h->hash = 1;
387
388 h->key.len = sizeof(X_MOD_STREAMING_KEY) - 1;
389 h->key.data = (u_char*)X_MOD_STREAMING_KEY;
390 h->value.len = sizeof(X_MOD_STREAMING_VERSION) - 1;
391 h->value.data = (u_char*)X_MOD_STREAMING_VERSION;
392
393 rc = ngx_http_send_header(r);
394
395 if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
396 return rc;
397 }
398
399 return ngx_http_output_filter(r, out);
400 }
401 }
402
ngx_streaming(ngx_conf_t * cf,ngx_command_t * cmd,void * conf)403 static char* ngx_streaming(ngx_conf_t* cf, ngx_command_t* cmd, void* conf)
404 {
405 ngx_http_core_loc_conf_t* clcf =
406 ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
407
408 clcf->handler = ngx_streaming_handler;
409
410 return NGX_CONF_OK;
411 }
412
413 // End Of File
414
415