diff options
author | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
---|---|---|
committer | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
commit | e2de64d6f1beb9e492daf5b886e19933c1fa41dd (patch) | |
tree | 9047cf9e6b5c43878d5bf82660adae77ceee097a /kscd/cddaslave.c | |
download | tdemultimedia-e2de64d6f1beb9e492daf5b886e19933c1fa41dd.tar.gz tdemultimedia-e2de64d6f1beb9e492daf5b886e19933c1fa41dd.zip |
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdemultimedia@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kscd/cddaslave.c')
-rw-r--r-- | kscd/cddaslave.c | 532 |
1 files changed, 532 insertions, 0 deletions
diff --git a/kscd/cddaslave.c b/kscd/cddaslave.c new file mode 100644 index 00000000..6b65c640 --- /dev/null +++ b/kscd/cddaslave.c @@ -0,0 +1,532 @@ +/* + * @(#)cddaslave.c 1.11 13 Sep 1995 + * + * Digital audio manipulator for WorkMan. + * + * The CDDA architecture looks like this: + * + * WorkMan (or another UI!) + * ^^^ + * ||| (separate processes connected by pipe) + * vvv + * +------------- cddaslave -------------+ + * | | | + * command module CDDA reader audio output + * (portable) (per platform) (per platform) + * + * This source file has the command module and some of the scaffolding + * to hold cddaslave together, plus some non-system-dependent audio + * processing code. Look in plat_*_cdda.c for system-specific stuff. + */ + + +#include "libwm/include/wm_config.h" +#include "libwm/include/wm_cdda.h" + +#ifdef BUILD_CDDA /* { */ + +#include <stdio.h> +#include <sys/types.h> +#include <sys/time.h> + +#ifndef timerclear +#define timerclear(tvp) ((tvp)->tv_sec = (tvp)->tv_usec = 0) +#endif + +int playing = 0; /* Should the CD be playing now? */ + +/* + * Loudness setting, plus the floating volume multiplier and decaying-average + * volume level. + */ +int loudness = 0; +unsigned int volume = 32768; +unsigned int level; + +/* + * Playback speed (0 = slow) + */ +int speed = 128; + +/* + * This is non-null if we're saving audio to a file. + */ +FILE *output = NULL; + +/* + * Audio file header format. + */ +typedef unsigned long u_32; +struct auheader { + u_32 magic; + u_32 hdr_size; + u_32 data_size; + u_32 encoding; + u_32 sample_rate; + u_32 channels; +}; + +#ifdef BIG_ENDIAN +# ifndef htonl +# define htonl(x) (x) +# endif +#else +extern unsigned long htonl(x); +#endif + +void *malloc(); +long cdda_transform(); + +/* + * Send status information upstream. + */ +void +send_status(struct cdda_block *blk) +{ + write(1, blk, sizeof(*blk)); +} + +/* + * Accept a command from our master. + * + * The protocol is byte-oriented: + * PmsfMSFxyz Play from msf to MSF (MSF can be 0,0,0 to play to end) + * xyz is the msf of the start of this chunk, i.e., the + * ending boundary for reverse play. + * S Stop. + * Q Quit. + * Vn Set volume level (0-255). + * Bn Set balance level (0-255). + * EnL Set an equalizer level (n = 0 for bass, 255 for treble) + * G Get current status. + * sn Set speed multiplier to n. + * dn Set direction to forward (n = 0) or reverse. + * Fllllx... Start saving to a file (length = l, followed by name) + * F0000 Stop saving. + * Ln Set loudness level (0-255). + */ +void +command(int cd_fd, struct cdda_block *blk) +{ + unsigned char inbuf[10]; + char *filename; + int namelen; + struct auheader hdr; + + if (read(0, inbuf, 1) <= 0) /* Parent died. */ + { + wmcdda_close(); + wmaudio_close(); + exit(0); + } + + switch (inbuf[0]) { + case 'P': + read(0, inbuf, 9); + playing = 1; + + wmaudio_stop(); + + wmcdda_setup(inbuf[0] * 60 * 75 + inbuf[1] * 75 + inbuf[2], + inbuf[3] * 60 * 75 + inbuf[4] * 75 + inbuf[5], + inbuf[6] * 60 * 75 + inbuf[7] * 75 + inbuf[8]); + + wmaudio_ready(); + + level = 2500; + volume = 1 << 15; + + blk->status = WMCDDA_ACK; + send_status(blk); + break; + + case 'S': + playing = 0; + wmaudio_stop(); + blk->status = WMCDDA_ACK; + send_status(blk); + blk->status = WMCDDA_STOPPED; + send_status(blk); + break; + + case 'B': + read(0, inbuf, 1); + wmaudio_balance(inbuf[0]); + blk->status = WMCDDA_ACK; + send_status(blk); + break; + + case 'V': + read(0, inbuf, 1); + wmaudio_volume(inbuf[0]); + blk->status = WMCDDA_ACK; + send_status(blk); + break; + + case 'G': + blk->status = WMCDDA_ACK; + send_status(blk); + + if (playing) + blk->status = WMCDDA_PLAYED; + else + blk->status = WMCDDA_STOPPED; + wmaudio_state(blk); + send_status(blk); + break; + + case 'Q': + blk->status = WMCDDA_ACK; + send_status(blk); + wmcdda_close(); + wmaudio_close(); + exit(0); + + case 's': + read(0, inbuf, 1); + speed = inbuf[0]; + wmcdda_speed(speed); + blk->status = WMCDDA_ACK; + send_status(blk); + break; + + case 'd': + read(0, inbuf, 1); + wmcdda_direction(inbuf[0]); + blk->status = WMCDDA_ACK; + send_status(blk); + break; + + case 'L': + read(0, inbuf, 1); + loudness = inbuf[0]; + blk->status = WMCDDA_ACK; + send_status(blk); + break; + + case 'F': + read(0, &namelen, sizeof(namelen)); + if (output != NULL) + { + fclose(output); + output = NULL; + } + if (namelen) + { + filename = malloc(namelen + 1); + if (filename == NULL) + { + perror("cddaslave"); + wmcdda_close(); + wmaudio_close(); + exit(1); + } + + read(0, filename, namelen); + filename[namelen] = '\0'; + output = fopen(filename, "w"); + if (output == NULL) + perror(filename); + else + { + /* Write an .au file header. */ + hdr.magic = htonl(0x2e736e64); + hdr.hdr_size = htonl(sizeof(hdr) + 28); + hdr.data_size = htonl(~0); + hdr.encoding = htonl(3); /* linear-16 */ + hdr.sample_rate = htonl(44100); + hdr.channels = htonl(2); + + fwrite(&hdr, sizeof(hdr), 1, output); + fwrite("Recorded from CD by WorkMan", 28, 1, + output); + } + + free(filename); + } + + blk->status = WMCDDA_ACK; + send_status(blk); + } +} + +/* + * Transform some CDDA data. + */ +long +wmcdda_transform(unsigned char *rawbuf, long buflen, struct cdda_block *block) +{ + long i; + long *buf32 = (long *)rawbuf; + register short *buf16 = (short *)rawbuf; + register int aval; + + /* + * Loudness transformation. Basically this is a self-adjusting + * volume control; our goal is to keep the average output level + * around a certain value (2500 seems to be pleasing.) We do this + * by maintaining a decaying average of the recent output levels + * (where "recent" is some fraction of a second.) All output levels + * are multiplied by the inverse of the decaying average; this has + * the volume-leveling effect we desire, and isn't too CPU-intensive. + * + * This is done by modifying the digital data, rather than adjusting + * the system volume control, because (at least on some systems) + * tweaking the system volume can generate little pops and clicks. + * + * There's probably a more elegant way to achieve this effect, but + * what the heck, I never took a DSP class and am making this up as + * I go along, with a little help from some friends. + * + * This is all done with fixed-point math, oriented around powers + * of two, which with luck will keep the CPU usage to a minimum. + * More could probably be done, for example using lookup tables to + * replace multiplies and divides; whether the memory hit (128K + * for each table) is worthwhile is unclear. + */ + if (loudness) + { + /* We aren't really going backwards, but i > 0 is fast */ + for (i = buflen / 2; i > 0; i--) + { + /* + * Adjust this sample to the current level. + */ + aval = (*buf16 = (((long)*buf16) * volume) >> 15); + buf16++; + + /* + * Don't adjust the decaying average for each sample; + * that just spends CPU time for very little benefit. + */ + if (i & 127) + continue; + + /* + * We want to use absolute values to compute the + * decaying average; otherwise it'd sit around 0. + */ + if (aval < 0) + aval = -aval; + + /* + * Adjust more quickly when we start hitting peaks, + * or we'll get clipping when there's a sudden loud + * section after lots of quiet. + */ + if (aval & ~8191) + aval <<= 3; + + /* + * Adjust the decaying average. + */ + level = ((level << 11) - level + aval) >> 11; + + /* + * Let *really* quiet sounds play softly, or we'll + * amplify background hiss to full volume and blast + * the user's speakers when real sound starts up. + */ + if (! (level & ~511)) + level = 512; + + /* + * And adjust the volume setting using the inverse + * of the decaying average. + */ + volume = (2500 << 15) / level; + } + } + + if (speed == 128) + return (buflen); + + /* + * Half-speed play. Stretch things out. + */ + if (speed == 0) + { + buflen /= 2; /* Since we're moving 32 bits at a time */ + + for (i = buflen - 1; i > 0; i--) + { + buf32[i] = buf32[i / 2]; + } + + buflen *= 4; /* 2 for doubling the buffer, 2 from above */ + } + + /* + * Slow play; can't optimize it as well as half-speed. + */ + if (speed && speed < 128) + { + int multiplier = ((speed + 128) * 128) / 256; + int newlen; + int tally = 0, pos; + + buflen /= 4; /* Get the number of 32-bit values */ + + /* + * Buffer length doubles when speed is 0, stays the same + * when speed is 128. + */ + newlen = (buflen * 128) / multiplier; + + pos = buflen - 1; + for (i = newlen - 1; i > 0; i--) + { + buf32[i] = buf32[pos]; + tally += multiplier; + if (tally & 128) + { + pos--; + tally ^= 128; + } + } + + buflen = newlen * 4; + } + + return (buflen); +} + + +main(argc, argv) + char **argv; +{ + int cd_fd = 3; + fd_set readfd, dummyfd; + struct timeval timeout; + char *cddabuf; + long cddabuflen; + struct cdda_block blockinfo; + long result; + int nfds; + char *devname; + + /* + * Device name should be the command-line argument. + */ + if (argc < 2) + devname = ""; + else + devname = argv[1]; + + /* + * If we're running setuid root, bump up our priority then lose + * superuser access. + */ + nice(-14); + setgid(getgid()); + setuid(getuid()); + if (getuid() != geteuid()) + return 255; + + FD_ZERO(&dummyfd); + FD_ZERO(&readfd); + + timerclear(&timeout); + + cd_fd = wmcdda_init(&cddabuf, &cddabuflen, cd_fd, devname); + if (cd_fd < 0) + exit(1); + wmaudio_init(); + + blockinfo.status = WMCDDA_ACK; + send_status(&blockinfo); + blockinfo.status = WMCDDA_STOPPED; + +fprintf(stderr,"cddaslave: done init."); + /* + * Accept commands as they come in, and play some sound if we're + * supposed to be doing that. + */ + while (1) + { + FD_SET(0, &readfd); + + /* + * If we're playing, we don't want select to block. Otherwise, + * wait a little while for the next command. + */ + if (playing) + timeout.tv_usec = 0; + else + timeout.tv_usec = 500000; + + nfds = select(1, &readfd, &dummyfd, &dummyfd, &timeout); + + if (nfds < 0) /* Broken pipe; our GUI exited. */ + { + wmcdda_close(cd_fd); + wmaudio_close(); + fprintf(stderr,"cddaslave: Borken pipe; GUI must have exited."); + exit(0); + } + + if (FD_ISSET(0, &readfd)) + { + command(cd_fd, &blockinfo); + /* + * Process all commands in rapid succession, rather + * than possibly waiting for a CDDA read. + */ + continue; + } + + if (playing) + { + result = wmcdda_read(cd_fd, cddabuf, cddabuflen, + &blockinfo); + if (result <= 0) + { + /* Let the output queue drain. */ + if (blockinfo.status == WMCDDA_DONE) + { + wmaudio_mark_last(); + if (wmaudio_send_status()) + { + /* queue drained, stop polling*/ + playing = 0; + } + } + else + { + playing = 0; + send_status(&blockinfo); + } + } + else + { + result = wmcdda_normalize(cddabuf, result, + &blockinfo); + result = wmcdda_transform(cddabuf, result, + &blockinfo); + if (output) + fwrite(cddabuf, result, 1, output); + result = wmaudio_convert(cddabuf, result, + &blockinfo); + if (wmaudio_play(cddabuf, result, &blockinfo)) + { + playing = 0; + wmaudio_stop(); + send_status(&blockinfo); + } + } + } + else + send_status(&blockinfo); + } +} + +#else /* BUILD_CDDA } { */ + +#include <stdio.h> +int main() +{ + printf("cddaslave: will work only on Solaris 2.4 or newer."); + exit(0); +} + +#endif /* } */ |