示例#1
0
TFTYPE
serverThread(void *arg)
{
  STREAMING_SERVER *server = arg;
  server->state = STREAMING_ACCEPTING;

  while (server->state == STREAMING_ACCEPTING)
    {
      struct sockaddr_in addr;
      socklen_t addrlen = sizeof(struct sockaddr_in);
      int sockfd =
	accept(server->socket, (struct sockaddr *) &addr, &addrlen);

      if (sockfd > 0)
	{
	  // Create a new process and transfer the control to that
	  RTMP_Log(RTMP_LOGDEBUG, "%s: accepted connection from %s\n", __FUNCTION__,
	      inet_ntoa(addr.sin_addr));
	  processTCPrequest(server, sockfd);
	  RTMP_Log(RTMP_LOGDEBUG, "%s: processed request\n", __FUNCTION__);
	}
      else
	{
	  RTMP_Log(RTMP_LOGERROR, "%s: accept failed", __FUNCTION__);
	}
    }
  server->state = STREAMING_STOPPED;
  TFRET();
}
示例#2
0
文件: log.c 项目: taolinbg/video_sdk
void RTMP_LogHex(int level, const uint8_t *data, unsigned long len)
{
	unsigned long i;
	char line[50], *ptr;

	if ( level > RTMP_debuglevel )
		return;

	ptr = line;

	for(i=0; i<len; i++) {
		*ptr++ = hexdig[0x0f & (data[i] >> 4)];
		*ptr++ = hexdig[0x0f & data[i]];
		if ((i & 0x0f) == 0x0f) {
			*ptr = '\0';
			ptr = line;
			RTMP_Log(level, "%s", line);
		} else {
			*ptr++ = ' ';
		}
	}
	if (i & 0x0f) {
		*ptr = '\0';
		RTMP_Log(level, "%s", line);
	}
}
示例#3
0
文件: yle.c 项目: qtportal/yle-dl
int ExecuteRPCResult(RTMP *r, AMFObject *obj, YLENGStream *yle, int *redirected) {
  AVal rpcKind;
  AVal mediaxml;
  char *playurl, *tvpayOnly;
  AVal parsedHost = {NULL, 0};
  AVal parsedPlaypath = {NULL, 0};
  AVal parsedApp = {NULL, 0};

  *redirected = FALSE;

  AMFProp_GetString(AMF_GetProp(obj, NULL, 3), &rpcKind);
  
  if (!AVMATCH(&rpcKind, &av_e0))
    return TRUE;

  AMFProp_GetString(AMF_GetProp(obj, NULL, 4), &mediaxml);
  RTMP_Log(RTMP_LOGDEBUG, "clip data:\n%.*s", mediaxml.av_len, mediaxml.av_val);

  playurl = GetXMLNodeContent(mediaxml.av_val, "url");
  if (!playurl)
    return FALSE;

  if (!ParseYLEPlaypath(playurl, &parsedHost, &parsedApp, &parsedPlaypath)) {
    RTMP_Log(RTMP_LOGERROR, "Couldn't parse stream url %s!", playurl);
    free(playurl);
    return FALSE;
  }

  // FIXME: old r->Link.playpath may be leaked
  r->Link.playpath.av_len = parsedPlaypath.av_len;
  r->Link.playpath.av_val = malloc(parsedPlaypath.av_len*sizeof(char));
  strncpy(r->Link.playpath.av_val, parsedPlaypath.av_val, r->Link.playpath.av_len);

  RTMP_Log(RTMP_LOGDEBUG, "New playpath   : %.*s",
           r->Link.playpath.av_len, r->Link.playpath.av_val);

  if (!AVMATCH(&parsedHost, &r->Link.hostname)) {
    RTMP_Log(RTMP_LOGDEBUG, "Redirected to another server: %.*s",
             parsedHost.av_len, parsedHost.av_val);

    // FIXME: old value may be leaked
    r->Link.hostname.av_val = malloc(parsedHost.av_len*sizeof(char));
    r->Link.hostname.av_len = parsedHost.av_len;
    memcpy(r->Link.hostname.av_val, parsedHost.av_val, parsedHost.av_len);

    *redirected = TRUE;
  }

  tvpayOnly = GetXMLNodeContent(mediaxml.av_val, "tvpayOnly");
  if (tvpayOnly) {
    yle->tvFeeRequired = (strcmp(tvpayOnly, "false")!=0);
    free(tvpayOnly);
  }

  free(playurl);

  return RTMP_SendCreateStream(r);
}
示例#4
0
文件: log.c 项目: bygreencn/rtmpdump
void RTMP_LogHexString ( int level, const uint8_t *data, unsigned long len )
{
#define BP_OFFSET 9
#define BP_GRAPH 60
#define BP_LEN	80
	char	line[BP_LEN];
	unsigned long i;

	if ( !data || level > RTMP_debuglevel )
	{
		return;
	}

	/* in case len is zero */
	line[0] = '\0';

	for ( i = 0 ; i < len ; i++ )
	{
		int n = i % 16;
		unsigned off;

		if ( !n )
		{
			if ( i )
			{
				RTMP_Log ( level, "%s", line );
			}

			memset ( line, ' ', sizeof ( line ) - 2 );
			line[sizeof ( line ) - 2] = '\0';

			off = i % 0x0ffffU;

			line[2] = hexdig[0x0f & ( off >> 12 )];
			line[3] = hexdig[0x0f & ( off >> 8 )];
			line[4] = hexdig[0x0f & ( off >> 4 )];
			line[5] = hexdig[0x0f & off];
			line[6] = ':';
		}

		off = BP_OFFSET + n * 3 + ( ( n >= 8 ) ? 1 : 0 );
		line[off] = hexdig[0x0f & ( data[i] >> 4 )];
		line[off + 1] = hexdig[0x0f & data[i]];

		off = BP_GRAPH + n + ( ( n >= 8 ) ? 1 : 0 );

		if ( isprint ( data[i] ) )
		{
			line[BP_GRAPH + n] = data[i];
		}
		else
		{
			line[BP_GRAPH + n] = '.';
		}
	}

	RTMP_Log ( level, "%s", line );
}
void QRtmp::setSwfAge(int age)
{
    if(age < 0)
        RTMP_Log(RTMP_LOGERROR, "SWF Age must be non-negative, ignoring\n");
    else
        m_swfAge = age;
}
void QRtmp::setConnData(const QByteArray &data)
{
    m_connData = data;
    AVal connData = aval(m_connData);
    if(!RTMP_SetOpt(m_rtmp, &av_conn, &connData))
        RTMP_Log(RTMP_LOGERROR, "Invalid AMF parameter");
}
void QRtmp::setSwfSize(uint32_t swfSize)
{
    if(swfSize <= 0)
        RTMP_Log(RTMP_LOGERROR, "SWF Size must be at least 1, ignoring\n");
    else
        m_swfSize = swfSize;
}
示例#8
0
STREAMING_SERVER *
startStreaming(const char *address, int port)
{
	struct sockaddr_in addr;
	int sockfd, tmp;
	STREAMING_SERVER *server;

	sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (sockfd == -1)
	{
		RTMP_Log(RTMP_LOGERROR, "%s, couldn't create socket", __FUNCTION__);
		return 0;
	}

	tmp = 1;
	setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,
		(char *)&tmp, sizeof(tmp));

	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = inet_addr(address);	//htonl(INADDR_ANY);
	addr.sin_port = htons(port);

	if (bind(sockfd, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) ==
		-1)
	{
		RTMP_Log(RTMP_LOGERROR, "%s, TCP bind failed for port number: %d", __FUNCTION__,
			port);
		return 0;
	}

	if (listen(sockfd, 10) == -1)
	{
		RTMP_Log(RTMP_LOGERROR, "%s, listen failed", __FUNCTION__);
		closesocket(sockfd);
		return 0;
	}

	server = (STREAMING_SERVER *)calloc(1, sizeof(STREAMING_SERVER));
	server->socket = sockfd;

	//ThreadCreate(serverThread, server);
	std::thread theThread(serverThread, server);
	theThread.join();

	return server;
}
void QRtmp::setSwfHash(const QByteArray &hash, bool isHex)
{
    QByteArray tmp = isHex ? QByteArray::fromHex(hash) : hash;

    if(tmp.size() != RTMP_SWF_HASHLEN)
        RTMP_Log(RTMP_LOGWARNING, "Couldn't parse swf hash hex string, not hexstring or not %d bytes, ignoring!", RTMP_SWF_HASHLEN);
    else
        m_swfHash = tmp;
}
void QRtmp::setSkipKeyFrames(int nSkipKeyFrames)
{
    if(nSkipKeyFrames < 0)
    {
        RTMP_Log(RTMP_LOGERROR, "Number of keyframes skipped must be greater or equal zero, using zero!");
        m_nSkipKeyFrames = 0;
    }
    else
        m_nSkipKeyFrames = nSkipKeyFrames;
}
示例#11
0
TFTYPE
serverThread(void *arg)
{
  STREAMING_SERVER *server = arg;
  server->state = STREAMING_ACCEPTING;

  while (server->state == STREAMING_ACCEPTING)
    {
      struct sockaddr_in addr;
      socklen_t addrlen = sizeof(struct sockaddr_in);
      int sockfd =
	accept(server->socket, (struct sockaddr *) &addr, &addrlen);

      if (sockfd > 0)
	{
#ifdef linux
	  struct sockaddr_in dest;
	  char destch[16];
	  socklen_t destlen = sizeof(struct sockaddr_in);
	  getsockopt(sockfd, SOL_IP, SO_ORIGINAL_DST, &dest, &destlen);
	  strcpy(destch, inet_ntoa(dest.sin_addr));
	  RTMP_Log(RTMP_LOGDEBUG, "%s: accepted connection from %s to %s\n", __FUNCTION__,
	      inet_ntoa(addr.sin_addr), destch);
#else
	  RTMP_Log(RTMP_LOGDEBUG, "%s: accepted connection from %s\n", __FUNCTION__,
	      inet_ntoa(addr.sin_addr));
#endif
	  /* Create a new thread and transfer the control to that */
	  doServe(server, sockfd);
	  RTMP_Log(RTMP_LOGDEBUG, "%s: processed request\n", __FUNCTION__);
	}
      else
	{
	  RTMP_Log(RTMP_LOGERROR, "%s: accept failed", __FUNCTION__);
	}
    }
  server->state = STREAMING_STOPPED;
  TFRET();
}
void QRtmp::stopBlocking(int timeoutSec)
{
    if(isRunning())
    {
        QEventLoop waiter;
        connect(this, SIGNAL(finished()), &waiter, SLOT(quit()));
        QTimer::singleShot(timeoutSec * 1000, &waiter, SLOT(quit()));
        m_stop = true;
        waiter.exec();

        if(isRunning()) // timeout exceeded
            RTMP_Log(RTMP_LOGWARNING, "Thread has not finished while timeout has exceeded");
    }
}
示例#13
0
文件: yle.c 项目: qtportal/yle-dl
int ExecuteAuthenticationDetails(RTMP *r, AMFObject *proplist, YLENGStream *yle) {
  long authResult = -1;
  int i;
  
  for (i=0; i<AMF_CountProp(proplist); i++) {
    AVal name;
    AMFObjectProperty *prop = AMF_GetProp(proplist, NULL, i);
    AMFProp_GetName(prop, &name);

    if (AVMATCH(&name, &av_locatedInBroadcastTerritory)) {
      yle->locatedInBroadcastTerritory = AMFProp_GetBoolean(prop);
    } else if (AVMATCH(&name, &av_randomAuth)) {
      authResult = ((long)AMFProp_GetNumber(prop) + 447537687) % 6834253;
    } else if (AVMATCH(&name, &av_tvFeeActivated)) {
      yle->tvFeeActivated = AMFProp_GetBoolean(prop);
    }
  }

  if (authResult != -1) {
    RTMPPacket packet;
    char pbuf[128], *pend = pbuf+sizeof(pbuf);

    packet.m_nChannel = 0x03;   // control channel
    packet.m_headerType = RTMP_PACKET_SIZE_LARGE;
    packet.m_packetType = 0x11; // FLEX MESSAGE
    packet.m_nTimeStamp = RTMP_GetTime();
    packet.m_nInfoField2 = 0;
    packet.m_hasAbsTimestamp = 0;

    packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE;
    char *enc = packet.m_body;
    *enc++ = 0x00;   // Unknown
    enc = AMF_EncodeString(enc, pend, &av_authenticateRandomNumber);
    enc = AMF_EncodeNumber(enc, pend, 0);
    *enc++ = AMF_NULL;
    enc = AMF_EncodeNumber(enc, pend, (double)authResult);

    packet.m_nBodySize = enc-packet.m_body;

    RTMP_Log(RTMP_LOGDEBUG, "sending authenticateRandomNumber");

    return RTMP_SendPacket(r, &packet, FALSE);
  }

  return FALSE;
}
示例#14
0
文件: yle.c 项目: qtportal/yle-dl
int RequestData(RTMP *r, YLENGStream *yle) {
  RTMPPacket packet;
  char pbuf[128], *pend = pbuf+sizeof(pbuf);
  AVal clipID;

  packet.m_nChannel = 0x03;   // control channel
  packet.m_headerType = RTMP_PACKET_SIZE_LARGE;
  packet.m_packetType = 0x11; // FLEX MESSAGE
  packet.m_nTimeStamp = RTMP_GetTime();
  packet.m_nInfoField2 = 0;
  packet.m_hasAbsTimestamp = 0;

  packet.m_body = pbuf + RTMP_MAX_HEADER_SIZE;
  char *enc = packet.m_body;
  *enc++ = 0x00;   // Unknown
  enc = AMF_EncodeString(enc, pend, &av_requestData);
  enc = AMF_EncodeNumber(enc, pend, 0);
  *enc++ = AMF_NULL;
  enc = AMF_EncodeString(enc, pend, &av_e0);

  if ((r->Link.lFlags & RTMP_LF_LIVE) != 0) {
    char *tmp = malloc(yle->clipID.av_len+12);
    strcpy(tmp, "streams/fi/");
    strncat(tmp, yle->clipID.av_val, yle->clipID.av_len);
    STR2AVAL(clipID, tmp);
    enc = AMF_EncodeString(enc, pend, &clipID);
    free(tmp);
  } else {
    char *tmp = malloc(yle->clipID.av_len+2);
    strcpy(tmp, "/");
    strncat(tmp, yle->clipID.av_val, yle->clipID.av_len);
    STR2AVAL(clipID, tmp);
    enc = AMF_EncodeString(enc, pend, &clipID);
    free(tmp);
  }

  if (!enc) {
    RTMP_Log(RTMP_LOGERROR, "Buffer too short in RequestData");
    return FALSE;
  }

  packet.m_nBodySize = enc-packet.m_body;

  return RTMP_SendPacket(r, &packet, FALSE);
}
示例#15
0
文件: yle.c 项目: qtportal/yle-dl
int ExecuteInvokedMethod(RTMP *r, const AVal *method, AMFObject *obj, void *ctx) {
  struct YLENGStream *yle = (struct YLENGStream *)ctx;
  int redirected = FALSE;

  if (!yle || yle->yleAuth == 0)
    return RTMP_CB_NOT_HANDLED;

  if (AVMATCH(method, &av_authenticationDetails)) {
    AMFObject list;
    AMFProp_GetObject(AMF_GetProp(obj, NULL, 3), &list);
    if (!ExecuteAuthenticationDetails(r, &list, yle))
      return RTMP_CB_ERROR_STOP;
    
    if (yle->clipID.av_len) {
      if (!RequestData(r, yle))
        return RTMP_CB_ERROR_STOP;
    } else if (!RTMP_SendCreateStream(r)) {
      return RTMP_CB_ERROR_STOP;
    }
        
    return RTMP_CB_SUCCESS;

  } else if (AVMATCH(method, &av_randomNumberAuthenticated)) {
    ExecuteRandomNumberAuthenticated(r);
    return RTMP_CB_SUCCESS;

  } else if (AVMATCH(method, &av_rpcResult)) {
    if (!ExecuteRPCResult(r, obj, yle, &redirected))
      return RTMP_CB_ERROR_STOP;

    //if (redirected && !ConnectRedirected(r, r->Link.seekTime, yle))
    //  return RTMP_CB_ERROR_STOP;

    return RTMP_CB_SUCCESS;

  } else if (AVMATCH(method, &av_rpcError)) {
    RTMP_Log(RTMP_LOGERROR, "RTMP server returned RPC error");
    return RTMP_CB_ERROR_STOP;
  }

  return RTMP_CB_NOT_HANDLED;
}
void QRtmp::start(const QString &rtmpString)
{
    m_error.clear();
    m_stop = false;

    if(dStartOffset > 0 && m_bLiveStream)
    {
        RTMP_Log(RTMP_LOGWARNING, "Can't seek in a live stream, ignoring --start option");
        dStartOffset = 0;
    }

    m_rtmpString = rtmpString.toLocal8Bit();
    RTMP_SetupURL(m_rtmp, m_rtmpString.data());

    /* Try to keep the stream moving if it pauses on us */
    if(!m_bLiveStream && !(rtmpProto(m_proto) & RTMP_FEATURE_HTTP))
        m_rtmp->Link.lFlags |= RTMP_LF_BUFX;

    QThread::start();
}
void QRtmp::setRtmpUrl(const QString &url)
{
    AVal parsedHost, parsedApp, parsedPlaypath;
    unsigned int parsedPort = 0;
    int parsedProtocol = RTMP_PROTOCOL_UNDEFINED;

    if(!RTMP_ParseURL(url.toLocal8Bit().data(), &parsedProtocol, &parsedHost, &parsedPort, &parsedPlaypath, &parsedApp))
        RTMP_Log(RTMP_LOGWARNING, "Couldn't parse the specified url!");
    else
    {
        // use constructor instead of QByteArray::fromRawData to perform a deep copy
        if(m_hostname.isEmpty())
            m_hostname = QByteArray(parsedHost.av_val, parsedHost.av_len);
        if(m_port == -1)
            m_port = parsedPort;
        if(m_playpath.isEmpty())
            m_playpath = QByteArray(parsedPlaypath.av_val, parsedPlaypath.av_len);
        if(m_app.isEmpty())
            m_app = QByteArray(parsedApp.av_val, parsedApp.av_len);
        if(m_proto == Undefined)
            m_proto = qrtmpProto(parsedProtocol);
    }
}
示例#18
0
void
stopStreaming(STREAMING_SERVER * server)
{
  assert(server);

  if (server->state != STREAMING_STOPPED)
    {
      if (server->state == STREAMING_IN_PROGRESS)
	{
	  server->state = STREAMING_STOPPING;

	  // wait for streaming threads to exit
	  while (server->state != STREAMING_STOPPED)
	    msleep(1);
	}

      if (closesocket(server->socket))
	RTMP_Log(RTMP_LOGERROR, "%s: Failed to close listening socket, error %d",
	    GetSockError());

      server->state = STREAMING_STOPPED;
    }
}
示例#19
0
HTTPResult
HTTP_get(struct HTTP_ctx *http, const char *url, HTTP_read_callback *cb) {
    char *host, *path;
    char *p1, *p2;
    char hbuf[256];
    int port = 80;
#ifdef CRYPTO
    int ssl = 0;
#endif
    int hlen, flen = 0;
    int rc, i;
    int len_known;
    HTTPResult ret = HTTPRES_OK;
    struct sockaddr_in sa;
    RTMPSockBuf sb = {0};

    http->status = -1;

    memset(&sa, 0, sizeof(struct sockaddr_in));
    sa.sin_family = AF_INET;

    /* we only handle http here */
    if (strncasecmp(url, "http", 4))
        return HTTPRES_BAD_REQUEST;

    if (url[4] == 's') {
#ifdef CRYPTO
        ssl = 1;
        port = 443;
        if (!RTMP_TLS_ctx)
            RTMP_TLS_Init();
#else
        return HTTPRES_BAD_REQUEST;
#endif
    }

    p1 = strchr(url + 4, ':');
    if (!p1 || strncmp(p1, "://", 3))
        return HTTPRES_BAD_REQUEST;

    host = p1 + 3;
    path = strchr(host, '/');
    hlen = path - host;
    strncpy(hbuf, host, hlen);
    hbuf[hlen] = '\0';
    host = hbuf;
    p1 = strrchr(host, ':');
    if (p1) {
        *p1++ = '\0';
        port = atoi(p1);
    }

    sa.sin_addr.s_addr = inet_addr(host);
    if (sa.sin_addr.s_addr == INADDR_NONE) {
        struct hostent *hp = gethostbyname(host);
        if (!hp || !hp->h_addr)
            return HTTPRES_LOST_CONNECTION;
        sa.sin_addr = *(struct in_addr *) hp->h_addr;
    }
    sa.sin_port = htons(port);
    sb.sb_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (sb.sb_socket == -1)
        return HTTPRES_LOST_CONNECTION;
    i =
        sprintf(sb.sb_buf,
                "GET %s HTTP/1.0\r\nUser-Agent: %s\r\nHost: %s\r\nReferer: %.*s\r\n",
                path, AGENT, host, (int) (path - url + 1), url);
    if (http->date[0])
        i += sprintf(sb.sb_buf + i, "If-Modified-Since: %s\r\n", http->date);
    i += sprintf(sb.sb_buf + i, "\r\n");

    if (connect
            (sb.sb_socket, (struct sockaddr *) &sa, sizeof(struct sockaddr)) < 0) {
        ret = HTTPRES_LOST_CONNECTION;
        goto leave;
    }
#ifdef CRYPTO
    if (ssl) {
#ifdef NO_SSL
        RTMP_Log(RTMP_LOGERROR, "%s, No SSL/TLS support", __FUNCTION__);
        ret = HTTPRES_BAD_REQUEST;
        goto leave;
#else
        TLS_client(RTMP_TLS_ctx, sb.sb_ssl);
        TLS_setfd(sb.sb_ssl, sb.sb_socket);
        if (TLS_connect(sb.sb_ssl) < 0) {
            RTMP_Log(RTMP_LOGERROR, "%s, TLS_Connect failed", __FUNCTION__);
            ret = HTTPRES_LOST_CONNECTION;
            goto leave;
        }
#endif
    }
#endif
    RTMPSockBuf_Send(&sb, sb.sb_buf, i);

    /* set timeout */
#define HTTP_TIMEOUT    5
    {
        SET_RCVTIMEO(tv, HTTP_TIMEOUT);
        if (setsockopt
                (sb.sb_socket, SOL_SOCKET, SO_RCVTIMEO, (char *) &tv, sizeof(tv))) {
            RTMP_Log(RTMP_LOGERROR, "%s, Setting socket timeout to %ds failed!",
                     __FUNCTION__, HTTP_TIMEOUT);
        }
    }

    sb.sb_size = 0;
    sb.sb_timedout = FALSE;
    if (RTMPSockBuf_Fill(&sb) < 1) {
        ret = HTTPRES_LOST_CONNECTION;
        goto leave;
    }
    if (strncmp(sb.sb_buf, "HTTP/1", 6)) {
        ret = HTTPRES_BAD_REQUEST;
        goto leave;
    }

    p1 = strchr(sb.sb_buf, ' ');
    rc = atoi(p1 + 1);
    http->status = rc;

    if (rc >= 300) {
        if (rc == 304) {
            ret = HTTPRES_OK_NOT_MODIFIED;
            goto leave;
        }
        else if (rc == 404)
            ret = HTTPRES_NOT_FOUND;
        else if (rc >= 500)
            ret = HTTPRES_SERVER_ERROR;
        else if (rc >= 400)
            ret = HTTPRES_BAD_REQUEST;
        else
            ret = HTTPRES_REDIRECTED;
    }

    p1 = memchr(sb.sb_buf, '\n', sb.sb_size);
    if (!p1) {
        ret = HTTPRES_BAD_REQUEST;
        goto leave;
    }
    sb.sb_start = p1 + 1;
    sb.sb_size -= sb.sb_start - sb.sb_buf;

    while ((p2 = memchr(sb.sb_start, '\r', sb.sb_size))) {
        if (*sb.sb_start == '\r') {
            sb.sb_start += 2;
            sb.sb_size -= 2;
            break;
        }
        else if (!strncasecmp
                 (sb.sb_start, "Content-Length: ", sizeof("Content-Length: ") - 1)) {
            flen = atoi(sb.sb_start + sizeof("Content-Length: ") - 1);
        }
        else if (!strncasecmp
                 (sb.sb_start, "Last-Modified: ", sizeof("Last-Modified: ") - 1)) {
            *p2 = '\0';
            strcpy(http->date, sb.sb_start + sizeof("Last-Modified: ") - 1);
        }
        p2 += 2;
        sb.sb_size -= p2 - sb.sb_start;
        sb.sb_start = p2;
        if (sb.sb_size < 1) {
            if (RTMPSockBuf_Fill(&sb) < 1) {
                ret = HTTPRES_LOST_CONNECTION;
                goto leave;
            }
        }
    }

    len_known = flen > 0;
    while ((!len_known || flen > 0) &&
            (sb.sb_size > 0 || RTMPSockBuf_Fill(&sb) > 0)) {
        cb(sb.sb_start, 1, sb.sb_size, http->data);
        if (len_known)
            flen -= sb.sb_size;
        http->size += sb.sb_size;
        sb.sb_size = 0;
    }

    if (flen > 0)
        ret = HTTPRES_LOST_CONNECTION;

leave:
    RTMPSockBuf_Close(&sb);
    return ret;
}
示例#20
0
int
RTMP_HashSWF(const char *url, unsigned int *size, unsigned char *hash,
             int age) {
    FILE *f = NULL;
    char *path, date[64], cctim[64];
    long pos = 0;
    time_t ctim = -1, cnow;
    int i, got = 0, ret = 0;
    unsigned int hlen;
    struct info in = {0};
    struct HTTP_ctx http = {0};
    HTTPResult httpres;
    z_stream zs = {0};
    AVal home, hpre;

    date[0] = '\0';
#ifdef _WIN32
#ifdef XBMC4XBOX
    hpre.av_val = "Q:";
    hpre.av_len = 2;
    home.av_val = "\\UserData";
#else
    hpre.av_val = getenv("HOMEDRIVE");
    hpre.av_len = strlen(hpre.av_val);
    home.av_val = getenv("HOMEPATH");
#endif
#define DIRSEP	"\\"

#else /* !_WIN32 */
    hpre.av_val = "";
    hpre.av_len = 0;
    home.av_val = getenv("HOME");
#define DIRSEP    "/"
#endif
    if (!home.av_val)
        home.av_val = ".";
    home.av_len = strlen(home.av_val);

    /* SWF hash info is cached in a fixed-format file.
     * url: <url of SWF file>
     * ctim: HTTP datestamp of when we last checked it.
     * date: HTTP datestamp of the SWF's last modification.
     * size: SWF size in hex
     * hash: SWF hash in hex
     *
     * These fields must be present in this order. All fields
     * besides URL are fixed size.
     */
    path = malloc(hpre.av_len + home.av_len + sizeof(DIRSEP ".swfinfo"));
    sprintf(path, "%s%s" DIRSEP ".swfinfo", hpre.av_val, home.av_val);

    f = fopen(path, "r+");
    while (f) {
        char buf[4096], *file, *p;

        file = strchr(url, '/');
        if (!file)
            break;
        file += 2;
        file = strchr(file, '/');
        if (!file)
            break;
        file++;
        hlen = file - url;
        p = strrchr(file, '/');
        if (p)
            file = p;
        else
            file--;

        while (fgets(buf, sizeof(buf), f)) {
            char *r1;

            got = 0;

            if (strncmp(buf, "url: ", 5))
                continue;
            if (strncmp(buf + 5, url, hlen))
                continue;
            r1 = strrchr(buf, '/');
            i = strlen(r1);
            r1[--i] = '\0';
            if (strncmp(r1, file, i))
                continue;
            pos = ftell(f);
            while (got < 4 && fgets(buf, sizeof(buf), f)) {
                if (!strncmp(buf, "size: ", 6)) {
                    *size = strtol(buf + 6, NULL, 16);
                    got++;
                }
                else if (!strncmp(buf, "hash: ", 6)) {
                    unsigned char *ptr = hash, *in = (unsigned char *) buf + 6;
                    int l = strlen((char *) in) - 1;
                    for (i = 0; i < l; i += 2)
                        *ptr++ = (HEX2BIN(in[i]) << 4) | HEX2BIN(in[i + 1]);
                    got++;
                }
                else if (!strncmp(buf, "date: ", 6)) {
                    buf[strlen(buf) - 1] = '\0';
                    strncpy(date, buf + 6, sizeof(date));
                    got++;
                }
                else if (!strncmp(buf, "ctim: ", 6)) {
                    buf[strlen(buf) - 1] = '\0';
                    ctim = make_unix_time(buf + 6);
                    got++;
                }
                else if (!strncmp(buf, "url: ", 5))
                    break;
            }
            break;
        }
        break;
    }

    cnow = time(NULL);
    /* If we got a cache time, see if it's young enough to use directly */
    if (age && ctim > 0) {
        ctim = cnow - ctim;
        ctim /= 3600 * 24;    /* seconds to days */
        if (ctim < age)        /* ok, it's new enough */
            goto out;
    }

    in.first = 1;
    HMAC_setup(in.ctx, "Genuine Adobe Flash Player 001", 30);
    inflateInit(&zs);
    in.zs = &zs;

    http.date = date;
    http.data = &in;

    httpres = HTTP_get(&http, url, swfcrunch);

    inflateEnd(&zs);

    if (httpres != HTTPRES_OK && httpres != HTTPRES_OK_NOT_MODIFIED) {
        ret = -1;
        if (httpres == HTTPRES_LOST_CONNECTION)
            RTMP_Log(RTMP_LOGERROR, "%s: connection lost while downloading swfurl %s",
                     __FUNCTION__, url);
        else if (httpres == HTTPRES_NOT_FOUND)
            RTMP_Log(RTMP_LOGERROR, "%s: swfurl %s not found", __FUNCTION__, url);
        else
            RTMP_Log(RTMP_LOGERROR, "%s: couldn't contact swfurl %s (HTTP error %d)",
                     __FUNCTION__, url, http.status);
    }
    else {
        if (got && pos)
            fseek(f, pos, SEEK_SET);
        else {
            char *q;
            if (!f)
                f = fopen(path, "w");
            if (!f) {
                int err = errno;
                RTMP_Log(RTMP_LOGERROR,
                         "%s: couldn't open %s for writing, errno %d (%s)",
                         __FUNCTION__, path, err, strerror(err));
                ret = -1;
                goto out;
            }
            fseek(f, 0, SEEK_END);
            q = strchr(url, '?');
            if (q)
                i = q - url;
            else
                i = strlen(url);

            fprintf(f, "url: %.*s\n", i, url);
        }
        strtime(&cnow, cctim);
        fprintf(f, "ctim: %s\n", cctim);

        if (!in.first) {
            HMAC_finish(in.ctx, hash, hlen);
            *size = in.size;

            fprintf(f, "date: %s\n", date);
            fprintf(f, "size: %08x\n", in.size);
            fprintf(f, "hash: ");
            for (i = 0; i < SHA256_DIGEST_LENGTH; i++)
                fprintf(f, "%02x", hash[i]);
            fprintf(f, "\n");
        }
    }
    HMAC_close(in.ctx);
out:
    free(path);
    if (f)
        fclose(f);
    return ret;
}
示例#21
0
int
ServePacket(STREAMING_SERVER *server, RTMP *r, RTMPPacket *packet)
{
  int ret = 0;

  RTMP_Log(RTMP_LOGDEBUG, "%s, received packet type %02X, size %lu bytes", __FUNCTION__,
    packet->m_packetType, packet->m_nBodySize);

  switch (packet->m_packetType)
    {
    case 0x01:
      // chunk size
//      HandleChangeChunkSize(r, packet);
      break;

    case 0x03:
      // bytes read report
      break;

    case 0x04:
      // ctrl
//      HandleCtrl(r, packet);
      break;

    case 0x05:
      // server bw
//      HandleServerBW(r, packet);
      break;

    case 0x06:
      // client bw
 //     HandleClientBW(r, packet);
      break;

    case 0x08:
      // audio data
      //RTMP_Log(RTMP_LOGDEBUG, "%s, received: audio %lu bytes", __FUNCTION__, packet.m_nBodySize);
      break;

    case 0x09:
      // video data
      //RTMP_Log(RTMP_LOGDEBUG, "%s, received: video %lu bytes", __FUNCTION__, packet.m_nBodySize);
      break;

    case 0x0F:			// flex stream send
      break;

    case 0x10:			// flex shared object
      break;

    case 0x11:			// flex message
      {
	RTMP_Log(RTMP_LOGDEBUG, "%s, flex message, size %lu bytes, not fully supported",
	    __FUNCTION__, packet->m_nBodySize);
	//RTMP_LogHex(packet.m_body, packet.m_nBodySize);

	// some DEBUG code
	/*RTMP_LIB_AMFObject obj;
	   int nRes = obj.Decode(packet.m_body+1, packet.m_nBodySize-1);
	   if(nRes < 0) {
	   RTMP_Log(RTMP_LOGERROR, "%s, error decoding AMF3 packet", __FUNCTION__);
	   //return;
	   }

	   obj.Dump(); */

	if (ServeInvoke(server, r, packet, 1))
	  RTMP_Close(r);
	break;
      }
    case 0x12:
      // metadata (notify)
      break;

    case 0x13:
      /* shared object */
      break;

    case 0x14:
      // invoke
      RTMP_Log(RTMP_LOGDEBUG, "%s, received: invoke %lu bytes", __FUNCTION__,
	  packet->m_nBodySize);
      //RTMP_LogHex(packet.m_body, packet.m_nBodySize);

      if (ServeInvoke(server, r, packet, 0))
	RTMP_Close(r);
      break;

    case 0x16:
      /* flv */
	break;
    default:
      RTMP_Log(RTMP_LOGDEBUG, "%s, unknown packet type received: 0x%02x", __FUNCTION__,
	  packet->m_packetType);
#ifdef _DEBUG
      RTMP_LogHex(RTMP_LOGDEBUG, packet->m_body, packet->m_nBodySize);
#endif
    }
  return ret;
}
示例#22
0
void doServe(STREAMING_SERVER * server,	// server socket and state (our listening socket)
  int sockfd	// client connection socket
  )
{
  server->state = STREAMING_IN_PROGRESS;

  RTMP rtmp = { 0 };		/* our session with the real client */
  RTMPPacket packet = { 0 };

  // timeout for http requests
  fd_set fds;
  struct timeval tv;

  memset(&tv, 0, sizeof(struct timeval));
  tv.tv_sec = 5;

  FD_ZERO(&fds);
  FD_SET(sockfd, &fds);

  if (select(sockfd + 1, &fds, NULL, NULL, &tv) <= 0)
    {
      RTMP_Log(RTMP_LOGERROR, "Request timeout/select failed, ignoring request");
      goto quit;
    }
  else
    {
      RTMP_Init(&rtmp);
      rtmp.m_sb.sb_socket = sockfd;
      if (!RTMP_Serve(&rtmp))
	{
	  RTMP_Log(RTMP_LOGERROR, "Handshake failed");
	  goto cleanup;
	}
    }
  server->arglen = 0;
  while (RTMP_IsConnected(&rtmp) && RTMP_ReadPacket(&rtmp, &packet))
    {
      if (!RTMPPacket_IsReady(&packet))
	continue;
      ServePacket(server, &rtmp, &packet);
      RTMPPacket_Free(&packet);
    }

cleanup:
  RTMP_LogPrintf("Closing connection... ");
  RTMP_Close(&rtmp);
  /* Should probably be done by RTMP_Close() ... */
  rtmp.Link.playpath.av_val = NULL;
  rtmp.Link.tcUrl.av_val = NULL;
  rtmp.Link.swfUrl.av_val = NULL;
  rtmp.Link.pageUrl.av_val = NULL;
  rtmp.Link.app.av_val = NULL;
  rtmp.Link.flashVer.av_val = NULL;
  RTMP_LogPrintf("done!\n\n");

quit:
  if (server->state == STREAMING_IN_PROGRESS)
    server->state = STREAMING_ACCEPTING;

  return;
}
示例#23
0
int
OpenResumeFile(const char *flvFile,	// file name [in]
			   FILE ** file,	// opened file [out]
			   off_t * size,	// size of the file [out]
			   char **metaHeader,	// meta data read from the file [out]
			   uint32_t * nMetaHeaderSize,	// length of metaHeader [out]
			   double *duration)	// duration of the stream in ms [out]
{
	size_t bufferSize = 0;
	char hbuf[16], *buffer = NULL;

	*nMetaHeaderSize = 0;
	*size = 0;

	*file = fopen(flvFile, "r+b");
	if (!*file)
		return RD_SUCCESS;		// RD_SUCCESS, because we go to fresh file mode instead of quiting

	fseek(*file, 0, SEEK_END);
	*size = ftello(*file);
	fseek(*file, 0, SEEK_SET);

	if (*size > 0)
	{
		// verify FLV format and read header
		uint32_t prevTagSize = 0;

		// check we've got a valid FLV file to continue!
		if (fread(hbuf, 1, 13, *file) != 13)
		{
			RTMP_Log(RTMP_LOGERROR, "Couldn't read FLV file header!");
			return RD_FAILED;
		}
		if (hbuf[0] != 'F' || hbuf[1] != 'L' || hbuf[2] != 'V'
			|| hbuf[3] != 0x01)
		{
			RTMP_Log(RTMP_LOGERROR, "Invalid FLV file!");
			return RD_FAILED;
		}

		if ((hbuf[4] & 0x05) == 0)
		{
			RTMP_Log(RTMP_LOGERROR,
					 "FLV file contains neither video nor audio, aborting!");
			return RD_FAILED;
		}

		uint32_t dataOffset = AMF_DecodeInt32(hbuf + 5);
		fseek(*file, dataOffset, SEEK_SET);

		if (fread(hbuf, 1, 4, *file) != 4)
		{
			RTMP_Log(RTMP_LOGERROR, "Invalid FLV file: missing first prevTagSize!");
			return RD_FAILED;
		}
		prevTagSize = AMF_DecodeInt32(hbuf);
		if (prevTagSize != 0)
		{
			RTMP_Log(RTMP_LOGWARNING,
					 "First prevTagSize is not zero: prevTagSize = 0x%08X",
					 prevTagSize);
		}

		// go through the file to find the meta data!
		off_t pos = dataOffset + 4;
		int bFoundMetaHeader = FALSE;

		while (pos < *size - 4 && !bFoundMetaHeader)
		{
			fseeko(*file, pos, SEEK_SET);
			if (fread(hbuf, 1, 4, *file) != 4)
				break;

			uint32_t dataSize = AMF_DecodeInt24(hbuf + 1);

			if (hbuf[0] == 0x12)
			{
				if (dataSize > bufferSize)
				{
					/* round up to next page boundary */
					bufferSize = dataSize + 4095;
					bufferSize ^= (bufferSize & 4095);
					free(buffer);
					buffer = (char *)malloc(bufferSize);
					if (!buffer)
						return RD_FAILED;
				}

				fseeko(*file, pos + 11, SEEK_SET);
				if (fread(buffer, 1, dataSize, *file) != dataSize)
					break;

				AMFObject metaObj;
				int nRes = AMF_Decode(&metaObj, buffer, dataSize, FALSE);
				if (nRes < 0)
				{
					RTMP_Log(RTMP_LOGERROR, "%s, error decoding meta data packet",
							 __FUNCTION__);
					break;
				}

				AVal metastring;
				AMFProp_GetString(AMF_GetProp(&metaObj, NULL, 0), &metastring);

				if (AVMATCH(&metastring, &av_onMetaData))
				{
					AMF_Dump(&metaObj);

					*nMetaHeaderSize = dataSize;
					if (*metaHeader)
						free(*metaHeader);
					*metaHeader = (char *) malloc(*nMetaHeaderSize);
					memcpy(*metaHeader, buffer, *nMetaHeaderSize);

					// get duration
					AMFObjectProperty prop;
					if (RTMP_FindFirstMatchingProperty
						(&metaObj, &av_duration, &prop))
					{
						*duration = AMFProp_GetNumber(&prop);
						RTMP_Log(RTMP_LOGDEBUG, "File has duration: %f", *duration);
					}

					bFoundMetaHeader = TRUE;
					break;
				}
				//metaObj.Reset();
				//delete obj;
			}
			pos += (dataSize + 11 + 4);
		}

		free(buffer);
		if (!bFoundMetaHeader)
			RTMP_Log(RTMP_LOGWARNING, "Couldn't locate meta data!");
	}

	return RD_SUCCESS;
}
int QRtmp::download()
{
    qint32 now, lastUpdate;
    int bufferSize = 64 * 1024;
    char *buffer = (char *) malloc(bufferSize);
    int nRead = 0;
    off_t size = 0;

    m_rtmp->m_read.timestamp = dSeek;
    m_percent = 0.0;

    if(m_rtmp->m_read.timestamp)
        RTMP_Log(RTMP_LOGDEBUG, "Continuing at TS: %d ms\n", m_rtmp->m_read.timestamp);

    if(m_bLiveStream)
        RTMP_LogPrintf("Starting Live Stream\n");
    else
    {
        // print initial status
        // Workaround to exit with 0 if the file is fully (> 99.9%) downloaded
        if(m_duration > 0)
        {
            if((double) m_rtmp->m_read.timestamp >= (double) m_duration * 999.0)
            {
                RTMP_LogPrintf("Already Completed at: %.3f sec Duration=%.3f sec\n",
                               (double) m_rtmp->m_read.timestamp / 1000.0,
                               (double) m_duration / 1000.0);
                return RD_SUCCESS;
            }
            else
            {
                m_percent = ((double) m_rtmp->m_read.timestamp) / (m_duration * 1000.0) * 100.0;
                m_percent = ((double) (int) (m_percent * 10.0)) / 10.0;
                RTMP_LogPrintf("%s download at: %.3f kB / %.3f sec (%.1f%%)\n",
                               m_bResume ? "Resuming" : "Starting",
                               (double) size / 1024.0, (double) m_rtmp->m_read.timestamp / 1000.0,
                               m_percent);
            }
        }
        else
        {
            RTMP_LogPrintf("%s download at: %.3f kB\n",
                           m_bResume ? "Resuming" : "Starting",
                           (double) size / 1024.0);
        }
    }

    if(dStopOffset > 0)
        RTMP_LogPrintf("For duration: %.3f sec\n", (double) (dStopOffset - dSeek) / 1000.0);

    m_rtmp->m_read.nResumeTS = dSeek;

    now = RTMP_GetTime();
    lastUpdate = now - 1000;
    do
    {
        nRead = RTMP_Read(m_rtmp, buffer, bufferSize);
        //RTMP_LogPrintf("nRead: %d\n", nRead);
        if(nRead > 0)
        {
            if(m_destFile.isOpen())
            {
                if(m_destFile.write(buffer, nRead) != nRead)
                {
                    setError(QString("Can't write to %1 - %2").arg(m_destFile.fileName()).arg(m_destFile.errorString()));
                    m_stop = true;
                }
            }
            else
                emit readData(QByteArray(buffer, nRead));
            m_destFile.flush();
            size += nRead;
            setStreamIsRunning(true);

            //RTMP_LogPrintf("write %dbytes (%.1f kB)\n", nRead, nRead/1024.0);
            if(m_duration <= 0)	// if duration unknown try to get it from the stream (onMetaData)
                m_duration = RTMP_GetDuration(m_rtmp);

            if(m_duration > 0)
            {
                // make sure we claim to have enough buffer time!
                if(!m_bOverrideBufferTime && m_bufferTime < (m_duration * 1000.0))
                {
                    m_bufferTime = (quint32) (m_duration * 1000.0) + 5000;	// extra 5sec to make sure we've got enough

                    RTMP_Log(RTMP_LOGDEBUG, "Detected that buffer time is less than duration, resetting to: %dms", m_bufferTime);
                    RTMP_SetBufferMS(m_rtmp, m_bufferTime);
                    RTMP_UpdateBufferMS(m_rtmp);
                }
                m_percent = ((double) m_rtmp->m_read.timestamp) / (m_duration * 1000.0) * 100.0;
                m_percent = ((double) (int) (m_percent * 10.0)) / 10.0;
                now = RTMP_GetTime();
                if(abs(now - lastUpdate) > 200)
                {
                    RTMP_LogStatus("\r%.3f kB / %.2f sec (%.1f%%)",
                                   (double) size / 1024.0,
                                   (double) (m_rtmp->m_read.timestamp) / 1000.0, m_percent);
                    lastUpdate = now;
                }
            }
            else
            {
                now = RTMP_GetTime();
                if(abs(now - lastUpdate) > 200)
                {
                    RTMP_LogStatus("\r%.3f kB / %.2f sec", (double) size / 1024.0,
                                   (double) (m_rtmp->m_read.timestamp) / 1000.0);
                    lastUpdate = now;
                }
            }
        }
    }while (!m_stop && nRead > -1 && RTMP_IsConnected(m_rtmp) && !RTMP_IsTimedout(m_rtmp));
    free(buffer);
    if (nRead < 0)
        nRead = m_rtmp->m_read.status;

    /* Final status update */
    if(m_duration > 0)
    {
        m_percent = ((double) m_rtmp->m_read.timestamp) / (m_duration * 1000.0) * 100.0;
        m_percent = ((double) (int) (m_percent * 10.0)) / 10.0;
        RTMP_LogStatus("\r%.3f kB / %.2f sec (%.1f%%)",
                       (double) size / 1024.0,
                       (double) (m_rtmp->m_read.timestamp) / 1000.0, m_percent);
    }
    else
    {
        RTMP_LogStatus("\r%.3f kB / %.2f sec", (double) size / 1024.0,
                       (double) (m_rtmp->m_read.timestamp) / 1000.0);
    }

    RTMP_Log(RTMP_LOGDEBUG, "RTMP_Read returned: %d", nRead);

    if(m_bResume && nRead == -2)
    {
        RTMP_LogPrintf("Couldn't resume FLV file, try --skip %d\n\n",
                       m_nSkipKeyFrames + 1);
        return RD_FAILED;
    }

    if(nRead == -3)
        return RD_SUCCESS;

    if((m_duration > 0 && m_percent < 99.9) || m_stop || nRead < 0
            || RTMP_IsTimedout(m_rtmp))
    {
        return RD_INCOMPLETE;
    }

    return RD_SUCCESS;
}
void QRtmp::run()
{
    bool first = true;
    int retries = 0;

    if(!m_destFile.fileName().isEmpty())
        if(!m_destFile.open(QIODevice::WriteOnly))
        {
            setError(QString("Can't open %1 for writing").arg(m_destFile.fileName()));
            return;
        }

    while(!m_stop)
    {
        RTMP_Log(RTMP_LOGDEBUG, "Setting buffer time to: %dms", m_bufferTime);
        RTMP_SetBufferMS(m_rtmp, m_bufferTime);

        if(first)
        {
            first = false;
            RTMP_LogPrintf("Connecting ...\n");

            if(!RTMP_Connect(m_rtmp, NULL))
            {
                setError("RTMP_Connect failed");
                break;
            }

            RTMP_Log(RTMP_LOGINFO, "Connected...");

            // User defined seek offset
            if(dStartOffset > 0)
            {
                // Don't need the start offset if resuming an existing file
                if(m_bResume)
                {
                    RTMP_Log(RTMP_LOGWARNING, "Can't seek a resumed stream, ignoring --start option");
                    dStartOffset = 0;
                }
                else
                    dSeek = dStartOffset;
            }

            // Calculate the length of the stream to still play
            if(dStopOffset > 0)
            {
                // Quit if start seek is past required stop offset
                if(dStopOffset <= dSeek)
                {
                    RTMP_LogPrintf("Already Completed\n");
                    break;
                }
            }

            if(!RTMP_ConnectStream(m_rtmp, dSeek))
            {
                setError("RTMP_ConnectStream failed");
                break;
            }
        }
        else
        {
            if(retries)
            {
                RTMP_Log(RTMP_LOGERROR, "Failed to resume the stream\n\n");
                if(!RTMP_IsTimedout(m_rtmp))
                    setError("RTMP_IsTimedout failed");
                else
                    setError("RTMP_IsTimedout RD_INCOMPLETE");
                break;
            }

            RTMP_Log(RTMP_LOGINFO, "Connection timed out, trying to resume.\n\n");

            /* Did we already try pausing, and it still didn't work? */
            if(m_rtmp->m_pausing == 3)
            {
                /* Only one try at reconnecting... */
                retries = 1;
                dSeek = m_rtmp->m_pauseStamp;
                if(dStopOffset > 0)
                {
                    if(dStopOffset <= dSeek)
                    {
                        RTMP_LogPrintf("Already Completed\n");
                        break;
                    }
                }
                if(!RTMP_ReconnectStream(m_rtmp, dSeek))
                {
                    RTMP_Log(RTMP_LOGERROR, "Failed to resume the stream\n\n");
                    if(!RTMP_IsTimedout(m_rtmp))
                        setError("RTMP_IsTimedout failed");
                    else
                        setError("RTMP_IsTimedout RD_INCOMPLETE");
                    break;
                }
            }
            else if(!RTMP_ToggleStream(m_rtmp))
            {
                RTMP_Log(RTMP_LOGERROR, "Failed to resume the stream\n\n");
                if(!RTMP_IsTimedout(m_rtmp))
                    setError("RTMP_IsTimedout failed");
                else
                    setError("RTMP_IsTimedout RD_INCOMPLETE");
                break;
            }
            m_bResume = true;
        }
        int nStatus = download();
        if(nStatus != RD_INCOMPLETE || !RTMP_IsTimedout(m_rtmp) || m_bLiveStream)
            break;
    }
    RTMP_Log(RTMP_LOGDEBUG, "Closing connection.\n");
    RTMP_Close(m_rtmp);
    if(m_destFile.isOpen())
        m_destFile.close();
    setStreamIsRunning(false);
}
void QRtmp::start()
{
    m_error.clear();
    m_stop = false;

    if(m_hostname.isEmpty())
    {
        setError("You must specify a hostname (--host) or url (-r \"rtmp://host[:port]/playpath\") containing a hostname");
        return;
    }
    if(m_playpath.isEmpty())
    {
        setError("You must specify a playpath (--playpath) or url (-r \"rtmp://host[:port]/playpath\") containing a playpath");
        return;
    }
    if(m_proto == Undefined)
    {
        RTMP_Log(RTMP_LOGWARNING, "You haven't specified a protocol (--protocol) or rtmp url (-r), using default protocol RTMP");
        m_proto = RTMP;
    }
    if(m_port == -1)
    {
        RTMP_Log(RTMP_LOGWARNING, "You haven't specified a port (--port) or rtmp url (-r), using default port");
        m_port = 0;
    }
    int protocol = rtmpProto(m_proto);
    if(m_port == 0)
    {
        if(protocol & RTMP_FEATURE_SSL)
            m_port = 443;
        else if(protocol & RTMP_FEATURE_HTTP)
            m_port = 80;
        else
            m_port = 1935;
    }

    // calculate hash
    {
        unsigned char hash[RTMP_SWF_HASHLEN];
        if(RTMP_HashSWF(m_swfUrl.data(), &m_swfSize, hash, m_swfAge) == 0)
            m_swfHash = QByteArray::fromRawData(reinterpret_cast<const char*>(hash), sizeof(hash));
    }

    if(m_swfHash.isEmpty() && m_swfSize > 0)
    {
        RTMP_Log(RTMP_LOGWARNING, "Ignoring SWF size, supply also the hash with --swfhash");
        m_swfSize = 0;
    }

    if(!m_swfHash.isEmpty() && m_swfSize == 0)
    {
        RTMP_Log(RTMP_LOGWARNING, "Ignoring SWF hash, supply also the swf size  with --swfsize");
        m_swfHash.clear();
    }

    if(m_tcUrl.isEmpty())
        m_tcUrl = QString("%1://%2:%3/%4")
                .arg(RTMPProtocolStringsLower[protocol])
                .arg(QString(m_hostname))
                .arg(m_port)
                .arg(QString(m_app))
                .toLocal8Bit();

    if(dStartOffset > 0 && m_bLiveStream)
    {
        RTMP_Log(RTMP_LOGWARNING, "Can't seek in a live stream, ignoring --start option");
        dStartOffset = 0;
    }

    // usherToken is unavailable for librtmp 2.3
    AVal hostname = aval(m_hostname),
            proxy = aval(m_proxy),
            playpath = aval(m_playpath),
            tcUrl = aval(m_tcUrl),
            swfUrl = aval(m_swfUrl),
            pageUrl = aval(m_pageUrl),
            app = aval(m_app),
            auth = aval(m_auth),
            swfHash = aval(m_swfHash),
            flashVer = aval(m_flashVer),
            subscribePath = aval(m_subscribepath);
    RTMP_SetupStream(m_rtmp, protocol, &hostname, m_port, &proxy, &playpath,
                     &tcUrl, &swfUrl, &pageUrl, &app, &auth,
                     &swfHash, m_swfSize, &flashVer, &subscribePath, /*&aval(m_usherToken),*/
                     dSeek, dStopOffset, m_bLiveStream, m_timeout);

    /* Try to keep the stream moving if it pauses on us */
    if(!m_bLiveStream && !(protocol & RTMP_FEATURE_HTTP))
        m_rtmp->Link.lFlags |= RTMP_LF_BUFX;

    QThread::start();
}
void QRtmp::setError(const QString &errorDesc)
{
    m_error = errorDesc;
    RTMP_Log(RTMP_LOGERROR, errorDesc.toLocal8Bit().data());
    emit error(m_error);
}
示例#28
0
int
GetLastKeyframe(FILE * file,	// output file [in]
				int nSkipKeyFrames,	// max number of frames to skip when searching for key frame [in]
				uint32_t * dSeek,	// offset of the last key frame [out]
				char **initialFrame,	// content of the last keyframe [out]
				int *initialFrameType,	// initial frame type (audio/video) [out]
				uint32_t * nInitialFrameSize)	// length of initialFrame [out]
{
	const size_t bufferSize = 16;
	char buffer[bufferSize];
	uint8_t dataType;
	int bAudioOnly;
	off_t size;

	fseek(file, 0, SEEK_END);
	size = ftello(file);

	fseek(file, 4, SEEK_SET);
	if (fread(&dataType, sizeof(uint8_t), 1, file) != 1)
		return RD_FAILED;

	bAudioOnly = (dataType & 0x4) && !(dataType & 0x1);

	RTMP_Log(RTMP_LOGDEBUG, "bAudioOnly: %d, size: %llu", bAudioOnly,
			 (unsigned long long) size);

	// ok, we have to get the timestamp of the last keyframe (only keyframes are seekable) / last audio frame (audio only streams)

	//if(!bAudioOnly) // we have to handle video/video+audio different since we have non-seekable frames
	//{
	// find the last seekable frame
	off_t tsize = 0;
	uint32_t prevTagSize = 0;

	// go through the file and find the last video keyframe
	do
	{
		int xread;
		skipkeyframe:
		if (size - tsize < 13)
		{
			RTMP_Log(RTMP_LOGERROR,
					 "Unexpected start of file, error in tag sizes, couldn't arrive at prevTagSize=0");
			return RD_FAILED;
		}
		fseeko(file, size - tsize - 4, SEEK_SET);
		xread = fread(buffer, 1, 4, file);
		if (xread != 4)
		{
			RTMP_Log(RTMP_LOGERROR, "Couldn't read prevTagSize from file!");
			return RD_FAILED;
		}

		prevTagSize = AMF_DecodeInt32(buffer);
		//RTMP_Log(RTMP_LOGDEBUG, "Last packet: prevTagSize: %d", prevTagSize);

		if (prevTagSize == 0)
		{
			RTMP_Log(RTMP_LOGERROR, "Couldn't find keyframe to resume from!");
			return RD_FAILED;
		}

		if (prevTagSize < 0 || prevTagSize > size - 4 - 13)
		{
			RTMP_Log(RTMP_LOGERROR,
					 "Last tag size must be greater/equal zero (prevTagSize=%d) and smaller then filesize, corrupt file!",
					 prevTagSize);
			return RD_FAILED;
		}
		tsize += prevTagSize + 4;

		// read header
		fseeko(file, size - tsize, SEEK_SET);
		if (fread(buffer, 1, 12, file) != 12)
		{
			RTMP_Log(RTMP_LOGERROR, "Couldn't read header!");
			return RD_FAILED;
		}
		//*
#ifdef _DEBUG
		uint32_t ts = AMF_DecodeInt24(buffer + 4);
		ts |= (buffer[7] << 24);
		RTMP_Log(RTMP_LOGDEBUG, "%02X: TS: %d ms", buffer[0], ts);
#endif //*/

		// this just continues the loop whenever the number of skipped frames is > 0,
		// so we look for the next keyframe to continue with
		//
		// this helps if resuming from the last keyframe fails and one doesn't want to start
		// the download from the beginning
		//
		if (nSkipKeyFrames > 0
			&& !(!bAudioOnly
				 && (buffer[0] != 0x09 || (buffer[11] & 0xf0) != 0x10)))
		{
#ifdef _DEBUG
			RTMP_Log(RTMP_LOGDEBUG,
					 "xxxxxxxxxxxxxxxxxxxxxxxx Well, lets go one more back!");
#endif
			nSkipKeyFrames--;
			goto skipkeyframe;
		}

	}
	while ((bAudioOnly && buffer[0] != 0x08) || (!bAudioOnly && (buffer[0] != 0x09 || (buffer[11] & 0xf0) != 0x10))); // as long as we don't have a keyframe / last audio frame

	// save keyframe to compare/find position in stream
	*initialFrameType = buffer[0];
	*nInitialFrameSize = prevTagSize - 11;
	*initialFrame = (char *) malloc(*nInitialFrameSize);

	fseeko(file, size - tsize + 11, SEEK_SET);
	if (fread(*initialFrame, 1, *nInitialFrameSize, file) != *nInitialFrameSize)
	{
		RTMP_Log(RTMP_LOGERROR, "Couldn't read last keyframe, aborting!");
		return RD_FAILED;
	}

	*dSeek = AMF_DecodeInt24(buffer + 4); // set seek position to keyframe tmestamp
	*dSeek |= (buffer[7] << 24);
	//}
	//else // handle audio only, we can seek anywhere we'd like
	//{
	//}

	if (*dSeek < 0)
	{
		RTMP_Log(RTMP_LOGERROR,
				 "Last keyframe timestamp is negative, aborting, your file is corrupt!");
		return RD_FAILED;
	}
	RTMP_Log(RTMP_LOGDEBUG, "Last keyframe found at: %d ms, size: %d, type: %02X", *dSeek,
			 *nInitialFrameSize, *initialFrameType);

	/*
	   // now read the timestamp of the frame before the seekable keyframe:
	   fseeko(file, size-tsize-4, SEEK_SET);
	   if(fread(buffer, 1, 4, file) != 4) {
	   RTMP_Log(RTMP_LOGERROR, "Couldn't read prevTagSize from file!");
	   goto start;
	   }
	   uint32_t prevTagSize = RTMP_LIB::AMF_DecodeInt32(buffer);
	   fseeko(file, size-tsize-4-prevTagSize+4, SEEK_SET);
	   if(fread(buffer, 1, 4, file) != 4) {
	   RTMP_Log(RTMP_LOGERROR, "Couldn't read previous timestamp!");
	   goto start;
	   }
	   uint32_t timestamp = RTMP_LIB::AMF_DecodeInt24(buffer);
	   timestamp |= (buffer[3]<<24);
  
	   RTMP_Log(RTMP_LOGDEBUG, "Previous timestamp: %d ms", timestamp);
	 */

	if (*dSeek != 0)
	{
		// seek to position after keyframe in our file (we will ignore the keyframes resent by the server
		// since they are sent a couple of times and handling this would be a mess)
		fseeko(file, size - tsize + prevTagSize + 4, SEEK_SET);

		// make sure the WriteStream doesn't write headers and ignores all the 0ms TS packets
		// (including several meta data headers and the keyframe we seeked to)
		//bNoHeader = TRUE; if bResume==true this is true anyway
	}

	//}

	return RD_SUCCESS;
}
示例#29
0
int
Download(RTMP * rtmp,		// connected RTMP object
		 FILE * file, uint32_t dSeek, uint32_t dStopOffset, double duration, int bResume, char *metaHeader, uint32_t nMetaHeaderSize, char *initialFrame, int initialFrameType, uint32_t nInitialFrameSize, int nSkipKeyFrames, int bStdoutMode, int bLiveStream, int bRealtimeStream, int bHashes, int bOverrideBufferTime, uint32_t bufferTime, double *percent)	// percentage downloaded [out]
{
	int32_t now, lastUpdate;
	int bufferSize = 64 * 1024;
	char *buffer = (char *) malloc(bufferSize);
	int nRead = 0;
	off_t size = ftello(file);
	unsigned long lastPercent = 0;

	rtmp->m_read.timestamp = dSeek;

	*percent = 0.0;

	if (rtmp->m_read.timestamp)
	{
		RTMP_Log(RTMP_LOGDEBUG, "Continuing at TS: %d ms\n", rtmp->m_read.timestamp);
	}

	if (bLiveStream)
	{
		RTMP_LogPrintf("Starting Live Stream\n");
	}
	else
	{
		// print initial status
		// Workaround to exit with 0 if the file is fully (> 99.9%) downloaded
		if (duration > 0)
		{
			if ((double) rtmp->m_read.timestamp >= (double) duration * 999.0)
			{
				RTMP_LogPrintf("Already Completed at: %.3f sec Duration=%.3f sec\n",
							   (double) rtmp->m_read.timestamp / 1000.0,
							   (double) duration / 1000.0);
				return RD_SUCCESS;
			}
			else
			{
				*percent = ((double) rtmp->m_read.timestamp) / (duration * 1000.0) * 100.0;
				*percent = ((double) (int) (*percent * 10.0)) / 10.0;
				RTMP_LogPrintf("%s download at: %.3f kB / %.3f sec (%.1f%%)\n",
							   bResume ? "Resuming" : "Starting",
							   (double) size / 1024.0, (double) rtmp->m_read.timestamp / 1000.0,
							   *percent);
			}
		}
		else
		{
			RTMP_LogPrintf("%s download at: %.3f kB\n",
						   bResume ? "Resuming" : "Starting",
						   (double) size / 1024.0);
		}
		if (bRealtimeStream)
			RTMP_LogPrintf("  in approximately realtime (disabled BUFX speedup hack)\n");
	}

	if (dStopOffset > 0)
		RTMP_LogPrintf("For duration: %.3f sec\n", (double) (dStopOffset - dSeek) / 1000.0);

	if (bResume && nInitialFrameSize > 0)
		rtmp->m_read.flags |= RTMP_READ_RESUME;
	rtmp->m_read.initialFrameType = initialFrameType;
	rtmp->m_read.nResumeTS = dSeek;
	rtmp->m_read.metaHeader = metaHeader;
	rtmp->m_read.initialFrame = initialFrame;
	rtmp->m_read.nMetaHeaderSize = nMetaHeaderSize;
	rtmp->m_read.nInitialFrameSize = nInitialFrameSize;

	buffer = (char *) malloc(bufferSize);

	now = RTMP_GetTime();
	lastUpdate = now - 1000;
	do
	{
		nRead = RTMP_Read(rtmp, buffer, bufferSize);
		//RTMP_LogPrintf("nRead: %d\n", nRead);
		if (nRead > 0)
		{
			if (fwrite(buffer, sizeof(unsigned char), nRead, file) !=
				(size_t) nRead)
			{
				RTMP_Log(RTMP_LOGERROR, "%s: Failed writing, exiting!", __FUNCTION__);
				free(buffer);
				return RD_FAILED;
			}
			size += nRead;

			//RTMP_LogPrintf("write %dbytes (%.1f kB)\n", nRead, nRead/1024.0);
			if (duration <= 0)	  // if duration unknown try to get it from the stream (onMetaData)
				duration = RTMP_GetDuration(rtmp);

			if (duration > 0)
			{
				// make sure we claim to have enough buffer time!
				if (!bOverrideBufferTime && bufferTime < (duration * 1000.0))
				{
					bufferTime = (uint32_t) (duration * 1000.0) + 5000;	  // extra 5sec to make sure we've got enough

					RTMP_Log(RTMP_LOGDEBUG,
							 "Detected that buffer time is less than duration, resetting to: %dms",
							 bufferTime);
					RTMP_SetBufferMS(rtmp, bufferTime);
					RTMP_UpdateBufferMS(rtmp);
				}
				*percent = ((double) rtmp->m_read.timestamp) / (duration * 1000.0) * 100.0;
				*percent = ((double) (int) (*percent * 10.0)) / 10.0;
				if (bHashes)
				{
					if (lastPercent + 1 <= *percent)
					{
						RTMP_LogStatus("#");
						lastPercent = (unsigned long) *percent;
					}
				}
				else
				{
					now = RTMP_GetTime();
					if (abs(now - lastUpdate) > 200)
					{
						RTMP_LogStatus("\r%.3f kB / %.2f sec (%.1f%%)",
									   (double) size / 1024.0,
									   (double) (rtmp->m_read.timestamp) / 1000.0, *percent);
						lastUpdate = now;
					}
				}
			}
			else
			{
				now = RTMP_GetTime();
				if (abs(now - lastUpdate) > 200)
				{
					if (bHashes)
						RTMP_LogStatus("#");
					else
						RTMP_LogStatus("\r%.3f kB / %.2f sec", (double) size / 1024.0,
									   (double) (rtmp->m_read.timestamp) / 1000.0);
					lastUpdate = now;
				}
			}
		}
		else
		{
#ifdef _DEBUG
			RTMP_Log(RTMP_LOGDEBUG, "zero read!");
#endif
			if (rtmp->m_read.status == RTMP_READ_EOF)
				break;
		}

	}
	while (!RTMP_ctrlC && nRead > -1 && RTMP_IsConnected(rtmp) && !RTMP_IsTimedout(rtmp));
	free(buffer);
	if (nRead < 0)
		nRead = rtmp->m_read.status;

	/* Final status update */
	if (!bHashes)
	{
		if (duration > 0)
		{
			*percent = ((double) rtmp->m_read.timestamp) / (duration * 1000.0) * 100.0;
			*percent = ((double) (int) (*percent * 10.0)) / 10.0;
			RTMP_LogStatus("\r%.3f kB / %.2f sec (%.1f%%)",
						   (double) size / 1024.0,
						   (double) (rtmp->m_read.timestamp) / 1000.0, *percent);
		}
		else
		{
			RTMP_LogStatus("\r%.3f kB / %.2f sec", (double) size / 1024.0,
						   (double) (rtmp->m_read.timestamp) / 1000.0);
		}
	}

	RTMP_Log(RTMP_LOGDEBUG, "RTMP_Read returned: %d", nRead);

	if (bResume && nRead == -2)
	{
		RTMP_LogPrintf("Couldn't resume FLV file, try --skip %d\n\n",
					   nSkipKeyFrames + 1);
		return RD_FAILED;
	}

	if (nRead == -3)
		return RD_SUCCESS;

	if ((duration > 0 && *percent < 99.9) || RTMP_ctrlC || nRead < 0
		|| RTMP_IsTimedout(rtmp))
	{
		return RD_INCOMPLETE;
	}

	return RD_SUCCESS;
}
示例#30
0
int
main(int argc, char **argv)
{
	extern char *optarg;

	int nStatus = RD_SUCCESS;
	double percent = 0;
	double duration = 0.0;

	int nSkipKeyFrames = DEF_SKIPFRM; // skip this number of keyframes when resuming

	int bOverrideBufferTime = FALSE;  // if the user specifies a buffer time override this is true
	int bStdoutMode = TRUE;	  // if true print the stream directly to stdout, messages go to stderr
	int bResume = FALSE;	  // true in resume mode
	uint32_t dSeek = 0;		  // seek position in resume mode, 0 otherwise
	uint32_t bufferTime = DEF_BUFTIME;

	// meta header and initial frame for the resume mode (they are read from the file and compared with
	// the stream we are trying to continue
	char *metaHeader = 0;
	uint32_t nMetaHeaderSize = 0;

	// video keyframe for matching
	char *initialFrame = 0;
	uint32_t nInitialFrameSize = 0;
	int initialFrameType = 0; // tye: audio or video

	AVal hostname = { 0, 0 };
	AVal playpath = { 0, 0 };
	AVal subscribepath = { 0, 0 };
	AVal usherToken = { 0, 0 };	//Justin.tv auth token
	int port = -1;
	int protocol = RTMP_PROTOCOL_UNDEFINED;
	int retries = 0;
	int bLiveStream = FALSE;  // is it a live stream? then we can't seek/resume
	int bRealtimeStream = FALSE;  // If true, disable the BUFX hack (be patient)
	int bHashes = FALSE;	  // display byte counters not hashes by default

	long int timeout = DEF_TIMEOUT;	  // timeout connection after 120 seconds
	uint32_t dStartOffset = 0;	  // seek position in non-live mode
	uint32_t dStopOffset = 0;
	RTMP rtmp = { 0 };
	FILE *pLogFile;

	AVal fullUrl = { 0, 0 };
	AVal swfUrl = { 0, 0 };
	AVal tcUrl = { 0, 0 };
	AVal pageUrl = { 0, 0 };
	AVal app = { 0, 0 };
	AVal auth = { 0, 0 };
	AVal swfHash = { 0, 0 };
	uint32_t swfSize = 0;
	AVal flashVer = { 0, 0 };
	AVal sockshost = { 0, 0 };

#ifdef CRYPTO
	int swfAge = 30;  /* 30 days for SWF cache by default */
	int swfVfy = 0;
	unsigned char hash[RTMP_SWF_HASHLEN];
#endif

	char *flvFile = 0;

	signal(SIGINT, sigIntHandler);
	signal(SIGTERM, sigIntHandler);
#ifndef WIN32
	signal(SIGHUP, sigIntHandler);
	signal(SIGPIPE, sigIntHandler);
	signal(SIGQUIT, sigIntHandler);
#endif

	RTMP_debuglevel = RTMP_LOGALL;

	//pLogFile = fopen("log.txt", "w");
	//RTMP_LogSetOutput(pLogFile);

	// Check for --quiet option before printing any output
	int index = 0;
	while (index < argc)
	{
		if (strcmp(argv[index], "--quiet") == 0
			|| strcmp(argv[index], "-q") == 0)
			RTMP_debuglevel = RTMP_LOGCRIT;
		index++;
	}
#define RTMPDUMP_VERSION "2.4"
	RTMP_LogPrintf("RTMPDump %s\n", RTMPDUMP_VERSION);
	RTMP_LogPrintf
	("(c) 2010 Andrej Stepanchuk, Howard Chu, The Flvstreamer Team; license: GPL\n");

	if (!InitSockets())
	{
		RTMP_Log(RTMP_LOGERROR,
				 "Couldn't load sockets support on your platform, exiting!");
		return RD_FAILED;
	}

	/* sleep(30); */

	RTMP_Init(&rtmp);

	int opt;
/*  struct option longopts[] = {
	{"help", 0, NULL, 'h'},
	{"host", 1, NULL, 'n'},
	{"port", 1, NULL, 'c'},
	{"socks", 1, NULL, 'S'},
	{"protocol", 1, NULL, 'l'},
	{"playpath", 1, NULL, 'y'},
	{"playlist", 0, NULL, 'Y'},
	{"rtmp", 1, NULL, 'r'},
	{"swfUrl", 1, NULL, 's'},
	{"tcUrl", 1, NULL, 't'},
	{"pageUrl", 1, NULL, 'p'},
	{"app", 1, NULL, 'a'},
	{"auth", 1, NULL, 'u'},
	{"conn", 1, NULL, 'C'},
#ifdef CRYPTO
	{"swfhash", 1, NULL, 'w'},
	{"swfsize", 1, NULL, 'x'},
	{"swfVfy", 1, NULL, 'W'},
	{"swfAge", 1, NULL, 'X'},
#endif
	{"flashVer", 1, NULL, 'f'},
	{"live", 0, NULL, 'v'},
	{"flv", 1, NULL, 'o'},
	{"resume", 0, NULL, 'e'},
	{"timeout", 1, NULL, 'm'},
	{"buffer", 1, NULL, 'b'},
	{"skip", 1, NULL, 'k'},
	{"subscribe", 1, NULL, 'd'},
	{"start", 1, NULL, 'A'},
	{"stop", 1, NULL, 'B'},
	{"token", 1, NULL, 'T'},
	{"hashes", 0, NULL, '#'},
	{"debug", 0, NULL, 'z'},
	{"quiet", 0, NULL, 'q'},
	{"verbose", 0, NULL, 'V'},
	{0, 0, 0, 0}
  };*/

	while ((opt =
			getopt/*_long*/(argc, argv,
							"hVveqzr:s:t:p:a:b:f:o:u:C:n:c:l:y:Ym:k:d:A:B:T:w:x:W:X:S:#"/*,
							longopts, NULL*/)) != -1)
	{
		switch (opt)
		{
			case 'h':
				usage(argv[0]);
				return RD_SUCCESS;
#ifdef CRYPTO
			case 'w':
				{
					int res = hex2bin(optarg, &swfHash.av_val);
					if (res != RTMP_SWF_HASHLEN)
					{
						swfHash.av_val = NULL;
						RTMP_Log(RTMP_LOGWARNING,
								 "Couldn't parse swf hash hex string, not hexstring or not %d bytes, ignoring!", RTMP_SWF_HASHLEN);
					}
					swfHash.av_len = RTMP_SWF_HASHLEN;
					break;
				}
			case 'x':
				{
					int size = atoi(optarg);
					if (size <= 0)
					{
						RTMP_Log(RTMP_LOGERROR, "SWF Size must be at least 1, ignoring\n");
					}
					else
					{
						swfSize = size;
					}
					break;
				}
			case 'W':
				STR2AVAL(swfUrl, optarg);
				swfVfy = 1;
				break;
			case 'X':
				{
					int num = atoi(optarg);
					if (num < 0)
					{
						RTMP_Log(RTMP_LOGERROR, "SWF Age must be non-negative, ignoring\n");
					}
					else
					{
						swfAge = num;
					}
				}
				break;
#endif
			case 'k':
				nSkipKeyFrames = atoi(optarg);
				if (nSkipKeyFrames < 0)
				{
					RTMP_Log(RTMP_LOGERROR,
							 "Number of keyframes skipped must be greater or equal zero, using zero!");
					nSkipKeyFrames = 0;
				}
				else
				{
					RTMP_Log(RTMP_LOGDEBUG, "Number of skipped key frames for resume: %d",
							 nSkipKeyFrames);
				}
				break;
			case 'b':
				{
					int32_t bt = atol(optarg);
					if (bt < 0)
					{
						RTMP_Log(RTMP_LOGERROR,
								 "Buffer time must be greater than zero, ignoring the specified value %d!",
								 bt);
					}
					else
					{
						bufferTime = bt;
						bOverrideBufferTime = TRUE;
					}
					break;
				}
			case 'v':
				bLiveStream = TRUE;	  // no seeking or resuming possible!
				break;
			case 'R':
				bRealtimeStream = TRUE;	// seeking and resuming is still possible
				break;
			case 'd':
				STR2AVAL(subscribepath, optarg);
				break;
			case 'n':
				STR2AVAL(hostname, optarg);
				break;
			case 'c':
				port = atoi(optarg);
				break;
			case 'l':
				protocol = atoi(optarg);
				if (protocol < RTMP_PROTOCOL_RTMP || protocol > RTMP_PROTOCOL_RTMPTS)
				{
					RTMP_Log(RTMP_LOGERROR, "Unknown protocol specified: %d", protocol);
					return RD_FAILED;
				}
				break;
			case 'y':
				STR2AVAL(playpath, optarg);
				break;
			case 'Y':
				RTMP_SetOpt(&rtmp, &av_playlist, (AVal *)&av_true);
				break;
			case 'r':
				{
					AVal parsedHost, parsedApp, parsedPlaypath;
					unsigned int parsedPort = 0;
					int parsedProtocol = RTMP_PROTOCOL_UNDEFINED;

					if (!RTMP_ParseURL
						(optarg, &parsedProtocol, &parsedHost, &parsedPort,
						 &parsedPlaypath, &parsedApp))
					{
						RTMP_Log(RTMP_LOGWARNING, "Couldn't parse the specified url (%s)!",
								 optarg);
					}
					else
					{
						if (!hostname.av_len)
							hostname = parsedHost;
						if (port == -1)
							port = parsedPort;
						if (playpath.av_len == 0 && parsedPlaypath.av_len)
						{
							playpath = parsedPlaypath;
						}
						if (protocol == RTMP_PROTOCOL_UNDEFINED)
							protocol = parsedProtocol;
						if (app.av_len == 0 && parsedApp.av_len)
						{
							app = parsedApp;
						}
					}
					break;
				}
			case 'i':
				STR2AVAL(fullUrl, optarg);
				break;
			case 's':
				STR2AVAL(swfUrl, optarg);
				break;
			case 't':
				STR2AVAL(tcUrl, optarg);
				break;
			case 'p':
				STR2AVAL(pageUrl, optarg);
				break;
			case 'a':
				STR2AVAL(app, optarg);
				break;
			case 'f':
				STR2AVAL(flashVer, optarg);
				break;
			case 'o':
				flvFile = optarg;
				if (strcmp(flvFile, "-"))
					bStdoutMode = FALSE;

				break;
			case 'e':
				bResume = TRUE;
				break;
			case 'u':
				STR2AVAL(auth, optarg);
				break;
			case 'C': {
					AVal av;
					STR2AVAL(av, optarg);
					if (!RTMP_SetOpt(&rtmp, &av_conn, &av))
					{
						RTMP_Log(RTMP_LOGERROR, "Invalid AMF parameter: %s", optarg);
						return RD_FAILED;
					}
				}
				break;
			case 'm':
				timeout = atoi(optarg);
				break;
			case 'A':
				dStartOffset = (int) (atof(optarg) * 1000.0);
				break;
			case 'B':
				dStopOffset = (int) (atof(optarg) * 1000.0);
				break;
			case 'T': {
					AVal token;
					STR2AVAL(token, optarg);
					RTMP_SetOpt(&rtmp, &av_token, &token);
				}
				break;
			case '#':
				bHashes = TRUE;
				break;
			case 'q':
				RTMP_debuglevel = RTMP_LOGCRIT;
				break;
			case 'V':
				RTMP_debuglevel = RTMP_LOGDEBUG;
				break;
			case 'z':
				RTMP_debuglevel = RTMP_LOGALL;
				break;
			case 'S':
				STR2AVAL(sockshost, optarg);
				break;
			case 'j':
				STR2AVAL(usherToken, optarg);
				break;
			default:
				RTMP_LogPrintf("unknown option: %c\n", opt);
				usage(argv[0]);
				return RD_FAILED;
				break;
		}
	}

	if (!hostname.av_len && !fullUrl.av_len)
	{
		RTMP_Log(RTMP_LOGERROR,
				 "You must specify a hostname (--host) or url (-r \"rtmp://host[:port]/playpath\") containing a hostname");
		return RD_FAILED;
	}
	if (playpath.av_len == 0 && !fullUrl.av_len)
	{
		RTMP_Log(RTMP_LOGERROR,
				 "You must specify a playpath (--playpath) or url (-r \"rtmp://host[:port]/playpath\") containing a playpath");
		return RD_FAILED;
	}

	if (protocol == RTMP_PROTOCOL_UNDEFINED && !fullUrl.av_len)
	{
		RTMP_Log(RTMP_LOGWARNING,
				 "You haven't specified a protocol (--protocol) or rtmp url (-r), using default protocol RTMP");
		protocol = RTMP_PROTOCOL_RTMP;
	}
	if (port == -1 && !fullUrl.av_len)
	{
		RTMP_Log(RTMP_LOGWARNING,
				 "You haven't specified a port (--port) or rtmp url (-r), using default port 1935");
		port = 0;
	}
	if (port == 0 && !fullUrl.av_len)
	{
		if (protocol & RTMP_FEATURE_SSL)
			port = 443;
		else if (protocol & RTMP_FEATURE_HTTP)
			port = 80;
		else
			port = 1935;
	}

	if (flvFile == 0)
	{
		RTMP_Log(RTMP_LOGWARNING,
				 "You haven't specified an output file (-o filename), using stdout");
		bStdoutMode = TRUE;
	}

	if (bStdoutMode && bResume)
	{
		RTMP_Log(RTMP_LOGWARNING,
				 "Can't resume in stdout mode, ignoring --resume option");
		bResume = FALSE;
	}

	if (bLiveStream && bResume)
	{
		RTMP_Log(RTMP_LOGWARNING, "Can't resume live stream, ignoring --resume option");
		bResume = FALSE;
	}

#ifdef CRYPTO
	if (swfVfy)
	{
		if (RTMP_HashSWF(swfUrl.av_val, (unsigned int *)&swfSize, hash, swfAge) == 0)
		{
			swfHash.av_val = (char *)hash;
			swfHash.av_len = RTMP_SWF_HASHLEN;
		}
	}

	if (swfHash.av_len == 0 && swfSize > 0)
	{
		RTMP_Log(RTMP_LOGWARNING,
				 "Ignoring SWF size, supply also the hash with --swfhash");
		swfSize = 0;
	}

	if (swfHash.av_len != 0 && swfSize == 0)
	{
		RTMP_Log(RTMP_LOGWARNING,
				 "Ignoring SWF hash, supply also the swf size  with --swfsize");
		swfHash.av_len = 0;
		swfHash.av_val = NULL;
	}
#endif

	if (tcUrl.av_len == 0)
	{
		tcUrl.av_len = strlen(RTMPProtocolStringsLower[protocol]) +
					   hostname.av_len + app.av_len + sizeof("://:65535/");
		tcUrl.av_val = (char *) malloc(tcUrl.av_len);
		if (!tcUrl.av_val)
			return RD_FAILED;
		tcUrl.av_len = snprintf(tcUrl.av_val, tcUrl.av_len, "%s://%.*s:%d/%.*s",
								RTMPProtocolStringsLower[protocol], hostname.av_len,
								hostname.av_val, port, app.av_len, app.av_val);
	}

	int first = 1;

	// User defined seek offset
	if (dStartOffset > 0)
	{
		// Live stream
		if (bLiveStream)
		{
			RTMP_Log(RTMP_LOGWARNING,
					 "Can't seek in a live stream, ignoring --start option");
			dStartOffset = 0;
		}
	}

	if (!fullUrl.av_len)
	{
		RTMP_SetupStream(&rtmp, protocol, &hostname, port, &sockshost, &playpath,
						 &tcUrl, &swfUrl, &pageUrl, &app, &auth, &swfHash, swfSize,
						 &flashVer, &subscribepath, &usherToken, dSeek, dStopOffset, bLiveStream, timeout);
	}
	else
	{
		if (RTMP_SetupURL(&rtmp, fullUrl.av_val) == FALSE)
		{
			RTMP_Log(RTMP_LOGERROR, "Couldn't parse URL: %s", fullUrl.av_val);
			return RD_FAILED;
		}
	}

	/* Try to keep the stream moving if it pauses on us */
	if (!bLiveStream && !bRealtimeStream && !(protocol & RTMP_FEATURE_HTTP))
		rtmp.Link.lFlags |= RTMP_LF_BUFX;

	off_t size = 0;

	// ok, we have to get the timestamp of the last keyframe (only keyframes are seekable) / last audio frame (audio only streams)
	if (bResume)
	{
		nStatus =
		OpenResumeFile(flvFile, &file, &size, &metaHeader, &nMetaHeaderSize,
					   &duration);
		if (nStatus == RD_FAILED)
			goto clean;

		if (!file)
		{
			// file does not exist, so go back into normal mode
			bResume = FALSE;  // we are back in fresh file mode (otherwise finalizing file won't be done)
		}
		else
		{
			nStatus = GetLastKeyframe(file, nSkipKeyFrames,
									  &dSeek, &initialFrame,
									  &initialFrameType, &nInitialFrameSize);
			if (nStatus == RD_FAILED)
			{
				RTMP_Log(RTMP_LOGDEBUG, "Failed to get last keyframe.");
				goto clean;
			}

			if (dSeek == 0)
			{
				RTMP_Log(RTMP_LOGDEBUG,
						 "Last keyframe is first frame in stream, switching from resume to normal mode!");
				bResume = FALSE;
			}
		}
	}

	if (!file)
	{
		if (bStdoutMode)
		{
			file = stdout;
			SET_BINMODE(file);
		}
		else
		{
			file = fopen(flvFile, "w+b");
			if (file == 0)
			{
				RTMP_LogPrintf("Failed to open file! %s\n", flvFile);
				return RD_FAILED;
			}
		}
	}

#ifdef _DEBUG
	netstackdump = fopen("netstackdump", "wb");
	netstackdump_read = fopen("netstackdump_read", "wb");
#endif

	while (!RTMP_ctrlC)
	{
		RTMP_Log(RTMP_LOGDEBUG, "Setting buffer time to: %dms", bufferTime);
		RTMP_SetBufferMS(&rtmp, bufferTime);

		if (first)
		{
			first = 0;
			RTMP_LogPrintf("Connecting ...\n");

			if (!RTMP_Connect(&rtmp, NULL))
			{
				nStatus = RD_NO_CONNECT;
				break;
			}

			RTMP_Log(RTMP_LOGINFO, "Connected...");

			// User defined seek offset
			if (dStartOffset > 0)
			{
				// Don't need the start offset if resuming an existing file
				if (bResume)
				{
					RTMP_Log(RTMP_LOGWARNING,
							 "Can't seek a resumed stream, ignoring --start option");
					dStartOffset = 0;
				}
				else
				{
					dSeek = dStartOffset;
				}
			}

			// Calculate the length of the stream to still play
			if (dStopOffset > 0)
			{
				// Quit if start seek is past required stop offset
				if (dStopOffset <= dSeek)
				{
					RTMP_LogPrintf("Already Completed\n");
					nStatus = RD_SUCCESS;
					break;
				}
			}

			if (!RTMP_ConnectStream(&rtmp, dSeek))
			{
				nStatus = RD_FAILED;
				break;
			}
		}
		else
		{
			nInitialFrameSize = 0;

			if (retries)
			{
				RTMP_Log(RTMP_LOGERROR, "Failed to resume the stream\n\n");
				if (!RTMP_IsTimedout(&rtmp))
					nStatus = RD_FAILED;
				else
					nStatus	= RD_INCOMPLETE;
				break;
			}
			RTMP_Log(RTMP_LOGINFO, "Connection timed out, trying to resume.\n\n");
			/* Did we already try pausing, and it still didn't work? */
			if (rtmp.m_pausing == 3)
			{
				/* Only one try at reconnecting... */
				retries = 1;
				dSeek = rtmp.m_pauseStamp;
				if (dStopOffset > 0)
				{
					if (dStopOffset <= dSeek)
					{
						RTMP_LogPrintf("Already Completed\n");
						nStatus = RD_SUCCESS;
						break;
					}
				}
				if (!RTMP_ReconnectStream(&rtmp, dSeek))
				{
					RTMP_Log(RTMP_LOGERROR, "Failed to resume the stream\n\n");
					if (!RTMP_IsTimedout(&rtmp))
						nStatus = RD_FAILED;
					else
						nStatus	= RD_INCOMPLETE;
					break;
				}
			}
			else if (!RTMP_ToggleStream(&rtmp))
			{
				RTMP_Log(RTMP_LOGERROR, "Failed to resume the stream\n\n");
				if (!RTMP_IsTimedout(&rtmp))
					nStatus = RD_FAILED;
				else
					nStatus	= RD_INCOMPLETE;
				break;
			}
			bResume = TRUE;
		}

		nStatus = Download(&rtmp, file, dSeek, dStopOffset, duration, bResume,
						   metaHeader, nMetaHeaderSize, initialFrame,
						   initialFrameType, nInitialFrameSize, nSkipKeyFrames,
						   bStdoutMode, bLiveStream, bRealtimeStream, bHashes,
						   bOverrideBufferTime, bufferTime, &percent);
		free(initialFrame);
		initialFrame = NULL;

		/* If we succeeded, we're done.
		 */
		if (nStatus != RD_INCOMPLETE || !RTMP_IsTimedout(&rtmp) || bLiveStream)
			break;
	}

	if (nStatus == RD_SUCCESS)
	{
		RTMP_LogPrintf("Download complete\n");
	}
	else if (nStatus == RD_INCOMPLETE)
	{
		RTMP_LogPrintf
		("Download may be incomplete (downloaded about %.2f%%), try resuming\n",
		 percent);
	}

	clean:
	RTMP_Log(RTMP_LOGDEBUG, "Closing connection.\n");
	RTMP_Close(&rtmp);

	if (file != 0)
		fclose(file);

	CleanupSockets();

#ifdef _DEBUG
	if (netstackdump != 0)
		fclose(netstackdump);
	if (netstackdump_read != 0)
		fclose(netstackdump_read);
#endif

	fclose(pLogFile);
	return nStatus;
}