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