h264 streaming support

IrPr

Well-Known Member
#3
Any server or script can do that now?
Yeah, lighttpd

It works similar to flv streaming module and offset must be passed by the same start variable but its not the same offset as flv seek offset as
i think its timeoffset in seconds while flv seeking uses byteoffset

It seems be easy to be implemented in next minor release and hope it to be
 
Last edited:

IrPr

Well-Known Member
#7
Thanks, we will take a closer look when we get a chance.

Move this thread to 4.0 release forum.
Thanks for your concern Mr Wang!
Just want to mention that nginx supports MP4 H264 streaming also so cant wait for it to be released in LSWS

I Appreciate your awesome support man
keep it on
 
Last edited:

mistwang

LiteSpeed Staff
#10
If someone can contribute a BSD licensed library which can seek H264 encoded movie file based on time (in seconds), it will accelerate the addition of this feature.

The code should be based on the spec of the video file, should not be similar to the code used for lighttpd, nginx or Apache from code-shop.com. And we will pay for this project. :)
 

IrPr

Well-Known Member
#11
If someone can contribute a BSD licensed library which can seek H264 encoded movie file based on time (in seconds), it will accelerate the addition of this feature.

The code should be based on the spec of the video file, should not be similar to the code used for lighttpd, nginx or Apache from code-shop.com. And we will pay for this project. :)
Check this out:
Pseudo-streaming MP4/H264 video from PHP

psstream.c( about line 215)

PHP:
// 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;
}
 

mistwang

LiteSpeed Staff
#14
I think the code use GPL license, even lighttpd or nginx cannot include the code in the official distribution.
We cannot include any GPL licensed code in our product.
 
Top