From 7fe60435bce6595a9c58a9bfd8244d74b5320e96 Mon Sep 17 00:00:00 2001 From: Benjamin Franzke Date: Tue, 15 Jan 2013 08:46:13 +0100 Subject: Import DirectFB141_2k11R3_beta5 --- Source/DirectFB/lib/direct/stream.c | 2286 +++++++++++++++++++++++++++++++++++ 1 file changed, 2286 insertions(+) create mode 100755 Source/DirectFB/lib/direct/stream.c (limited to 'Source/DirectFB/lib/direct/stream.c') diff --git a/Source/DirectFB/lib/direct/stream.c b/Source/DirectFB/lib/direct/stream.c new file mode 100755 index 0000000..1e531d9 --- /dev/null +++ b/Source/DirectFB/lib/direct/stream.c @@ -0,0 +1,2286 @@ +/* + (c) Copyright 2001-2009 The world wide DirectFB Open Source Community (directfb.org) + (c) Copyright 2000-2004 Convergence (integrated media) GmbH + + All rights reserved. + + Written by Denis Oliver Kropp , + Andreas Hundt , + Sven Neumann , + Ville Syrjälä and + Claudio Ciccani . + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + + +struct __D_DirectStream { + int magic; + int ref; + + int fd; + unsigned int offset; + int length; + + char *mime; + + /* cache for piped streams */ + void *cache; + unsigned int cache_size; + +#if DIRECT_BUILD_NETWORK + /* remote streams data */ + struct { + int sd; + + char *host; + int port; + struct addrinfo *addr; + + char *user; + char *pass; + char *auth; + + char *path; + + int redirects; + + void *data; + + bool real_rtsp; + bool real_pack; + } remote; +#endif + + DirectResult (*wait) ( DirectStream *stream, + unsigned int length, + struct timeval *tv ); + DirectResult (*peek) ( DirectStream *stream, + unsigned int length, + int offset, + void *buf, + unsigned int *read_out ); + DirectResult (*read) ( DirectStream *stream, + unsigned int length, + void *buf, + unsigned int *read_out ); + DirectResult (*seek) ( DirectStream *stream, + unsigned int offset ); +}; + + +static void direct_stream_close( DirectStream *stream ); + + +#if DIRECT_BUILD_NETWORK +D_DEBUG_DOMAIN( Direct_Stream, "Direct/Stream", "Stream wrapper" ); +#endif + +/************************** Begin Network Support ***************************/ + +#if DIRECT_BUILD_NETWORK + +#define NET_TIMEOUT 15 +#define HTTP_PORT 80 +#define FTP_PORT 21 +#define RTSP_PORT 554 +#define HTTP_MAX_REDIRECTS 15 + +static DirectResult http_open( DirectStream *stream, const char *filename ); +static DirectResult ftp_open ( DirectStream *stream, const char *filename ); +static DirectResult rtsp_open( DirectStream *stream, const char *filename ); + + +static inline char* trim( char *s ) +{ + char *e; + +#define space( c ) ((c) == ' ' || (c) == '\t' || \ + (c) == '\r' || (c) == '\n' || \ + (c) == '"' || (c) == '\'') + + for (; space(*s); s++); + + e = s + strlen(s) - 1; + for (; e > s && space(*e); *e-- = '\0'); + +#undef space + + return s; +} + +static void +parse_url( const char *url, char **ret_host, int *ret_port, + char **ret_user, char **ret_pass, char **ret_path ) +{ + char *host = NULL; + int port = 0; + char *user = NULL; + char *pass = NULL; + char *path; + char *tmp; + + tmp = strchr( url, '/' ); + if (tmp) { + host = alloca( tmp - url + 1 ); + memcpy( host, url, tmp - url ); + host[tmp-url] = '\0'; + path = tmp; + } else { + host = alloca( strlen( url ) + 1 ); + memcpy( host, url, strlen( url ) + 1 ); + path = "/"; + } + + tmp = strrchr( host, '@' ); + if (tmp) { + *tmp = '\0'; + pass = strchr( host, ':' ); + if (pass) { + *pass = '\0'; + pass++; + } + user = host; + host = tmp + 1; + } + + tmp = strchr( host, ':' ); + if (tmp) { + port = strtol( tmp+1, NULL, 10 ); + *tmp = '\0'; + } + + /* IPv6 variant (host within brackets) */ + if (*host == '[') { + host++; + tmp = strchr( host, ']' ); + if (tmp) + *tmp = '\0'; + } + + if (ret_host) + *ret_host = D_STRDUP( host ); + + if (ret_port && port) + *ret_port = port; + + if (ret_user && user) + *ret_user = D_STRDUP( user ); + + if (ret_pass && pass) + *ret_pass = D_STRDUP( pass ); + + if (ret_path) + *ret_path = D_STRDUP( path ); +} + +/*****************************************************************************/ + +static int +net_response( DirectStream *stream, char *buf, size_t size ) +{ + fd_set set; + struct timeval tv; + int i; + + FD_ZERO( &set ); + FD_SET( stream->remote.sd, &set ); + + for (i = 0; i < size-1; i++) { + tv.tv_sec = NET_TIMEOUT; + tv.tv_usec = 0; + select( stream->remote.sd+1, &set, NULL, NULL, &tv ); + + if (recv( stream->remote.sd, buf+i, 1, 0 ) != 1) + break; + + if (buf[i] == '\n') { + if (i > 0 && buf[i-1] == '\r') + i--; + break; + } + } + + buf[i] = '\0'; + + D_DEBUG_AT( Direct_Stream, "got [%s].\n", buf ); + + return i; +} + +static int +net_command( DirectStream *stream, char *buf, size_t size ) +{ + fd_set s; + struct timeval t; + int status; + int version; + char space; + + FD_ZERO( &s ); + FD_SET( stream->remote.sd, &s ); + + t.tv_sec = NET_TIMEOUT; + t.tv_usec = 0; + + switch (select( stream->remote.sd+1, NULL, &s, NULL, &t )) { + case 0: + D_DEBUG_AT( Direct_Stream, "Timeout!\n" ); + case -1: + return -1; + } + + send( stream->remote.sd, buf, strlen(buf), 0 ); + send( stream->remote.sd, "\r\n", 2, 0 ); + + D_DEBUG_AT( Direct_Stream, "sent [%s].\n", buf ); + + while (net_response( stream, buf, size ) > 0) { + status = 0; + if (sscanf( buf, "HTTP/1.%d %3d", &version, &status ) == 2 || + sscanf( buf, "RTSP/1.%d %3d", &version, &status ) == 2 || + sscanf( buf, "ICY %3d", &status ) == 1 || + sscanf( buf, "%3d%[ ]", &status, &space ) == 2) + return status; + } + + return 0; +} + +static DirectResult +net_wait( DirectStream *stream, + unsigned int length, + struct timeval *tv ) +{ + fd_set s; + + if (stream->cache_size >= length) + return DR_OK; + + if (stream->fd == -1) + return DR_EOF; + + FD_ZERO( &s ); + FD_SET( stream->fd, &s ); + + switch (select( stream->fd+1, &s, NULL, NULL, tv )) { + case 0: + if (!tv && !stream->cache_size) + return DR_EOF; + return DR_TIMEOUT; + case -1: + return errno2result( errno ); + } + + return DR_OK; +} + +static DirectResult +net_peek( DirectStream *stream, + unsigned int length, + int offset, + void *buf, + unsigned int *read_out ) +{ + char *tmp; + int size; + + if (offset < 0) + return DR_UNSUPPORTED; + + tmp = alloca( length + offset ); + + size = recv( stream->fd, tmp, length+offset, MSG_PEEK ); + switch (size) { + case 0: + return DR_EOF; + case -1: + if (errno == EAGAIN || errno == EWOULDBLOCK) + return DR_BUFFEREMPTY; + return errno2result( errno ); + default: + if (size < offset) + return DR_BUFFEREMPTY; + size -= offset; + break; + } + + direct_memcpy( buf, tmp+offset, size ); + + if (read_out) + *read_out = size; + + return DR_OK; +} + +static DirectResult +net_read( DirectStream *stream, + unsigned int length, + void *buf, + unsigned int *read_out ) +{ + int size; + + size = recv( stream->fd, buf, length, 0 ); + switch (size) { + case 0: + return DR_EOF; + case -1: + if (errno == EAGAIN || errno == EWOULDBLOCK) + return DR_BUFFEREMPTY; + return errno2result( errno ); + } + + stream->offset += size; + + if (read_out) + *read_out = size; + + return DR_OK; +} + +static DirectResult +net_connect( struct addrinfo *addr, int sock, int proto, int *ret_fd ) +{ + DirectResult ret = DR_OK; + int fd = -1; + struct addrinfo *tmp; + + D_ASSERT( addr != NULL ); + D_ASSERT( ret_fd != NULL ); + + for (tmp = addr; tmp; tmp = tmp->ai_next) { + int err; + + fd = socket( tmp->ai_family, sock, proto ); + if (fd < 0) { + ret = errno2result( errno ); + D_DEBUG_AT( Direct_Stream, + "failed to create socket!\n\t->%s", + strerror(errno) ); + continue; + } + + fcntl( fd, F_SETFL, fcntl( fd, F_GETFL ) | O_NONBLOCK ); + + D_DEBUG_AT( Direct_Stream, + "connecting to %s...\n", tmp->ai_canonname ); + + if (proto == IPPROTO_UDP) + err = bind( fd, tmp->ai_addr, tmp->ai_addrlen ); + else + err = connect( fd, tmp->ai_addr, tmp->ai_addrlen ); + + if (err == 0 || errno == EINPROGRESS) { + struct timeval t = { NET_TIMEOUT, 0 }; + fd_set s; + + /* Join multicast group? */ + if (tmp->ai_addr->sa_family == AF_INET) { + struct sockaddr_in *saddr = (struct sockaddr_in *) tmp->ai_addr; + + if (IN_MULTICAST( ntohl(saddr->sin_addr.s_addr) )) { + struct ip_mreq req; + + D_DEBUG_AT( Direct_Stream, + "joining multicast group (%u.%u.%u.%u)...\n", + (u8)tmp->ai_addr->sa_data[2], (u8)tmp->ai_addr->sa_data[3], + (u8)tmp->ai_addr->sa_data[4], (u8)tmp->ai_addr->sa_data[5] ); + + req.imr_multiaddr.s_addr = saddr->sin_addr.s_addr; + req.imr_interface.s_addr = 0; + + err = setsockopt( fd, SOL_IP, IP_ADD_MEMBERSHIP, &req, sizeof(req) ); + if (err < 0) { + ret = errno2result( errno ); + D_PERROR( "Direct/Stream: Could not join multicast group (%u.%u.%u.%u)!\n", + (u8)tmp->ai_addr->sa_data[2], (u8)tmp->ai_addr->sa_data[3], + (u8)tmp->ai_addr->sa_data[4], (u8)tmp->ai_addr->sa_data[5] ); + close( fd ); + continue; + } + + setsockopt( fd, SOL_SOCKET, SO_REUSEADDR, saddr, sizeof(*saddr) ); + } + } + + FD_ZERO( &s ); + FD_SET( fd, &s ); + + err = select( fd+1, NULL, &s, NULL, &t ); + if (err < 1) { + D_DEBUG_AT( Direct_Stream, "...connection failed.\n" ); + + close( fd ); + fd = -1; + + if (err == 0) { + ret = DR_TIMEOUT; + continue; + } else { + ret = errno2result( errno ); + break; + } + } + + D_DEBUG_AT( Direct_Stream, "...connected.\n" ); + + ret = DR_OK; + break; + } + } + + *ret_fd = fd; + + return ret; +} + +static DirectResult +net_open( DirectStream *stream, const char *filename, int proto ) +{ + DirectResult ret = DR_OK; + int sock = (proto == IPPROTO_TCP) ? SOCK_STREAM : SOCK_DGRAM; + struct addrinfo hints; + char port[16]; + + parse_url( filename, + &stream->remote.host, + &stream->remote.port, + &stream->remote.user, + &stream->remote.pass, + &stream->remote.path ); + + snprintf( port, sizeof(port), "%d", stream->remote.port ); + + memset( &hints, 0, sizeof(hints) ); + hints.ai_flags = AI_CANONNAME; + hints.ai_socktype = sock; + hints.ai_family = PF_UNSPEC; + + if (getaddrinfo( stream->remote.host, port, + &hints, &stream->remote.addr )) { + D_ERROR( "Direct/Stream: " + "failed to resolve host '%s'!\n", stream->remote.host ); + return DR_FAILURE; + } + + ret = net_connect( stream->remote.addr, sock, proto, &stream->remote.sd ); + if (ret) + return ret; + + stream->fd = stream->remote.sd; + stream->length = -1; + stream->wait = net_wait; + stream->peek = net_peek; + stream->read = net_read; + + return ret; +} + +/*****************************************************************************/ + +static DirectResult +http_seek( DirectStream *stream, unsigned int offset ) +{ + DirectResult ret; + char buf[1280]; + int status, len; + + close( stream->remote.sd ); + stream->remote.sd = -1; + + ret = net_connect( stream->remote.addr, + SOCK_STREAM, IPPROTO_TCP, &stream->remote.sd ); + if (ret) + return ret; + + stream->fd = stream->remote.sd; + + len = snprintf( buf, sizeof(buf), + "GET %s HTTP/1.0\r\n" + "Host: %s:%d\r\n", + stream->remote.path, + stream->remote.host, + stream->remote.port ); + if (stream->remote.auth) { + len += snprintf( buf+len, sizeof(buf)-len, + "Authorization: Basic %s\r\n", + stream->remote.auth ); + } + snprintf( buf+len, sizeof(buf)-len, + "User-Agent: DirectFB/%s\r\n" + "Accept: */*\r\n" + "Range: bytes=%d-\r\n" + "Connection: Close\r\n", + DIRECTFB_VERSION, offset ); + + status = net_command( stream, buf, sizeof(buf) ); + switch (status) { + case 200 ... 299: + stream->offset = offset; + break; + default: + if (status) + D_DEBUG_AT( Direct_Stream, + "server returned status %d.\n", status ); + return DR_FAILURE; + } + + /* discard remaining headers */ + while (net_response( stream, buf, sizeof(buf) ) > 0); + + return DR_OK; +} + +static DirectResult +http_open( DirectStream *stream, const char *filename ) +{ + DirectResult ret; + char buf[1280]; + int status, len; + + stream->remote.port = HTTP_PORT; + + ret = net_open( stream, filename, IPPROTO_TCP ); + if (ret) + return ret; + + if (stream->remote.user) { + char *tmp; + + if (stream->remote.pass) { + tmp = alloca( strlen( stream->remote.user ) + + strlen( stream->remote.pass ) + 2 ); + len = sprintf( tmp, "%s:%s", + stream->remote.user, stream->remote.pass ); + } else { + tmp = alloca( strlen( stream->remote.user ) + 2 ); + len = sprintf( tmp, "%s:", stream->remote.user ); + } + + stream->remote.auth = direct_base64_encode( tmp, len ); + } + + len = snprintf( buf, sizeof(buf), + "GET %s HTTP/1.0\r\n" + "Host: %s:%d\r\n", + stream->remote.path, + stream->remote.host, + stream->remote.port ); + if (stream->remote.auth) { + len += snprintf( buf+len, sizeof(buf)-len, + "Authorization: Basic %s\r\n", + stream->remote.auth ); + } + snprintf( buf+len, sizeof(buf)-len, + "User-Agent: DirectFB/%s\r\n" + "Accept: */*\r\n" + "Connection: Close\r\n", + DIRECTFB_VERSION ); + + status = net_command( stream, buf, sizeof(buf) ); + + while (net_response( stream, buf, sizeof(buf) ) > 0) { + if (!strncasecmp( buf, "Accept-Ranges:", 14 )) { + if (strcmp( trim( buf+14 ), "none" )) + stream->seek = http_seek; + } + else if (!strncasecmp( buf, "Content-Type:", 13 )) { + char *mime = trim( buf+13 ); + char *tmp = strchr( mime, ';' ); + if (tmp) + *tmp = '\0'; + if (stream->mime) + D_FREE( stream->mime ); + stream->mime = D_STRDUP( mime ); + } + else if (!strncasecmp( buf, "Content-Length:", 15 )) { + char *tmp = trim( buf+15 ); + if (sscanf( tmp, "%d", &stream->length ) < 1) + sscanf( tmp, "bytes=%d", &stream->length ); + } + else if (!strncasecmp( buf, "Location:", 9 )) { + direct_stream_close( stream ); + stream->seek = NULL; + + if (++stream->remote.redirects > HTTP_MAX_REDIRECTS) { + D_ERROR( "Direct/Stream: " + "reached maximum number of redirects (%d).\n", + HTTP_MAX_REDIRECTS ); + return DR_LIMITEXCEEDED; + } + + filename = trim( buf+9 ); + if (!strncmp( filename, "http://", 7 )) + return http_open( stream, filename+7 ); + if (!strncmp( filename, "ftp://", 6 )) + return ftp_open( stream, filename+6 ); + if (!strncmp( filename, "rtsp://", 7 )) + return rtsp_open( stream, filename+7 ); + + return DR_UNSUPPORTED; + } + } + + switch (status) { + case 200 ... 299: + break; + default: + if (status) + D_DEBUG_AT( Direct_Stream, + "server returned status %d.\n", status ); + return (status == 404) ? DR_FILENOTFOUND : DR_FAILURE; + } + + return DR_OK; +} + +/*****************************************************************************/ + +static DirectResult +ftp_open_pasv( DirectStream *stream, char *buf, size_t size ) +{ + DirectResult ret; + int i, len; + + snprintf( buf, size, "PASV" ); + if (net_command( stream, buf, size ) != 227) + return DR_FAILURE; + + /* parse IP and port for passive mode */ + for (i = 4; buf[i]; i++) { + unsigned int d[6]; + + if (sscanf( &buf[i], "%u,%u,%u,%u,%u,%u", + &d[0], &d[1], &d[2], &d[3], &d[4], &d[5] ) == 6) + { + struct addrinfo hints, *addr; + + /* address */ + len = snprintf( buf, size, + "%u.%u.%u.%u", + d[0], d[1], d[2], d[3] ); + /* port */ + snprintf( buf+len+1, size-len-1, + "%u", ((d[4] & 0xff) << 8) | (d[5] & 0xff) ); + + memset( &hints, 0, sizeof(hints) ); + hints.ai_flags = AI_CANONNAME; + hints.ai_socktype = SOCK_STREAM; + hints.ai_family = PF_UNSPEC; + + if (getaddrinfo( buf, buf+len+1, &hints, &addr )) { + D_DEBUG_AT( Direct_Stream, + "failed to resolve host '%s'.\n", buf ); + return DR_FAILURE; + } + + ret = net_connect( addr, SOCK_STREAM, IPPROTO_TCP, &stream->fd ); + + freeaddrinfo( addr ); + + return ret; + } + } + + return DR_FAILURE; +} + +static DirectResult +ftp_seek( DirectStream *stream, unsigned int offset ) +{ + DirectResult ret = DR_OK; + char buf[512]; + + if (stream->fd > 0) { + int status; + + close( stream->fd ); + stream->fd = -1; + + /* ignore response */ + while (net_response( stream, buf, sizeof(buf) ) > 0) { + if (sscanf( buf, "%3d%[ ]", &status, buf ) == 2) + break; + } + } + + ret = ftp_open_pasv( stream, buf, sizeof(buf) ); + if (ret) + return ret; + + snprintf( buf, sizeof(buf), "REST %d", offset ); + if (net_command( stream, buf, sizeof(buf) ) != 350) + goto error; + + snprintf( buf, sizeof(buf), "RETR %s", stream->remote.path ); + switch (net_command( stream, buf, sizeof(buf) )) { + case 150: + case 125: + break; + default: + goto error; + } + + stream->offset = offset; + + return DR_OK; + +error: + close( stream->fd ); + stream->fd = -1; + + return DR_FAILURE; +} + +static DirectResult +ftp_open( DirectStream *stream, const char *filename ) +{ + DirectResult ret; + int status = 0; + char buf[512]; + + stream->remote.port = FTP_PORT; + + ret = net_open( stream, filename, IPPROTO_TCP ); + if (ret) + return ret; + + while (net_response( stream, buf, sizeof(buf) ) > 0) { + if (sscanf( buf, "%3d%[ ]", &status, buf ) == 2) + break; + } + if (status != 220) + return DR_FAILURE; + + /* login */ + snprintf( buf, sizeof(buf), "USER %s", stream->remote.user ? : "anonymous" ); + switch (net_command( stream, buf, sizeof(buf) )) { + case 230: + case 331: + break; + default: + return DR_FAILURE; + } + + if (stream->remote.pass) { + snprintf( buf, sizeof(buf), "PASS %s", stream->remote.pass ); + if (net_command( stream, buf, sizeof(buf) ) != 230) + return DR_FAILURE; + } + + /* enter binary mode */ + snprintf( buf, sizeof(buf), "TYPE I" ); + if (net_command( stream, buf, sizeof(buf) ) != 200) + return DR_FAILURE; + + /* get file size */ + snprintf( buf, sizeof(buf), "SIZE %s", stream->remote.path ); + if (net_command( stream, buf, sizeof(buf) ) == 213) + stream->length = strtol( buf+4, NULL, 10 ); + + /* enter passive mode by default */ + ret = ftp_open_pasv( stream, buf, sizeof(buf) ); + if (ret) + return ret; + + /* retrieve file */ + snprintf( buf, sizeof(buf), "RETR %s", stream->remote.path ); + switch (net_command( stream, buf, sizeof(buf) )) { + case 125: + case 150: + break; + default: + return DR_FAILURE; + } + + stream->seek = ftp_seek; + + return DR_OK; +} + +/*****************************************************************************/ + +typedef struct { + s8 pt; // payload type (-1: dymanic) + u8 type; // codec type + const char *name; + const char *mime; + u32 fcc; +} RTPPayload; + +typedef struct { + char *control; + + int pt; + const RTPPayload *payload; + + int dur; // duration + int abr; // avg bitrate + int mbr; // max bitrate + int aps; // avg packet size + int mps; // max packet size + int str; // start time + int prl; // preroll + + char *mime; + int mime_size; + + void *data; + int data_size; +} SDPStreamDesc; + +#define PAYLOAD_VIDEO 1 +#define PAYLOAD_AUDIO 2 + +#define FCC( a, b, c, d ) ((a) | ((b)<<8) | ((c)<<16) | ((d)<<24)) + +static const RTPPayload payloads[] = { + { 31, PAYLOAD_VIDEO, "H261", "video/h261", FCC('H','2','6','1') }, + { 34, PAYLOAD_VIDEO, "H263", "video/h263", FCC('H','2','6','3') }, + //{ -1, PAYLOAD_VIDEO, "H264", "video/h264", FCC('H','2','6','4') }, + { 32, PAYLOAD_VIDEO, "MPV", "video/mpeg", FCC('M','P','E','G') }, + { 33, 0, "MP2T", "video/mpegts", 0 }, + { -1, PAYLOAD_VIDEO, "MP4V-ES", "video/mpeg4", FCC('M','P','4','S') }, + { 14, PAYLOAD_AUDIO, "MPA", "audio/mpeg", 0x0055 }, + //{ -1, PAYLOAD_AUDIO, "mpeg4-generic", "audio/aac", 0x00ff }, +}; + +#define NUM_PAYLOADS D_ARRAY_SIZE( payloads ) + +static DirectResult +sdp_parse( DirectStream *stream, int length, SDPStreamDesc **ret_streams, int *ret_num ) +{ + char *buf, *tmp; + SDPStreamDesc *desc = NULL; + int num = 0; + fd_set set; + struct timeval tv; + int i; + + buf = D_CALLOC( 1, length+1 ); + if (!buf) + return D_OOM(); + + FD_ZERO( &set ); + FD_SET( stream->remote.sd, &set ); + + for (tmp = buf; length;) { + int size; + + tv.tv_sec = NET_TIMEOUT; + tv.tv_usec = 0; + select( stream->remote.sd+1, &set, NULL, NULL, &tv ); + + size = recv( stream->remote.sd, tmp, length, MSG_WAITALL ); + if (size < 1) + break; + tmp += size; + length -= size; + } + + for (tmp = buf; tmp && *tmp;) { + char *end; + + end = strchr( tmp, '\n' ); + if (end) { + if (end > tmp && *(end-1) == '\r') + *(end-1) = '\0'; + *end = '\0'; + } + + D_DEBUG_AT( Direct_Stream, "SDP [%s]\n", tmp ); + + switch (*tmp) { + case 'm': + /* media */ + if (*(tmp+1) == '=') { + desc = D_REALLOC( desc, ++num*sizeof(SDPStreamDesc) ); + memset( &desc[num-1], 0, sizeof(SDPStreamDesc) ); + + tmp += 2; + if (sscanf( tmp, "audio %d RTP/AVP %d", &i, &desc[num-1].pt ) == 2 || + sscanf( tmp, "video %d RTP/AVP %d", &i, &desc[num-1].pt ) == 2) { + for (i = 0; i < NUM_PAYLOADS; i++) { + if (desc[num-1].pt == payloads[i].pt) { + desc[num-1].payload = &payloads[i]; + desc[num-1].mime = D_STRDUP( payloads[i].mime ); + desc[num-1].mime_size = strlen( payloads[i].mime ); + break; + } + } + } + } + break; + case 'a': + /* attribute */ + if (*(tmp+1) == '=' && num) { + tmp += 2; + if (!strncmp( tmp, "control:", 8 )) { + desc[num-1].control = D_STRDUP( trim( tmp+8 ) ); + } + else if (!strncmp( tmp, "rtpmap:", 7 )) { + if (!desc[num-1].payload && desc[num-1].pt) { + char *sep; + + tmp = strchr( trim( tmp+7 ), ' ' ); + if (!tmp) break; + sep = strchr( ++tmp, '/' ); + if (sep) *sep = '\0'; + + for (i = 0; i < NUM_PAYLOADS; i++) { + if (strcmp( tmp, payloads[i].name )) + continue; + desc[num-1].payload = &payloads[i]; + desc[num-1].mime = D_STRDUP( payloads[i].mime ); + desc[num-1].mime_size = strlen( payloads[i].mime ); + break; + } + } + } + else if (!strncmp( tmp, "length:npt=", 11 )) { + double val = atof( tmp+11 ); + desc[num-1].dur = val * 1000.0; + } + else if (!strncmp( tmp, "mimetype:string;", 16 )) { + if (desc[num-1].mime) + D_FREE( desc[num-1].mime ); + desc[num-1].mime = D_STRDUP( trim( tmp+16 ) ); + desc[num-1].mime_size = strlen( desc[num-1].mime ); + } + else if (!strncmp( tmp, "AvgBitRate:", 11 )) { + sscanf( tmp+11, "integer;%d", &desc[num-1].abr ); + } + else if (!strncmp( tmp, "MaxBitRate:", 11 )) { + sscanf( tmp+11, "integer;%d", &desc[num-1].mbr ); + } + else if (!strncmp( tmp, "AvgPacketSize:", 14 )) { + sscanf( tmp+14, "integer;%d", &desc[num-1].aps ); + } + else if (!strncmp( tmp, "MaxPacketSize:", 14 )) { + sscanf( tmp+14, "integer;%d", &desc[num-1].mps ); + } + else if (!strncmp( tmp, "StartTime:", 10 )) { + sscanf( tmp+10, "integer;%d", &desc[num-1].str ); + } + else if (!strncmp( tmp, "Preroll:", 8 )) { + sscanf( tmp+8, "integer;%d", &desc[num-1].prl ); + } + else if (!strncmp( tmp, "OpaqueData:buffer;", 18 )) { + desc[num-1].data = + direct_base64_decode( trim( tmp+18 ), + &desc[num-1].data_size ); + } + } + break; + default: + break; + } + + tmp = end; + if (tmp) tmp++; + } + + D_FREE( buf ); + + *ret_streams = desc; + *ret_num = num; + + return desc ? DR_OK : DR_EOF; +} + +static void +sdp_free( SDPStreamDesc *streams, int num ) +{ + int i; + + for (i = 0; i < num; i++) { + if (streams[i].control) + D_FREE( streams[i].control ); + if (streams[i].mime) + D_FREE( streams[i].mime ); + if (streams[i].data) + D_FREE( streams[i].data ); + } + D_FREE( streams ); +} + +static DirectResult +rmf_write_header( SDPStreamDesc *streams, int n_streams, void **ret_buf, unsigned int *ret_size ) +{ + unsigned char *dst, *tmp; + int abr = 0; + int mbr = 0; + int aps = 0; + int mps = 0; + int str = 0; + int prl = 0; + int dur = 0; + int i, len; + + len = 86 + n_streams*46; + for (i = 0; i < n_streams; i++) { + abr += streams[i].abr; + aps += streams[i].aps; + if (mbr < streams[i].mbr) + mbr = streams[i].mbr; + if (mps < streams[i].mps) + mps = streams[i].mps; + if (dur < streams[i].dur) + dur = streams[i].dur; + if (streams[i].mime) + len += streams[i].mime_size; + if (streams[i].data) + len += streams[i].data_size; + else if (streams[i].payload) + len += 74; + } + + *ret_buf = dst = D_MALLOC( len ); + if (!dst) + return D_OOM(); + *ret_size = len; + + /* RMF */ + dst[0] = '.'; dst[1] = 'R', dst[2] = 'M'; dst[3] = 'F'; + dst[4] = dst[5] = dst[6] = 0; dst[7] = 18; // size + dst[8] = dst[9] = 0; // version + dst[10] = dst[11] = dst[12] = dst[13] = 0; // ?? + dst[14] = dst[15] = dst[16] = 0; dst[17] = 4+n_streams; // num streams + dst += 18; + + /* PROP */ + dst[0] = 'P'; dst[1] = 'R'; dst[2] = 'O'; dst[3] = 'P'; + dst[4] = dst[5] = dst[6] = 0; dst[7] = 50; + dst[8] = dst[9] = 0; + dst[10] = mbr>>24; dst[11] = mbr>>16; dst[12] = mbr>>8; dst[13] = mbr; + dst[14] = abr>>24; dst[15] = abr>>16; dst[16] = abr>>8; dst[17] = abr; + dst[18] = mps>>24; dst[19] = mps>>16; dst[20] = mps>>8; dst[21] = mps; + dst[22] = aps>>24; dst[23] = aps>>16; dst[24] = aps>>8; dst[25] = aps; + dst[26] = dst[27] = dst[28] = dst[29] = 0; // num packets + dst[30] = dur>>24; dst[31] = dur>>16; dst[32] = dur>>8; dst[33] = dur; + dst[34] = dst[35] = dst[36] = dst[37] = 0; // preroll + dst[38] = dst[39] = dst[40] = dst[41] = 0; // index offset + dst[42] = len>>24; dst[43] = len>>16; dst[44] = len>>8; dst[45] = len; + dst[46] = 0; dst[47] = n_streams; // num streams + dst[48] = 0; dst[49] = 7; // flags + dst += 50; + + for (i = 0; i < n_streams; i++) { + len = 46 + streams[i].mime_size; + if (streams[i].data) + len += streams[i].data_size; + else if (streams[i].payload) + len += 74; + + abr = streams[i].abr; + mbr = streams[i].mbr; + aps = streams[i].aps; + mps = streams[i].mps; + str = streams[i].str; + prl = streams[i].prl; + dur = streams[i].dur; + + /* MDPR */ + dst[0] = 'M'; dst[1] = 'D'; dst[2] = 'P'; dst[3] = 'R'; + dst[4] = len>>24; dst[5] = len>>16; dst[6] = len>>8; dst[7] = len; + dst[8] = dst[9] = 0; + dst[10] = 0; dst[11] = i; + dst[12] = mbr>>24; dst[13] = mbr>>16; dst[14] = mbr>>8; dst[15] = mbr; + dst[16] = abr>>24; dst[17] = abr>>16; dst[18] = abr>>8; dst[19] = abr; + dst[20] = mps>>24; dst[21] = mps>>16; dst[22] = mps>>8; dst[23] = mps; + dst[24] = aps>>24; dst[25] = aps>>16; dst[26] = aps>>8; dst[27] = aps; + dst[28] = str>>24; dst[29] = str>>16; dst[30] = str>>8; dst[31] = str; + dst[32] = prl>>24; dst[33] = prl>>16; dst[34] = prl>>8; dst[35] = prl; + dst[36] = dur>>24; dst[37] = dur>>16; dst[38] = dur>>8; dst[39] = dur; + dst += 40; + + /* description */ + *dst++ = 0; + /* mimetype */ + *dst++ = streams[i].mime_size; + for (tmp = (unsigned char*)streams[i].mime; tmp && *tmp;) + *dst++ = *tmp++; + + /* codec data */ + if (streams[i].data) { + len = streams[i].data_size; + dst[0] = len>>24; dst[1] = len>>16; dst[2] = len>>8; dst[3] = len; + direct_memcpy( dst+4, streams[i].data, streams[i].data_size ); + dst += len+4; + } + else if (streams[i].payload) { + u32 fcc = streams[i].payload->fcc; + + dst[0] = dst[1] = dst[2] = 0; dst[3] = 74; + dst += 4; + memset( dst, 0, 74 ); + + if (streams[i].payload->type == PAYLOAD_AUDIO) { + dst[0] = '.'; dst[1] = 'r'; dst[2] = 'a'; dst[3] = 0xfd; + dst[4] = 0; dst[5] = 5; // version + dst[66] = fcc; dst[67] = fcc>>8; dst[68] = fcc>>16; dst[69] = fcc>>24; + } + else { + dst[0] = dst[1] = dst[2] = 0; dst[3] = 34; + dst[4] = 'V'; dst[5] = 'I'; dst[6] = 'D'; dst[7] = 'O'; + dst[8] = fcc; dst[9] = fcc>>8; dst[10] = fcc>>16; dst[11] = fcc>>24; + dst[30] = 0x10; + } + dst += 74; + } + else { + dst[0] = dst[1] = dst[2] = dst[3] = 0; + dst += 4; + } + } + + /* DATA */ + dst[0] = 'D'; dst[1] = 'A'; dst[2] = 'T'; dst[3] = 'A'; + dst[4] = dst[5] = dst[6] = 0; dst[7] = 18; // size + dst[8] = dst[9] = 0; // version + dst[10] = dst[11] = dst[12] = dst[13] = 0; // num packets + dst[14] = dst[15] = dst[16] = dst[17] = 0; // next data + + return DR_OK; +} + +static int +rmf_write_pheader( unsigned char *dst, int id, int sz, unsigned int ts ) +{ + /* version */ + dst[0] = dst[1] = 0; + /* length */ + dst[2] = (sz+12)>>8; dst[3] = sz+12; + /* indentifier */ + dst[4] = id>>8; dst[5] = id; + /* timestamp */ + dst[6] = ts>>24; dst[7] = ts>>16; dst[8] = ts>>8; dst[9] = ts; + /* reserved */ + dst[10] = 0; + /* flags */ + dst[11] = 0; + + return 12; +} + +static void +real_calc_challenge2( char response[64], char checksum[32], char *challenge ) +{ + const unsigned char xor_table[37] = { + 0x05, 0x18, 0x74, 0xd0, 0x0d, 0x09, 0x02, 0x53, + 0xc0, 0x01, 0x05, 0x05, 0x67, 0x03, 0x19, 0x70, + 0x08, 0x27, 0x66, 0x10, 0x10, 0x72, 0x08, 0x09, + 0x63, 0x11, 0x03, 0x71, 0x08, 0x08, 0x70, 0x02, + 0x10, 0x57, 0x05, 0x18, 0x54 + }; + char buf[128]; + char md5[16]; + int len; + int i; + + memset( response, 0, 64 ); + memset( checksum, 0, 32 ); + + buf[0] = 0xa1; buf[1] = 0xe9; buf[2] = 0x14; buf[3] = 0x9d; + buf[4] = 0x0e; buf[5] = 0x6b; buf[6] = 0x3b; buf[7] = 0x59; + memset( buf+8, 0, 120 ); + + len = strlen( challenge ); + if (len == 40) { + challenge[32] = '\0'; + len = 32; + } + memcpy( buf+8, challenge, MAX(len,56) ); + + for (i = 0; i < 37; i++) + buf[8+i] ^= xor_table[i]; + + /* compute response */ + direct_md5_sum( md5, buf, 64 ); + /* convert to ascii */ + for (i = 0; i < 16; i++) { + char a, b; + a = (md5[i] >> 4) & 15; + b = md5[i] & 15; + response[i*2+0] = ((a < 10) ? (a + 48) : (a + 87)) & 255; + response[i*2+1] = ((b < 10) ? (b + 48) : (b + 87)) & 255; + } + /* tail */ + len = strlen( response ); + direct_snputs( &response[len], "01d0a8e3", 64-len ); + + /* compute checksum */ + for (i = 0; i < len/4; i++) + checksum[i] = response[i*4]; +} + +static DirectResult +rtsp_session_open( DirectStream *stream ) +{ + DirectResult ret; + int status; + int cseq = 0; + SDPStreamDesc *streams = NULL; + int n_streams = 0; + char session[32] = {0, }; + char challen[64] = {0, }; + char buf[1280]; + int i, len; + + snprintf( buf, sizeof(buf), + "OPTIONS rtsp://%s:%d RTSP/1.0\r\n" + "CSeq: %d\r\n" + "User-Agent: DirectFB/%s\r\n" + "ClientChallenge: 9e26d33f2984236010ef6253fb1887f7\r\n" + "PlayerStarttime: [28/03/2003:22:50:23 00:00]\r\n" + "CompanyID: KnKV4M4I/B2FjJ1TToLycw==\r\n" + "GUID: 00000000-0000-0000-0000-000000000000\r\n" + "RegionData: 0\r\n", + stream->remote.host, + stream->remote.port, + ++cseq, DIRECTFB_VERSION ); + + if (net_command( stream, buf, sizeof(buf) ) != 200) + return DR_FAILURE; + + while (net_response( stream, buf, sizeof(buf) ) > 0) { + if (!strncmp( buf, "RealChallenge1:", 15 )) { + snprintf( challen, sizeof(challen), "%s", trim( buf+15 ) ); + stream->remote.real_rtsp = true; + } + } + + len = snprintf( buf, sizeof(buf), + "DESCRIBE rtsp://%s:%d%s RTSP/1.0\r\n" + "CSeq: %d\r\n" + "Accept: application/sdp\r\n" + "Bandwidth: 10485800\r\n", + stream->remote.host, + stream->remote.port, + stream->remote.path, + ++cseq ); + if (stream->remote.real_rtsp) { + snprintf( buf+len, sizeof(buf)-len, + "GUID: 00000000-0000-0000-0000-000000000000\r\n" + "RegionData: 0\r\n" + "ClientID: Linux_2.4_6.0.9.1235_play32_RN01_EN_586\r\n" + "SupportsMaximumASMBandwidth: 1\r\n" + "Require: com.real.retain-entity-for-setup\r\n" ); + } + + status = net_command( stream, buf, sizeof(buf) ); + if (status != 200) + return (status == 404) ? DR_FILENOTFOUND : DR_FAILURE; + + len = 0; + while (net_response( stream, buf, sizeof(buf) ) > 0) { + if (!strncasecmp( buf, "ETag:", 5 )) { + snprintf( session, sizeof(session), "%s", trim( buf+5 ) ); + } + else if (!strncasecmp( buf, "Content-Length:", 15 )) { + char *tmp = trim( buf+15 ); + if (sscanf( tmp, "%d", &len ) != 1) + sscanf( tmp, "bytes=%d", &len ); + } + } + + if (!len) { + D_DEBUG_AT( Direct_Stream, "Couldn't get sdp length!\n" ); + return DR_FAILURE; + } + + ret = sdp_parse( stream, len, &streams, &n_streams ); + if (ret) + return ret; + + for (i = 0; i < n_streams; i++) { + /* skip unhandled payload types */ + if (!stream->remote.real_rtsp && !streams[i].payload) + continue; + + len = snprintf( buf, sizeof(buf), + "SETUP rtsp://%s:%d%s/%s RTSP/1.0\r\n" + "CSeq: %d\r\n", + stream->remote.host, + stream->remote.port, + stream->remote.path, + streams[i].control, ++cseq ); + if (*session) { + if (*challen) { + char response[64]; + char checksum[32]; + + real_calc_challenge2( response, checksum, challen ); + len += snprintf( buf+len, sizeof(buf)-len, + "RealChallenge2: %s, sd=%s\r\n", + response, checksum ); + *challen = '\0'; + } + len += snprintf( buf+len, sizeof(buf)-len, + "%s: %s\r\n", + i ? "Session" : "If-Match", session ); + } + snprintf( buf+len, sizeof(buf)-len, + "Transport: %s\r\n", + stream->remote.real_rtsp + ? "x-pn-tng/tcp;mode=play,rtp/avp/tcp;unicast;mode=play" + : "RTP/AVP/TCP;unicast" ); + + if (net_command( stream, buf, sizeof(buf) ) != 200) { + sdp_free( streams, n_streams ); + return DR_FAILURE; + } + + while (net_response( stream, buf, sizeof(buf) ) > 0) { + if (!strncmp( buf, "Session:", 8 )) + snprintf( session, sizeof(session), "%s", trim( buf+8 ) ); + } + } + + len = snprintf( buf, sizeof(buf), + "PLAY rtsp://%s:%d%s RTSP/1.0\r\n" + "CSeq: %d\r\n", + stream->remote.host, + stream->remote.port, + stream->remote.path, + ++cseq ); + if (*session) { + len += snprintf( buf+len, sizeof(buf)-len, + "Session: %s\r\n", session ); + } + snprintf( buf+len, sizeof(buf)-len, + "Range: npt=0-\r\n" + "Connection: Close\r\n" ); + + if (net_command( stream, buf, sizeof(buf) ) != 200) { + sdp_free( streams, n_streams ); + return DR_FAILURE; + } + + /* discard remaining headers */ + while (net_response( stream, buf, sizeof(buf) ) > 0); + + /* revert to blocking mode */ + fcntl( stream->fd, F_SETFL, + fcntl( stream->fd, F_GETFL ) & ~O_NONBLOCK ); + + if (!stream->remote.real_rtsp) { + RTPPayload *p; + + stream->remote.data = D_CALLOC( 1, (n_streams+1)*sizeof(RTPPayload) ); + if (!stream->remote.data) { + sdp_free( streams, n_streams ); + return D_OOM(); + } + + p = (RTPPayload*) stream->remote.data; + for (i = 0; i < n_streams; i++) { + if (streams[i].payload) { + *p = *(streams[i].payload); + p->pt = streams[i].pt; + p++; + } + } + } + + stream->remote.real_pack = true; + if (n_streams == 1 && streams[0].mime) { + if (!strcmp( streams[0].mime, "audio/mpeg" ) || + !strcmp( streams[0].mime, "audio/aac" ) || + !strcmp( streams[0].mime, "video/mpeg" ) || + !strcmp( streams[0].mime, "video/mpegts" )) + { + stream->mime = D_STRDUP( streams[0].mime ); + stream->remote.real_pack = false; + } + } + if (stream->remote.real_pack) { + ret = rmf_write_header( streams, n_streams, + &stream->cache, &stream->cache_size ); + if (ret) { + sdp_free( streams, n_streams ); + return ret; + } + stream->mime = D_STRDUP( "application/vnd.rn-realmedia" ); + } + + sdp_free( streams, n_streams ); + + return DR_OK; +} + +static DirectResult +rvp_read_packet( DirectStream *stream ) +{ + unsigned char buf[9]; + int size; + int len; + unsigned char id; + unsigned int ts; + + while (1) { + do { + size = recv( stream->fd, buf, 1, MSG_WAITALL ); + if (size < 1) + return DR_EOF; + } while (buf[0] != '$'); + + size = recv( stream->fd, buf, 7, MSG_WAITALL ); + if (size < 7) + return DR_EOF; + + len = (buf[0] << 16) + (buf[1] << 8) + buf[2]; + id = buf[3]; + if (id != 0x40 && id != 0x42) { + if (buf[5] == 0x06) // EOS + return DR_EOF; + size = recv( stream->fd, buf, 9, MSG_WAITALL ); + if (size < 9) + return DR_EOF; + id = buf[5]; + len -= 9; + } + id = (id >> 1) & 1; + + size = recv( stream->fd, buf, 6, MSG_WAITALL ); + if (size < 6) + return DR_EOF; + ts = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; + + len -= 10; + if (len > 0) { + unsigned char *dst; + + size = len + 12; + stream->cache = D_REALLOC( stream->cache, stream->cache_size+size ); + if (!stream->cache) + return D_OOM(); + dst = stream->cache+stream->cache_size; + stream->cache_size += size; + + dst += rmf_write_pheader( dst, id, len, ts ); + + while (len) { + size = recv( stream->fd, dst, len, MSG_WAITALL ); + if (size < 1) + return DR_EOF; + dst += size; + len -= size; + } + break; + } + } + + return DR_OK; +} + +static DirectResult +rtp_read_packet( DirectStream *stream ) +{ + RTPPayload *payloads = (RTPPayload*)stream->remote.data; + unsigned char buf[12]; + int size; + int len; + unsigned char id; + unsigned short seq; + unsigned int ts; + int skip; + + while (1) { + RTPPayload *p; + + do { + size = recv( stream->fd, buf, 1, MSG_WAITALL ); + if (size < 1) + return DR_EOF; + } while (buf[0] != '$'); + + size = recv( stream->fd, buf, 3, MSG_WAITALL ); + if (size < 3) + return DR_EOF; + + id = buf[0]; + len = (buf[1] << 8) | buf[2]; + if (len < 12) + continue; + + size = recv( stream->fd, buf, 12, MSG_WAITALL ); + if (size < 12) + return DR_EOF; + len -= 12; + + if ((buf[0] & 0xc0) != (2 << 6)) + D_DEBUG_AT( Direct_Stream, "Bad RTP version %d!\n", buf[0] ); + seq = (buf[2] << 8) | buf[3]; + ts = (buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7]; + + for (p = payloads; p->pt; p++) { + if (p->pt == (buf[1] & 0x7f)) + break; + } + + switch (p->pt) { + case 0: // Unhandled + skip = len; + break; + case 14: // MPEG Audio + skip = 4; + break; + case 32: // MPEG Video + size = recv( stream->fd, buf, 1, MSG_WAITALL ); + if (size < 1) + return DR_EOF; + len--; + skip = 3; + if (buf[0] & (1 << 2)) + skip += 4; + break; + case 34: // H263 + size = recv( stream->fd, buf, 1, MSG_WAITALL ); + if (size < 1) + return DR_EOF; + len--; + skip = 3; + if (buf[0] & (1 << 7)) + skip += 4; + if (buf[0] & (1 << 6)) + skip += 4; + break; + default: + skip = 0; + break; + } + + while (skip) { + size = recv( stream->fd, buf, MIN(skip,12), MSG_WAITALL ); + if (size < 1) + return DR_EOF; + len -= size; + skip -= size; + } + + if (len > 0) { + unsigned char *dst; + + size = len; + if (stream->remote.real_pack) { + size += 12; + if (p->type == PAYLOAD_VIDEO) + size += 7; + } + + stream->cache = D_REALLOC( stream->cache, stream->cache_size+size ); + if (!stream->cache) + return D_OOM(); + dst = stream->cache+stream->cache_size; + stream->cache_size += size; + + if (stream->remote.real_pack) { + if (p->type == PAYLOAD_VIDEO) { + dst += rmf_write_pheader( dst, id, len+7, ts ); + dst[0] = 0x81; + dst[1] = 0x01; + dst[2] = (len | 0x4000)>>8; dst[3] = len; + dst[4] = (len | 0x4000)>>8; dst[5] = len; + dst[6] = seq; + dst += 7; + } else { + dst += rmf_write_pheader( dst, id, len, ts ); + } + } + + while (len) { + size = recv( stream->fd, dst, len, MSG_WAITALL ); + if (size < 1) + return DR_EOF; + dst += size; + len -= size; + } + break; + } + } + + return DR_OK; +} + +static DirectResult +rtsp_peek( DirectStream *stream, + unsigned int length, + int offset, + void *buf, + unsigned int *read_out ) +{ + DirectResult ret; + unsigned int len; + + if (offset < 0) + return DR_UNSUPPORTED; + + len = length + offset; + while (len > stream->cache_size) { + ret = (stream->remote.real_rtsp) + ? rvp_read_packet( stream ) + : rtp_read_packet( stream ); + if (ret) { + if (stream->cache_size < offset) + return ret; + break; + } + } + + len = MIN( stream->cache_size-offset, length ); + direct_memcpy( buf, stream->cache+offset, len ); + + if (read_out) + *read_out = len; + + return DR_OK; +} + +static DirectResult +rtsp_read( DirectStream *stream, + unsigned int length, + void *buf, + unsigned int *read_out ) +{ + DirectResult ret; + unsigned int size = 0; + + while (size < length) { + if (stream->cache_size) { + unsigned int len = MIN( stream->cache_size, length-size ); + + direct_memcpy( buf+size, stream->cache, len ); + size += len; + stream->cache_size -= len; + + if (stream->cache_size) { + direct_memcpy( stream->cache, + stream->cache+len, stream->cache_size ); + } else { + D_FREE( stream->cache ); + stream->cache = NULL; + } + } + + if (size < length) { + ret = (stream->remote.real_rtsp) + ? rvp_read_packet( stream ) + : rtp_read_packet( stream ); + if (ret) { + if (!size) + return ret; + break; + } + } + } + + stream->offset += size; + + if (read_out) + *read_out = size; + + return DR_OK; +} + +static DirectResult +rtsp_open( DirectStream *stream, const char *filename ) +{ + DirectResult ret; + + stream->remote.port = RTSP_PORT; + + ret = net_open( stream, filename, IPPROTO_TCP ); + if (ret) + return ret; + + ret = rtsp_session_open( stream ); + if (ret) { + close( stream->remote.sd ); + return ret; + } + + stream->peek = rtsp_peek; + stream->read = rtsp_read; + + return DR_OK; +} + +#endif /* DIRECT_BUILD_NETWORK */ + +/************************** End of Network Support ***************************/ + +static DirectResult +pipe_wait( DirectStream *stream, + unsigned int length, + struct timeval *tv ) +{ + fd_set s; + + if (stream->cache_size >= length) + return DR_OK; + + FD_ZERO( &s ); + FD_SET( stream->fd, &s ); + + switch (select( stream->fd+1, &s, NULL, NULL, tv )) { + case 0: + if (!tv && !stream->cache_size) + return DR_EOF; + return DR_TIMEOUT; + case -1: + return errno2result( errno ); + } + + return DR_OK; +} + +static DirectResult +pipe_peek( DirectStream *stream, + unsigned int length, + int offset, + void *buf, + unsigned int *read_out ) +{ + unsigned int size = length; + int len; + + if (offset < 0) + return DR_UNSUPPORTED; + + len = length + offset; + if (len > stream->cache_size) { + ssize_t s; + + stream->cache = D_REALLOC( stream->cache, len ); + if (!stream->cache) { + stream->cache_size = 0; + return D_OOM(); + } + + s = read( stream->fd, + stream->cache + stream->cache_size, + len - stream->cache_size ); + if (s < 0) { + if (errno != EAGAIN || stream->cache_size == 0) + return errno2result( errno ); + s = 0; + } + + stream->cache_size += s; + if (stream->cache_size <= offset) + return DR_BUFFEREMPTY; + + size = stream->cache_size - offset; + } + + direct_memcpy( buf, stream->cache+offset, size ); + + if (read_out) + *read_out = size; + + return DR_OK; +} + +static DirectResult +pipe_read( DirectStream *stream, + unsigned int length, + void *buf, + unsigned int *read_out ) +{ + unsigned int size = 0; + + if (stream->cache_size) { + size = MIN( stream->cache_size, length ); + + direct_memcpy( buf, stream->cache, size ); + + length -= size; + stream->cache_size -= size; + + if (stream->cache_size) { + direct_memcpy( stream->cache, + stream->cache+size, stream->cache_size ); + } else { + D_FREE( stream->cache ); + stream->cache = NULL; + } + } + + if (length) { + ssize_t s; + + s = read( stream->fd, buf+size, length-size ); + switch (s) { + case 0: + if (!size) + return DR_EOF; + break; + case -1: + if (!size) { + return (errno == EAGAIN) + ? DR_BUFFEREMPTY + : errno2result( errno ); + } + break; + default: + size += s; + break; + } + } + + stream->offset += size; + + if (read_out) + *read_out = size; + + return DR_OK; +} + +/*****************************************************************************/ + +static DirectResult +file_peek( DirectStream *stream, + unsigned int length, + int offset, + void *buf, + unsigned int *read_out ) +{ + DirectResult ret = DR_OK; + ssize_t size; + + if (lseek( stream->fd, offset, SEEK_CUR ) < 0) + return DR_FAILURE; + + size = read( stream->fd, buf, length ); + switch (size) { + case 0: + ret = DR_EOF; + break; + case -1: + ret = (errno == EAGAIN) + ? DR_BUFFEREMPTY + : errno2result( errno ); + size = 0; + break; + } + + if (lseek( stream->fd, - offset - size, SEEK_CUR ) < 0) + return DR_FAILURE; + + if (read_out) + *read_out = size; + + return ret; +} + +static DirectResult +file_read( DirectStream *stream, + unsigned int length, + void *buf, + unsigned int *read_out ) +{ + ssize_t size; + + size = read( stream->fd, buf, length ); + switch (size) { + case 0: + return DR_EOF; + case -1: + if (errno == EAGAIN) + return DR_BUFFEREMPTY; + return errno2result( errno ); + } + + stream->offset += size; + + if (read_out) + *read_out = size; + + return DR_OK; +} + +static DirectResult +file_seek( DirectStream *stream, unsigned int offset ) +{ + off_t off; + + off = lseek( stream->fd, offset, SEEK_SET ); + if (off < 0) + return DR_FAILURE; + + stream->offset = off; + + return DR_OK; +} + +static DirectResult +file_open( DirectStream *stream, const char *filename, int fileno ) +{ + if (filename) + stream->fd = open( filename, O_RDONLY | O_NONBLOCK ); + else + stream->fd = dup( fileno ); + + if (stream->fd < 0) + return errno2result( errno ); + + fcntl( stream->fd, F_SETFL, + fcntl( stream->fd, F_GETFL ) | O_NONBLOCK ); + + if (lseek( stream->fd, 0, SEEK_CUR ) < 0 && errno == ESPIPE) { + stream->length = -1; + stream->wait = pipe_wait; + stream->peek = pipe_peek; + stream->read = pipe_read; + } + else { + struct stat s; + + if (fstat( stream->fd, &s ) < 0) + return errno2result( errno ); + + stream->length = s.st_size; + stream->peek = file_peek; + stream->read = file_read; + stream->seek = file_seek; + } + + return DR_OK; +} + +/*****************************************************************************/ + +DirectResult +direct_stream_create( const char *filename, + DirectStream **ret_stream ) +{ + DirectStream *stream; + DirectResult ret; + + D_ASSERT( filename != NULL ); + D_ASSERT( ret_stream != NULL ); + + stream = D_CALLOC( 1, sizeof(DirectStream) ); + if (!stream) + return D_OOM(); + + D_MAGIC_SET( stream, DirectStream ); + + stream->ref = 1; + stream->fd = -1; + + if (!strncmp( filename, "stdin:/", 7 )) { + ret = file_open( stream, NULL, STDIN_FILENO ); + } + else if (!strncmp( filename, "file:/", 6 )) { + ret = file_open( stream, filename+6, -1 ); + } + else if (!strncmp( filename, "fd:/", 4 )) { + ret = (filename[4] >= '0' && filename[4] <= '9') + ? file_open( stream, NULL, atoi(filename+4) ) : DR_INVARG; + } +#if DIRECT_BUILD_NETWORK + else if (!strncmp( filename, "http://", 7 ) || + !strncmp( filename, "unsv://", 7 )) { + ret = http_open( stream, filename+7 ); + } + else if (!strncmp( filename, "ftp://", 6 )) { + ret = ftp_open( stream, filename+6 ); + } + else if (!strncmp( filename, "rtsp://", 7 )) { + ret = rtsp_open( stream, filename+7 ); + } + else if (!strncmp( filename, "tcp://", 6 )) { + ret = net_open( stream, filename+6, IPPROTO_TCP ); + } + else if (!strncmp( filename, "udp://", 6 )) { + ret = net_open( stream, filename+6, IPPROTO_UDP ); + } +#endif + else { + ret = file_open( stream, filename, -1 ); + } + + if (ret) { + direct_stream_close( stream ); + D_FREE( stream ); + return ret; + } + + *ret_stream = stream; + + return DR_OK; +} + +DirectStream* +direct_stream_dup( DirectStream *stream ) +{ + D_ASSERT( stream != NULL ); + + D_MAGIC_ASSERT( stream, DirectStream ); + + stream->ref++; + + return stream; +} + +int +direct_stream_fileno( DirectStream *stream ) +{ + D_ASSERT( stream != NULL ); + + D_MAGIC_ASSERT( stream, DirectStream ); + + return stream->fd; +} + +bool +direct_stream_seekable( DirectStream *stream ) +{ + D_ASSERT( stream != NULL ); + + D_MAGIC_ASSERT( stream, DirectStream ); + + return stream->seek ? true : false; +} + +bool +direct_stream_remote( DirectStream *stream ) +{ + D_ASSERT( stream != NULL ); + + D_MAGIC_ASSERT( stream, DirectStream ); + +#if DIRECT_BUILD_NETWORK + if (stream->remote.host) + return true; +#endif + return false; +} + +const char* +direct_stream_mime( DirectStream *stream ) +{ + D_ASSERT( stream != NULL ); + + D_MAGIC_ASSERT( stream, DirectStream ); + + return stream->mime; +} + +unsigned int +direct_stream_offset( DirectStream *stream ) +{ + D_ASSERT( stream != NULL ); + + D_MAGIC_ASSERT( stream, DirectStream ); + + return stream->offset; +} + +unsigned int +direct_stream_length( DirectStream *stream ) +{ + D_ASSERT( stream != NULL ); + + D_MAGIC_ASSERT( stream, DirectStream ); + + return (stream->length >= 0) ? stream->length : stream->offset; +} + +DirectResult +direct_stream_wait( DirectStream *stream, + unsigned int length, + struct timeval *tv ) +{ + D_ASSERT( stream != NULL ); + + D_MAGIC_ASSERT( stream, DirectStream ); + + if (length && stream->wait) + return stream->wait( stream, length, tv ); + + return DR_OK; +} + +DirectResult +direct_stream_peek( DirectStream *stream, + unsigned int length, + int offset, + void *buf, + unsigned int *read_out ) +{ + D_ASSERT( stream != NULL ); + D_ASSERT( length != 0 ); + D_ASSERT( buf != NULL ); + + D_MAGIC_ASSERT( stream, DirectStream ); + + if (stream->length >= 0 && (stream->offset + offset) >= stream->length) + return DR_EOF; + + if (stream->peek) + return stream->peek( stream, length, offset, buf, read_out ); + + return DR_UNSUPPORTED; +} + +DirectResult +direct_stream_read( DirectStream *stream, + unsigned int length, + void *buf, + unsigned int *read_out ) +{ + D_ASSERT( stream != NULL ); + D_ASSERT( length != 0 ); + D_ASSERT( buf != NULL ); + + D_MAGIC_ASSERT( stream, DirectStream ); + + if (stream->length >= 0 && stream->offset >= stream->length) + return DR_EOF; + + if (stream->read) + return stream->read( stream, length, buf, read_out ); + + return DR_UNSUPPORTED; +} + +DirectResult +direct_stream_seek( DirectStream *stream, + unsigned int offset ) +{ + D_ASSERT( stream != NULL ); + + D_MAGIC_ASSERT( stream, DirectStream ); + + if (stream->offset == offset) + return DR_OK; + + if (stream->length >= 0 && offset > stream->length) + offset = stream->length; + + if (stream->seek) + return stream->seek( stream, offset ); + + return DR_UNSUPPORTED; +} + +static void +direct_stream_close( DirectStream *stream ) +{ +#if DIRECT_BUILD_NETWORK + if (stream->remote.host) { + D_FREE( stream->remote.host ); + stream->remote.host = NULL; + } + + if (stream->remote.user) { + D_FREE( stream->remote.user ); + stream->remote.user = NULL; + } + + if (stream->remote.pass) { + D_FREE( stream->remote.pass ); + stream->remote.pass = NULL; + } + + if (stream->remote.auth) { + D_FREE( stream->remote.auth ); + stream->remote.auth = NULL; + } + + if (stream->remote.path) { + D_FREE( stream->remote.path ); + stream->remote.path = NULL; + } + + if (stream->remote.addr) { + freeaddrinfo( stream->remote.addr ); + stream->remote.addr = NULL; + } + + if (stream->remote.data) { + D_FREE( stream->remote.data ); + stream->remote.data = NULL; + } + + if (stream->remote.sd > 0) { + close( stream->remote.sd ); + stream->remote.sd = -1; + } +#endif + + if (stream->mime) { + D_FREE( stream->mime ); + stream->mime = NULL; + } + + if (stream->cache) { + D_FREE( stream->cache ); + stream->cache = NULL; + stream->cache_size = 0; + } + + if (stream->fd >= 0) { + fcntl( stream->fd, F_SETFL, + fcntl( stream->fd, F_GETFL ) & ~O_NONBLOCK ); + close( stream->fd ); + stream->fd = -1; + } +} + +void +direct_stream_destroy( DirectStream *stream ) +{ + D_ASSERT( stream != NULL ); + + D_MAGIC_ASSERT( stream, DirectStream ); + + if (--stream->ref == 0) { + direct_stream_close( stream ); + + D_FREE( stream ); + } +} -- cgit