/* glidetv - display TV picture on a 3dfx card and control the TV tuner (3dfx card and glide library required) Copyright (C) 1997 Ralph Metzler (rjkm@thp.uni-koeln.de) Copyright (C) 1998 Heikki Hannikainen Copyright (C) 1999 Christian Gebauer (gebauer@bigfoot.com) 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; either version 2 of the License, or (at your option) any later version. 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., 675 Mass Ave, Cambridge, MA 02139, USA. Changelog: Initial version by Ralph Metzler (rjkm@thp.uni-koeln.de) sometime during the year 1997... 31-Jan-1998 Heikki Hannikainen : - Reformatted (indent...) - Split up the stuff to functions, comment it to some degree (just to make myself clear how it works...) - Added support for xtvscreen's config file ~/.xtvrc for input of channel data - Added commands for selecting channels - 7-segment LED styled channel indicator (ugly code) 23-Jan-1999 Christian Gebauer (gebauer@bigfoot.com): - Removed the channel indicator (it is not very useful if you want to use your computer monitor as a tv-set). - Added proper initialization of the bttv-driver. - Added support for the picture-parameters in xtvscreen´s config file. - Added support for different video-sources by pressing 'V' - sound card support: glidetv is now able to adjust the audio volume. On exit the original volume is restored The mayor advantage is that glidetv avoids the annoying cracking sound during the channel-switch by muting the audio during the procedure. - Add a key for muting audio. - Added a "Hot-key". With '/' the current channel is saved, the '*'-Key recalls it latter. This is useful for switching to a channel with a high number. - Added more "channel-keys" in the row "asdfghjklöä" (Channel Number 10 to 20) - Fixed the weird reaction on function-keys. - Tweaked the grSstWinOpen-call a little bit, because the old one doesn't work with a newer glide on my machine. */ #define CFGLEN 256 #include #include #include #include #include #include #include #include #include #include #include #include #include "../driver/bttv.h" #include "../driver/bt848.h" struct channel_t { char *name; int freq; int nr; int contrast; int brightness; int hue; int colour; struct channel_t *next; struct channel_t *prev; } *channels = NULL; char *bttvname = "/dev/bttv"; /* BTTV device name */ int fbttv; /* File descriptor for the device */ int one=1; int zero=0; int bttv_up=0; struct video_capability vcap; struct video_channel *vchan; int cur_source=0; /* Real video resolution used: */ int width = 768, height = 576; /* set this to 640 x 480 for NTSC */ /* Glide graphics: */ GrHwConfiguration hwconfig; /* Hardware configuration */ GrLfbInfo_t info; /* Mixer */ int mix_fd=-1; int vol_up; int vol_orig; int mix_id; #define LEFT 0x0001 #define RIGHT 0x0100 int audio_mute = 0; int audio_disabled = 1; /* * Print error-message and try close glide */ void fail(char *msg) { if (bttv_up) { ioctl(fbttv, VIDIOCCAPTURE,&zero); close(fbttv); } printf(msg); grLfbUnlock(GR_LFB_WRITE_ONLY, GR_BUFFER_FRONTBUFFER); grGlideShutdown(); exit(1); } /* * Initialize Mixer */ void open_mixer(void) { if (!audio_disabled) { mix_fd=open("/dev/mixer",O_RDWR); if(mix_fd<0) { printf("Fail: open /dev/mixer/\n"); exit(1); } if(ioctl(mix_fd,MIXER_READ(mix_id),&vol_orig)<0) { printf("Fail: get volume\n"); exit(1); } } } /* * Shutdown mixer */ void close_mixer(void) { if (!audio_disabled) { if(ioctl(mix_fd,MIXER_WRITE(mix_id),&vol_orig)<0) fail("Fail: set volume\n"); close(mix_fd); } } /* * Initialize the Glide library */ void init_glide(void) { GrScreenResolution_t resolution = GR_RESOLUTION_800x600; float scrWidth = 800.0f; float scrHeight = 600.0f; grGlideInit(); assert(grSstQueryHardware(&hwconfig)); grSstSelect(0); assert(grSstWinOpen(0, resolution, GR_REFRESH_60Hz, GR_COLORFORMAT_ABGR, GR_ORIGIN_UPPER_LEFT, 2, 0)); grDitherMode(GR_DITHER_DISABLE); grClipWindow(0, 0, (FxU32) scrWidth, (FxU32) scrHeight); grBufferClear(0x00, 0, 0); info.size = sizeof(GrLfbInfo_t); if (grLfbLock(GR_LFB_WRITE_ONLY, GR_BUFFER_FRONTBUFFER, GR_LFBWRITEMODE_565, GR_ORIGIN_UPPER_LEFT, FXFALSE, &info) == FXFALSE) { fprintf(stderr, "grLfbLock: Failed to take write lock\n"); grGlideShutdown(); exit(1); } } /* * Close the Glide library */ void close_glide(void) { grLfbUnlock(GR_LFB_WRITE_ONLY, GR_BUFFER_FRONTBUFFER); grGlideShutdown(); } /* * Open the bttv device */ void open_bttv(void) { if ((fbttv = open(bttvname, O_RDWR)) < 0) { fprintf(stderr, "Cannot open %s: %s\n", bttvname, strerror(errno)); exit(1); } bttv_up =1; } /* * Initialize the bttv device */ void init_bttv(void) { struct video_window vwin; struct video_buffer vbuf; struct video_audio *vaudio; int i; struct channel_t *channel; if(ioctl(fbttv,VIDIOCGCAP,&vcap)) fail("VIDIOCGCAP failed \n"); vchan = (struct video_channel*)malloc(sizeof(struct video_channel)*vcap.channels); memset(vchan,0,sizeof(struct video_channel)*vcap.channels); for (i = 0; i < vcap.channels; i++) { vchan[i].channel = i; if (ioctl(fbttv,VIDIOCGCHAN,&vchan[i])) fail("VIDIOCGCHAN failed \n"); } vaudio = (struct video_audio*)malloc(sizeof(struct video_audio)*vcap.audios); memset(vaudio,0,sizeof(struct video_audio)*vcap.audios); for (i = 0; i < vcap.audios; i++) { vaudio[i].audio = i; if (-1 == ioctl(fbttv,VIDIOCGAUDIO,&vaudio[i])) fail("VIDIOCGAUDIO failed \n"); } if(ioctl(fbttv, VIDIOCGWIN, &(vwin)) == -1) fail("VIDIOCGWIN failed \n"); if(ioctl(fbttv, VIDIOCGFBUF, &(vbuf)) == -1) fail("VIDIOCGFBUF failed \n"); vbuf.base=(void *)(1|(unsigned long)info.lfbPtr); vbuf.depth=16; vbuf.bytesperline=info.strideInBytes; vbuf.width=800; vbuf.height=600; if(ioctl(fbttv, VIDIOCSFBUF, &vbuf)) fail("VIDIOCSFBUF failed \n"); vaudio[0].flags &= ~VIDEO_AUDIO_MUTE; if (-1 == ioctl(fbttv,VIDIOCSAUDIO,&vaudio[0])) fail("VIDIOCSAUDIO failed \n"); vwin.clipcount=0; vwin.flags=0; vwin.y=0; vwin.x=0; vwin.width=width; vwin.height=height; if (ioctl(fbttv, VIDIOCSWIN, &vwin)) fail("VIDIOCSWIN failed \n"); if ((cur_source >= vcap.channels) || (cur_source < 0)) /* Wrong commandline parameter */ cur_source = 0; if(ioctl(fbttv, VIDIOCSCHAN,&vchan[cur_source])) fail("VIDIOCSCHAN failed \n"); channel=channels; i = channel->freq; if(ioctl(fbttv, VIDIOCSFREQ,&i)) fail("VIDIOCSFREQ failed \n"); if(ioctl(fbttv, VIDIOCCAPTURE,&one)) fail("VIDIOCCAPTURE failed \n"); free(vaudio); } /* * Close the bttv device */ void close_bttv(void) { free(vchan); if(ioctl(fbttv, VIDIOCCAPTURE,&zero)) fail("VIDIOCCAPTURE failed \n"); close(fbttv); } /* * Read the configuration */ void get_xtvrc(char *path, int size) { const char *home = getenv("HOME"); const char *file = ".xtvrc"; strncpy(path, home, size); strncat(path, "/", size); strncat(path, file, size); } void read_config(void) { FILE *f; char fn[1024]; char s[CFGLEN]; char *cmd, *par; int i = 0; struct channel_t *channel = NULL, *prev, **prevp = &channels; get_xtvrc(fn, 1024); if (!(f = fopen(fn, "r"))) { fprintf(stderr, "Could not open %s for reading: %s\n", fn, strerror(errno)); exit(1); } while (fgets(s, CFGLEN, f)) { cmd = strtok(s, " "); par = strtok(NULL, "\n"); if (!strcasecmp(cmd, "Channel:")) { prev = channel; channel = malloc(sizeof(struct channel_t)); *prevp = channel; prevp = &channel->next; channel->prev = prev; i++; channel->nr = i; channel->name = strdup(par); } else { if ((!strcasecmp(cmd, "Frequency:")) && (channel)) { channel->freq = atoi(par); fprintf(stderr, "Channel %d: %s at %d", channel->nr, channel->name, channel->freq); } if ((!strcasecmp(cmd, "CBHC:")) && (channel)) { sscanf(par,"%i%i%i%i",&(channel->contrast),&(channel->brightness),&(channel->hue),&(channel->colour)); fprintf(stderr," (%s)\n",par); } } } fclose(f); if (!channel) { fprintf(stderr, "Uh-oh. No channels found! Need to create ~/.xtvrc with xtvscreen first.\n"); exit(1); } channel->next = channels; channel->next->prev = channel; } /* * Find a channel by number */ struct channel_t *find_channel(struct channel_t *old, int i) { struct channel_t *ch = channels; do { ch = ch->next; } while ((ch->nr != i) && (ch != channels)); if (ch->nr == i) return ch; else return old; } /* * Parse commandline */ void cmd_line(int argc, char **argv) { int i = 1; while (i < argc) { if (strcmp("-h",argv[i]) == 0) { printf("Usage: glidetv [-h] [-s Number] [-a Channel Volume]\n\n"); printf(" -h this message\n"); printf(" -s Number select the default videosource\n"); printf(" -a Channel Volume enable soundcard support:\n"); printf(" Channel: {line/cd/mic}\n"); printf(" Volume: 0-100\n"); exit(0); } else { if (strcmp("-s",argv[i]) == 0) { cur_source=atoi(argv[i+1]); i+=2; } else { if (strcmp("-a",argv[i]) == 0) { audio_disabled = 0; if (strcmp("line",argv[i+1]) == 0) mix_id = SOUND_MIXER_LINE; else if (strcmp("cd",argv[i+1]) == 0) mix_id = SOUND_MIXER_CD; else if (strcmp("mic",argv[i+1]) == 0) mix_id = SOUND_MIXER_MIC; else { printf("Wrong audio-parameter\n"); exit(1); } vol_up = atoi(argv[i+2]); if ((vol_up > 100)||(vol_up <= 0)) { printf("Wrong audio-parameter\n"); exit(1); } i+=3; } } } } } /* * Start it up */ void init(int argc, char **argv) { cmd_line(argc,argv); read_config(); open_mixer(); open_bttv(); init_glide(); init_bttv(); } /* * Close it down */ void closedown(void) { close_mixer(); close_bttv(); close_glide(); fprintf(stderr, "TV closed.\r\n"); exit(0); } /* * Change the audio volume */ void set_volume(int vol) { if (!audio_disabled) { if (!audio_mute) { vol=(LEFT+RIGHT)*vol; if(ioctl(mix_fd,MIXER_WRITE(mix_id),&vol)<0) fail("Fail: set volume\n"); } } } /* * Change the tuner frequency */ void change_channel(struct channel_t *channel) { struct video_picture params; ulong i = channel->freq; if (!channel) { fprintf(stderr, "Cannot change to a NULL channel!\n"); closedown(); } set_volume(0); if(ioctl(fbttv, VIDIOCCAPTURE,&zero)) fail("VIDIOCCAPTURE failed \n"); fprintf(stderr, "Choosing channel %d: %s\r\n", channel->nr, channel->name); if (ioctl(fbttv, VIDIOCSFREQ, &i)) fail("VIDIOCSFREQ failed \n"); usleep(99999); if (ioctl(fbttv, VIDIOCGPICT, ¶ms)) fail("VIDIOCGPICT failed \n"); else { params.contrast = channel->contrast<<7; params.brightness = (channel->brightness+128)<<8; params.hue = (channel->hue+128)<<8; params.colour = channel-> colour<<7; if (ioctl(fbttv, VIDIOCSPICT, ¶ms)) fail("VIDIOCSPICT failed \n"); } if(ioctl(fbttv, VIDIOCCAPTURE,&one)) fail("VIDIOCCAPTURE failed \n"); set_volume(vol_up); } /* * start/stop capturing */ void toggle_capturing(void) { static int capturing = 1; if (capturing) { if(ioctl(fbttv, VIDIOCCAPTURE,&zero)) fail("VIDIOCCAPTURE failed \n"); close_glide(); fprintf(stderr, "Capturing disabled, press C again to continue...\r\n"); capturing = 0; } else { init_glide(); if(ioctl(fbttv, VIDIOCCAPTURE,&one)) fail("VIDIOCCAPTURE failed \n"); fprintf(stderr, "Capturing enabled.\r\n"); capturing = 1; } } /* * mute/unmute audio */ void toggle_audio(void) { if (!audio_disabled) { if (audio_mute) { audio_mute = 0; set_volume(vol_up); fprintf(stderr, "Audio unmuted.\r\n"); } else { set_volume(0); fprintf(stderr, "Audio muted, press M again to unmute...\r\n"); audio_mute = 1; } } } /* * Decrease audio volume */ void decrease_volume(void) { if (!audio_disabled) if (vol_up) { if ((vol_up-=2)<0) vol_up = 0; set_volume(vol_up); fprintf(stderr, "Audio volume at %i.\r\n",vol_up); } } /* * Increase audio volume */ void increase_volume(void) { if (!audio_disabled) if (vol_up<100) { if ((vol_up+=2)>100) vol_up = 100; set_volume(vol_up); fprintf(stderr, "Audio volume at %i.\r\n",vol_up); } } /* * Switch video source */ void switch_source(void) { if (++cur_source >= vcap.channels) cur_source = 0; if(ioctl(fbttv, VIDIOCSCHAN,&vchan[cur_source])) fail("VIDIOCSCHAN failed \n"); fprintf(stderr, "Video Source: %s.\r\n",vchan[cur_source].name); } /* * Process the channelkeys */ void process_channelkeys(struct channel_t **channel, char c) { int num=0; if ((c >= '1') && (c <= '9')) { *channel = find_channel(*channel, (int)c - 48); change_channel(*channel); } else { if (c == 'a') num = 10; if (c == 's') num = 11; if (c == 'd') num = 12; if (c == 'f') num = 13; if (c == 'g') num = 14; if (c == 'h') num = 15; if (c == 'j') num = 16; if (c == 'k') num = 17; if (c == 'l') num = 18; if ((c == 'ö')||(c == ';')) num = 19; if ((c == 'ä')||(c == '\'')) num = 20; if (num != 0) { *channel = find_channel(*channel,num); change_channel(*channel); } } } /* * Main */ int main(int argc, char **argv) { char c; struct channel_t *channel, *jump; init(argc,argv); jump = channel = channels; change_channel(channel); while (1) { c = tolower(lin_getch()); if (lin_kbhit()) { /* Catch function-keys */ while (lin_kbhit()) c = lin_getch(); c = 0; } switch (c) { case '+': case 'n': case ' ': channel = channel->next; change_channel(channel); break; case '-': case 'p': channel = channel->prev; change_channel(channel); break; case '/': jump=channel; break; case '*': channel=jump; change_channel(channel); break; case 'c': toggle_capturing(); break; case 'm': toggle_audio(); break; case 'z': case 'y': decrease_volume(); break; case 'x': increase_volume(); break; case 'v': switch_source(); break; case 'q': closedown(); break; default : process_channelkeys(&channel,c); } } return 0; }