mjpg-streamer/plugins/input_http/mjpg-proxy.c

245 lines
8.3 KiB
C

/*******************************************************************************
# #
# Copyright (C) 2011 Eugene Katsevman #
# #
# This program is free software; you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program; if not, write to the Free Software #
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #
# #
*******************************************************************************/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <getopt.h>
#include "version.h"
#include "mjpg-proxy.h"
#include "misc.h"
#define HEADER 1
#define CONTENT 0
#define NETBUFFER_SIZE 1024 * 4
#define TRUE 1
#define FALSE 0
const char * CONTENT_LENGTH = "Content-Length:";
// TODO: this must be decoupled from mjpeg-streamer
const char * BOUNDARY = "--boundarydonotcross";
void init_extractor_state(struct extractor_state * state) {
state->length = 0;
state->part = HEADER;
state->last_four_bytes = 0;
state->contentlength.string = CONTENT_LENGTH;
state->boundary.string = BOUNDARY;
search_pattern_reset(&state->contentlength);
search_pattern_reset(&state->boundary);
}
void init_mjpg_proxy(struct extractor_state * state){
state->hostname = strdup("localhost");
state->port = strdup("8080");
init_extractor_state(state);
}
// main method
// we process all incoming buffer byte per byte and extract binary data from it to state->buffer
// if boundary is detected, then callback for image processing is run
// TODO; decouple from mjpeg streamer and ensure content-length processing
// for that, we must properly work with headers, not just detect them
void extract_data(struct extractor_state * state, char * buffer, int length) {
int i;
for (i = 0; i < length && !*(state->should_stop); i++) {
switch (state->part) {
case HEADER:
push_byte(&state->last_four_bytes, buffer[i]);
if (is_crlfcrlf(state->last_four_bytes))
state->part = CONTENT;
else if (is_crlf(state->last_four_bytes))
search_pattern_reset(&state->contentlength);
else {
search_pattern_compare(&state->contentlength, buffer[i]);
if (search_pattern_matches(&state->contentlength)) {
DBG("Content length found\n");
search_pattern_reset(&state->contentlength);
}
}
break;
case CONTENT:
if (state->length >= BUFFER_SIZE - 1) {
perror("Buffer too small\n");
break;
}
state->buffer[state->length++] = buffer[i];
search_pattern_compare(&state->boundary, buffer[i]);
if (search_pattern_matches(&state->boundary)) {
state->length -= (strlen(state->boundary.string)+2); // magic happens here
DBG("Image of length %d received\n", (int)state->length);
if (state->on_image_received) // callback
state->on_image_received(state->buffer, state->length);
init_extractor_state(state); // reset fsm
}
break;
}
}
}
char request [] = "GET /?action=stream HTTP/1.0\r\n\r\n";
void send_request_and_process_response(struct extractor_state * state) {
int recv_length;
char netbuffer[NETBUFFER_SIZE];
init_extractor_state(state);
// send request
send(state->sockfd, request, sizeof(request), 0);
// and listen for answer until sockerror or THEY stop us
// TODO: we must handle EINTR here, it really might occur
while ( (recv_length = recv(state->sockfd, netbuffer, sizeof(netbuffer), 0)) > 0 && !*(state->should_stop))
extract_data(state, netbuffer, recv_length);
}
// TODO:this must be reworked to decouple from mjpeg-streamer
void show_help(char * program_name) {
fprintf(stderr, " ---------------------------------------------------------------\n" \
" Help for input plugin..: %s\n" \
" ---------------------------------------------------------------\n" \
" The following parameters can be passed to this plugin:\n\n" \
" [-v | --version ]........: current SVN Revision\n" \
" [-h | --help]............: show this message\n"
" [-H | --host]............: select host to data from, localhost is default\n"
" [-p | --port]............: port, defaults to 8080\n"
" ---------------------------------------------------------------\n", program_name);
}
// TODO: this must be reworked, too. I don't know how
void show_version() {
printf("Version - %s\n", VERSION);
}
int parse_cmd_line(struct extractor_state * state, int argc, char * argv []) {
while (TRUE) {
static struct option long_options [] = {
{"help", no_argument, 0, 'h'},
{"version", no_argument, 0, 'v'},
{"host", required_argument, 0, 'H'},
{"port", required_argument, 0, 'p'},
{0,0,0,0}
};
int index = 0, c = 0;
c = getopt_long_only(argc,argv, "hvH:p:", long_options, &index);
if (c==-1) break;
if (c=='?'){
show_help(argv[0]);
return 1;
}
else
switch (c) {
case 'h' :
show_help(argv[0]);
return 1;
break;
case 'v' :
show_version();
return 1;
break;
case 'H' :
free(state->hostname);
state->hostname = strdup(optarg);
break;
case 'p' :
free(state->port);
state->port = strdup(optarg);
break;
}
}
return 0;
}
// TODO: consider using hints for http
// TODO: consider moving delays to plugin command line arguments
void connect_and_stream(struct extractor_state * state){
struct addrinfo * info, * rp;
int errorcode;
while (TRUE) {
errorcode = getaddrinfo(state->hostname, state->port, NULL, &info);
if (errorcode) {
perror(gai_strerror(errorcode));
};
for (rp = info ; rp != NULL; rp = rp->ai_next) {
state->sockfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (state->sockfd <0) {
perror("Can't allocate socket, will continue probing\n");
continue;
}
DBG("socket value is %d\n", state->sockfd);
if (connect(state->sockfd, (struct sockaddr *) rp->ai_addr, rp->ai_addrlen)>=0 ) {
DBG("connected to host\n");
break;
}
close(state->sockfd);
}
freeaddrinfo(info);
if (rp==NULL) {
perror("Can't connect to server, will retry in 5 sec");
sleep(5);
}
else
{
send_request_and_process_response(state);
DBG ("Closing socket\n");
close (state->sockfd);
if (*state->should_stop)
break;
sleep(1);
};
}
}
void close_mjpg_proxy(struct extractor_state * state){
free(state->hostname);
free(state->port);
}