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