From 2356acfbb312273cd26750508c22058cdbc4f3e0 Mon Sep 17 00:00:00 2001 From: Roman Bazalevsky Date: Fri, 11 Jul 2014 15:30:35 +0400 Subject: [PATCH] OpenWrt lightweight streamer for OSS (/dev/dsp) uncompressed sound data. --- etc/init.d/oss-streamer | 26 ++++ oss-streamer.c | 336 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 362 insertions(+) create mode 100644 etc/init.d/oss-streamer create mode 100644 oss-streamer.c diff --git a/etc/init.d/oss-streamer b/etc/init.d/oss-streamer new file mode 100644 index 0000000..9f0baf3 --- /dev/null +++ b/etc/init.d/oss-streamer @@ -0,0 +1,26 @@ +#!/bin/sh /etc/rc.common +# Copyright (C) 2009-2013 OpenWrt.org + +START=90 +STOP=10 + +USE_PROCD=1 +PROG=/usr/bin/oss-streamer + +error() { + echo "${initscript}:" "$@" 1>&2 +} + +start_instance() { + procd_open_instance + procd_set_param command "$PROG" -p 8081 -d /dev/dsp + procd_close_instance +} + +start_service() { + start_instance 'oss-streamer' +} + +service_triggers() { + procd_add_reload_trigger 'oss-streamer' +} diff --git a/oss-streamer.c b/oss-streamer.c new file mode 100644 index 0000000..c5702d4 --- /dev/null +++ b/oss-streamer.c @@ -0,0 +1,336 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SOURCE_VERSION "0.0" +#define BOUNDARY "arflebarfle" + +typedef enum { SNAPSHOT, STREAM } answer_t; + +/* globals */ +int stop=0, sd; +int force_delay=0; +#define BUF_SIZE 4096 +int source_dev; + +/* signal fresh frames */ +pthread_mutex_t db = PTHREAD_MUTEX_INITIALIZER; +pthread_cond_t db_update = PTHREAD_COND_INITIALIZER; + +/* global JPG frame, this is more or less the "database" */ +char g_buf[BUF_SIZE]; + +/* thread for clients that connected to this server */ +void *client_thread( void *arg ) { + int fd = *((int *)arg); + fd_set fds; + char frame[BUF_SIZE]; + int ok = 1; + char buffer[1024] = {0}; + struct timeval to; + + if (arg!=NULL) free(arg); else exit(1); + + /* set timeout to 5 seconds */ + to.tv_sec = 5; + to.tv_usec = 0; + FD_ZERO(&fds); + FD_SET(fd, &fds); + if( select(fd+1, &fds, NULL, NULL, &to) <= 0) { + close(fd); + return NULL; + } + + /* find out if we should deliver something other than a stream */ + + sprintf(buffer, "HTTP/1.0 200 OK\r\n" \ + "Server: OSS Streamer\r\n" \ + "Content-Type: audio/wav\r\n" \ + "Cache-Control: no-cache\r\n" \ + "Cache-Control: private\r\n" \ + "Pragma: no-cache\r\n" \ + "\r\n" + ); + + ok = ( write(fd, buffer, strlen(buffer)) >= 0)?1:0; + + while ( ok >= 0 && !stop ) { + + /* having a problem with windows (do we not always) browsers not updating the + stream display, unless the browser cache is disabled - try and implement a delay + to allow movement to end before streem goes on - kind of works, but not well enough */ + + /* wait for fresh frames */ + pthread_cond_wait(&db_update, &db); + + /* read buffer */ + memcpy(frame, g_buf, BUF_SIZE); + + pthread_mutex_unlock( &db ); + + ok = ( write(fd, frame, BUF_SIZE) >= 0)?1:0; + if( ok == 0 ) break; + + } + + close(fd); + + return NULL; +} + +/* the single writer thread */ +void *get_thread( void *arg ) { + + char rbuffer[BUF_SIZE]; + + while( !stop ) { + /* grab a frame */ + if( read(source_dev,rbuffer,BUF_SIZE) < 0 ) { + fprintf(stderr, "Error grabbing\n"); + exit(1); + } + + /* copy frame to global buffer */ + pthread_mutex_lock( &db ); + + memcpy(g_buf, rbuffer, BUF_SIZE); + + /* signal fresh_frame */ + pthread_cond_broadcast(&db_update); + pthread_mutex_unlock( &db ); + + /* only use usleep if the fps is below 5, otherwise the overhead is too long */ + } + + return NULL; +} + +void help(char *progname) +{ + fprintf(stderr, "------------------------------------------------------------------\n"); + fprintf(stderr, "Usage: oss-stream" \ + " [-h | --help ]........: display this help\n" \ + " [-d | --device ]......: video device to open (your camera)\n" \ + " [-p | --port ]........: TCP-port for the server\n" \ + " [-v | --version ].....: display version information\n" \ + " [-b | --background]...: fork to the background, daemon mode\n"); + fprintf(stderr, "------------------------------------------------------------------\n"); +} + +void signal_handler(int sigm) { + /* signal "stop" to threads */ + stop = 1; + + /* cleanup most important structures */ + fprintf(stderr, "shutdown...\n"); + usleep(1000*1000); + if (close (sd) < 0) + perror ("close sd");; + pthread_cond_destroy(&db_update); + pthread_mutex_destroy(&db); + exit(0); + return; +} + +void daemon_mode(void) { + int fr=0; + + fr = fork(); + if( fr < 0 ) { + fprintf(stderr, "fork() failed\n"); + exit(1); + } + if ( fr > 0 ) { + exit(0); + } + + if( setsid() < 0 ) { + fprintf(stderr, "setsid() failed\n"); + exit(1); + } + + fr = fork(); + if( fr < 0 ) { + fprintf(stderr, "fork() failed\n"); + exit(1); + } + if ( fr > 0 ) { + fprintf(stderr, "forked to background (%d)\n", fr); + exit(0); + } + + umask(0); + + chdir("/"); + close(0); + close(1); + close(2); + + open("/dev/null", O_RDWR); + dup(0); + dup(0); +} + +/* ######################################################################### +Main +######################################################################### */ +int main(int argc, char *argv[]) +{ + struct sockaddr_in addr; + int on=1; + pthread_t client, get; + char *dev = "/dev/dsp"; + unsigned int stream_port = 0; + int daemon=0; + + while(1) { + int option_index = 0, c=0; + static struct option long_options[] = \ + { + {"h", no_argument, 0, 0}, + {"help", no_argument, 0, 0}, + {"d", required_argument, 0, 0}, + {"device", required_argument, 0, 0}, + {"p", required_argument, 0, 0}, + {"port", required_argument, 0, 0}, + {"v", no_argument, 0, 0}, + {"version", no_argument, 0, 0}, + {"b", no_argument, 0, 0}, + {"background", no_argument, 0, 0}, + {0, 0, 0, 0} + }; + + c = getopt_long_only(argc, argv, "", long_options, &option_index); + + /* no more options to parse */ + if (c == -1) break; + + /* unrecognized option */ + if(c=='?'){ help(argv[0]); return 0; } + + switch (option_index) { + /* h, help */ + case 0: + case 1: + help(argv[0]); + return 0; + break; + + /* d, device */ + case 2: + case 3: + dev = strdup(optarg); + break; + + /* p, port */ + case 4: + case 5: + stream_port=htons(atoi(optarg)); + break; + + /* v, version */ + case 6: + case 7: + printf("OSS Streamer Version: %s\n" \ + "Compilation Date....: %s\n" \ + "Compilation Time....: %s\n", SOURCE_VERSION, __DATE__, __TIME__); + return 0; + break; + + /* b, background */ + case 8: + case 9: + daemon=1; + break; + + default: + help(argv[0]); + return 0; + } + } + + if ( stream_port==0 ) { + fprintf(stderr, "server port not set\n"); + exit(2); + } + + /* ignore SIGPIPE (send if transmitting to closed sockets) */ + signal(SIGPIPE, SIG_IGN); + if (signal(SIGINT, signal_handler) == SIG_ERR) { + fprintf(stderr, "could not register signal handler\n"); + exit(1); + } + + /* fork to the background */ + if ( daemon ) { + daemon_mode(); + } + + /* allocate audio datastructure */ + + /* open video device and prepare data structure */ + source_dev = open(dev,O_RDONLY); + if (source_dev < 0) { + fprintf(stderr, "error opening source device\n"); + exit(1); + } + + /* open socket for server */ + sd = socket(PF_INET, SOCK_STREAM, 0); + if ( sd < 0 ) { + fprintf(stderr, "socket failed\n"); + exit(1); + } + + /* ignore "socket already in use" errors */ + if (setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) { + perror("setsockopt(SO_REUSEADDR) failed"); + exit(1); + } + + /* configure server address to listen to all local IPs */ + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = stream_port; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + if ( bind(sd, (struct sockaddr*)&addr, sizeof(addr)) != 0 ) { + fprintf(stderr, "bind failed\n"); + perror("Bind"); + exit(1); + } + + /* start listening on socket */ + if ( listen(sd, 10) != 0 ) { + fprintf(stderr, "listen failed\n"); + exit(1); + } + + /* start to read the camera, push picture buffers into global buffer */ + pthread_create(&get, 0, get_thread, NULL); + pthread_detach(get); + + /* start motor control server */ + + /* create a child for every client that connects */ + while ( 1 ) { + int *pfd = (int *)calloc(1, sizeof(int)); + *pfd = accept(sd, 0, 0); + pthread_create(&client, NULL, &client_thread, pfd); + pthread_detach(client); + + } + + return 0; +} -- 2.34.1