Mercurial > pt1.oyama
view src/http.c @ 149:a9f60d56d673
Fix DLNA problem
author | Naoya OYAMA <naoya.oyama@gmail.com> |
---|---|
date | Sat, 25 Aug 2012 11:10:24 +0900 |
parents | 066f33b2213a |
children | 30e91361506a |
line wrap: on
line source
/* -*- tab-width: 4; indent-tabs-mode: nil -*- */ /* vim: set ts=4 sts=4 sw=4 expandtab number : */ /* * http.c : GeeXboX uShare Web Server handler. * Originally developped for the GeeXboX project. * Parts of the code are originated from GMediaServer from Oskar Liljeblad. * Copyright (C) 2005-2007 Benjamin Zores <ben@geexbox.org> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <upnp.h> #include <upnptools.h> #include "services.h" #include "cds.h" #include "cms.h" #include "msr.h" #include "metadata.h" #include "http.h" #include "minmax.h" #include "trace.h" #include "presentation.h" #include "osdep.h" #include "mime.h" #include "recpt1.h" #include "tssplitter_lite.h" #define PROTOCOL_TYPE_PRE_SZ 11 /* for the str length of "http-get:*:" */ #define PROTOCOL_TYPE_SUFF_SZ 2 /* for the str length of ":*" */ extern thread_data tdata; struct web_file_t { char *fullpath; off_t pos; enum { FILE_LOCAL, FILE_MEMORY, FILE_STREAM } type; union { struct { int fd; struct upnp_entry_t *entry; } local; struct { char *contents; off_t len; } memory; struct { int id; STREAM_QUEUE_T *p_queue; ARIB_STD_B25_BUFFER *qbuf; off_t len; } stream; } detail; }; static off_t get_streaming_length (void); static inline void set_info_file (struct File_Info *info, const size_t length, const char *content_type) { info->file_length = length; info->last_modified = 0; info->is_directory = 0; info->is_readable = 1; info->content_type = ixmlCloneDOMString (content_type); } //#define STREAM_LOCATION "/web/stream.ts" static int http_get_info (const char *filename, struct File_Info *info) { extern struct ushare_t *ut; struct upnp_entry_t *entry = NULL; int i, upnp_id = 0; char *content_type = NULL; char *protocol = NULL; if (!filename || !info) return -1; log_verbose ("http_get_info, filename : %s\n", filename); upnp_id = atoi (strrchr (filename, '/') + 1); entry = upnp_get_entry (ut, upnp_id); if (ut->nr_channel == 0) { if (!strcmp (filename, STREAM_LOCATION)) { log_verbose ("http_get_info, stream location found.\n"); info->is_readable = 1; info->file_length = get_streaming_length(); info->last_modified = time(NULL); info->is_directory = 0; info->content_type = ixmlCloneDOMString ("video/mpeg"); return 0; } } else { for (i=0; i < ut->nr_channel; i++) { if (!strcmp(filename, ut->location_name[i])) { log_verbose ("http_get_info, stream location found [%s].\n", filename); info->is_readable = 1; info->file_length = get_streaming_length(); info->last_modified = time(NULL); info->is_directory = 0; info->content_type = ixmlCloneDOMString ("video/mpeg"); return 0; } } } if (!strcmp (filename, CDS_LOCATION)) { set_info_file (info, CDS_DESCRIPTION_LEN, SERVICE_CONTENT_TYPE); return 0; } if (!strcmp (filename, CMS_LOCATION)) { set_info_file (info, CMS_DESCRIPTION_LEN, SERVICE_CONTENT_TYPE); return 0; } if (!strcmp (filename, MSR_LOCATION)) { set_info_file (info, MSR_DESCRIPTION_LEN, SERVICE_CONTENT_TYPE); return 0; } if (ut->use_presentation && !strcmp (filename, USHARE_PRESENTATION_PAGE)) { if (build_presentation_page (ut) < 0) return -1; set_info_file (info, ut->presentation->len, PRESENTATION_PAGE_CONTENT_TYPE); return 0; } if (ut->use_presentation && !strncmp (filename, USHARE_CGI, strlen (USHARE_CGI))) { if (process_cgi (ut, (char *) (filename + strlen (USHARE_CGI) + 1)) < 0) return -1; set_info_file (info, ut->presentation->len, PRESENTATION_PAGE_CONTENT_TYPE); return 0; } if (!entry) return -1; log_verbose ("http_get_info, entry found.\n"); if (!entry->fullpath) return -1; #if 0 if (stat (entry->fullpath, &st) < 0) return -1; if (access (entry->fullpath, R_OK) < 0) { if (errno != EACCES) { log_verbose ("http_get_info, access() error.\n"); return -1; } info->is_readable = 0; } else info->is_readable = 1; /* file exist and can be read */ info->file_length = st.st_size; info->last_modified = st.st_mtime; info->is_directory = S_ISDIR (st.st_mode); #endif info->is_readable = 1; info->file_length = get_streaming_length(); info->last_modified = time(NULL); info->is_directory = 0; protocol = #ifdef HAVE_DLNA entry->dlna_profile ? dlna_write_protocol_info (DLNA_PROTOCOL_INFO_TYPE_HTTP, DLNA_ORG_PLAY_SPEED_NORMAL, DLNA_ORG_CONVERSION_NONE, DLNA_ORG_OPERATION_RANGE, ut->dlna_flags, entry->dlna_profile) : #endif /* HAVE_DLNA */ mime_get_protocol (entry->mime_type); content_type = strndup ((protocol + PROTOCOL_TYPE_PRE_SZ), strlen (protocol + PROTOCOL_TYPE_PRE_SZ) - PROTOCOL_TYPE_SUFF_SZ); free (protocol); if (content_type) { info->content_type = ixmlCloneDOMString (content_type); free (content_type); } else info->content_type = ixmlCloneDOMString (""); return 0; } static UpnpWebFileHandle get_file_memory (const char *fullpath, const char *description, const size_t length) { struct web_file_t *file; // log_verbose ("get_file_memory() description[%s]\n", // description); file = malloc (sizeof (struct web_file_t)); file->fullpath = strdup (fullpath); file->pos = 0; file->type = FILE_MEMORY; file->detail.memory.contents = strdup (description); if ( file->detail.memory.contents == NULL ) { log_verbose ("get_file_memory() null\n"); } file->detail.memory.len = length; // log_verbose ("get_file_memory() path[%s] contents[%s]\n", // file->fullpath, file->detail.memory.contents); return ((UpnpWebFileHandle) file); } /* * 2. get_file_stream() $B$G$O!"(Bopen()$B;~$NA`:n(B($B%U%!%$%k>pJs$NIU2C(B)$B$NB>!"%U%!%$%k>pJs$H(B queue $B$NI3IU$1$r<B;\(B * $B"((B get_file_memory() $B$+$i$N2~B$E@$KCe4c$7$F5-:\(B * 2.1 tdata->streamer->mutex $B$r(B lock $B$7$F$+$i0J2<$N(Bloop$B=hM}$r9T$&(B(loop$B:GBg?t$O(B16$B$G7h$aBG$A(B) * 2.1.1 tdata->streamer->stream_session[i] $B$,(B NULL $B$G$"$k$+3NG'(B * 2.1.1.2 NULL$B$G$"$k>l9g(B * 2.1.1.2.1 create_queue $B$r<B;\(B * 2.1.1.3 NULL $B$G$O$J$$>l9g(B * 2.1.1.2.1 $BF@$K$d$k$3$HL5$7(B * 2.2 tdata->streamer->mutex $B$r(B unlock */ static UpnpWebFileHandle get_file_stream (const char *fullpath, thread_data *tdata) { #define STREAM_QUEUE (8192) struct web_file_t *file; int i = 0; file = malloc (sizeof (struct web_file_t)); // 2.1 tdata->streamer->mutex $B$r(B lock $B$7$F$+$i0J2<$N(Bloop$B=hM}$r9T$&(B(loop$B:GBg?t$O(B16$B$G7h$aBG$A(B) pthread_mutex_lock(&tdata->streamer->mutex); for( i=0; i < STREAM_MAX; i++ ) { if ( tdata->streamer->stream_session[i] == NULL ) { // 2.1.1 tdata->streamer->stream_session[i] $B$,(B NULL $B$G$"$k>l9g(B // 2.1.1.1 $B?75,%9%H%j!<%`MQ$N%-%e!<$r:n@.(B file->detail.stream.id = tdata->streamer->stream_nr; tdata->streamer->stream_session[i] = malloc(sizeof(session)); if ( tdata->streamer->stream_session[i] == NULL ) { return NULL; } tdata->streamer->stream_session[i]->is_valid = true; tdata->streamer->stream_session[i]->p_queue = create_queue(STREAM_QUEUE); if ( tdata->streamer->stream_session[i]->p_queue == NULL ) { log_error ("get_file_stream(): tdata->streamer->stream_session[%d].p_queue alloc failed.\n", i); return NULL; } pthread_mutex_init(&tdata->streamer->stream_session[i]->p_queue->mutex, NULL); pthread_cond_init(&tdata->streamer->stream_session[i]->p_queue->cond_avail, NULL); pthread_cond_init(&tdata->streamer->stream_session[i]->p_queue->cond_used, NULL); tdata->streamer->stream_nr++; break; } else { // 2.1.2 tdata->streamer->stream_session[i] $B$,(B NULL $B$G$O$J$$(B if ( ! tdata->streamer->stream_session[i]->is_valid ) { // 2.1.2.1 tdata->streamer->stream_session[i] $B$,L$;HMQ>uBV$G$"$k>l9g(B file->detail.stream.id = i; //tdata->streamer->stream_nr++; tdata->streamer->stream_session[i]->is_valid = true; tdata->streamer->stream_session[i]->p_queue = create_queue(STREAM_QUEUE); if ( tdata->streamer->stream_session[i]->p_queue != NULL ) { pthread_mutex_init(&tdata->streamer->stream_session[i]->p_queue->mutex, NULL); pthread_cond_init(&tdata->streamer->stream_session[i]->p_queue->cond_avail, NULL); pthread_cond_init(&tdata->streamer->stream_session[i]->p_queue->cond_used, NULL); } else { log_error ("get_file_stream(): tdata->streamer->stream_session[%d].p_queue alloc failed.\n", i); return NULL; } break; } } } pthread_mutex_unlock(&tdata->streamer->mutex); if ( i == STREAM_MAX ) { log_verbose ("get_file_stream(): cannot get new file_stream.\n"); } file->detail.stream.p_queue = tdata->streamer->stream_session[i]->p_queue; file->fullpath = strdup (fullpath); file->pos = 0; file->type = FILE_STREAM; file->detail.stream.len = get_streaming_length(); //$B%U%!%$%k%5%$%:(B($B;DO?2h;~4V(Bx$B%S%C%H%l!<%H(B) file->detail.stream.qbuf = stream_dequeue(file->detail.stream.p_queue); if ( file->detail.stream.qbuf == NULL ) { log_error ("get_file_stream(): stream_dequeue error.\n"); return NULL; } log_verbose ("get_file_stream(): finish.\n"); return ((UpnpWebFileHandle) file); } static UpnpWebFileHandle http_open (const char *filename, enum UpnpOpenFileMode mode) { extern struct ushare_t *ut; struct upnp_entry_t *entry = NULL; struct web_file_t *file; int i, fd, upnp_id = 0; extern thread_data *gp_tdata; thread_data *tdata = gp_tdata; int channel_length = 0; if (!filename) return NULL; if (mode != UPNP_READ) return NULL; if (!strcmp (filename, CDS_LOCATION)) return get_file_memory (CDS_LOCATION, CDS_DESCRIPTION, CDS_DESCRIPTION_LEN); if (!strcmp (filename, CMS_LOCATION)) return get_file_memory (CMS_LOCATION, CMS_DESCRIPTION, CMS_DESCRIPTION_LEN); if (!strcmp (filename, MSR_LOCATION)) return get_file_memory (MSR_LOCATION, MSR_DESCRIPTION, MSR_DESCRIPTION_LEN); if (ut->use_presentation && ( !strcmp (filename, USHARE_PRESENTATION_PAGE) || !strncmp (filename, USHARE_CGI, strlen (USHARE_CGI)))) return get_file_memory (USHARE_PRESENTATION_PAGE, ut->presentation->buf, ut->presentation->len); upnp_id = atoi (strrchr (filename, '/') + 1); entry = upnp_get_entry (ut, upnp_id); if (!entry) return NULL; if (!entry->fullpath) return NULL; /* * 1. http_open() $B$G$O(B entry $B$,%9%H%j!<%`:F@8MQ$N$b$N$G$"$k>l9g$K!"(B * get_file_stream()$B$r8F$S=P$7%O%s%I%i$rJV5Q$9$k(B */ log_verbose ("Fullpath : %s\n", entry->fullpath); if (ut->nr_channel == 0) { if (!strcmp (entry->fullpath, STREAM_LOCATION)) return get_file_stream (STREAM_LOCATION, tdata); } else { for (i=0; i < ut->nr_channel; i++) if (!strcmp(entry->fullpath, ut->location_name[i])) { channel_length = strspn(ut->channel_name[i], "0123456789"); strncpy(ut->request_channel, ut->channel_name[i], channel_length); log_verbose ("http_open: request_channel[%s].\n", ut->request_channel); return get_file_stream (ut->location_name[i], tdata); } } fd = open (entry->fullpath, O_RDONLY | O_NONBLOCK | O_SYNC | O_NDELAY); if (fd < 0) return NULL; file = malloc (sizeof (struct web_file_t)); file->fullpath = strdup (entry->fullpath); file->pos = 0; file->type = FILE_LOCAL; file->detail.local.entry = entry; file->detail.local.fd = fd; return ((UpnpWebFileHandle) file); } static int http_read (UpnpWebFileHandle fh, char *buf, size_t buflen) { struct web_file_t *file = (struct web_file_t *) fh; ssize_t len = -1; //log_verbose ("http_read file:[%s]\n", file->fullpath); if (!file) return -1; switch (file->type) { case FILE_LOCAL: log_verbose ("Read local file.\n"); len = read (file->detail.local.fd, buf, buflen); break; case FILE_MEMORY: log_verbose ("Read file from memory.\n"); len = (size_t) MIN (buflen, file->detail.memory.len - file->pos); memcpy (buf, file->detail.memory.contents + file->pos, (size_t) len); break; case FILE_STREAM: //log_verbose ("Read file from stream.\n"); if ( file->detail.stream.qbuf->size <= file->pos ) { free(file->detail.stream.qbuf->data); file->detail.stream.qbuf->data = NULL; free(file->detail.stream.qbuf); file->detail.stream.qbuf = NULL; file->detail.stream.qbuf = stream_dequeue(file->detail.stream.p_queue); file->pos = 0; } if ( file->detail.stream.qbuf == NULL ) { log_verbose ("http_read stream_dequeue error NULL\n"); return 0; } len = (size_t) MIN (buflen, file->detail.stream.qbuf->size - file->pos); memcpy (buf, file->detail.stream.qbuf->data + file->pos, (size_t) len); break; default: log_verbose ("Unknown file type.\n"); break; } if (len >= 0) file->pos += len; //log_verbose ("Read %zd bytes.\n", len); return len; } static int http_write (UpnpWebFileHandle fh __attribute__((unused)), char *buf __attribute__((unused)), size_t buflen __attribute__((unused))) { log_verbose ("http write\n"); return 0; } static int http_seek (UpnpWebFileHandle fh, off_t offset, int origin) { struct web_file_t *file = (struct web_file_t *) fh; off_t newpos = -1; log_verbose ("http_seek\n"); if (!file) return -1; switch (origin) { case SEEK_SET: log_verbose ("Attempting to seek to %lld (was at %lld) in %s\n", offset, file->pos, file->fullpath); newpos = offset; break; case SEEK_CUR: log_verbose ("Attempting to seek by %lld from %lld in %s\n", offset, file->pos, file->fullpath); newpos = file->pos + offset; break; case SEEK_END: log_verbose ("Attempting to seek by %lld from end (was at %lld) in %s\n", offset, file->pos, file->fullpath); if (file->type == FILE_LOCAL) { struct stat sb; if (stat (file->fullpath, &sb) < 0) { log_verbose ("%s: cannot stat: %s\n", file->fullpath, strerror (errno)); return -1; } newpos = sb.st_size + offset; } else if (file->type == FILE_MEMORY) newpos = file->detail.memory.len + offset; break; } switch (file->type) { case FILE_LOCAL: /* Just make sure we cannot seek before start of file. */ if (newpos < 0) { log_verbose ("%s: cannot seek: %s\n", file->fullpath, strerror (EINVAL)); return -1; } /* Don't seek with origin as specified above, as file may have changed in size since our last stat. */ if (lseek (file->detail.local.fd, newpos, SEEK_SET) == -1) { log_verbose ("%s: cannot seek: %s\n", file->fullpath, strerror (errno)); return -1; } break; case FILE_MEMORY: if (newpos < 0 || newpos > file->detail.memory.len) { log_verbose ("%s: cannot seek: %s\n", file->fullpath, strerror (EINVAL)); return -1; } break; case FILE_STREAM: log_verbose ("%s: cannot seek: %s\n", file->fullpath, "STREAM"); newpos = file->pos; break; } file->pos = newpos; return 0; } static int http_close (UpnpWebFileHandle fh) { extern struct ushare_t *ut; struct web_file_t *file = (struct web_file_t *) fh; extern thread_data *gp_tdata; thread_data *tdata = gp_tdata; STREAM_QUEUE_T *p_queue; int id = 0; if (!file) return -1; switch (file->type) { case FILE_LOCAL: close (file->detail.local.fd); break; case FILE_MEMORY: /* no close operation */ if (file->detail.memory.contents) free (file->detail.memory.contents); break; case FILE_STREAM: p_queue = file->detail.stream.p_queue; if ( p_queue != NULL) { id = file->detail.stream.id; pthread_mutex_lock(&tdata->streamer->mutex); tdata->streamer->stream_session[id]->is_valid = false; pthread_mutex_unlock(&tdata->streamer->mutex); pthread_mutex_lock(&p_queue->mutex); while ( 0 < p_queue->num_used ) { free(p_queue->buffer[p_queue->out]->data); p_queue->buffer[p_queue->out]->data = NULL; free(p_queue->buffer[p_queue->out]); p_queue->buffer[p_queue->out] = NULL; p_queue->out++; p_queue->out %= p_queue->size; p_queue->num_avail++; p_queue->num_used--; } pthread_mutex_unlock(&p_queue->mutex); destroy_stream_queue(p_queue); tdata->streamer->stream_session[id]->p_queue = NULL; } break; default: log_verbose ("Unknown file type.\n"); break; } if (file->fullpath) free (file->fullpath); free (file); return 0; } struct UpnpVirtualDirCallbacks virtual_dir_callbacks = { http_get_info, http_open, http_read, http_write, http_seek, http_close }; // TS_BITRATE$B$O$d$C$D$1;E;v2a$.$k5$$,$7$^$9(B #define TS_BITRATE (10*1000*1000/8) #define MAX_STREAMING ((off_t)100*1000*1000*1000) static off_t get_streaming_length (void) { off_t length = 0; extern thread_data *gp_tdata; thread_data *tdata = gp_tdata; time_t cur_time; struct timespec cur; struct timespec diff; clock_gettime(CLOCK_REALTIME, &cur); off_t bitrate = 0; if ( tdata->indefinite ) { return MAX_STREAMING; } diff.tv_nsec = cur.tv_nsec - tdata->streamer->start.tv_nsec; diff.tv_sec = cur.tv_sec - tdata->streamer->start.tv_sec; if ( diff.tv_sec < 1 ) { bitrate = TS_BITRATE; } else { bitrate = (((off_t)tdata->streamer->total_byte)*1e9) / ( (((off_t)diff.tv_sec)*1e9) + diff.tv_nsec); } time(&cur_time); length = (tdata->start_time +tdata->recsec -cur_time) * bitrate; if ( length < 0 ) { length = 0; } length = length - (length % LENGTH_PACKET); return length; }