// Main functions
/* MP4 (H264) pseudo-streaming.
* This function does nothing to check if the requested file is really a valid MP4/H264 file.
*/
PHP_FUNCTION(psstream_mp4)
{
char *path;
unsigned long path_size;
double t_start = 0.0;
double t_end = 0.0;
if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|d", &path, &path_size, &t_start) == FAILURE)
WRONG_PARAM_COUNT;
if (t_start < 0) {
php_error(E_WARNING, "Invalid start offset!");
RETURN_FALSE;
}
FILE* infile;
struct atom_t ftyp_atom;
struct atom_t moov_atom;
struct atom_t mdat_atom;
unsigned char* moov_data = 0;
unsigned char* ftyp_data = 0;
struct stat filestat;
if(VCWD_STAT(path, &filestat) || !(infile = VCWD_FOPEN(path, "rb"))) {
php_error(E_WARNING, "Could not open file... [%s]", path);
RETURN_FALSE;
}
unsigned long filesize = filestat.st_size;
if (SG(headers_sent)) {
php_error(E_WARNING, "Can not start streaming: headers were already sent!");
RETURN_FALSE;
}
// Send H264 structure
struct atom_t leaf_atom;
while(ftell(infile) < filesize) {
if(!atom_read_header(infile, &leaf_atom))
break;
atom_print(&leaf_atom);
if(atom_is(&leaf_atom, "ftyp")) {
ftyp_atom = leaf_atom;
ftyp_data = malloc(ftyp_atom.size_);
fseek(infile, ftyp_atom.start_, SEEK_SET);
if (!fread(ftyp_data, ftyp_atom.size_, 1, infile)) {
STREAMING_ERROR("file read error");
RETURN_FALSE;
}
}
else if(atom_is(&leaf_atom, "moov")) {
moov_atom = leaf_atom;
moov_data = malloc(moov_atom.size_);
fseek(infile, moov_atom.start_, SEEK_SET);
if (!fread(moov_data, moov_atom.size_, 1, infile)) {
STREAMING_ERROR("file read error");
RETURN_FALSE;
}
}
else if(atom_is(&leaf_atom, "mdat")) {
mdat_atom = leaf_atom;
}
atom_skip(infile, &leaf_atom);
}
fseek(infile, 0, SEEK_SET);
if(!moov_data) {
STREAMING_ERROR("null/empty moov_data");
RETURN_FALSE;
}
unsigned int mdat_start = (ftyp_data ? ftyp_atom.size_ : 0) + moov_atom.size_;
if(!moov_seek(moov_data,
&moov_atom.size_,
t_start, t_end,
&mdat_atom.start_, &mdat_atom.size_,
mdat_start - mdat_atom.start_)) {
STREAMING_ERROR("moov_seek failed");
RETURN_FALSE;
}
// Compute start/end file offsets
unsigned long start = mdat_atom.start_ + ATOM_PREAMBLE_SIZE;
unsigned long end = start + mdat_atom.size_ - ATOM_PREAMBLE_SIZE;
// Send headers
char last_modified[200];
time_t t = time(NULL);
struct tm *tmp;
if (tmp = localtime(&t)) {
strftime(last_modified, sizeof(last_modified), "Last-Modified: %a, %d %B %y %H:%M:%S GMT", tmp);
}
unsigned long delta = end - start;
delta += ftyp_data ? ftyp_atom.size_ : 0;
delta += moov_atom.size_;
delta += ATOM_PREAMBLE_SIZE;
char content_length[100];
sprintf(content_length, "Content-Length: %lu", delta);
INIT_HEADERS;
ADD_HEADER("Content-Type: video/mp4");
ADD_HEADER("Expires: Thu, 19 Nov 1981 08:52:00 GMT");
ADD_HEADER(last_modified);
ADD_HEADER("Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0");
ADD_HEADER("Pragma: no-cache");
ADD_HEADER(content_length);
SEND_HEADERS;
// Send meta atoms
if(ftyp_data) {
PHPWRITE(ftyp_data, ftyp_atom.size_);
free(ftyp_data);
}
PHPWRITE(moov_data, moov_atom.size_);
free(moov_data);
unsigned char mdat_bytes[ATOM_PREAMBLE_SIZE];
atom_write_header(mdat_bytes, &mdat_atom);
PHPWRITE(mdat_bytes, ATOM_PREAMBLE_SIZE);
// Configure options
int bandwidth_limit = INI_BOOL("psstream.bandwidth_limit");
unsigned long bandwidth_chunk_size = INI_INT("psstream.bandwidth_chunk_size");
double bandwidth_chunk_interval = INI_FLT("psstream.bandwidth_chunk_interval");
if (bandwidth_chunk_size < 1024) bandwidth_chunk_size = 1024;
else if (bandwidth_chunk_size > 1048576) bandwidth_chunk_size = 1048576;
if (bandwidth_chunk_interval < 0.1) bandwidth_chunk_interval = 0.1;
else if (bandwidth_chunk_interval > 2) bandwidth_chunk_interval = 2;
unsigned long chunk_size = bandwidth_limit ? bandwidth_chunk_size : 204800;
// Set some ini settings
zend_alter_ini_entry("session.cache_limiter", sizeof("session.cache_limiter"),
"nocache", sizeof("nocache"), PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
// Stream data
double t_delta = 0.0;
unsigned long result;
unsigned char *buffer = (unsigned char*)malloc(chunk_size);
if (!buffer) {
RETURN_FALSE;
}
fseek(infile, start, SEEK_SET);
while(start < end) {
if (end - start < chunk_size) {
chunk_size = end - start;
}
t_start = precise_time();
result = fread(buffer, 1, chunk_size, infile);
if (result != chunk_size) {
free(buffer);
RETURN_FALSE;
}
PHPWRITE(buffer, chunk_size);
start += chunk_size;
if(bandwidth_limit) {
t_end = precise_time();
t_delta = t_end - t_start;
if(t_delta < bandwidth_chunk_interval) {
usleep(bandwidth_chunk_interval * 1000000 - t_delta * 1000000);
}
}
}
// Close file
fclose(infile);
free(buffer);
RETURN_TRUE;
}