mod_flvx. Передача потокового Flash видео
Недавно на своем блоге Paul Querna выложил собственный модуль к Apache 2 для потоковой передачи Flash видео.
Конфигурация модуля производится очень просто:
AddHandler flv-stream .flv
Сам модуль очень компактный - размер исходного текста всего около 3 Кб. Теперь давайте посмотрим, что и как он делает.
Основа модуля - обработчик хука Handler. Это единственный хук, который обрабатывается в этом модуле. Вот его обработчик:
static int flvx_handler(request_rec *r)
{
if ((!r->handler) ||
(strcmp(r->handler, FLVX_HANDLER))) {
return DECLINED;
}
r->allowed |= (AP_METHOD_BIT << M_GET);
if (r->method_number != M_GET) {
return HTTP_METHOD_NOT_ALLOWED;
}
return drive_flvx(r);
}
{
if ((!r->handler) ||
(strcmp(r->handler, FLVX_HANDLER))) {
return DECLINED;
}
r->allowed |= (AP_METHOD_BIT << M_GET);
if (r->method_number != M_GET) {
return HTTP_METHOD_NOT_ALLOWED;
}
return drive_flvx(r);
}
В нем происходит две проверки: действительно ли данный запрос должен обрабатываться этим обработчиком (для установления связи "запрос-обработчик" и нужна директива AddHandler) и разрешен ли метод GET.
Затем вызывается главная функция модуля drive_flvx(r), в качестве аргумента которой передается структура request_rec, которая описывает обрабатываемый запрос. (Подробнее о request_rec).
Теперь рассмотрим функцию drive_flvx. Она довольно большая, поэтому описание оформил в виде комментариев:
static int drive_flvx(request_rec *r)
{
apr_finfo_t fi;
apr_bucket_brigade *bb;
apr_off_t offset = 0;
apr_off_t length = 0;
apr_file_t *fp = NULL;
apr_status_t rv = APR_SUCCESS; //APR_SUCCESS = 0 - ошибок нет.
// Получаем информацию о запрошенном flv файле.
// Функция apr_stat возвращает структуру apr_finfo_t
// из которой нам нужен размер файла - поле size.
// С полным описанием этой структуры можно ознакомиться тут.
rv = apr_stat(&fi, r->filename, APR_FINFO_SIZE, r->pool);
if (rv) {
// В случае ошибки передаем обработку ядру
// сервера - вдруг у него что-нибудь да получится
return DECLINED;
}
// открываем файл
rv = apr_file_open(&fp, r->filename, APR_READ,
APR_OS_DEFAULT, r->pool);
if (rv) {
// Доступ запрещен -> пишем в лог и возвращаем HTTP код 403 FORBIDDEN.
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
"file permissions deny server access: %s", r->filename);
return HTTP_FORBIDDEN;
}
// Анализируем запрос на наличие смещения (ну а какая
// потоковая передача да без возможности смещения?!!).
// Функцию get_start(r) рассмотрим ниже.
offset = get_start(r);
// Определяем длину блока, необходимого для
// передачи: "длина файла - смещение".
if (offset != 0 && offset < fi.size) {
length = fi.size - offset;
}
else {
length = fi.size;
}
// Создаем бригаду для хранения передаваемых данных
bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
// Если передача файла требуется не с начала
// (т.е. задано ненулевое смещение), то сперва
// записываем в бригаду заголовок flv
// файла - FLVX_HEADER, иначе файл не будет
// корректно обработан проигрывателем Flash видео.
if (offset != 0) {
rv = apr_brigade_write(bb, NULL, NULL, FLVX_HEADER, FLVX_HEADER_LEN);
if (rv) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
"unable to write flv header in brigade");
return HTTP_INTERNAL_SERVER_ERROR;
}
}
// Затем записываем в бригаду сам файл (или нужный его кусок).
apr_brigade_insert_file(bb, fp, offset, length, r->pool);
// Устанавливаем нужный Content-type
ap_set_content_type(r, "video/x-flv");
// И все. Отправляем подготовленную бригаду вниз
// по цепочке выходных фильтров. Готово.
return ap_pass_brigade(r->output_filters, bb);
}
{
apr_finfo_t fi;
apr_bucket_brigade *bb;
apr_off_t offset = 0;
apr_off_t length = 0;
apr_file_t *fp = NULL;
apr_status_t rv = APR_SUCCESS; //APR_SUCCESS = 0 - ошибок нет.
// Получаем информацию о запрошенном flv файле.
// Функция apr_stat возвращает структуру apr_finfo_t
// из которой нам нужен размер файла - поле size.
// С полным описанием этой структуры можно ознакомиться тут.
rv = apr_stat(&fi, r->filename, APR_FINFO_SIZE, r->pool);
if (rv) {
// В случае ошибки передаем обработку ядру
// сервера - вдруг у него что-нибудь да получится

return DECLINED;
}
// открываем файл
rv = apr_file_open(&fp, r->filename, APR_READ,
APR_OS_DEFAULT, r->pool);
if (rv) {
// Доступ запрещен -> пишем в лог и возвращаем HTTP код 403 FORBIDDEN.
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
"file permissions deny server access: %s", r->filename);
return HTTP_FORBIDDEN;
}
// Анализируем запрос на наличие смещения (ну а какая
// потоковая передача да без возможности смещения?!!).
// Функцию get_start(r) рассмотрим ниже.
offset = get_start(r);
// Определяем длину блока, необходимого для
// передачи: "длина файла - смещение".
if (offset != 0 && offset < fi.size) {
length = fi.size - offset;
}
else {
length = fi.size;
}
// Создаем бригаду для хранения передаваемых данных
bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
// Если передача файла требуется не с начала
// (т.е. задано ненулевое смещение), то сперва
// записываем в бригаду заголовок flv
// файла - FLVX_HEADER, иначе файл не будет
// корректно обработан проигрывателем Flash видео.
if (offset != 0) {
rv = apr_brigade_write(bb, NULL, NULL, FLVX_HEADER, FLVX_HEADER_LEN);
if (rv) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
"unable to write flv header in brigade");
return HTTP_INTERNAL_SERVER_ERROR;
}
}
// Затем записываем в бригаду сам файл (или нужный его кусок).
apr_brigade_insert_file(bb, fp, offset, length, r->pool);
// Устанавливаем нужный Content-type
ap_set_content_type(r, "video/x-flv");
// И все. Отправляем подготовленную бригаду вниз
// по цепочке выходных фильтров. Готово.
return ap_pass_brigade(r->output_filters, bb);
}
С основной функцией разобрались. Осталось только посмотреть, как же задается смещение в потоке. Место, с которого надо передавать файл, определяет функция get_start(r):
static apr_off_t get_start(request_rec *r)
{
apr_off_t start = 0;
char *p = NULL;
if (!r->args) {
return start;
}
p = strstr(r->args, "start=");
if (p) {
p = p + 6;
apr_strtoff(&start, p, NULL, 10);
}
return start;
}
{
apr_off_t start = 0;
char *p = NULL;
if (!r->args) {
return start;
}
p = strstr(r->args, "start=");
if (p) {
p = p + 6;
apr_strtoff(&start, p, NULL, 10);
}
return start;
}
Единственное, что она делает, это возвращает значение аргумента start, значение которого определяет смещение в файле в байтах. Например, следующий запрос:
http://somehost/flash-video/clip.flv?start=255
запрашивает файл clip.flv с 266 байта. Если start не задан, то файл передается полностью.
Ну, вот и все. Пользуйтесь на здоровье. Исходный код модуля полностью можно взять отсюда.
И напоследок несколько ссылок по теме потокового Flash видео:
http://www.asvguy.com/2005/11/streaming_flash.html
http://www.adobe.com/devnet/flashmediaserver/articles/fms_dual_buffering.html
http://www.rich-media-project.com/
http://www.flashcomguru.com/index.cfm/2005/11/2/Streaming-flv-video-via-PHP-take-two
http://www.adobe.com/devnet/flashmediaserver/articles/fms_dual_buffering.html
http://www.rich-media-project.com/
http://www.flashcomguru.com/index.cfm/2005/11/2/Streaming-flv-video-via-PHP-take-two
Комментарий от Andrew — Июль 27, 2006 @ 4:55 pm
#include “httpd.h”
#include “http_core.h”
#include “http_config.h”
#include “http_protocol.h”
#include “http_log.h”
#include “apr_buckets.h”
и компилим как shared объект.
Заголовочные файлы можно найти в исходниках/бинарниках Apache
Комментарий от Администратор — Июль 27, 2006 @ 5:26 pm
Комментарий от Andrew — Июль 27, 2006 @ 5:58 pm
Комментарий от Администратор — Июль 27, 2006 @ 10:59 pm
The mod can compile under apache2.0 but cannot resolve several symbols. So, I compile it in apache 2.2
BUT after no error, seem nothing happen even I try to put ?start=25555 but the whole .flv was sent to the clients.
Please advise me to what should be doing.
Thank You,
supat
ps. I compile with apxs -i -a -c options.
Комментарий от supat — Август 13, 2006 @ 5:28 am
— mod_flvx.orig 2006-07-11 09:25:59.000000000 +0400
+++ mod_flvx.c 2007-03-08 04:43:46.000000000 +0300
@@ -95,6 +95,7 @@
apr_brigade_insert_file(bb, fp, offset, length, r->pool);
ap_set_content_type(r, “video/x-flv”);
+ ap_set_content_length(r, length);
return ap_pass_brigade(r->output_filters, bb);
}
Комментарий от juunitaki — Март 8, 2007 @ 4:57 am
Где скачать откомпилированный файл как это можно сделать самому?
Комментарий от Дмирий — Апрель 3, 2007 @ 10:07 pm
Если выложите линку, буду очень благодарен…..
ICQ: 219505427
Комментарий от Андрей — Декабрь 12, 2007 @ 10:30 pm
ICQ: 349506453
Комментарий от Миха — Февраль 12, 2008 @ 10:54 pm
Комментарий от sergey — Февраль 20, 2008 @ 1:50 am
не делается проверка на offset > fi.size
- if (offset != 0 && offset fi.size) {
+ offset = fi.size;
+ }
+ length = fi.size - offset;
к длине контента не прибавляется длина заголовка FLV
- ap_set_content_length(r, length);
+ ap_set_content_length(r, length + FLVX_HEADER_LEN);
а это точно не помешает
+ ap_update_mtime(r, r->finfo.mtime);
+ ap_set_last_modified(r);
Комментарий от techman — Март 14, 2008 @ 12:22 pm
первую часть кода читать так:
- if (offset != 0 && offset fi.size) {
+ offset = fi.size;
+ }
Комментарий от techman — Март 14, 2008 @ 12:25 pm
ломается при посте
суть кода заключается в более тщательной проверке (и выравниванию) offset за выход из диапазона 0 > offset
Комментарий от techman — Март 14, 2008 @ 12:29 pm
Комментарий от Матвей — Апрель 2, 2009 @ 6:19 pm