Commit a9ec8fef authored by Kenneth Arnold's avatar Kenneth Arnold

Buffering for ogg123. It's not as robust as it could be, but still

better than nothing.

gcc 2.95.2 20000220 does _not_ compile this right with -O1 or higher.
gdb cannot properly debug multithreaded apps (it follows the parent
process even when I tell it to follow the child explicitly), so I have
not been able to figure out where the culprit is either.

This is a Unix buffer implementation. Non-Unix platforms should replace
buffer.c with a pass-through in submit_chunk that just calls devices_write.

svn path=/trunk/vorbis-tools/; revision=1138
parent 7e83c980
......@@ -12,9 +12,9 @@ INCLUDES = @OGG_CFLAGS@ @VORBIS_CFLAGS@ @AO_CFLAGS@
ogg123_LDFLAGS = @OGG_LIBS@ @VORBIS_LIBS@ @VORBISFILE_LIBS@ @AO_LIBS@
ogg123_INCLUDES = ogg123.h
ogg123_INCLUDES = ogg123.h buffer.h
ogg123_SOURCES = ogg123.c ao_interface.c
ogg123_SOURCES = ogg123.c ao_interface.c buffer.c
EXTRA_DIST = $(man_MANS) $(doc_DATA)
......
/* buffer.c
* buffering code for ogg123. This is Unix-specific. Other OSes anyone?
*
* Thanks to Lee McLouchlin's buffer(1) for inspiration; no code from
* that program is used in this buffer.
*/
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h> /* for fork and pipe*/
#include <fcntl.h>
#include <malloc.h>
#include "ogg123.h"
#include "buffer.h"
/* Initialize the buffer structure. */
void buffer_init (buf_t *buf, long size)
{
buf->status = 0;
buf->reader = buf->writer = buf->buffer;
buf->end = buf->buffer + (size - 1);
}
/* Main write loop. No semaphores. No deadlock. No problem. I hope. */
void writer_main (buf_t *buf, devices_t *d)
{
devices_t *d1;
while (! (buf->status & STAT_SHUTDOWN && buf->reader == buf->writer))
{
/* Writer just waits on reader to be done with buf_write.
* Reader must ensure that we don't deadlock. */
write (buf->fds[1], "1", 1); /* This identifier could hold a lot
* more detail in the future. */
while (buf->reader == buf->writer && !(buf->status & STAT_SHUTDOWN));
if (buf->reader == buf->writer) break;
/* devices_write (buf->writer->data, buf->writer->len, d); */
{
d1 = d;
while (d1 != NULL) {
ao_play(d1->device, buf->writer->data, buf->writer->len);
d1 = d1->next_device;
}
}
if (buf->writer == buf->end)
buf->writer = buf->buffer;
else
buf->writer++;
}
buf->status = 0;
write (buf->fds[1], "2", 1);
_exit(0);
}
/* fork_writer is called to create the writer process. This creates
* the shared memory segment of 'size' chunks, and returns a pointer
* to the buffer structure that is shared. Just pass this straight to
* submit_chunk and all will be happy. */
buf_t *fork_writer (long size, devices_t *d)
{
int childpid;
buf_t *buf;
/* Get the shared memory segment. */
int shmid = shmget (IPC_PRIVATE,
sizeof(buf_t) + sizeof (chunk_t) * (size - 1),
IPC_CREAT|S_IREAD|S_IWRITE);
if (shmid == -1)
{
perror ("shmget");
exit (1);
}
/* Attach the segment to us (and our kids). Get and store the pointer. */
buf = (buf_t *) shmat (shmid, 0, 0);
if (buf == NULL)
{
perror ("shmat");
exit (1);
}
buffer_init (buf, size);
/* Create a pipe for communication between the two processes. Unlike
* the first incarnation of an ogg123 buffer, the data is not transferred
* over this channel, only occasional "WAKE UP!"'s. */
if (pipe (buf->fds))
{
perror ("pipe");
exit (1);
}
fcntl (buf->fds[1], F_SETFL, O_NONBLOCK);
/* write should never block; read should always block. */
fflush (stdout);
/* buffer flushes stderr, but stderr is unbuffered (*duh*!) */
childpid = fork();
if (childpid == -1)
{
perror ("fork");
exit (1);
}
if (childpid == 0)
{
writer_main (buf, d);
return NULL;
}
else
return buf;
}
void submit_chunk (buf_t *buf, chunk_t chunk)
{
struct timeval tv;
static fd_set set;
FD_ZERO(&set);
FD_SET(buf->fds[0], &set);
/* Wait wait, don't step on my sample! */
while (!((buf->reader != buf->end && buf->reader + 1 != buf->writer) ||
(buf->reader == buf->end && buf->writer != buf->buffer)))
{
/* buffer overflow (yikes! no actually it's a GOOD thing) */
int ret;
char t;
tv.tv_sec = 1;
tv.tv_usec = 0;
ret = select (buf->fds[0]+1, &set, NULL, NULL, &tv);
while (ret-- > 0)
read (buf->fds[0], &t, 1);
}
*(buf->reader) = chunk;
/* do this atomically */
if (buf->reader == buf->end)
buf->reader = buf->buffer;
else
buf->reader++;
}
void buffer_shutdown (buf_t *buf)
{
struct timeval tv;
buf->status |= STAT_SHUTDOWN;
while (buf->status != 0)
{
tv.tv_sec = 1;
tv.tv_usec = 0;
select (0, NULL, NULL, NULL, &tv);
}
}
/* Common things between reader and writer threads */
#ifndef __BUFFER_H
#define __BUFFER_H
#include "ogg123.h"
typedef struct chunk_s
{
long len; /* Length of the chunk (for if we only got partial data) */
char data[4096]; /* Data. 4096 is the chunk size we request from libvorbis. */
} chunk_t;
typedef struct buf_s
{
char status; /* Status. See STAT_* below. */
int fds[2]; /* Pipe file descriptors. */
chunk_t *reader; /* Chunk the reader is busy with */
chunk_t *writer; /* Chunk the writer is busy with */
chunk_t *end; /* Last chunk in the buffer (for convenience) */
chunk_t buffer[1]; /* The buffer itself. It's more than one chunk. */
} buf_t;
buf_t *fork_writer (long size, devices_t *d);
void submit_chunk (buf_t *buf, chunk_t chunk);
void buffer_shutdown (buf_t *buf);
#define STAT_FLUSH 1
#define STAT_SHUTDOWN 2
#endif /* !defined (__BUFFER_H) */
......@@ -14,7 +14,7 @@
* *
********************************************************************
last mod: $Id: ogg123.c,v 1.17 2000/12/30 04:43:47 msmith Exp $
last mod: $Id: ogg123.c,v 1.18 2000/12/30 05:44:31 kcarnold Exp $
********************************************************************/
......@@ -28,8 +28,8 @@
#include <netdb.h>
#include <netinet/in.h>
#include <errno.h>
#include <getopt.h>
#include <time.h>
#include <getopt.h>
#include "ogg123.h"
......@@ -62,6 +62,7 @@ struct option long_options[] = {
{"verbose", no_argument, 0, 'v'},
{"quiet", no_argument, 0, 'q'},
{"shuffle", no_argument, 0, 'z'},
{"buffer", required_argument, 0, 'b'},
{0, 0, 0, 0}
};
......@@ -76,7 +77,7 @@ void usage(void)
"Usage: ogg123 [<options>] <input file> ...\n\n"
" -h, --help this help\n"
" -V, --version display Ogg123 version\n"
" -d, --device=d uses 'd' as an output device)\n"
" -d, --device=d uses 'd' as an output device\n"
" Possible devices are (some may not be compiled):\n"
" null (output nothing), oss (for Linux and *BSD),\n"
" irix, solaris, wav (write to a .WAV file)\n"
......@@ -84,6 +85,7 @@ void usage(void)
" -o, --device-option=k:v passes special option k with value\n"
" v to previously specified device (with -d). See\n"
" man page for more info.\n"
" -b n, --buffer n use a buffer of 'n' chunks (4096 bytes)\n"
" -v, --verbose display progress and other useful stuff\n"
" -q, --quiet don't display anything (no title)\n"
" -z, --shuffle shuffle play\n");
......@@ -98,6 +100,7 @@ int main(int argc, char **argv)
int temp_driver_id = -1;
devices_t *current;
int bits, rate, channels;
buf_t *buffer = NULL;
opt.read_file = NULL;
opt.shuffle = 0;
......@@ -106,18 +109,22 @@ int main(int argc, char **argv)
opt.seekpos = 0;
opt.instream = NULL;
opt.outdevices = NULL;
opt.buffer_size = 0;
ao_initialize();
temp_driver_id = get_default_device();
while (-1 != (ret = getopt_long(argc, argv, "d:hqk:o:vV:z",
while (-1 != (ret = getopt_long(argc, argv, "b:d:hk:o:qvV:z",
long_options, &option_index))) {
switch (ret) {
case 0:
fprintf(stderr,
"Internal error: long option given when none expected.\n");
exit(1);
case 'b':
opt.buffer_size = atoi (optarg);
break;
case 'd':
/* Need to store previous device before gathering options for
this device */
......@@ -216,6 +223,9 @@ int main(int argc, char **argv)
current = current->next_device;
}
if (opt.buffer_size)
buffer = fork_writer (opt.buffer_size, opt.outdevices);
if (opt.shuffle) {
/* Messy code that I didn't write -ken */
int i;
......@@ -233,21 +243,23 @@ int main(int argc, char **argv)
}
for (i = 0; i < nb; i++) {
opt.read_file = argv[p[i] + optind];
play_file(opt);
play_file(opt, buffer);
}
} else {
while (optind < argc) {
opt.read_file = argv[optind];
play_file(opt);
play_file(opt, buffer);
optind++;
}
}
buffer_shutdown (buffer);
while (opt.outdevices != NULL) {
ao_close(opt.outdevices->device);
current = opt.outdevices->next_device;
free(opt.outdevices);
opt.outdevices = current;
ao_close(opt.outdevices->device);
current = opt.outdevices->next_device;
free(opt.outdevices);
opt.outdevices = current;
}
ao_shutdown();
......@@ -255,7 +267,7 @@ int main(int argc, char **argv)
return (0);
}
void play_file(ogg123_options_t opt)
void play_file(ogg123_options_t opt, buf_t *buffer)
{
/* Oh my gosh this is disgusting. Big cleanups here will include an
almost complete rewrite of the hacked-out HTTP streaming, a shift
......@@ -322,12 +334,13 @@ void play_file(ogg123_options_t opt)
char last = 0, in = 0;
int eol = 0;
/* Need to 'quiet' this header dump */
fprintf(stderr, "HTTP Headers:\n");
if (opt.verbose > 0)
fprintf(stderr, "HTTP Headers:\n");
for (;;) {
last = in;
in = getc(opt.instream);
putc(in, stderr);
if (opt.verbose > 0)
putc(in, stderr);
if (last == 13 && in == 10) {
if (eol)
break;
......@@ -372,7 +385,7 @@ void play_file(ogg123_options_t opt)
int i;
for (i = 0; ogg_comment_keys[i].key != NULL; i++)
if (!strncmp
if (!strncasecmp
(ogg_comment_keys[i].key, cc,
strlen(ogg_comment_keys[i].key))) {
fprintf(stderr, ogg_comment_keys[i].formatstr,
......@@ -421,7 +434,17 @@ void play_file(ogg123_options_t opt)
if (old_section != current_section && old_section != -1)
eos = 1;
devices_write(convbuffer, ret, opt.outdevices);
if (buffer)
{
chunk_t chunk;
chunk.len = ret;
memcpy (chunk.data, convbuffer, ret);
submit_chunk (buffer, chunk);
}
else
devices_write(convbuffer, ret, opt.outdevices);
if (opt.verbose > 0) {
u_pos = ov_time_tell(&vf);
c_min = (long) u_pos / (long) 60;
......
......@@ -25,15 +25,19 @@ typedef struct ogg123_options_s {
double seekpos; /* Amount to seek by */
FILE *instream; /* Stream to read from. */
devices_t *outdevices; /* Streams to write to. */
int buffer_size; /* Size of the buffer in chunks. */
} ogg123_options_t; /* Changed in 0.6 to be non-static */
/* This goes here because it relies on some of the above. */
#include "buffer.h"
devices_t *append_device(devices_t * devices_list, int driver_id,
ao_option_t * options);
void devices_write(void *ptr, size_t size, devices_t * d);
void usage(void);
int add_option(ao_option_t ** op_h, const char *optstring);
int get_default_device(void);
void play_file(ogg123_options_t opt);
void play_file(ogg123_options_t opt, buf_t *buffer);
int get_tcp_socket(void); /* Will be going soon. */
FILE *http_open(char *server, int port, char *path); /* ditto */
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment