1 /*******************************************************************************
2  mod_h264_streaming.c
3 
4  mod_h264_streaming - An Apache module for streaming Quicktime/MPEG4 files.
5 
6  Copyright (C) 2007-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 <httpd.h>
25 #include <http_core.h>
26 #include <http_config.h>
27 #include <http_protocol.h>
28 #include <http_log.h>
29 #include <http_request.h> // or ap_update_mtime
30 #include <apr_version.h>
31 #include <apr_strings.h>  // for apr_itoa, apr_pstrcat
32 #include <apr_buckets.h>
33 #include "mp4_io.h"
34 #include "mp4_process.h"
35 #include "moov.h"
36 #include "output_bucket.h"
37 #ifdef BUILDING_H264_STREAMING
38 #include "output_mp4.h"
39 #define X_MOD_STREAMING_KEY X_MOD_H264_STREAMING_KEY
40 #define X_MOD_STREAMING_VERSION X_MOD_H264_STREAMING_VERSION
41 #define H264_STREAMING_HANDLER "h264-streaming.extensions"
42 #endif
43 #ifdef BUILDING_SMOOTH_STREAMING
44 #include "ism_reader.h"
45 #include "output_ismv.h"
46 #define X_MOD_STREAMING_KEY X_MOD_SMOOTH_STREAMING_KEY
47 #define X_MOD_STREAMING_VERSION X_MOD_SMOOTH_STREAMING_VERSION
48 #define H264_STREAMING_HANDLER "smooth-streaming.extensions"
49 #endif
50 #ifdef BUILDING_FLV_STREAMING
51 #define H264_STREAMING_HANDLER "flv-streaming.extensions"
52 #include "output_flv.h"
53 #endif
54 
55 #if 0
56 /* Mod-H264-Streaming configuration
57 
58 [httpd.conf]
59 LoadModule h264_streaming_module /usr/lib/apache2/modules/mod_h264_streaming.so
60 AddHandler h264-streaming.extensions .mp4
61 
62 */
63 
64 /* Mod-Smooth-Streaming configuration
65 
66 [httpd.conf]
67 LoadModule smooth_streaming_module /usr/lib/apache2/modules/mod_smooth_streaming.so
68 AddHandler smooth-streaming.extensions .ism
69 
70 [.htaccess]
71 RewriteEngine On
72 
73 RewriteRule ^(.*/)?(.*)\.([is])sm/[Mm]anifest$ $1$2.$3sm/$2.ismc [L]
74 RewriteRule ^(.*/)?(.*)\.([is])sm/QualityLevels\(([0-9]+)\)/Fragments\((.*)=([0-9]+)\)(.*)$ $1$2.$3sm/$2.ism?bitrate=$4&$5=$6 [L]
75 */
76 #endif
77 
drive_h264_streaming(request_rec * r)78 static int drive_h264_streaming(request_rec *r)
79 {
80   apr_finfo_t fi;
81   apr_bucket_brigade *bb;
82   apr_file_t *fp = NULL;
83   apr_status_t rv = APR_SUCCESS;
84   struct mp4_split_options_t* options;
85   struct bucket_t* buckets;
86   char filename[256];
87 
88   // Module version info
89   apr_table_set(r->headers_out, X_MOD_STREAMING_KEY, X_MOD_STREAMING_VERSION);
90 
91   options = mp4_split_options_init();
92   if(r->args && !mp4_split_options_set(options, r->args, strlen(r->args)))
93   {
94     return HTTP_FORBIDDEN;
95   }
96 
97   strncpy(filename, r->filename, sizeof(filename) / sizeof(char) - 1);
98   filename[sizeof(filename) / sizeof(char) - 1] = '\0';
99 
100 #ifdef BUILDING_SMOOTH_STREAMING
101   // if it is a fragment request then we read the server manifest file
102   // and based on the bitrate and track type we set the filename and track id
103   if(ends_with(filename, ".ism"))
104   {
105     if(options->output_format != OUTPUT_FORMAT_TS)
106     {
107       ism_t* ism = ism_init(filename);
108 
109       if(ism == NULL)
110       {
111         return HTTP_NOT_FOUND;
112       }
113 
114       {
115         char* dir_end = strrchr(filename, '/');
116 
117         const char* src;
118         if(!ism_get_source(ism, options->fragment_bitrate,
119           options->fragment_type, &src, &options->fragment_track_id))
120         {
121           return HTTP_NOT_FOUND;
122         }
123 
124         dir_end = dir_end == NULL ? filename : (dir_end + 1);
125         strcpy(dir_end, src);
126 
127         ism_exit(ism);
128       }
129     }
130   }
131 #endif
132 
133   rv = apr_stat(&fi, filename, APR_FINFO_SIZE, r->pool);
134 
135   if(rv)
136   {
137     /* Let the core handle it. */
138     return DECLINED;
139   }
140 
141   /* Open the file */
142   rv = apr_file_open(&fp, filename, APR_READ, APR_OS_DEFAULT, r->pool);
143 
144   if(rv)
145   {
146     ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
147                   "file permissions deny server access: %s", r->filename);
148     return HTTP_FORBIDDEN;
149   }
150 
151   // our ouput bucket
152   buckets = 0;
153 
154   {
155     int verbose = 0;
156     int http_status =
157       mp4_process(filename, fi.size, verbose, &buckets, options);
158 
159     mp4_split_options_exit(options);
160 
161     if(http_status != 200)
162     {
163       if(buckets)
164       {
165         buckets_exit(buckets);
166       }
167 
168       return http_status;
169     }
170 
171     ap_set_content_type(r, "video/mp4");
172   }
173 
174   {
175     uint64_t content_length = 0;
176     bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
177     {
178       bucket_t* bucket = buckets;
179       if(bucket)
180       {
181         do
182         {
183           switch(bucket->type_)
184           {
185           case BUCKET_TYPE_MEMORY:
186             rv = apr_brigade_write(bb, NULL, NULL,
187                                    (const char*)bucket->buf_, bucket->size_);
188             if(rv)
189             {
190               ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
191                             "unable to write memory bucket in brigade");
192               return HTTP_INTERNAL_SERVER_ERROR;
193             }
194             break;
195           case BUCKET_TYPE_FILE:
196 #if APR_MAJOR_VERSION >= 1 && APR_MINOR_VERSION >= 1
197             apr_brigade_insert_file(bb, fp, bucket->offset_, bucket->size_, r->pool);
198 #else
199             {
200               apr_bucket *e;
201               e = apr_bucket_file_create(fp, bucket->offset_, bucket->size_, r->pool,
202                                          r->connection->bucket_alloc);
203               APR_BRIGADE_INSERT_TAIL(bb, e);
204             }
205 #endif
206             break;
207           }
208           content_length += bucket->size_;
209           bucket = bucket->next_;
210         } while(bucket != buckets);
211         buckets_exit(buckets);
212       }
213     }
214 
215     // Add EOS bucket for byterange_filter to work
216     {
217       apr_bucket* eos_bucket = apr_bucket_eos_create(bb->bucket_alloc);
218       APR_BRIGADE_INSERT_TAIL(bb, eos_bucket);
219     }
220 
221     ap_set_content_length(r, content_length);
222 
223     // Add last-modified headers
224     ap_update_mtime(r, r->finfo.mtime);
225     ap_set_last_modified(r);
226 
227     // Create an ETag with an additional mdat_offset and mdat_size. The first
228     // character in a vlist_validator string is "W" for weak and also close the
229     // ETag header with a ".
230     // TODO: Make it strong
231     r->vlist_validator = apr_pstrcat(r->pool, "X",
232       apr_itoa(r->pool, content_length), "\"", NULL);
233     ap_set_etag(r);
234 
235     // Allow byte range requests
236     apr_table_setn(r->headers_out, "Accept-Ranges", "bytes");
237 
238     // Check for conditional requests
239     {
240       int errstatus;
241       if((errstatus = ap_meets_conditions(r)) != OK)
242       {
243         return errstatus;
244       }
245     }
246   }
247 
248   return ap_pass_brigade(r->output_filters, bb);
249 }
250 
h264_streaming_handler(request_rec * r)251 static int h264_streaming_handler(request_rec *r)
252 {
253   if ((!r->handler) || (strcmp(r->handler, H264_STREAMING_HANDLER)))
254   {
255     return DECLINED;
256   }
257 
258   r->allowed |= (AP_METHOD_BIT << M_GET);
259   if (r->method_number != M_GET)
260   {
261     return HTTP_METHOD_NOT_ALLOWED;
262   }
263 
264   return drive_h264_streaming(r);
265 }
266 
267 static const command_rec h264_streaming_cmds[] =
268 {
269     {NULL}
270 };
271 
register_hooks(apr_pool_t * p)272 static void register_hooks(apr_pool_t *p)
273 {
274   ap_hook_handler(h264_streaming_handler, NULL, NULL, APR_HOOK_MIDDLE);
275 }
276 
277 #ifdef __cplusplus
278 extern "C" {
279 #endif
280 
281 #ifdef BUILDING_H264_STREAMING
282 module AP_MODULE_DECLARE_DATA h264_streaming_module =
283 #elif BUILDING_SMOOTH_STREAMING
284 module AP_MODULE_DECLARE_DATA smooth_streaming_module =
285 #endif
286 {
287   STANDARD20_MODULE_STUFF,
288   NULL,
289   NULL,
290   NULL,
291   NULL,
292   h264_streaming_cmds,
293   register_hooks
294 };
295 
296 #ifdef __cplusplus
297 } /* extern C definitions */
298 #endif
299 
300 // End Of File
301 
302