[PATCH] Enable faststart for mp4 module
hungnv at opensource.com.vn
hungnv at opensource.com.vn
Thu Feb 5 07:21:48 UTC 2015
# HG changeset patch
# User Hung Nguyen <hungnv at opensource.com.vn
# Date 1422591060 -25200
# Fri Jan 30 11:11:00 2015 +0700
# Node ID 031db7af488c045fc4f05385141fdf00f001615f
# Parent 78271500b8ded9d9cc3ccc5f36d4c41f4e32a4a7
Enable mp4 module to move moov atom from last file to the beginning of file, for faststart and stream immediately
diff -r 78271500b8de -r 031db7af488c src/http/modules/ngx_http_mp4_faststart.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/http/modules/ngx_http_mp4_faststart.h Fri Jan 30 11:11:00 2015 +0700
@@ -0,0 +1,423 @@
+/*
+ * File: ngx_http_mp4_faststart.h
+ * Author: hungnguyen
+ *
+ * Created on January 23, 2015, 11:33 AM
+ */
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <ngx_files.h>
+#ifdef WIN32
+#include <io.h>
+#include <windows.h>
+#define DIR_SEPARATOR '\\'
+#define strdup _strdup
+#define open _open
+#define close _close
+#define write _write
+#define lseek _lseeki64
+#define stat _stat64
+#else
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#endif
+
+
+
+#ifdef __MINGW32__
+#define fseeko(x,y,z) fseeko64(x,y,z)
+#define ftello(x) ftello64(x)
+#endif
+
+#define BE_16(x) ((((uint8_t*)(x))[0] << 8) | ((uint8_t*)(x))[1])
+
+#define BE_32(x) ((((uint8_t*)(x))[0] << 24) | \
+ (((uint8_t*)(x))[1] << 16) | \
+ (((uint8_t*)(x))[2] << 8) | \
+ ((uint8_t*)(x))[3])
+
+#define BE_64(x) (((uint64_t)(((uint8_t*)(x))[0]) << 56) | \
+ ((uint64_t)(((uint8_t*)(x))[1]) << 48) | \
+ ((uint64_t)(((uint8_t*)(x))[2]) << 40) | \
+ ((uint64_t)(((uint8_t*)(x))[3]) << 32) | \
+ ((uint64_t)(((uint8_t*)(x))[4]) << 24) | \
+ ((uint64_t)(((uint8_t*)(x))[5]) << 16) | \
+ ((uint64_t)(((uint8_t*)(x))[6]) << 8) | \
+ ((uint64_t)((uint8_t*)(x))[7]))
+
+#define BE_FOURCC( ch0, ch1, ch2, ch3 ) \
+ ( (uint32_t)(unsigned char)(ch3) | \
+ ( (uint32_t)(unsigned char)(ch2) << 8 ) | \
+ ( (uint32_t)(unsigned char)(ch1) << 16 ) | \
+ ( (uint32_t)(unsigned char)(ch0) << 24 ) )
+
+#define QT_ATOM BE_FOURCC
+
+/* top level atoms */
+#define FREE_ATOM QT_ATOM('f', 'r', 'e', 'e')
+
+#define JUNK_ATOM QT_ATOM('j', 'u', 'n', 'k')
+
+#define MDAT_ATOM QT_ATOM('m', 'd', 'a', 't')
+
+#define MOOV_ATOM QT_ATOM('m', 'o', 'o', 'v')
+
+#define PNOT_ATOM QT_ATOM('p', 'n', 'o', 't')
+
+#define SKIP_ATOM QT_ATOM('s', 'k', 'i', 'p')
+
+#define WIDE_ATOM QT_ATOM('w', 'i', 'd', 'e')
+
+#define PICT_ATOM QT_ATOM('P', 'I', 'C', 'T')
+
+#define FTYP_ATOM QT_ATOM('f', 't', 'y', 'p')
+
+#define UUID_ATOM QT_ATOM('u', 'u', 'i', 'd')
+
+#define CMOV_ATOM QT_ATOM('c', 'm', 'o', 'v')
+
+#define STCO_ATOM QT_ATOM('s', 't', 'c', 'o')
+
+#define CO64_ATOM QT_ATOM('c', 'o', '6', '4')
+
+#define ATOM_PREAMBLE_SIZE 8
+
+#define COPY_BUFFER_SIZE 1024
+
+
+/* we take 2 arguments from ngx_http_mp4_module
+ * path: to open it in write mode once source file need to be modified.
+ * file descriptor: nginx already opened the file, we dont have to open again
+ * ngx_fd_t is actually an integer (see ngx_files.h)
+ */
+
+
+int ngx_http_enable_fast_start(ngx_str_t *path, ngx_fd_t
+ngx_open_file_cached_fd, ngx_http_request_t *r) {
+ unsigned char atom_bytes[ATOM_PREAMBLE_SIZE];
+ uint32_t atom_type = 0;
+ uint64_t atom_size = 0;
+ uint64_t atom_offset = 0;
+ uint64_t last_offset;
+ unsigned char *moov_atom = NULL;
+ unsigned char *ftyp_atom = NULL;
+ uint64_t moov_atom_size;
+ uint64_t ftyp_atom_size = 0;
+ uint64_t i, j;
+ uint32_t offset_count;
+ uint64_t current_offset;
+ uint64_t start_offset = 0;
+ int outfile_fd = -1;
+ unsigned char *temp_buf = NULL;
+ ngx_log_t *log = r->connection->log;
+
+
+ /* traverse through the atoms in the file to make sure that 'moov' is
+ * at the end */
+ while (1) {
+
+ if (read(ngx_open_file_cached_fd, atom_bytes, ATOM_PREAMBLE_SIZE) == 0)
+ break;
+
+ atom_size = (uint32_t) BE_32(&atom_bytes[0]);
+ atom_type = BE_32(&atom_bytes[4]);
+ /* keep ftyp atom */
+
+ if (atom_type == FTYP_ATOM) {
+ ftyp_atom_size = atom_size;
+ free(ftyp_atom);
+ ftyp_atom = ngx_palloc(r->connection->pool, ftyp_atom_size);
+ // ftyp_atom = malloc(ftyp_atom_size);
+
+ if (!ftyp_atom) {
+ ngx_log_error(NGX_LOG_ERR, log, ngx_errno, "could not allocate "
+ "%"PRIu64" byte for ftyp atom\n", atom_size);
+ goto error_out;
+ }
+
+ lseek(ngx_open_file_cached_fd, -ATOM_PREAMBLE_SIZE, SEEK_CUR);
+ ngx_log_debug(NGX_LOG_DEBUG, log, 0, "atom_size: "
+ "%"PRIu64" \n", atom_size);
+
+ if (read(ngx_open_file_cached_fd, ftyp_atom, atom_size) < 0) {
+ perror((const char *) path->data);
+ goto error_out;
+ }
+
+ start_offset = atom_size;
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
+ "start_offset to verify: %"PRIu64" \n", start_offset);
+
+ } else {
+ /* 64-bit special case */
+ if (atom_size == 1) {
+ if (read(ngx_open_file_cached_fd, atom_bytes,
+ ATOM_PREAMBLE_SIZE) == 0) {
+ break;
+ }
+
+ atom_size = BE_64(&atom_bytes[0]);
+ lseek(ngx_open_file_cached_fd, atom_size -
+ ATOM_PREAMBLE_SIZE * 2, SEEK_CUR);
+
+ } else {
+ lseek(ngx_open_file_cached_fd, atom_size - ATOM_PREAMBLE_SIZE,
+ SEEK_CUR);
+ }
+
+ }
+
+ ngx_log_debug(NGX_LOG_DEBUG_HTTP, log, 0, "%c%c%c%c %10"PRIu64""
+ " %"PRIu64"\n",
+ (atom_type >> 24) & 255,
+ (atom_type >> 16) & 255,
+ (atom_type >> 8) & 255,
+ (atom_type >> 0) & 255,
+ atom_offset,
+ atom_size);
+
+ if ((atom_type != FREE_ATOM) &&
+ (atom_type != JUNK_ATOM) &&
+ (atom_type != MDAT_ATOM) &&
+ (atom_type != MOOV_ATOM) &&
+ (atom_type != PNOT_ATOM) &&
+ (atom_type != SKIP_ATOM) &&
+ (atom_type != WIDE_ATOM) &&
+ (atom_type != PICT_ATOM) &&
+ (atom_type != UUID_ATOM) &&
+ (atom_type != FTYP_ATOM)) {
+ ngx_log_error(NGX_LOG_ERR, log, ngx_errno, "encountered non-QT "
+ "top-level atom (is this a Quicktime file?)\n");
+ break;
+ }
+
+ atom_offset += atom_size;
+
+ /* The atom header is 8 (or 16 bytes), if the atom size (which
+ * includes these 8 or 16 bytes) is less than that, we won't be
+ * able to continue scanning sensibly after this atom, so break. */
+ if (atom_size < 8)
+ break;
+ }
+
+ if (atom_type != MOOV_ATOM) {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "last atom in file: "
+ "%s was not a moov atom\n", path->data);
+ if (ftyp_atom) ngx_pfree(r->connection->pool, ftyp_atom);
+ // dont close file, not our job
+ return NGX_OK;
+ }
+
+ /* moov atom was, in fact, the last atom in the chunk; load the whole
+ * moov atom */
+ last_offset = lseek(ngx_open_file_cached_fd, -atom_size, SEEK_END);
+ moov_atom_size = atom_size;
+ moov_atom = ngx_palloc(r->connection->pool, moov_atom_size);
+
+ if (!moov_atom) {
+ ngx_log_error(NGX_LOG_ERR, log, ngx_errno, "could not allocate "
+ "%"PRIu64" byte for moov atom\n", atom_size);
+ goto error_out;
+ }
+
+ if (read(ngx_open_file_cached_fd, moov_atom, atom_size) < 0) {
+ perror((const char *) path->data);
+ goto error_out;
+ }
+
+ /* this utility does not support compressed atoms yet, so disqualify
+ * files with compressed QT atoms */
+ if (BE_32(&moov_atom[12]) == CMOV_ATOM) {
+ ngx_log_error(NGX_LOG_ERR, log, ngx_errno, "this module does "
+ "not support compressed moov atoms yet\n");
+ free(ftyp_atom);
+ if (moov_atom) free(moov_atom);
+ /* should not return error, if we cannot fix it,
+ * let player download the whole file then play it*/
+ return NGX_OK;
+ }
+
+ /* read next move_atom_size bytes
+ * since we read/write file in same time, we must read before write into
+ * the buffer
+ */
+ temp_buf = ngx_palloc(r->connection->pool, moov_atom_size);
+
+ if (!temp_buf) {
+ ngx_log_error(NGX_LOG_ERR, log, ngx_errno, "Cannot allocate %"PRIu64" "
+ "byte for temp buf \n", moov_atom_size);
+ goto error_out;
+ }
+
+ /* seek to after ftyp_atom */
+ lseek(ngx_open_file_cached_fd, ftyp_atom_size, SEEK_SET);
+
+ if (read(ngx_open_file_cached_fd, temp_buf, moov_atom_size) < 0) {
+ perror((const char *) path->data);
+ goto error_out;
+ }
+
+ start_offset += moov_atom_size;
+
+ /* end read temp buffer bytes */
+
+ /* crawl through the moov chunk in search of stco or co64 atoms */
+ for (i = 4; i < moov_atom_size - 4; i++) {
+ atom_type = BE_32(&moov_atom[i]);
+
+ if (atom_type == STCO_ATOM) {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "%s patching stco "
+ "atom...\n", path->data);
+ atom_size = BE_32(&moov_atom[i - 4]);
+
+ if (i + atom_size - 4 > moov_atom_size) {
+ ngx_log_error(NGX_LOG_ERR, log, ngx_errno, " bad atom size\n");
+ goto error_out;
+ }
+
+ offset_count = BE_32(&moov_atom[i + 8]);
+
+ for (j = 0; j < offset_count; j++) {
+ current_offset = BE_32(&moov_atom[i + 12 + j * 4]);
+ current_offset += moov_atom_size;
+ moov_atom[i + 12 + j * 4 + 0] = (current_offset >> 24) & 0xFF;
+ moov_atom[i + 12 + j * 4 + 1] = (current_offset >> 16) & 0xFF;
+ moov_atom[i + 12 + j * 4 + 2] = (current_offset >> 8) & 0xFF;
+ moov_atom[i + 12 + j * 4 + 3] = (current_offset >> 0) & 0xFF;
+ }
+
+ i += atom_size - 4;
+
+ } else if (atom_type == CO64_ATOM) {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "%s patching co64 "
+ "atom...\n", path->data);
+ atom_size = BE_32(&moov_atom[i - 4]);
+
+ if (i + atom_size - 4 > moov_atom_size) {
+ ngx_log_error(NGX_LOG_ERR, log, ngx_errno, " bad atom size\n");
+ goto error_out;
+ }
+
+ offset_count = BE_32(&moov_atom[i + 8]);
+
+ for (j = 0; j < offset_count; j++) {
+ current_offset = BE_64(&moov_atom[i + 12 + j * 8]);
+ current_offset += moov_atom_size;
+ moov_atom[i + 12 + j * 8 + 0] = (current_offset >> 56) & 0xFF;
+ moov_atom[i + 12 + j * 8 + 1] = (current_offset >> 48) & 0xFF;
+ moov_atom[i + 12 + j * 8 + 2] = (current_offset >> 40) & 0xFF;
+ moov_atom[i + 12 + j * 8 + 3] = (current_offset >> 32) & 0xFF;
+ moov_atom[i + 12 + j * 8 + 4] = (current_offset >> 24) & 0xFF;
+ moov_atom[i + 12 + j * 8 + 5] = (current_offset >> 16) & 0xFF;
+ moov_atom[i + 12 + j * 8 + 6] = (current_offset >> 8) & 0xFF;
+ moov_atom[i + 12 + j * 8 + 7] = (current_offset >> 0) & 0xFF;
+ }
+
+ i += atom_size - 4;
+ }
+ }
+
+
+ if (start_offset > 0) { /* seek after ftyp atom */
+ lseek(ngx_open_file_cached_fd, start_offset, SEEK_SET);
+ last_offset -= start_offset;
+ }
+
+ outfile_fd = open((const char *) path->data, O_WRONLY);
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "outfile fd: %d\n", outfile_fd);
+
+ if (outfile_fd < 0) {
+ perror((const char *) path->data);
+ goto error_out;
+ }
+
+ /* dump the same ftyp atom */
+ if (ftyp_atom_size > 0) {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "%s: writing ftyp atom...\n"
+ , path->data);
+
+ if (write(outfile_fd, ftyp_atom, ftyp_atom_size) < 0) {
+ perror((const char *) path->data);
+ goto error_out;
+ }
+
+ }
+
+ i = 0;
+ /*
+ we must use 2 buffer to read/write
+ */
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, " moov_atom_size: %"PRIu64" \n"
+ , moov_atom_size);
+
+ while (last_offset) {
+ // printf("last offset: %"PRIu64" \n", last_offset);
+ if (i == 0) {
+ ngx_log_debug(NGX_LOG_DEBUG, log, 0, " writing moov atom...\n");
+ i = 1;
+ }
+
+ if (write(outfile_fd, moov_atom, moov_atom_size) < 0) {
+ perror((const char *) path->data);
+ goto error_out;
+ }
+
+ if (last_offset < moov_atom_size)
+ moov_atom_size = last_offset;
+
+ if (read(ngx_open_file_cached_fd, moov_atom, moov_atom_size) < 0) {
+ perror((const char *) path->data);
+ goto error_out;
+ }
+
+ last_offset -= moov_atom_size;
+
+ if (write(outfile_fd, temp_buf, moov_atom_size) < 0) {
+ perror((const char *) path->data);
+ goto error_out;
+ }
+
+ if (last_offset < moov_atom_size)
+ moov_atom_size = last_offset;
+
+ if (read(ngx_open_file_cached_fd, temp_buf, moov_atom_size) < 0) {
+ perror((const char *) path->data);
+ goto error_out;
+ }
+
+ last_offset -= moov_atom_size;
+ }
+
+ /* seek to beginning of source file*/
+ lseek(ngx_open_file_cached_fd, 0, SEEK_SET);
+
+ close(outfile_fd);
+ ngx_pfree(r->connection->pool, moov_atom);
+ ngx_pfree(r->connection->pool, ftyp_atom);
+ ngx_pfree(r->connection->pool, temp_buf);
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, " finish fixing file: %s\n"
+ , path->data);
+ return NGX_OK;
+
+error_out:
+ if (outfile_fd > 0)
+ close(outfile_fd);
+
+ if (moov_atom)
+ ngx_pfree(r->connection->pool, moov_atom);
+
+ if (ftyp_atom)
+ ngx_pfree(r->connection->pool, ftyp_atom);
+
+ if (temp_buf)
+ ngx_pfree(r->connection->pool, temp_buf);
+
+ return NGX_ERROR;
+}
diff -r 78271500b8de -r 031db7af488c src/http/modules/ngx_http_mp4_module.c
--- a/src/http/modules/ngx_http_mp4_module.c Tue Jan 27 15:38:15 2015 +0300
+++ b/src/http/modules/ngx_http_mp4_module.c Fri Jan 30 11:11:00 2015 +0700
@@ -7,6 +7,7 @@
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
+#include "ngx_http_mp4_faststart.h"
#define NGX_HTTP_MP4_TRAK_ATOM 0
@@ -43,6 +44,7 @@
typedef struct {
size_t buffer_size;
size_t max_buffer_size;
+ ngx_flag_t mp4_enhance;
} ngx_http_mp4_conf_t;
@@ -332,7 +334,14 @@
offsetof(ngx_http_mp4_conf_t, max_buffer_size),
NULL },
- ngx_null_command
+ { ngx_string("fix_mp4"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_mp4_conf_t, mp4_enhance),
+ NULL },
+
+ ngx_null_command
};
@@ -429,6 +438,7 @@
ngx_http_mp4_file_t *mp4;
ngx_open_file_info_t of;
ngx_http_core_loc_conf_t *clcf;
+ ngx_http_mp4_conf_t *mlcf;
if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
return NGX_HTTP_NOT_ALLOWED;
@@ -522,6 +532,18 @@
return NGX_DECLINED;
}
+ /* move atom to beginning of file if it's in the last*/
+ mlcf = ngx_http_get_module_loc_conf(r, ngx_http_mp4_module);
+ if (mlcf->mp4_enhance == 1) {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
+ "examine mp4 filename: \"%V\"", &path);
+
+ if (ngx_http_enable_fast_start(&path, of.fd, r) != NGX_OK) {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ }
+
r->root_tested = !r->error_page;
r->allow_ranges = 1;
@@ -3495,6 +3517,7 @@
ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size, 512 * 1024);
ngx_conf_merge_size_value(conf->max_buffer_size, prev->max_buffer_size,
10 * 1024 * 1024);
+ ngx_conf_merge_off_value(conf->mp4_enhance, prev->mp4_enhance, 0);
return NGX_CONF_OK;
}
More information about the nginx-devel
mailing list