diff --git a/BUGS b/BUGS new file mode 100644 index 00000000..bcb37728 --- /dev/null +++ b/BUGS @@ -0,0 +1,23 @@ +Video Disk Recorder - Known Bugs +-------------------------------- + +* Sometimes picture and sound drift apart. + Presumably this is a problem in the card driver or firmware? + +* When the on-screen display is activated during recording, + the video data stream gets corrupted, which results in a + distorted picture when replaying such a recording. + I assume this is a problem in the driver of firmware. + There is no such problem in replay mode. + +* After a replay session the screen may go blank. + Haven't figured out yet how to ensure that it switches back to + the current channel. + +* Every now and then the on-screen display shows nothing but + "noise". If that occurs, I have to stop the 'osm' program + and do a 'make reload' for the card driver. After that it + works fine again. + Presumably this is a problem in the card driver or firmware? + Or could it be a problem with the hardware? + Does anybody else observe this? diff --git a/HISTORY b/HISTORY index f7eeb65e..c013340d 100644 --- a/HISTORY +++ b/HISTORY @@ -10,3 +10,11 @@ Video Disk Recorder OSM Revision History - Support for "Red", "Green", "Yellow", "Blue" buttons. - Channels and Timers can now be added, deleted and moved. - Basic record/play file handling support (no actual record/playback yet). + +2000-04-15: Version 0.03 + +- Actual record/replay now works. +- Dropped the idea of different "recording qualities" (a 36GB harddisk is + able to store some 18 hours in full quality, so we don't really need that). +- Termination signals are now caught and the program cleans up before exiting. +- Support for CICAM. diff --git a/MANUAL b/MANUAL new file mode 100644 index 00000000..9e5036c1 --- /dev/null +++ b/MANUAL @@ -0,0 +1,39 @@ +Video Disk Recorder User's Manual +--------------------------------- + +* Selecting a Channel + + You can select a channel either by pressing the "Up" or "Down" key (while + no On Screen Menu is displayed), or browsing through the channel list in + the menu and pressing "Ok" on the desired channel. + +* Instant Recording + + You can start recording the current channel by pressing the "Record" + button. This will create a timer event named "instant" that start + at the current time and records for two hours. + If you want to modify the recording time you need to edit the timer. + Stop instant recording by disabling or deleting the timer. + +* Replaying a Recording + + All recordings are listed in the "Recordings" menu. Browse through the + list with the "Up" and "Down" button and press "Ok" (or the "Red" button) + to start playback. + +* Replay Control + + - "Begin" Positions to beginning of the recording and starts playback + from there. + - "Pause" Halts playback at the current frame. Press again to continue + playback. + - "Stop" Stops playback and stores the current position, so that + playback can be resumed later at that point. + - "Search" Runs playback forward or backward at a higher speed. Press + again to resume normal speed. + - "Skip" Skips about 60 seconds forward or backward. + +* Programming the Timer + + Use the "Timer" menu to maintain your list of timer controlled recordings. + diff --git a/README b/README index 5c6928cb..e2b27a4f 100644 --- a/README +++ b/README @@ -36,34 +36,13 @@ about that driver). For example, if the DVB driver was extracted into the directory /home/kls/vdr/DVB, then this package should be extracted into /home/kls/vdr/OSM. -In order for the menu colors to work correctly you may want -to replace the function RGB2YUV() in DVB/driver/dvb.c with - -static u32 RGB2YUV(u16 R, u16 G, u16 B) -{ - u16 y, u, v; - u16 Y, Cr, Cb; - - y = R * 77 + G * 150 + B * 29; // Luma=0.299R+0.587G+0.114B 0..65535 - u = 2048+B * 8 -(y>>5); // Cr 0..4095 - v = 2048+R * 8 -(y>>5); // Cb 0..4095 - - Y = y >> 8; - Cb= u >> 4; - Cr= v >> 4; - - return Cr|(Cb<<16)|(Y<<8); -} - -(this may no longer be necessary with driver versions after 0.03c). +This program requires the card driver version 0.04 or higher +to work properly. After extracting the package, change into the OSM directory and type 'make'. This should produce an executable file named 'osm', which can be run after the DVB driver has been -installed. There may be several warnings about "implicit declaration -of function `int asprintf(...)'" during the compilation, which I was -unable to avoid (anybody know how to avoid them?). Just ignore them, -the program will work, anyway. +installed. There are two macros you can use to customize the 'osm' program at compile time. Adding "DEBUG_REMOTE=1" to the 'make' call @@ -88,28 +67,43 @@ The meaning of the data entries may still vary in future releases, so for the moment please look at the source code (config.c) to see the meaning of the various fields. -There is no way of adding or deleting channels or timers yet, this -will be implemented later. - Learning the remote control keys: --------------------------------- -The remote control configuration file 'keys.conf' that comes with -this package contains the codes for the "d-box" remote control unit. -If you want to use a different remote control unit, simply delete -the file 'keys.conf' and restart the 'osm' program. The program will -then start a key learning session in which it first attempts to determine -the basic data transfer mode and timing of your remote control unit, -and then will ask you to press one key after the other so that it can -learn the various key codes. You will at least need to provide an "Up" -and a "Down" key, so that you can switch channels. The rest of the key -definitions is optional, but the more keys you define, the more you -will be able to navigate through the menus. - +There is no default 'keys.conf' file, so if you compile the program +without 'DEBUG_REMOTE=1' you will have to go through a "teach-in" +session that allows the program to learn your remote control codes. +It will first attempt to determine the basic data transfer mode and +timing of your remote control unit, and then will ask you to press one +key after the other so that it can learn the various key codes. You will +at least need to provide an "Up" and a "Down" key, so that you can switch +channels. The rest of the key definitions is optional, but the more keys +you define, the more you will be able to navigate through the menus and +control recording/replaying. If the program has been built with "DEBUG_REMOTE=1", it will use the key configuration file 'keys-pc.conf', so that you won't loose data when switching between normal and debug mode. +The default PC key assignments are: + + Up, Down, Left, Right Crsr keys in numeric block + Menu '5' in numeric block + Ok Enter + Back Backspace + 0..9 '0'..'9' in top row + Red, Green, Yellow, Blue 'F1'..'F4' + Record 'r' + Pause 'p' + Stop 's' + Begin 'B' + SearchForward 'f' + SearchBack 'b' + SkipForward 'PgDn' in numeric block + SkipBack 'PgUp' in numeric block + +If you prefer different key assignments, simply delete the file +'keys-pc.conf' and restart 'osm' to get into learning mode. + Navigating through the On Screen Menus: --------------------------------------- diff --git a/TODO b/TODO index 35e3aa30..8154c01d 100644 --- a/TODO +++ b/TODO @@ -1,7 +1,7 @@ TODO list for the Video Disk Recorder project --------------------------------------------- -* Implement recording to disk and playback from disk. +* Channel select via numeric keys. * Make it work with two DVB-S PCI cards to allow simultaneous recording of one programme, while replaying another programme (or maybe the same one, but time delayed). @@ -10,3 +10,7 @@ TODO list for the Video Disk Recorder project * Implement "on-disk editing" to allow "cutting out" of certain scenes in order to archive them (or, reversely, cut out commercial breaks). +* Implement on-screen display of replay progress (progress bar + and/or time index). +* Implement channel scanning. +* Better support for encrypted channels. diff --git a/channels.conf b/channels.conf index 1b579636..c0e191ac 100644 --- a/channels.conf +++ b/channels.conf @@ -1,109 +1,109 @@ -RTL:12188:h:1:27500:163:104 -Sat.1:12552:v:1:22000:163:104 -Pro 7:12480:v:1:27500:255:256 -RTL2:12188:h:1:27500:166:128 -ARD:11837:h:1:27500:101:102 -BR3:11837:h:1:27500:201:202 -Hessen 3:11837:h:1:27500:301:302 -N3:11837:h:1:27500:401:402 -SR3:11837:h:1:27500:501:502 -WDR:11837:h:1:27500:601:602 -BR alpha:11837:h:1:27500:701:702 -SWR BW:11837:h:1:27500:801:802 -Phoenix:11837:h:1:27500:901:902 -ZDF:11954:h:1:27500:110:120 -3sat:11954:h:1:27500:210:220 -Kinderkanal:11954:h:1:27500:310:320 -arte:11954:h:1:27500:360:370 -phoenix:11954:h:1:27500:410:420 -ORF Sat:11954:h:1:27500:506:507 -ZDF Infobox:11954:h:1:27500:610:620 -CNN:12168:v:1:27500:165:100 -Super RTL:12188:h:1:27500:165:120 -VOX:12188:h:1:27500:167:136 -DW TV:12363:v:1:27500:305:306 -Kabel 1:12480:v:1:27500:511:512 -TM3:12480:v:1:27500:767:768 -DSF:12480:v:1:27500:1023:1024 -HOT:12480:v:1:27500:1279:1280 -BloombergTV:12552:v:1:22000:162:99 -Sky News:12552:v:1:22000:305:306 -KinderNet:12574:h:1:22000:163:92 -Alice:12610:v:1:22000:162:96 -n-tv:12670:v:1:22000:162:96 -Grand Tour.:12670:v:1:22000:289:290 -TW1:12692:h:1:22000:166:167 -Eins Extra:12722:h:1:22000:101:102 -Eins Festival:12722:h:1:22000:201:202 -Eins MuXx:12722:h:1:22000:301:302 -MDR:12722:h:1:22000:401:402 -ORB:12722:h:1:22000:501:502 -B1:12722:h:1:22000:601:602 -ARD Online-Kanal:12722:h:1:22000:8191:701 -Premiere World Promo:11798:h:1:27500:255:256 -TV Niepokalanow:11876:h:1:27500:305:321 -test card:11798:h:1:27500:511:512 -Mosaico:11934:v:1:27500:165:100 -Andalucia TV:11934:v:1:27500:166:104 -TVC Internacional:11934:v:1:27500:167:108 -Nasza TV:11992:h:1:27500:165:98 -WishLine test:12012:v:1:27500:163:90 -Pro 7 Austria:12051:v:1:27500:161:84 -Kabel 1 Schweiz:12051:v:1:27500:162:163 -Kabel 1 Austria:12051:v:1:27500:166:167 -Pro 7 Schweiz:12051:v:1:27500:289:290 -Kiosque:12129:v:1:27500:160:80 -KTO:12129:v:1:27500:170:120 -TCM:12168:v:1:27500:160:80 -Cartoon Network France & Spain:12168:v:1:27500:161:84 -TVBS Europe:12168:v:1:27500:162:88 -TVBS Europe:12168:v:1:27500:162:89 -Travel:12168:v:1:27500:163:92 -TCM Espania:12168:v:1:27500:164:96 -MTV Spain:12168:v:1:27500:167:112 -TCM France:12168:v:1:27500:169:64 -RTL2 CH:12188:h:1:27500:164:112 -La Cinquieme:12207:v:1:27500:160:80 -ARTE:12207:v:1:27500:165:100 -Post Filial TV:12226:h:1:27500:255:256 -Canal Canaris:12246:v:1:27500:160:80 -Canal Canaris:12246:v:1:27500:160:81 -Canal Canaris:12246:v:1:27500:160:82 -Canal Canaris:12246:v:1:27500:160:83 -AB Sat Passion promo:12266:h:1:27500:160:80 -AB Channel 1:12266:h:1:27500:161:84 -Taquilla 0:12285:v:1:27500:165:100 -CSAT:12324:v:1:27500:160:80 -Mosaique:12324:v:1:27500:162:88 -Mosaique 2:12324:v:1:27500:163:92 -Mosaique 3:12324:v:1:27500:164:96 -Le Sesame C+:12324:v:1:27500:165:1965 -FEED:12344:h:1:27500:163:92 -RTM 1:12363:v:1:27500:162:96 -ESC 1:12363:v:1:27500:163:104 -TV5 Europe:12363:v:1:27500:164:112 -TV7 Tunisia:12363:v:1:27500:166:128 -ARTE:12363:v:1:27500:167:137 -RAI Uno:12363:v:1:27500:289:290 -RTP International:12363:v:1:27500:300:301 -Fashion TV:12402:v:1:27500:163:92 -VideoService:12422:h:1:27500:255:256 -Beta Research promo:12422:h:1:27500:1023:1024 -Canal Canarias:12441:v:1:27500:160:80 -TVC International:12441:v:1:27500:512:660 -Fitur:12441:v:1:27500:514:662 -Astra Info 1:12552:v:1:22000:164:112 -Astra Info 2:12552:v:1:22000:165:120 -Astra Vision 1:12552:v:1:22000:168:144 -Astra Vision 1:12552:v:1:22000:168:145 -Astra Vision 1:12552:v:1:22000:168:146 -Astra Vision 1:12552:v:1:22000:168:147 -Astra Vision 1:12552:v:1:22000:168:148 -Astra Vision 1:12552:v:1:22000:168:149 -Astra Vision 1:12552:v:1:22000:168:150 -RTL Tele Letzebuerg:12552:v:1:22000:168:144 -Astra Mosaic:12552:v:1:22000:175:176 -MHP test:12604:h:1:22000:5632:8191 -Bloomberg TV Spain:12610:v:1:22000:45:49 -Video Italia:12610:v:1:22000:121:122 -AC 3 promo:12670:v:1:22000:308:256 +RTL:12188:h:1:27500:163:104:0:0 +Sat.1:12552:v:1:22000:163:104:0:0 +Pro 7:12480:v:1:27500:255:256:0:0 +RTL2:12188:h:1:27500:166:128:0:0 +ARD:11837:h:1:27500:101:102:0:0 +BR3:11837:h:1:27500:201:202:0:0 +Hessen 3:11837:h:1:27500:301:302:0:0 +N3:11837:h:1:27500:401:402:0:0 +SR3:11837:h:1:27500:501:502:0:0 +WDR:11837:h:1:27500:601:602:0:0 +BR alpha:11837:h:1:27500:701:702:0:0 +SWR BW:11837:h:1:27500:801:802:0:0 +Phoenix:11837:h:1:27500:901:902:0:0 +ZDF:11954:h:1:27500:110:120:0:0 +3sat:11954:h:1:27500:210:220:0:0 +Kinderkanal:11954:h:1:27500:310:320:0:0 +arte:11954:h:1:27500:360:370:0:0 +phoenix:11954:h:1:27500:410:420:0:0 +ORF Sat:11954:h:1:27500:506:507:0:0 +ZDF Infobox:11954:h:1:27500:610:620:0:0 +CNN:12168:v:1:27500:165:100:0:0 +Super RTL:12188:h:1:27500:165:120:0:0 +VOX:12188:h:1:27500:167:136:0:0 +DW TV:12363:v:1:27500:305:306:0:0 +Kabel 1:12480:v:1:27500:511:512:0:0 +TM3:12480:v:1:27500:767:768:0:0 +DSF:12480:v:1:27500:1023:1024:0:0 +HOT:12480:v:1:27500:1279:1280:0:0 +BloombergTV:12552:v:1:22000:162:99:0:0 +Sky News:12552:v:1:22000:305:306:0:0 +KinderNet:12574:h:1:22000:163:92:0:0 +Alice:12610:v:1:22000:162:96:0:0 +n-tv:12670:v:1:22000:162:96:0:0 +Grand Tour.:12670:v:1:22000:289:290:0:0 +TW1:12692:h:1:22000:166:167:0:0 +Eins Extra:12722:h:1:22000:101:102:0:0 +Eins Festival:12722:h:1:22000:201:202:0:0 +Eins MuXx:12722:h:1:22000:301:302:0:0 +MDR:12722:h:1:22000:401:402:0:0 +ORB:12722:h:1:22000:501:502:0:0 +B1:12722:h:1:22000:601:602:0:0 +ARD Online-Kanal:12722:h:1:22000:8191:701:0:0 +Premiere World Promo:11798:h:1:27500:255:256:0:0 +TV Niepokalanow:11876:h:1:27500:305:321:0:0 +Premiere:11798:h:1:27500:1023:1024:1:10 +Mosaico:11934:v:1:27500:165:100:0:0 +Andalucia TV:11934:v:1:27500:166:104:0:0 +TVC Internacional:11934:v:1:27500:167:108:0:0 +Nasza TV:11992:h:1:27500:165:98:0:0 +WishLine test:12012:v:1:27500:163:90:0:0 +Pro 7 Austria:12051:v:1:27500:161:84:0:0 +Kabel 1 Schweiz:12051:v:1:27500:162:163:0:0 +Kabel 1 Austria:12051:v:1:27500:166:167:0:0 +Pro 7 Schweiz:12051:v:1:27500:289:290:0:0 +Kiosque:12129:v:1:27500:160:80:0:0 +KTO:12129:v:1:27500:170:120:0:0 +TCM:12168:v:1:27500:160:80:0:0 +Cartoon Network France & Spain:12168:v:1:27500:161:84:0:0 +TVBS Europe:12168:v:1:27500:162:88:0:0 +TVBS Europe:12168:v:1:27500:162:89:0:0 +Travel:12168:v:1:27500:163:92:0:0 +TCM Espania:12168:v:1:27500:164:96:0:0 +MTV Spain:12168:v:1:27500:167:112:0:0 +TCM France:12168:v:1:27500:169:64:0:0 +RTL2 CH:12188:h:1:27500:164:112:0:0 +La Cinquieme:12207:v:1:27500:160:80:0:0 +ARTE:12207:v:1:27500:165:100:0:0 +Post Filial TV:12226:h:1:27500:255:256:0:0 +Canal Canaris:12246:v:1:27500:160:80:0:0 +Canal Canaris:12246:v:1:27500:160:81:0:0 +Canal Canaris:12246:v:1:27500:160:82:0:0 +Canal Canaris:12246:v:1:27500:160:83:0:0 +AB Sat Passion promo:12266:h:1:27500:160:80:0:0 +AB Channel 1:12266:h:1:27500:161:84:0:0 +Taquilla 0:12285:v:1:27500:165:100:0:0 +CSAT:12324:v:1:27500:160:80:0:0 +Mosaique:12324:v:1:27500:162:88:0:0 +Mosaique 2:12324:v:1:27500:163:92:0:0 +Mosaique 3:12324:v:1:27500:164:96:0:0 +Le Sesame C+:12324:v:1:27500:165:1965:0:0 +FEED:12344:h:1:27500:163:92:0:0 +RTM 1:12363:v:1:27500:162:96:0:0 +ESC 1:12363:v:1:27500:163:104:0:0 +TV5 Europe:12363:v:1:27500:164:112:0:0 +TV7 Tunisia:12363:v:1:27500:166:128:0:0 +ARTE:12363:v:1:27500:167:137:0:0 +RAI Uno:12363:v:1:27500:289:290:0:0 +RTP International:12363:v:1:27500:300:301:0:0 +Fashion TV:12402:v:1:27500:163:92:0:0 +VideoService:12422:h:1:27500:255:256:0:0 +Beta Research promo:12422:h:1:27500:1023:1024:0:0 +Canal Canarias:12441:v:1:27500:160:80:0:0 +TVC International:12441:v:1:27500:512:660:0:0 +Fitur:12441:v:1:27500:514:662:0:0 +Astra Info 1:12552:v:1:22000:164:112:0:0 +Astra Info 2:12552:v:1:22000:165:120:0:0 +Astra Vision 1:12552:v:1:22000:168:144:0:0 +Astra Vision 1:12552:v:1:22000:168:145:0:0 +Astra Vision 1:12552:v:1:22000:168:146:0:0 +Astra Vision 1:12552:v:1:22000:168:147:0:0 +Astra Vision 1:12552:v:1:22000:168:148:0:0 +Astra Vision 1:12552:v:1:22000:168:149:0:0 +Astra Vision 1:12552:v:1:22000:168:150:0:0 +RTL Tele Letzebuerg:12552:v:1:22000:168:144:0:0 +Astra Mosaic:12552:v:1:22000:175:176:0:0 +MHP test:12604:h:1:22000:5632:8191:0:0 +Bloomberg TV Spain:12610:v:1:22000:45:49:0:0 +Video Italia:12610:v:1:22000:121:122:0:0 +AC 3 promo:12670:v:1:22000:308:256:0:0 diff --git a/config.c b/config.c index af97a5cb..c29fea8b 100644 --- a/config.c +++ b/config.c @@ -4,7 +4,7 @@ * See the main source file 'osm.c' for copyright information and * how to reach the author. * - * $Id: config.c 1.2 2000/03/05 16:14:27 kls Exp $ + * $Id: config.c 1.3 2000/04/15 12:48:00 kls Exp $ */ #include "config.h" @@ -16,28 +16,36 @@ // -- cKeys ------------------------------------------------------------------ tKey keyTable[] = { // "Up" and "Down" must be the first two keys! - { kUp, "Up", 0 }, - { kDown, "Down", 0 }, - { kMenu, "Menu", 0 }, - { kOk, "Ok", 0 }, - { kBack, "Back", 0 }, - { kLeft, "Left", 0 }, - { kRight, "Right", 0 }, - { k0, "0", 0 }, - { k1, "1", 0 }, - { k2, "2", 0 }, - { k3, "3", 0 }, - { k4, "4", 0 }, - { k5, "5", 0 }, - { k6, "6", 0 }, - { k7, "7", 0 }, - { k8, "8", 0 }, - { k9, "9", 0 }, - { kRed, "Red", 0 }, - { kGreen, "Green", 0 }, - { kYellow, "Yellow", 0 }, - { kBlue, "Blue", 0 }, - { kNone, "", 0 }, + { kUp, "Up", 0 }, + { kDown, "Down", 0 }, + { kMenu, "Menu", 0 }, + { kOk, "Ok", 0 }, + { kBack, "Back", 0 }, + { kLeft, "Left", 0 }, + { kRight, "Right", 0 }, + { k0, "0", 0 }, + { k1, "1", 0 }, + { k2, "2", 0 }, + { k3, "3", 0 }, + { k4, "4", 0 }, + { k5, "5", 0 }, + { k6, "6", 0 }, + { k7, "7", 0 }, + { k8, "8", 0 }, + { k9, "9", 0 }, + { kRed, "Red", 0 }, + { kGreen, "Green", 0 }, + { kYellow, "Yellow", 0 }, + { kBlue, "Blue", 0 }, + { kRecord, "Record", 0 }, + { kPause, "Pause", 0 }, + { kStop, "Stop", 0 }, + { kBegin, "Begin", 0 }, + { kSearchForward, "SearchForward", 0 }, + { kSearchBack, "SearchBack", 0 }, + { kSkipForward, "SkipForward", 0 }, + { kSkipBack, "SkipBack", 0 }, + { kNone, "", 0 }, }; cKeys::cKeys(void) @@ -88,7 +96,7 @@ bool cKeys::Load(char *FileName) } } if (Name) { - fprintf(stderr, "unknown key in %s, line %d\n", fileName, line); + esyslog(LOG_ERR, "unknown key in %s, line %d\n", fileName, line); result = false; break; } @@ -96,17 +104,17 @@ bool cKeys::Load(char *FileName) } continue; } - fprintf(stderr, "error in %s, line %d\n", fileName, line); + esyslog(LOG_ERR, "error in %s, line %d\n", fileName, line); result = false; break; } fclose(f); } else - fprintf(stderr, "can't open '%s'\n", fileName); + esyslog(LOG_ERR, "can't open '%s'\n", fileName); } else - fprintf(stderr, "no key configuration file name supplied!\n"); + esyslog(LOG_ERR, "no key configuration file name supplied!\n"); return result; } @@ -172,12 +180,14 @@ cChannel::cChannel(const cChannel *Channel) srate = Channel ? Channel->srate : 27500; vpid = Channel ? Channel->vpid : 255; apid = Channel ? Channel->apid : 256; + ca = Channel ? Channel->ca : 0; + pnr = Channel ? Channel->pnr : 0; } bool cChannel::Parse(char *s) { char *buffer = NULL; - if (7 == sscanf(s, "%a[^:]:%d:%c:%d:%d:%d:%d", &buffer, &frequency, &polarization, &diseqc, &srate, &vpid, &apid)) { + if (9 == sscanf(s, "%a[^:]:%d:%c:%d:%d:%d:%d:%d:%d", &buffer, &frequency, &polarization, &diseqc, &srate, &vpid, &apid, &ca, &pnr)) { strncpy(name, buffer, MaxChannelName - 1); name[strlen(buffer)] = 0; delete buffer; @@ -188,20 +198,21 @@ bool cChannel::Parse(char *s) bool cChannel::Save(FILE *f) { - return fprintf(f, "%s:%d:%c:%d:%d:%d:%d\n", name, frequency, polarization, diseqc, srate, vpid, apid) > 0; + return fprintf(f, "%s:%d:%c:%d:%d:%d:%d:%d:%d\n", name, frequency, polarization, diseqc, srate, vpid, apid, ca, pnr) > 0; } bool cChannel::Switch(void) { - if (!ChannelLocked) { + if (!DvbApi.Recording()) { isyslog(LOG_INFO, "switching to channel %d", Index() + 1); CurrentChannel = Index(); Interface.DisplayChannel(CurrentChannel + 1, name); for (int i = 3; --i;) { - if (DvbSetChannel(frequency, polarization, diseqc, srate, vpid, apid)) + if (DvbApi.SetChannel(frequency, polarization, diseqc, srate, vpid, apid, ca, pnr)) return true; esyslog(LOG_ERR, "retrying"); } + return false; } Interface.Info("Channel locked (recording)!"); return false; @@ -215,20 +226,23 @@ bool cChannel::SwitchTo(int i) // -- cTimer ----------------------------------------------------------------- -cTimer::cTimer(void) +cTimer::cTimer(bool Instant) { startTime = stopTime = 0; recording = false; - active = 1; + active = Instant; channel = CurrentChannel + 1; - day = 1; //XXX today! - start = 0; //XXX now! - stop = 0; //XXX now + 2h! + time_t t = time(NULL); + struct tm *now = localtime(&t); + day = now->tm_mday; + start = now->tm_hour * 100 + now->tm_min; + stop = start + 200; // "instant recording" records 2 hours by default + if (stop >= 2400) + stop -= 2400; //TODO VPS??? - quality = 'H'; priority = 99; lifetime = 99; - *file = 0; + strcpy(file, Instant ? "instant" : ""); } int cTimer::TimeToInt(int t) @@ -286,7 +300,7 @@ bool cTimer::Parse(char *s) { char *buffer1 = NULL; char *buffer2 = NULL; - if (9 == sscanf(s, "%d:%d:%a[^:]:%d:%d:%c:%d:%d:%as", &active, &channel, &buffer1, &start, &stop, &quality, &priority, &lifetime, &buffer2)) { + if (8 == sscanf(s, "%d:%d:%a[^:]:%d:%d:%d:%d:%as", &active, &channel, &buffer1, &start, &stop, &priority, &lifetime, &buffer2)) { day = ParseDay(buffer1); strncpy(file, buffer2, MaxFileName - 1); file[strlen(buffer2)] = 0; @@ -299,7 +313,7 @@ bool cTimer::Parse(char *s) bool cTimer::Save(FILE *f) { - return fprintf(f, "%d:%d:%s:%d:%d:%c:%d:%d:%s\n", active, channel, PrintDay(day), start, stop, quality, priority, lifetime, file) > 0; + return fprintf(f, "%d:%d:%s:%d:%d:%d:%d:%s\n", active, channel, PrintDay(day), start, stop, priority, lifetime, file) > 0; } bool cTimer::IsSingleEvent(void) @@ -383,7 +397,6 @@ cKeys Keys; // -- cChannels -------------------------------------------------------------- int CurrentChannel = 0; -bool ChannelLocked = false; cChannels Channels; diff --git a/config.h b/config.h index 51fd22e0..6184f562 100644 --- a/config.h +++ b/config.h @@ -4,12 +4,13 @@ * See the main source file 'osm.c' for copyright information and * how to reach the author. * - * $Id: config.h 1.2 2000/03/05 14:58:23 kls Exp $ + * $Id: config.h 1.3 2000/04/15 12:44:23 kls Exp $ */ #ifndef __CONFIG_H #define __CONFIG_H +#define _GNU_SOURCE #include #include #include @@ -30,6 +31,14 @@ enum eKeys { // "Up" and "Down" must be the first two keys! kGreen, kYellow, kBlue, + kRecord, + kPause, + kStop, + kBegin, + kSearchForward, + kSearchBack, + kSkipForward, + kSkipBack, kNone }; @@ -64,6 +73,8 @@ public: int srate; int vpid; int apid; + int ca; + int pnr; cChannel(void); cChannel(const cChannel *Channel); bool Parse(char *s); @@ -84,11 +95,10 @@ public: int start; int stop; //TODO VPS??? - char quality; int priority; int lifetime; char file[MaxFileName]; - cTimer(void); + cTimer(bool Instant = false); bool Parse(char *s); bool Save(FILE *f); bool IsSingleEvent(void); @@ -128,7 +138,7 @@ public: if (l->Parse(buffer)) Add(l); else { - fprintf(stderr, "error in %s, line %d\n", fileName, line); + esyslog(LOG_ERR, "error in %s, line %d\n", fileName, line); delete l; result = false; break; @@ -137,7 +147,7 @@ public: fclose(f); } else { - fprintf(stderr, "can't open '%s'\n", fileName); + esyslog(LOG_ERR, "can't open '%s'\n", fileName); result = false; } return result; @@ -168,7 +178,6 @@ class cChannels : public cConfig {}; class cTimers : public cConfig {}; extern int CurrentChannel; -extern bool ChannelLocked; extern cChannels Channels; extern cTimers Timers; diff --git a/dvbapi.c b/dvbapi.c index 484a22e5..ef2f8086 100644 --- a/dvbapi.c +++ b/dvbapi.c @@ -4,171 +4,1024 @@ * See the main source file 'osm.c' for copyright information and * how to reach the author. * - * $Id: dvbapi.c 1.2 2000/03/11 10:39:09 kls Exp $ + * $Id: dvbapi.c 1.3 2000/04/15 14:45:04 kls Exp $ */ #include "dvbapi.h" +#include #include -#include +#include +#include #include +#include +#include +#include #include #include "interface.h" #include "tools.h" #define VIDEODEVICE "/dev/video" -const char *DvbQuality = "LMH"; // Low, Medium, High +// The size of the array used to buffer video data: +#define VIDEOBUFSIZE (1024*1024) -bool DvbSetChannel(int FrequencyMHz, char Polarization, int Diseqc, int Srate, int Vpid, int Apid) +// The minimum amount of video data necessary to identify frames +// (must be smaller than VIDEOBUFSIZE!): +#define MINVIDEODATA (20*1024) // just a safe guess (max. size of any AV_PES block, plus some safety) + +// The maximum time the buffer is allowed to write data to disk when recording: +#define MAXRECORDWRITETIME 50 // ms + +#define AV_PES_HEADER_LEN 8 + +// AV_PES block types: +#define AV_PES_VIDEO 1 +#define AV_PES_AUDIO 2 + +// Picture types: +#define NO_PICTURE 0 +#define I_FRAME 1 +#define P_FRAME 2 +#define B_FRAME 3 + +#define FRAMESPERSEC 25 + +// The maximum file size is limited by the range that can be covered +// with 'int'. 4GB might be possible (if the range is considered +// 'unsigned'), 2GB should be possible (even if the range is considered +// 'signed'), so let's use 1GB for absolute safety (the actual file size +// may be slightly higher because we stop recording only before the next +// 'I' frame, to have a complete Group Of Pictures): +#define MAXVIDEOFILESIZE (1024*1024*1024) +#define MAXFILESPERRECORDING 255 + +#define INDEXFILESUFFIX "/index.vdr" +#define RESUMEFILESUFFIX "/resume.vdr" +#define RECORDFILESUFFIX "/%03d.vdr" +#define RECORDFILESUFFIXLEN 20 // some additional bytes for safety... +#define MAXPROCESSTIMEOUT 3 // seconds + +// The number of frames to back up when resuming an interrupted replay session: +#define RESUMEBACKUP (10 * FRAMESPERSEC) + +typedef unsigned char uchar; + +// --- cIndexFile ------------------------------------------------------------ + +class cIndexFile { +private: + struct tIndex { int offset; uchar type; uchar number; short reserved; }; + int f; + char *fileName, *pFileExt; + int last, resume; + tIndex *index; +public: + cIndexFile(const char *FileName, bool Record = false); + ~cIndexFile(); + void Write(uchar PictureType, uchar FileNumber, int FileOffset); + bool Get(int Index, uchar *FileNumber, int *FileOffset, uchar *PictureType = NULL); + int GetNextIFrame(int Index, bool Forward, uchar *FileNumber, int *FileOffset, int *Length = NULL); + int Get(uchar FileNumber, int FileOffset); + int Last(void) { return last; } + int GetResume(void) { return resume; } + bool StoreResume(int Index); + static char *Str(int Index); + }; + +cIndexFile::cIndexFile(const char *FileName, bool Record) { - int v = open(VIDEODEVICE, O_RDWR); - - if (v >= 0) { - struct frontend front; - ioctl(v, VIDIOCGFRONTEND, &front); - unsigned int freq = FrequencyMHz; - front.ttk = (freq < 11800UL) ? 0 : 1; - if (freq < 11800UL) - freq -= 9750UL; + f = -1; + fileName = pFileExt = NULL; + last = resume = -1; + index = NULL; + if (FileName) { + fileName = new char[strlen(FileName) + strlen(INDEXFILESUFFIX) + strlen(RESUMEFILESUFFIX) + 1]; + if (fileName) { // no max() function at hand... + strcpy(fileName, FileName); + pFileExt = fileName + strlen(fileName); + strcpy(pFileExt, INDEXFILESUFFIX); + int delta = 0; + if (access(fileName, R_OK) == 0) { + struct stat buf; + if (stat(fileName, &buf) == 0) { + delta = buf.st_size % sizeof(tIndex); + if (delta) { + delta = sizeof(tIndex) - delta; + esyslog(LOG_ERR, "ERROR: invalid file size (%d) in '%s'", buf.st_size, fileName); + } + last = (buf.st_size + delta) / sizeof(tIndex) - 1; + if (!Record && last >= 0) { + index = new tIndex[last + 1]; + int fi = open(fileName, O_RDONLY); + if (fi >= 0) { + if ((int)read(fi, index, buf.st_size) != buf.st_size) { + esyslog(LOG_ERR, "ERROR: can't read from file '%s'", fileName); + delete index; + index = NULL; + } + close(fi); + } + else + LOG_ERROR_STR(fileName); + } + } + else + LOG_ERROR; + } + if (Record) { + if ((f = open(fileName, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR)) >= 0) { + if (delta) { + esyslog(LOG_ERR, "ERROR: padding index file with %d '0' bytes", delta); + while (delta--) + writechar(f, 0); + } + } + else + LOG_ERROR_STR(fileName); + delete fileName; + fileName = pFileExt = NULL; + } + else { + strcpy(pFileExt, RESUMEFILESUFFIX); + int resumeFile = open(fileName, O_RDONLY); + if (resumeFile >= 0) { + if (read(resumeFile, &resume, sizeof(resume)) != sizeof(resume)) { + resume = -1; + LOG_ERROR_STR(fileName); + } + close(resumeFile); + } + else if (errno != ENOENT) + LOG_ERROR_STR(fileName); + } + } else - freq -= 10600UL; - front.freq = freq * 1000000UL; - front.diseqc = Diseqc; - front.srate = Srate * 1000; - front.volt = (Polarization == 'v') ? 0 : 1; - front.video_pid = Vpid; - front.audio_pid = Apid; - front.AFC = 1; - ioctl(v, VIDIOCSFRONTEND, &front); - close(v); - if (front.sync & 0x1F == 0x1F) - return true; - esyslog(LOG_ERR, "channel not sync'ed (front.sync=%X)!", front.sync); + esyslog(LOG_ERR, "ERROR: can't copy file name '%s'", FileName); + } +} + +cIndexFile::~cIndexFile() +{ + if (f >= 0) + close(f); + delete fileName; +} + +void cIndexFile::Write(uchar PictureType, uchar FileNumber, int FileOffset) +{ + if (f >= 0) { + tIndex i = { FileOffset, PictureType, FileNumber, 0 }; + if (write(f, &i, sizeof(i)) != sizeof(i)) { + esyslog(LOG_ERR, "ERROR: can't write to index file"); + close(f); + f = -1; + return; + } + last++; + } +} + +bool cIndexFile::Get(int Index, uchar *FileNumber, int *FileOffset, uchar *PictureType) +{ + if (index) { + if (Index >= 0 && Index <= last) { + *FileNumber = index[Index].number; + *FileOffset = index[Index].offset; + if (PictureType) + *PictureType = index[Index].type; + return true; + } } - else - Interface.Error("can't open VIDEODEVICE");//XXX return false; } -// -- cDvbRecorder ----------------------------------------------------------- - -cDvbRecorder::cDvbRecorder(void) +int cIndexFile::GetNextIFrame(int Index, bool Forward, uchar *FileNumber, int *FileOffset, int *Length) { - recording = false; + if (index) { + int d = Forward ? 1 : -1; + for (;;) { + Index += d; + if (Index >= 0 && Index <= last) { + if (index[Index].type == I_FRAME) { + *FileNumber = index[Index].number; + *FileOffset = index[Index].offset; + if (Length) { + // all recordings end with a non-I_FRAME, so the following should be safe: + int fn = index[Index + 1].number; + int fo = index[Index + 1].offset; + if (fn == *FileNumber) + *Length = fo - *FileOffset; + else { + esyslog(LOG_ERR, "ERROR: 'I' frame at end of file #%d", *FileNumber); + *Length = -1; + } + } + return Index; + } + } + else + break; + } + } + return -1; } -cDvbRecorder::~cDvbRecorder() +int cIndexFile::Get(uchar FileNumber, int FileOffset) { - Stop(); + if (index) { + //TODO implement binary search! + int i; + for (i = 0; i < last; i++) { + if (index[i].number > FileNumber || (index[i].number == FileNumber) && index[i].offset >= FileOffset) + break; + } + return i; + } + return -1; } -bool cDvbRecorder::Recording(void) +bool cIndexFile::StoreResume(int Index) { - return recording; -} - -bool cDvbRecorder::Record(const char *FileName, char Quality) -{ - isyslog(LOG_INFO, "record %s (%c)", FileName, Quality); - if (MakeDirs(FileName)) { - FILE *f = fopen(FileName, "a"); - if (f) { - fprintf(f, "%s, %c\n", FileName, Quality); - fclose(f); - recording = true; - // TODO - Interface.Error("Recording not yet implemented!"); + if (fileName) { + int resumeFile = open(fileName, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + if (resumeFile >= 0) { + if (write(resumeFile, &Index, sizeof(Index)) != sizeof(Index)) + LOG_ERROR_STR(fileName); + close(resumeFile); return true; } - else { - Interface.Error("Can't write to file!"); + else if (errno != ENOENT) + LOG_ERROR_STR(fileName); + } + return false; +} + +char *cIndexFile::Str(int Index) +{ + static char buffer[16]; + int f = (Index % FRAMESPERSEC) + 1; + int s = (Index / FRAMESPERSEC); + int m = s / 60 % 60; + int h = s / 3600; + s %= 60; + snprintf(buffer, sizeof(buffer), "%d:%02d:%02d.%02d", h, m, s, f); + return buffer; +} + +// --- cRingBuffer ----------------------------------------------------------- + +/* cRingBuffer reads data from an input file, stores it in a buffer and writes + it to an output file upon request. The Read() and Write() functions should + be called only when the associated file is ready to provide or receive data + (use the 'select()' function to determine that), and the files should be + opened in non-blocking mode. + The '...Limit' parameters define safety limits. If they are exceeded a log entry + will be made. +*/ + +class cRingBuffer { +private: + uchar *buffer; + int size, head, tail, freeLimit, availLimit; + int countLimit, countOverflow; + int minFree; + bool eof; + int *inFile, *outFile; +protected: + int Free(void) { return ((tail >= head) ? size + head - tail : head - tail) - 1; } + int Available(void) { return (tail >= head) ? tail - head : size - head + tail; } + int Readable(void) { return (tail >= head) ? size - tail - (head ? 0 : 1) : head - tail - 1; } // keep a 1 byte gap! + int Writeable(void) { return (tail > head) ? tail - head : size - head; } + int Byte(int Offset); + bool WaitForOutFile(int Timeout); +public: + cRingBuffer(int *InFile, int *OutFile, int Size, int FreeLimit = 0, int AvailLimit = 0); + virtual ~cRingBuffer(); + virtual int Read(int Max = -1); + virtual int Write(int Max = -1); + bool EndOfFile(void) { return eof; } + bool Empty(void) { return Available() == 0; } + void Clear(void) { head = tail = 0; } + void Skip(int n); + }; + +cRingBuffer::cRingBuffer(int *InFile, int *OutFile, int Size, int FreeLimit, int AvailLimit) +{ + inFile = InFile; + outFile = OutFile; + size = Size; + Clear(); + freeLimit = FreeLimit; + availLimit = AvailLimit; + eof = false; + countLimit = countOverflow = 0; + minFree = size - 1; + buffer = new uchar[size]; + if (!buffer) + esyslog(LOG_ERR, "ERROR: can't allocate ring buffer (size=%d)", size); +} + +cRingBuffer::~cRingBuffer() +{ + dsyslog(LOG_INFO, "buffer stats: %d free, %d overflows, limit exceeded %d times", minFree, countOverflow, countLimit); + delete buffer; +} + +int cRingBuffer::Byte(int Offset) +{ + if (buffer && Offset < Available()) { + Offset += head; + if (Offset >= size) + Offset -= size; + return buffer[Offset]; + } + return -1; +} + +void cRingBuffer::Skip(int n) +{ + if (head < tail) { + head += n; + if (head > tail) + head = tail; + } + else if (head > tail) { + head += n; + if (head >= size) + head -= size; + if (head > tail) + head = tail; + } +} + +bool cRingBuffer::WaitForOutFile(int Timeout) +{ + fd_set set; + FD_ZERO(&set); + FD_SET(*outFile, &set); + struct timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = Timeout; + if (select(FD_SETSIZE, NULL, &set, NULL, &timeout) > 0) { + if (FD_ISSET(*outFile, &set)) + return true; + } + esyslog(LOG_ERR, "ERROR: timeout in WaitForOutFile(%d)", Timeout); + return false; +} + +int cRingBuffer::Read(int Max) +{ + if (buffer) { + eof = false; + int free = Free(); + if (free < minFree) + minFree = free; + if (freeLimit) { + if (free == 0) { + esyslog(LOG_ERR, "ERROR: buffer overflow (size=%d)", size); + countOverflow++; + } + else if (free < freeLimit) { + dsyslog(LOG_INFO, "free buffer space dipped into limit (%d < %d)", free, freeLimit); + countLimit++; + } + } + if (free == 0) + return 0; // the buffer is full + int readin = 0; + for (int i = 0; i < 2; i++) { + // If we read in exactly as many bytes as are immediately + // "readable" we have to do it again, because that means we + // were at the very end of the physical buffer and possibly only + // read in very few bytes. + int immediate = Readable(); + int n = immediate; + if (Max > 0 && n > Max) + n = Max; + if (n > 0) { + int r = read(*inFile, buffer + tail, n); + if (r > 0) { + readin += r; + tail += r; + if (tail > size) + esyslog(LOG_ERR, "ERROR: ooops: buffer tail (%d) exceeds size (%d)", tail, size); + if (tail >= size) + tail = 0; + } + else if (r < 0 && errno != EAGAIN) { + LOG_ERROR; + return -1; + } + else + eof = true; + if (r == immediate && Max != immediate && tail == 0) + Max -= immediate; + else + break; + } + } + return readin; + } + return -1; +} + +int cRingBuffer::Write(int Max) +{ + if (buffer) { + int avail = Available(); + if (availLimit) { + //XXX stats??? + if (avail == 0) + //XXX esyslog(LOG_ERR, "ERROR: buffer empty!"); + {//XXX + esyslog(LOG_ERR, "ERROR: buffer empty! %d", Max); + return Max > 0 ? Max : 0; + }//XXX + else if (avail < availLimit) +;//XXX dsyslog(LOG_INFO, "available buffer data dipped into limit (%d < %d)", avail, availLimit); + } + if (avail == 0) + return 0; // the buffer is empty + int n = Writeable(); + if (Max > 0 && n > Max) + n = Max; + int w = write(*outFile, buffer + head, n); + if (w > 0) { + head += w; + if (head > size) + esyslog(LOG_ERR, "ERROR: ooops: buffer head (%d) exceeds size (%d)", head, size); + if (head >= size) + head = 0; + } + else if (w < 0) { + if (errno != EAGAIN) + LOG_ERROR; + else + w = 0; + } + return w; + } + return -1; +} + +// --- cFileBuffer ----------------------------------------------------------- + +class cFileBuffer : public cRingBuffer { +protected: + cIndexFile *index; + uchar fileNumber; + char *fileName, *pFileNumber; + bool stop; + int GetAvPesLength(void) + { + if (Byte(0) == 'A' && Byte(1) == 'V' && Byte(4) == 'U') + return (Byte(6) << 8) + Byte(7) + AV_PES_HEADER_LEN; + return 0; + } + int GetAvPesType(void) { return Byte(2); } + int GetAvPesTag(void) { return Byte(3); } + int FindAvPesBlock(void); + int GetPictureType(int Offset) { return (Byte(Offset + 5) >> 3) & 0x07; } + int FindPictureStartCode(int Length); +public: + cFileBuffer(int *InFile, int *OutFile, const char *FileName, bool Recording, int Size, int FreeLimit = 0, int AvailLimit = 0); + virtual ~cFileBuffer(); + void Stop(void) { stop = true; } + }; + +cFileBuffer::cFileBuffer(int *InFile, int *OutFile, const char *FileName, bool Recording, int Size, int FreeLimit = 0, int AvailLimit = 0) +:cRingBuffer(InFile, OutFile, Size, FreeLimit, AvailLimit) +{ + index = NULL; + fileNumber = 0; + stop = false; + // Prepare the file name: + fileName = new char[strlen(FileName) + RECORDFILESUFFIXLEN]; + if (!fileName) { + esyslog(LOG_ERR, "ERROR: can't copy file name '%s'", fileName); + return; + } + strcpy(fileName, FileName); + pFileNumber = fileName + strlen(fileName); + // Create the index file: + index = new cIndexFile(FileName, Recording); + if (!index) + esyslog(LOG_ERR, "ERROR: can't allocate index"); + // let's continue without index, so we'll at least have the recording +} + +cFileBuffer::~cFileBuffer() +{ + delete index; + delete fileName; +} + +int cFileBuffer::FindAvPesBlock(void) +{ + int Skipped = 0; + + while (Available() > MINVIDEODATA) { + if (GetAvPesLength()) + return Skipped; + Skip(1); + Skipped++; + } + return -1; +} + +int cFileBuffer::FindPictureStartCode(int Length) +{ + for (int i = AV_PES_HEADER_LEN; i < Length; i++) { + if (Byte(i) == 0 && Byte(i + 1) == 0 && Byte(i + 2) == 1 && Byte(i + 3) == 0) + return i; + } + return -1; +} + +// --- cRecordBuffer --------------------------------------------------------- + +class cRecordBuffer : public cFileBuffer { +private: + uchar pictureType; + int fileSize; + int recordFile; + uchar tagAudio, tagVideo; + bool ok, synced; + int Synchronize(void); + bool NextFile(void); + virtual int Write(int Max = -1); +public: + cRecordBuffer(int *InFile, const char *FileName); + virtual ~cRecordBuffer(); + int WriteWithTimeout(void); + }; + +cRecordBuffer::cRecordBuffer(int *InFile, const char *FileName) +:cFileBuffer(InFile, &recordFile, FileName, true, VIDEOBUFSIZE, VIDEOBUFSIZE / 10, 0) +{ + pictureType = NO_PICTURE; + fileSize = 0; + recordFile = -1; + tagAudio = tagVideo = 0; + ok = synced = false; + if (!fileName) + return;//XXX find a better way??? + // Find the highest existing file suffix: + for (;;) { + sprintf(pFileNumber, RECORDFILESUFFIX, ++fileNumber); + if (access(fileName, F_OK) < 0) { + if (errno == ENOENT) + break; // found a non existing file suffix + LOG_ERROR; + return; + } + } + ok = true; + //XXX hack to make the video device go into 'recording' mode: + char dummy; + read(*InFile, &dummy, sizeof(dummy)); +} + +cRecordBuffer::~cRecordBuffer() +{ + if (recordFile >= 0) + close(recordFile); +} + +int cRecordBuffer::Synchronize(void) +{ + int Length = 0; + int Skipped = 0; + int s; + + while ((s = FindAvPesBlock()) >= 0) { + pictureType = NO_PICTURE; + Skipped += s; + Length = GetAvPesLength(); + if (Length <= MINVIDEODATA) { + switch (GetAvPesType()) { + case AV_PES_VIDEO: + { + int PictureOffset = FindPictureStartCode(Length); + if (PictureOffset >= 0) { + pictureType = GetPictureType(PictureOffset); + if (pictureType < I_FRAME || pictureType > B_FRAME) + esyslog(LOG_ERR, "ERROR: unknown picture type '%d'", pictureType); + } + } + if (!synced) { + tagVideo = GetAvPesTag(); + if (pictureType == I_FRAME) { + synced = true; + Skipped = 0; + } + else { + Skip(Length - 1); + Length = 0; + } + } + else { + if (++tagVideo != GetAvPesTag()) { + esyslog(LOG_ERR, "ERROR: missed video data block #%d (next block is #%d)", tagVideo, GetAvPesTag()); + tagVideo = GetAvPesTag(); + } + } + break; + case AV_PES_AUDIO: + if (!synced) { + tagAudio = GetAvPesTag(); + Skip(Length - 1); + Length = 0; + } + else { + //XXX might get fooled the first time!!! + if (++tagAudio != GetAvPesTag()) { + esyslog(LOG_ERR, "ERROR: missed audio data block #%d (next block is #%d)", tagAudio, GetAvPesTag()); + tagAudio = GetAvPesTag(); + } + } + break; + default: // unknown data + Length = 0; // don't skip entire block - maybe we're just not in sync with AV_PES yet + if (synced) + esyslog(LOG_ERR, "ERROR: unknown data type '%d'", GetAvPesType()); + } + if (Length > 0) + break; + } + else if (synced) { + esyslog(LOG_ERR, "ERROR: block length too big (%d)", Length); + Length = 0; + } + Skip(1); + Skipped++; + } + if (synced && Skipped) + esyslog(LOG_ERR, "ERROR: skipped %d bytes", Skipped); + return Length; +} + +bool cRecordBuffer::NextFile(void) +{ + if (recordFile >= 0 && fileSize > MAXVIDEOFILESIZE && pictureType == I_FRAME) { + if (close(recordFile) < 0) + LOG_ERROR; + // don't return 'false', maybe we can still record into the next file + recordFile = -1; + fileNumber++; + if (fileNumber == 0) + esyslog(LOG_ERR, "ERROR: max number of files (%d) exceeded", MAXFILESPERRECORDING); + fileSize = 0; + } + if (recordFile < 0) { + sprintf(pFileNumber, RECORDFILESUFFIX, fileNumber); + dsyslog(LOG_INFO, "recording to '%s'", fileName); + recordFile = open(fileName, O_RDWR | O_CREAT | O_NONBLOCK, S_IRUSR | S_IWUSR); + if (recordFile < 0) { + LOG_ERROR; return false; } } - // TODO + return true; +} + +int cRecordBuffer::Write(int Max) +{ + // This function ignores the incoming 'Max'! + // It tries to write out exactly *one* block of AV_PES data. + if (!ok) + return -1; + int n = Synchronize(); + if (n) { + if (stop && pictureType == I_FRAME) { + ok = false; + return -1; // finish the recording before the next 'I' frame + } + if (NextFile()) { + if (index && pictureType != NO_PICTURE) + index->Write(pictureType, fileNumber, fileSize); + int written = 0; + for (;;) { + int w = cFileBuffer::Write(n); + if (w >= 0) { + fileSize += w; + written += w; + n -= w; + if (n == 0) + return written; + } + else + return w; + } + } + return -1; + } + return 0; +} + +int cRecordBuffer::WriteWithTimeout(void) +{ + int w, written = 0; + int t0 = time_ms(); + while ((w = Write()) > 0 && time_ms() - t0 < MAXRECORDWRITETIME) + written += w; + return w < 0 ? w : written; +} + +// --- cReplayBuffer --------------------------------------------------------- + +enum eReplayMode { rmPlay, rmFastForward, rmFastRewind }; + +class cReplayBuffer : public cFileBuffer { +private: + int fileOffset; + int replayFile; + eReplayMode mode; + bool skipAudio; + int lastIndex; + void SkipAudioBlocks(void); + bool NextFile(uchar FileNumber = 0, int FileOffset = -1); + void Close(void); +public: + cReplayBuffer(int *OutFile, const char *FileName); + virtual ~cReplayBuffer(); + virtual int Read(int Max = -1); + virtual int Write(int Max = -1); + void SetMode(eReplayMode Mode); + int Resume(void); + bool Save(void); + void SkipSeconds(int Seconds); + }; + +cReplayBuffer::cReplayBuffer(int *OutFile, const char *FileName) +:cFileBuffer(&replayFile, OutFile, FileName, false, VIDEOBUFSIZE, 0, VIDEOBUFSIZE / 10) +{ + fileOffset = 0; + replayFile = -1; + mode = rmPlay; + skipAudio = false; + lastIndex = -1; + if (!fileName) + return;//XXX find a better way??? + // All recordings start with '1': + fileNumber = 1; //TODO what if it doesn't start with '1'??? + //XXX hack to make the video device go into 'replaying' mode: + char dummy; + write(*OutFile, &dummy, sizeof(dummy)); +} + +cReplayBuffer::~cReplayBuffer() +{ + Close(); +} + +void cReplayBuffer::Close(void) +{ + if (replayFile >= 0) { + if (close(replayFile) < 0) + LOG_ERROR; + replayFile = -1; + fileOffset = 0; + } +} + +void cReplayBuffer::SetMode(eReplayMode Mode) +{ + mode = Mode; + skipAudio = Mode != rmPlay; + if (mode != rmPlay) + Clear(); +} + +int cReplayBuffer::Resume(void) +{ + if (index) { + int Index = index->GetResume(); + if (Index >= 0) { + uchar FileNumber; + int FileOffset; + if (index->Get(Index, &FileNumber, &FileOffset) && NextFile(FileNumber, FileOffset)) + return Index; + } + } + return -1; +} + +bool cReplayBuffer::Save(void) +{ + if (index) { + int Index = index->Get(fileNumber, fileOffset); + if (Index >= 0) { + Index -= RESUMEBACKUP; + if (Index > 0) { + uchar FileNumber; + int FileOffset; + Index = index->GetNextIFrame(Index, false, &FileNumber, &FileOffset); + } + else + Index = 0; + if (Index >= 0) + return index->StoreResume(Index); + } + } return false; } -bool cDvbRecorder::Play(const char *FileName, int Frame) +void cReplayBuffer::SkipSeconds(int Seconds) { - if (!recording) { - isyslog(LOG_INFO, "play %s (%d)", FileName, Frame); - // TODO - Interface.Error("Playback not yet implemented!"); + if (index && Seconds) { + int Index = index->Get(fileNumber, fileOffset); + if (Index >= 0) { + if (Seconds < 0) { + int sec = index->Last() / FRAMESPERSEC; + if (Seconds < -sec) + Seconds = - sec; + } + Index += Seconds * FRAMESPERSEC; + if (Index < 0) + Index = 1; + uchar FileNumber; + int FileOffset; + if (index->GetNextIFrame(Index, false, &FileNumber, &FileOffset) >= 0) + NextFile(FileNumber, FileOffset); + } + } +} + +void cReplayBuffer::SkipAudioBlocks(void) +{ + int Length; + + while ((Length = GetAvPesLength()) > 0) { + if (GetAvPesType() == AV_PES_AUDIO) + Skip(Length); + else + break; + } +} + +bool cReplayBuffer::NextFile(uchar FileNumber, int FileOffset) +{ + if (FileNumber > 0) { + Clear(); + if (FileNumber != fileNumber) { + Close(); + fileNumber = FileNumber; + } + } + if (replayFile >= 0 && EndOfFile()) { + Close(); + fileNumber++; + if (fileNumber == 0) + esyslog(LOG_ERR, "ERROR: max number of files (%d) exceeded", MAXFILESPERRECORDING); + } + if (replayFile < 0) { + sprintf(pFileNumber, RECORDFILESUFFIX, fileNumber); + if (access(fileName, R_OK) == 0) { + dsyslog(LOG_INFO, "playing '%s'", fileName); + replayFile = open(fileName, O_RDONLY | O_NONBLOCK); + if (replayFile < 0) { + LOG_ERROR; + return false; + } + } + else if (errno != ENOENT) + LOG_ERROR; + } + if (replayFile >= 0) { + if (FileOffset >= 0) { + if ((fileOffset = lseek(replayFile, FileOffset, SEEK_SET)) != FileOffset) + LOG_ERROR; + // don't return 'false', maybe we can still replay the file + } return true; } return false; } -bool cDvbRecorder::FastForward(void) +int cReplayBuffer::Read(int Max = -1) { - isyslog(LOG_INFO, "fast forward"); - // TODO - return false; + if (stop) + return -1; + if (mode != rmPlay) { + if (index) { + if (Available()) + return 0; // write out the entire block + int Index = (lastIndex >= 0) ? lastIndex : index->Get(fileNumber, fileOffset); + if (Index >= 0) { + uchar FileNumber; + int FileOffset, Length; + Index = index->GetNextIFrame(Index, mode == rmFastForward, &FileNumber, &FileOffset, &Length); + if (Index >= 0) { + if (!NextFile(FileNumber, FileOffset)) + return -1; + Max = Length; + } + lastIndex = Index; + } + if (Index < 0) { + // This results in normal replay after a fast rewind. + // After a fast forward it will stop. + // TODO Could we cause it to pause at the last frame? + SetMode(rmPlay); + return 0; + } + } + } + else + lastIndex = -1; + //XXX timeout as in recording??? + if (NextFile()) { + int readin = 0; + do { + // If Max is > 0 here we need to make sure we read in the entire block! + int r = cFileBuffer::Read(Max); + if (r >= 0) + readin += r; + else + return -1; + } while (readin < Max && Free() > 0); + return readin; + } + if (Available() > 0) + return 0; + return -1; } -bool cDvbRecorder::FastRewind(void) +int cReplayBuffer::Write(int Max) { - isyslog(LOG_INFO, "fast rewind"); - // TODO - return false; + int Written = 0; + + do { + if (skipAudio) { + SkipAudioBlocks(); + Max = GetAvPesLength(); + } + while (Max) { + int w = cFileBuffer::Write(Max); + if (w >= 0) { + fileOffset += w; + Written += w; + if (Max < 0) + break; + Max -= w; + } + else + return w; + //XXX??? Why does the buffer get empty here??? + if (Empty() || !WaitForOutFile(1000000)) + return Written; + } + } while (skipAudio && Available()); + return Written; } -bool cDvbRecorder::Pause(void) -{ - isyslog(LOG_INFO, "pause"); - // TODO - return false; -} +// --- cDvbApi --------------------------------------------------------------- -void cDvbRecorder::Stop(void) -{ - isyslog(LOG_INFO, "stop"); - recording = false; - // TODO -} - -int cDvbRecorder::Frame(void) -{ - isyslog(LOG_INFO, "frame"); - // TODO - return 0; -} - -// --- cDvbOsd --------------------------------------------------------------- - -cDvbOsd::cDvbOsd(void) +cDvbApi::cDvbApi(void) { + isMainProcess = true; + pidRecord = pidReplay = 0; + fromRecord = toRecord = -1; + fromReplay = toReplay = -1; + videoDev = open(VIDEODEVICE, O_RDWR | O_NONBLOCK); + if (videoDev < 0) + LOG_ERROR; cols = rows = 0; -#ifdef DEBUG_OSD - memset(&colorPairs, 0, sizeof(colorPairs)); +#if defined(DEBUG_OSD) || defined(DEBUG_REMOTE) initscr(); - start_color(); keypad(stdscr, TRUE); nonl(); cbreak(); noecho(); timeout(1000); +#endif +#if defined(DEBUG_OSD) + memset(&colorPairs, 0, sizeof(colorPairs)); + start_color(); leaveok(stdscr, TRUE); window = stdscr; #endif -#ifdef DEBUG_REMOTE - initscr(); - keypad(stdscr, TRUE); - nonl(); - cbreak(); - noecho(); - timeout(1000); -#endif } -cDvbOsd::~cDvbOsd() +cDvbApi::~cDvbApi() { - Close(); + if (isMainProcess) { + if (videoDev >= 0) { + Close(); + StopReplay(); + StopRecord(); + close(videoDev); + } +#if defined(DEBUG_REMOTE) || defined(DEBUG_OSD) + endwin(); +#endif + } } #ifdef DEBUG_OSD -void cDvbOsd::SetColor(eDvbColor colorFg, eDvbColor colorBg) +void cDvbApi::SetColor(eDvbColor colorFg, eDvbColor colorBg) { int color = (colorBg << 16) | colorFg | 0x80000000; for (int i = 0; i < MaxColorPairs; i++) { @@ -185,11 +1038,9 @@ void cDvbOsd::SetColor(eDvbColor colorFg, eDvbColor colorBg) } } #else -void cDvbOsd::Cmd(OSD_Command cmd, int color, int x0, int y0, int x1, int y1, const void *data) +void cDvbApi::Cmd(OSD_Command cmd, int color, int x0, int y0, int x1, int y1, const void *data) { - int v = open(VIDEODEVICE, O_RDWR); - - if (v >= 0) { + if (videoDev >= 0) { struct drawcmd dc; dc.cmd = cmd; dc.color = color; @@ -198,15 +1049,12 @@ void cDvbOsd::Cmd(OSD_Command cmd, int color, int x0, int y0, int x1, int y1, co dc.x1 = x1; dc.y1 = y1; dc.data = (void *)data; - ioctl(v, VIDIOCSOSDCOMMAND, &dc); - close(v); + ioctl(videoDev, VIDIOCSOSDCOMMAND, &dc); } - else - Interface.Error("can't open VIDEODEVICE");//XXX } #endif -void cDvbOsd::Open(int w, int h) +void cDvbApi::Open(int w, int h) { cols = w; rows = h; @@ -233,14 +1081,14 @@ void cDvbOsd::Open(int w, int h) SETCOLOR(clrWhite, 0xFC, 0xFC, 0xFC, 255); } -void cDvbOsd::Close(void) +void cDvbApi::Close(void) { #ifndef DEBUG_OSD Cmd(OSD_Close); #endif } -void cDvbOsd::Clear(void) +void cDvbApi::Clear(void) { #ifdef DEBUG_OSD SetColor(clrBackground, clrBackground); @@ -250,7 +1098,7 @@ void cDvbOsd::Clear(void) #endif } -void cDvbOsd::Fill(int x, int y, int w, int h, eDvbColor color) +void cDvbApi::Fill(int x, int y, int w, int h, eDvbColor color) { if (x < 0) x = cols + x; if (y < 0) y = rows + y; @@ -265,12 +1113,12 @@ void cDvbOsd::Fill(int x, int y, int w, int h, eDvbColor color) #endif } -void cDvbOsd::ClrEol(int x, int y, eDvbColor color) +void cDvbApi::ClrEol(int x, int y, eDvbColor color) { Fill(x, y, cols - x, 1, color); } -void cDvbOsd::Text(int x, int y, const char *s, eDvbColor colorFg, eDvbColor colorBg) +void cDvbApi::Text(int x, int y, const char *s, eDvbColor colorFg, eDvbColor colorBg) { if (x < 0) x = cols + x; if (y < 0) y = rows + y; @@ -282,3 +1130,343 @@ void cDvbOsd::Text(int x, int y, const char *s, eDvbColor colorFg, eDvbColor col Cmd(OSD_Text, (int(colorBg) << 16) | colorFg, x * charWidth, y * lineHeight, 1, 0, s); #endif } + +bool cDvbApi::SetChannel(int FrequencyMHz, char Polarization, int Diseqc, int Srate, int Vpid, int Apid, int Ca, int Pnr) +{ + if (videoDev >= 0) { + struct frontend front; + ioctl(videoDev, VIDIOCGFRONTEND, &front); + unsigned int freq = FrequencyMHz; + front.ttk = (freq < 11800UL) ? 0 : 1; + if (freq < 11800UL) + freq -= 9750UL; + else + freq -= 10600UL; + front.channel_flags = Ca ? DVB_CHANNEL_CA : DVB_CHANNEL_FTA; + front.pnr = Pnr; + front.freq = freq * 1000000UL; + front.diseqc = Diseqc; + front.srate = Srate * 1000; + front.volt = (Polarization == 'v') ? 0 : 1; + front.video_pid = Vpid; + front.audio_pid = Apid; + front.fec = 8; + front.AFC = 1; + ioctl(videoDev, VIDIOCSFRONTEND, &front); + if (front.sync & 0x1F == 0x1F) + return true; + esyslog(LOG_ERR, "ERROR: channel not sync'ed (front.sync=%X)!", front.sync); + } + return false; +} + +void cDvbApi::KillProcess(pid_t pid) +{ + pid_t Pid2Wait4 = pid; + for (time_t t0 = time(NULL); time(NULL) - t0 < MAXPROCESSTIMEOUT; ) { + int status; + pid_t pid = waitpid(Pid2Wait4, &status, WNOHANG); + if (pid < 0) { + if (errno != ECHILD) + LOG_ERROR; + return; + } + if (pid == Pid2Wait4) + return; + } + esyslog(LOG_ERR, "ERROR: process %d won't end (waited %d seconds) - terminating it...", Pid2Wait4, MAXPROCESSTIMEOUT); + if (kill(Pid2Wait4, SIGTERM) < 0) { + esyslog(LOG_ERR, "ERROR: process %d won't terminate (%s) - killing it...", Pid2Wait4, strerror(errno)); + if (kill(Pid2Wait4, SIGKILL) < 0) + esyslog(LOG_ERR, "ERROR: process %d won't die (%s) - giving up", Pid2Wait4, strerror(errno)); + } +} + +bool cDvbApi::Recording(void) +{ + return pidRecord; +} + +bool cDvbApi::Replaying(void) +{ + return pidReplay; +} + +bool cDvbApi::StartRecord(const char *FileName) +{ + if (Recording()) { + esyslog(LOG_ERR, "ERROR: StartRecord() called while recording - ignored!"); + return false; + } + if (videoDev >= 0) { + + // Check FileName: + + if (!FileName) { + esyslog(LOG_ERR, "ERROR: StartRecord: file name is (null)"); + return false; + } + isyslog(LOG_INFO, "record %s", FileName); + + // Create directories if necessary: + + if (!MakeDirs(FileName, true)) + return false; + + // Open pipes for recording process: + + int fromRecordPipe[2], toRecordPipe[2]; + + if (pipe(fromRecordPipe) != 0) { + LOG_ERROR; + return false; + } + if (pipe(toRecordPipe) != 0) { + LOG_ERROR; + return false; + } + + // Create recording process: + + pidRecord = fork(); + if (pidRecord < 0) { + LOG_ERROR; + return false; + } + if (pidRecord == 0) { + + // This is the actual recording process + + dsyslog(LOG_INFO, "start recording process (pid=%d)", getpid()); + isMainProcess = false; + int fromMain = toRecordPipe[0]; + int toMain = fromRecordPipe[1]; + cRecordBuffer *Buffer = new cRecordBuffer(&videoDev, FileName); + if (Buffer) { + for (;;) { + fd_set set; + FD_ZERO(&set); + FD_SET(videoDev, &set); + FD_SET(fromMain, &set); + struct timeval timeout; + timeout.tv_sec = 1; + timeout.tv_usec = 0; + if (select(FD_SETSIZE, &set, NULL, NULL, &timeout) > 0) { + if (FD_ISSET(videoDev, &set)) { + if (Buffer->Read() < 0) + break; + } + if (FD_ISSET(fromMain, &set)) { + switch (readchar(fromMain)) { + case dvbStop: Buffer->Stop(); break; + break; + } + } + } + else + esyslog(LOG_ERR, "ERROR: video data stream broken"); + if (Buffer->WriteWithTimeout() < 0) + break; + } + delete Buffer; + } + else + esyslog(LOG_ERR, "ERROR: can't allocate recording buffer"); + close(fromMain); + close(toMain); + dsyslog(LOG_INFO, "end recording process"); + exit(0); + } + + // Establish communication with the recording process: + + fromRecord = fromRecordPipe[0]; + toRecord = toRecordPipe[1]; + return true; + } + return false; +} + +void cDvbApi::StopRecord(void) +{ + if (pidRecord) { + writechar(toRecord, dvbStop); + close(toRecord); + close(fromRecord); + toRecord = fromRecord = -1; + KillProcess(pidRecord); + pidRecord = 0; + SetReplayMode(VID_PLAY_RESET); //XXX + } +} + +void cDvbApi::SetReplayMode(int Mode) +{ + if (videoDev >= 0) { + struct video_play_mode pmode; + pmode.mode = Mode; + ioctl(videoDev, VIDIOCSPLAYMODE, &pmode); + } +} + +bool cDvbApi::StartReplay(const char *FileName) +{ + if (Recording()) { + esyslog(LOG_ERR, "ERROR: StartReplay() called while recording - ignored!"); + return false; + } + StopReplay(); + if (videoDev >= 0) { + + // Check FileName: + + if (!FileName) { + esyslog(LOG_ERR, "ERROR: StartReplay: file name is (null)"); + return false; + } + isyslog(LOG_INFO, "replay %s", FileName); + + // Open pipes for replay process: + + int fromReplayPipe[2], toReplayPipe[2]; + + if (pipe(fromReplayPipe) != 0) { + LOG_ERROR; + return false; + } + if (pipe(toReplayPipe) != 0) { + LOG_ERROR; + return false; + } + + // Create replay process: + + pidReplay = fork(); + if (pidReplay < 0) { + LOG_ERROR; + return false; + } + if (pidReplay == 0) { + + // This is the actual replaying process + + dsyslog(LOG_INFO, "start replaying process (pid=%d)", getpid()); + isMainProcess = false; + int fromMain = toReplayPipe[0]; + int toMain = fromReplayPipe[1]; + cReplayBuffer *Buffer = new cReplayBuffer(&videoDev, FileName); + if (Buffer) { + bool Paused = false; + bool FastForward = false; + bool FastRewind = false; + int ResumeIndex = Buffer->Resume(); + if (ResumeIndex >= 0) + isyslog(LOG_INFO, "resuming replay at index %d (%s)", ResumeIndex, cIndexFile::Str(ResumeIndex)); + for (;;) { + if (Buffer->Read() < 0) + break; + fd_set setIn, setOut; + FD_ZERO(&setIn); + FD_ZERO(&setOut); + FD_SET(fromMain, &setIn); + FD_SET(videoDev, &setOut); + struct timeval timeout; + timeout.tv_sec = 1; + timeout.tv_usec = 0; + if (select(FD_SETSIZE, &setIn, &setOut, NULL, &timeout) > 0) { + if (FD_ISSET(videoDev, &setOut)) { + if (Buffer->Write() < 0) + break; + } + if (FD_ISSET(fromMain, &setIn)) { + switch (readchar(fromMain)) { + case dvbStop: Buffer->Stop(); break; + case dvbPauseReplay: SetReplayMode(Paused ? VID_PLAY_NORMAL : VID_PLAY_PAUSE); + Paused = !Paused; + FastForward = FastRewind = false; + Buffer->SetMode(rmPlay); + break; + case dvbFastForward: SetReplayMode(VID_PLAY_NORMAL); + FastForward = !FastForward; + FastRewind = Paused = false; + Buffer->SetMode(FastForward ? rmFastForward : rmPlay); + break; + case dvbFastRewind: SetReplayMode(VID_PLAY_NORMAL); + FastRewind = !FastRewind; + FastForward = Paused = false; + Buffer->SetMode(FastRewind ? rmFastRewind : rmPlay); + break; + case dvbSkip: { + int Seconds; + if (readint(fromMain, Seconds)) { + SetReplayMode(VID_PLAY_NORMAL); + FastForward = FastRewind = Paused = false; + Buffer->SetMode(rmPlay); + Buffer->SkipSeconds(Seconds); + } + } + break; + } + } + } + } + Buffer->Save(); + delete Buffer; + } + else + esyslog(LOG_ERR, "ERROR: can't allocate replaying buffer"); + close(fromMain); + close(toMain); + SetReplayMode(VID_PLAY_RESET); //XXX + dsyslog(LOG_INFO, "end replaying process"); + exit(0); + } + + // Establish communication with the replay process: + + fromReplay = fromReplayPipe[0]; + toReplay = toReplayPipe[1]; + return true; + } + return false; +} + +void cDvbApi::StopReplay(void) +{ + if (pidReplay) { + writechar(toReplay, dvbStop); + close(toReplay); + close(fromReplay); + toReplay = fromReplay = -1; + KillProcess(pidReplay); + pidReplay = 0; + SetReplayMode(VID_PLAY_RESET); //XXX + } +} + +void cDvbApi::PauseReplay(void) +{ + if (pidReplay) + writechar(toReplay, dvbPauseReplay); +} + +void cDvbApi::FastForward(void) +{ + if (pidReplay) + writechar(toReplay, dvbFastForward); +} + +void cDvbApi::FastRewind(void) +{ + if (pidReplay) + writechar(toReplay, dvbFastRewind); +} + +void cDvbApi::Skip(int Seconds) +{ + if (pidReplay) { + writechar(toReplay, dvbSkip); + writeint(toReplay, Seconds); + } +} + diff --git a/dvbapi.h b/dvbapi.h index 2e8406ee..3c447cf1 100644 --- a/dvbapi.h +++ b/dvbapi.h @@ -4,20 +4,21 @@ * See the main source file 'osm.c' for copyright information and * how to reach the author. * - * $Id: dvbapi.h 1.2 2000/03/06 19:47:20 kls Exp $ + * $Id: dvbapi.h 1.3 2000/04/15 13:36:10 kls Exp $ */ #ifndef __DVBAPI_H #define __DVBAPI_H // FIXME: these should be defined in ../DVB/driver/dvb.h!!! -typedef unsigned int u32; -typedef unsigned short u16; -typedef unsigned char u8; +typedef unsigned int __u32; +typedef unsigned short __u16; +typedef unsigned char __u8; #if defined(DEBUG_OSD) || defined(DEBUG_REMOTE) #include #endif +#include #include "../DVB/driver/dvb.h" enum eDvbColor { clrBackground, @@ -36,56 +37,15 @@ enum eDvbColor { clrBackground, clrWhite, }; -extern const char *DvbQuality; // Low, Medium, High - -bool DvbSetChannel(int FrequencyMHz, char Polarization, int Diseqc, int Srate, int Vpid, int Apid); - -class cDvbRecorder { +class cDvbApi { private: - bool recording; + int videoDev; public: - cDvbRecorder(void); - ~cDvbRecorder(); - bool Recording(void); - // Returns true if this recorder is currently recording, false if it - // is playing back or does nothing. - bool Record(const char *FileName, char Quality); - // Starts recording the current channel into the given file, with the - // given quality level. Any existing file will be overwritten. - // Returns true if recording was started successfully. - // If there is already a recording session active, false will be - // returned. - bool Play(const char *FileName, int Frame = 0); - // Starts playback of the given file, at the optional Frame (default - // is the beginning of the file). If Frame is beyond the last recorded - // frame in the file (or if it is negative), playback will be positioned - // to the last frame in the file (or the frame with the absolute value of - // Frame) and will do an implicit Pause() there. - // If there is already a playback session active, it will be stopped - // and the new file or frame (which may be in the same file) will - // be played back. - bool FastForward(void); - // Runs the current playback session forward at a higher speed. - // TODO allow different fast forward speeds??? - bool FastRewind(void); - // Runs the current playback session backwards forward at a higher speed. - // TODO allow different fast rewind speeds??? - bool Pause(void); - // Pauses the current recording or playback session, or resumes a paused - // session. - // Returns true if there is actually a recording or playback session - // active that was paused/resumed. - void Stop(void); - // Stops the current recording or playback session. - int Frame(void); - // Returns the number of the current frame in the current recording or - // playback session, which can be used to start playback at a given position. - // The number returned is the actual number of frames counted from the - // beginning of the current file. - // The very first frame has the number 1. - }; + cDvbApi(void); + ~cDvbApi(); + + // On Screen Display facilities -class cDvbOsd { private: enum { charWidth = 12, // average character width lineHeight = 27 // smallest text height @@ -95,19 +55,70 @@ private: enum { MaxColorPairs = 16 }; int colorPairs[MaxColorPairs]; void SetColor(eDvbColor colorFg, eDvbColor colorBg = clrBackground); -#else - void Cmd(OSD_Command cmd, int color = 0, int x0 = 0, int y0 = 0, int x1 = 0, int y1 = 0, const void *data = NULL); #endif int cols, rows; + void Cmd(OSD_Command cmd, int color = 0, int x0 = 0, int y0 = 0, int x1 = 0, int y1 = 0, const void *data = NULL); public: - cDvbOsd(void); - ~cDvbOsd(); void Open(int w, int h); void Close(void); void Clear(void); void Fill(int x, int y, int w, int h, eDvbColor color = clrBackground); void ClrEol(int x, int y, eDvbColor color = clrBackground); void Text(int x, int y, const char *s, eDvbColor colorFg = clrWhite, eDvbColor colorBg = clrBackground); + + // Channel facilities + + bool SetChannel(int FrequencyMHz, char Polarization, int Diseqc, int Srate, int Vpid, int Apid, int Ca, int Pnr); + + // Record/Replay facilities + +private: + enum { dvbStop = 1, // let's not have 0 as a command + dvbPauseReplay, + dvbFastForward, + dvbFastRewind, + dvbSkip, + }; + bool isMainProcess; + pid_t pidRecord, pidReplay; + int fromRecord, toRecord; + int fromReplay, toReplay; + void SetReplayMode(int Mode); + void KillProcess(pid_t pid); +public: + bool Recording(void); + // Returns true if we are currently recording. + bool Replaying(void); + // Returns true if we are currently replaying. + bool StartRecord(const char *FileName); + // Starts recording the current channel into the given file. + // In order to be able to record longer movies, + // a numerical suffix will be appended to the file name. The inital + // value of that suffix will be larger than any existing file under + // the given name, thus allowing an interrupted recording to continue + // gracefully. + // Returns true if recording was started successfully. + // If there is already a recording session active, false will be + // returned. + void StopRecord(void); + // Stops the current recording session (if any). + bool StartReplay(const char *FileName); + // Starts replaying the given file. + // If there is already a replay session active, it will be stopped + // and the new file will be played back. + void StopReplay(void); + // Stops the current replay session (if any). + void PauseReplay(void); + // Pauses the current replay session, or resumes a paused session. + void FastForward(void); + // Runs the current replay session forward at a higher speed. + void FastRewind(void); + // Runs the current replay session backwards at a higher speed. + void Skip(int Seconds); + // Skips the given number of seconds in the current replay session. + // The sign of 'Seconds' determines the direction in which to skip. + // Use a very large negative value to go all the way back to the + // beginning of the recording. }; #endif //__DVBAPI_H diff --git a/interface.c b/interface.c index 6a4b2a4e..a5465135 100644 --- a/interface.c +++ b/interface.c @@ -4,7 +4,7 @@ * See the main source file 'osm.c' for copyright information and * how to reach the author. * - * $Id: interface.c 1.2 2000/03/06 19:45:03 kls Exp $ + * $Id: interface.c 1.3 2000/04/15 17:38:11 kls Exp $ */ #include "interface.h" @@ -15,10 +15,10 @@ #define MenuColumns 40 #ifndef DEBUG_REMOTE -cRcIo RcIo("/dev/ttyS1");//XXX +cRcIo RcIo("/dev/ttyS1"); #endif -cDvbOsd DvbOsd; //XXX member of cInterface??? +cDvbApi DvbApi; //XXX member of cInterface??? cInterface Interface; @@ -38,19 +38,22 @@ void cInterface::Init(void) void cInterface::Open(void) { if (!open++) - DvbOsd.Open(MenuColumns, MenuLines); + DvbApi.Open(MenuColumns, MenuLines); } void cInterface::Close(void) { + if (open == 1) + Clear(); if (!--open) - DvbOsd.Close(); + DvbApi.Close(); } unsigned int cInterface::GetCh(void) { #ifdef DEBUG_REMOTE - return getch(); + int c = getch(); + return (c > 0) ? c : 0; #else //XXX #ifdef DEBUG_OSD //XXX wrefresh(window);//XXX @@ -80,13 +83,13 @@ eKeys cInterface::Wait(int Seconds) void cInterface::Clear(void) { if (open) - DvbOsd.Clear(); + DvbApi.Clear(); } void cInterface::ClearEol(int x, int y, eDvbColor Color) { if (open) - DvbOsd.ClrEol(x, y, Color); + DvbApi.ClrEol(x, y, Color); } void cInterface::SetCols(int *c) @@ -101,7 +104,7 @@ void cInterface::SetCols(int *c) void cInterface::Write(int x, int y, const char *s, eDvbColor FgColor, eDvbColor BgColor) { if (open) - DvbOsd.Text(x, y, s, FgColor, BgColor); + DvbApi.Text(x, y, s, FgColor, BgColor); } void cInterface::WriteText(int x, int y, const char *s, bool Current) @@ -187,8 +190,8 @@ void cInterface::HelpButton(int Index, const char *Text, eDvbColor FgColor, eDvb int l = (w - strlen(Text)) / 2; if (l < 0) l = 0; - DvbOsd.Fill(Index * w, -1, w, 1, BgColor); - DvbOsd.Text(Index * w + l, -1, Text, FgColor, BgColor); + DvbApi.Fill(Index * w, -1, w, 1, BgColor); + DvbApi.Text(Index * w + l, -1, Text, FgColor, BgColor); } } diff --git a/interface.h b/interface.h index 9322cf39..1e5329d5 100644 --- a/interface.h +++ b/interface.h @@ -4,7 +4,7 @@ * See the main source file 'osm.c' for copyright information and * how to reach the author. * - * $Id: interface.h 1.2 2000/02/27 14:54:02 kls Exp $ + * $Id: interface.h 1.3 2000/03/19 14:03:28 kls Exp $ */ #ifndef __INTERFACE_H @@ -45,5 +45,6 @@ public: }; extern cInterface Interface; +extern cDvbApi DvbApi; //XXX member of cInterface??? #endif //__INTERFACE_H diff --git a/keys-pc.conf b/keys-pc.conf index cfbe4587..744609d5 100644 Binary files a/keys-pc.conf and b/keys-pc.conf differ diff --git a/keys.conf b/keys.conf deleted file mode 100644 index 0e3c07bc..00000000 --- a/keys.conf +++ /dev/null @@ -1,23 +0,0 @@ -Code B -Address 0000 -Up 000047E2 -Down 000007E2 -Menu 000011E2 -Ok 000079E2 -Back 00001AE2 -Left 000005E2 -Right 000045E2 -0 00007FE2 -1 00003FE2 -2 00005FE2 -3 00001FE2 -4 00006FE2 -5 00002FE2 -6 00004FE2 -7 00000FE2 -8 000077E2 -9 000037E2 -Red 000025E2 -Green 00002AE2 -Yellow 00005AE2 -Blue 00000000 diff --git a/menu.c b/menu.c index 45f25a46..92c0bd3e 100644 --- a/menu.c +++ b/menu.c @@ -4,7 +4,7 @@ * See the main source file 'osm.c' for copyright information and * how to reach the author. * - * $Id: menu.c 1.2 2000/03/05 15:37:31 kls Exp $ + * $Id: menu.c 1.3 2000/04/15 15:07:36 kls Exp $ */ #include "menu.h" @@ -502,13 +502,15 @@ cMenuEditChannel::cMenuEditChannel(int Index) channel = Channels.Get(Index); if (channel) { data = *channel; - Add(new cMenuEditStrItem("Name", data.name, sizeof(data.name), FileNameChars)); - Add(new cMenuEditIntItem("Frequency", &data.frequency, 10000, 13000)); //TODO exact limits??? - Add(new cMenuEditChrItem("Polarization", &data.polarization, "hv")); - Add(new cMenuEditIntItem("Diseqc", &data.diseqc, 0, 10)); //TODO exact limits??? - Add(new cMenuEditIntItem("Srate", &data.srate, 22000, 27500)); //TODO exact limits - toggle??? - Add(new cMenuEditIntItem("Vpid", &data.vpid, 0, 10000)); //TODO exact limits??? - Add(new cMenuEditIntItem("Apid", &data.apid, 0, 10000)); //TODO exact limits??? + Add(new cMenuEditStrItem( "Name", data.name, sizeof(data.name), FileNameChars)); + Add(new cMenuEditIntItem( "Frequency", &data.frequency, 10000, 13000)); //TODO exact limits??? + Add(new cMenuEditChrItem( "Polarization", &data.polarization, "hv")); + Add(new cMenuEditIntItem( "Diseqc", &data.diseqc, 0, 10)); //TODO exact limits??? + Add(new cMenuEditIntItem( "Srate", &data.srate, 22000, 27500)); //TODO exact limits - toggle??? + Add(new cMenuEditIntItem( "Vpid", &data.vpid, 0, 10000)); //TODO exact limits??? + Add(new cMenuEditIntItem( "Apid", &data.apid, 0, 10000)); //TODO exact limits??? + Add(new cMenuEditBoolItem("CA", &data.ca)); + Add(new cMenuEditIntItem( "Pnr", &data.pnr, 0, 10000)); //TODO exact limits??? } } @@ -599,7 +601,7 @@ eOSState cMenuChannels::Edit(void) { if (HasSubMenu() || Count() == 0) return osContinue; - isyslog(LOG_INFO, "editing timer %d", Current() + 1); + isyslog(LOG_INFO, "editing channel %d", Current() + 1); return AddSubMenu(new cMenuEditChannel(Current())); } @@ -711,23 +713,24 @@ private: cTimer *timer; cTimer data; public: - cMenuEditTimer(int Index); + cMenuEditTimer(int Index, bool New = false); virtual eOSState ProcessKey(eKeys Key); }; -cMenuEditTimer::cMenuEditTimer(int Index) +cMenuEditTimer::cMenuEditTimer(int Index, bool New) :cOsdMenu("Edit Timer", 10) { timer = Timers.Get(Index); if (timer) { data = *timer; + if (New) + data.active = 1; Add(new cMenuEditBoolItem("Active", &data.active)); Add(new cMenuEditChanItem("Channel", &data.channel)); Add(new cMenuEditDayItem( "Day", &data.day)); Add(new cMenuEditTimeItem("Start", &data.start)); Add(new cMenuEditTimeItem("Stop", &data.stop)); //TODO VPS??? - Add(new cMenuEditChrItem( "Quality", &data.quality, DvbQuality)); Add(new cMenuEditIntItem( "Priority", &data.priority, 0, 99)); Add(new cMenuEditIntItem( "Lifetime", &data.lifetime, 0, 99)); Add(new cMenuEditStrItem( "File", data.file, sizeof(data.file), FileNameChars)); @@ -843,7 +846,7 @@ eOSState cMenuTimers::New(void) Add(new cMenuTimerItem(timer->Index()/*XXX*/, timer), true); Timers.Save(); isyslog(LOG_INFO, "timer %d added", timer->Index() + 1); - return AddSubMenu(new cMenuEditTimer(Current())); + return AddSubMenu(new cMenuEditTimer(Current(), true)); } eOSState cMenuTimers::Del(void) diff --git a/osm.c b/osm.c index 7031d689..70305ce8 100644 --- a/osm.c +++ b/osm.c @@ -22,9 +22,10 @@ * * The project's page is at http://www.cadsoft.de/people/kls/vdr * - * $Id: osm.c 1.2 2000/03/05 17:18:15 kls Exp $ + * $Id: osm.c 1.3 2000/04/15 14:04:21 kls Exp $ */ +#include #include "config.h" #include "interface.h" #include "menu.h" @@ -37,6 +38,13 @@ #define KEYS_CONF "keys.conf" #endif +static int Interrupted = 0; + +void SignalHandler(int signum) +{ + Interrupted = signum; +} + int main(int argc, char *argv[]) { openlog("vdr", LOG_PID | LOG_CONS, LOG_USER); @@ -50,69 +58,93 @@ int main(int argc, char *argv[]) cChannel::SwitchTo(CurrentChannel); + if (signal(SIGHUP, SignalHandler) == SIG_IGN) signal(SIGHUP, SIG_IGN); + if (signal(SIGINT, SignalHandler) == SIG_IGN) signal(SIGINT, SIG_IGN); + if (signal(SIGTERM, SignalHandler) == SIG_IGN) signal(SIGTERM, SIG_IGN); + cMenuMain *Menu = NULL; cTimer *Timer = NULL; cRecording *Recording = NULL; - for (;;) { - AssertFreeDiskSpace(); - if (!Recording && !Timer && (Timer = cTimer::GetMatch()) != NULL) { - DELETENULL(Menu); - // make sure the timer won't be deleted: - Timer->SetRecording(true); - // switch to channel: - cChannel::SwitchTo(Timer->channel - 1); - ChannelLocked = true; - // start recording: - Recording = new cRecording(Timer); - if (!Recording->Record()) - DELETENULL(Recording); - } - if (Timer && !Timer->Matches()) { - // stop recording: - if (Recording) { - Recording->Stop(); - DELETENULL(Recording); - } - // release channel and timer: - ChannelLocked = false; - Timer->SetRecording(false); - // clear single event timer: - if (Timer->IsSingleEvent()) { - DELETENULL(Menu); // must make sure no menu uses it - isyslog(LOG_INFO, "deleting timer %d", Timer->Index() + 1); - Timers.Del(Timer); - Timers.Save(); - } - Timer = NULL; - } - eKeys key = Interface.GetKey(); - if (Menu) { - switch (Menu->ProcessKey(key)) { - default: if (key != kMenu) - break; - case osBack: - case osEnd: DELETENULL(Menu); - break; + while (!Interrupted) { + AssertFreeDiskSpace(); + if (!Recording && !Timer && (Timer = cTimer::GetMatch()) != NULL) { + DELETENULL(Menu); + // make sure the timer won't be deleted: + Timer->SetRecording(true); + // switch to channel: + cChannel::SwitchTo(Timer->channel - 1); + // start recording: + Recording = new cRecording(Timer); + if (!Recording->Record()) + DELETENULL(Recording); } - } - else { - switch (key) { - case kMenu: Menu = new cMenuMain; - Menu->Display(); - break; - case kUp: - case kDown: { - int n = CurrentChannel + (key == kUp ? 1 : -1); - cChannel *channel = Channels.Get(n); - if (channel) - channel->Switch(); - } - break; - default: break; + if (Timer && !Timer->Matches()) { + // stop recording: + if (Recording) { + Recording->Stop(); + DELETENULL(Recording); + } + // release timer: + Timer->SetRecording(false); + // clear single event timer: + if (Timer->IsSingleEvent()) { + DELETENULL(Menu); // must make sure no menu uses it + isyslog(LOG_INFO, "deleting timer %d", Timer->Index() + 1); + Timers.Del(Timer); + Timers.Save(); + } + Timer = NULL; } - } - } + eKeys key = Interface.GetKey(); + if (Menu) { + switch (Menu->ProcessKey(key)) { + default: if (key != kMenu) + break; + case osBack: + case osEnd: DELETENULL(Menu); + break; + } + } + else { + switch (key) { + // Record/Replay Control: + case kBegin: DvbApi.Skip(-INT_MAX); break; + case kRecord: if (!DvbApi.Recording()) { + cTimer *timer = new cTimer(true); + Timers.Add(timer); + Timers.Save(); + } + else + Interface.Error("Already recording!"); + break; + case kPause: DvbApi.PauseReplay(); break; + case kStop: DvbApi.StopReplay(); break; + case kSearchBack: DvbApi.FastRewind(); break; + case kSearchForward: DvbApi.FastForward(); break; + case kSkipBack: DvbApi.Skip(-60); break; + case kSkipForward: DvbApi.Skip(60); break; + // Menu Control: + case kMenu: Menu = new cMenuMain; + Menu->Display(); + break; + case kUp: + case kDown: { + int n = CurrentChannel + (key == kUp ? 1 : -1); + cChannel *channel = Channels.Get(n); + if (channel) + channel->Switch(); + } + break; + default: break; + } + } + } + isyslog(LOG_INFO, "caught signal %d", Interrupted); + DvbApi.StopRecord(); + DvbApi.StopReplay(); + //TODO kill any remaining sub-processes! + isyslog(LOG_INFO, "exiting", Interrupted); closelog(); - return 1; + return 0; } diff --git a/recording.c b/recording.c index afc2d8b3..15c71630 100644 --- a/recording.c +++ b/recording.c @@ -4,9 +4,10 @@ * See the main source file 'osm.c' for copyright information and * how to reach the author. * - * $Id: recording.c 1.1 2000/03/05 17:16:22 kls Exp $ + * $Id: recording.c 1.2 2000/04/15 13:29:02 kls Exp $ */ +#define _GNU_SOURCE #include "recording.h" #include #include @@ -16,17 +17,17 @@ #define RECEXT ".rec" #define DELEXT ".del" -#define DATAFORMAT "%4d-%02d-%02d.%02d:%02d.%c.%02d.%02d" RECEXT +#define DATAFORMAT "%4d-%02d-%02d.%02d:%02d.%02d.%02d" RECEXT #define NAMEFORMAT "%s/%s/" DATAFORMAT -#define FINDCMD "find %s -type f -name '%s'" +#define FINDCMD "find %s -type d -name '%s'" #define DFCMD "df -m %s" #define MINDISKSPACE 1024 // MB -const char *BaseDir = "/video";//XXX +#define DISKCHECKDELTA 300 // seconds between checks for free disk space -cDvbRecorder *Recorder = NULL; +const char *BaseDir = "/video"; static bool LowDiskSpace(void) { @@ -58,51 +59,54 @@ void AssertFreeDiskSpace(void) // With every call to this function we try to actually remove // a file, or mark a file for removal ("delete" it), so that // it will get removed during the next call. - if (Recorder && Recorder->Recording() && LowDiskSpace()) { - // Remove the oldest file that has been "deleted": - cRecordings Recordings; - if (Recordings.Load(true)) { - cRecording *r = Recordings.First(); - cRecording *r0 = r; - while (r) { - if (r->start < r0->start) - r0 = r; - r = Recordings.Next(r); - } - if (r0 && r0->Remove()) - return; - } - // No "deleted" files to remove, so let's see if we can delete a recording: - if (Recordings.Load(false)) { - cRecording *r = Recordings.First(); - cRecording *r0 = NULL; - while (r) { - if ((time(NULL) - r->start) / SECSINDAY > r->lifetime) { - if (r0) { - if (r->priority < r0->priority) + static time_t LastFreeDiskCheck = 0; + if (time(NULL) - LastFreeDiskCheck > DISKCHECKDELTA) { + LastFreeDiskCheck = time(NULL); + if (DvbApi.Recording() && LowDiskSpace()) { + // Remove the oldest file that has been "deleted": + cRecordings Recordings; + if (Recordings.Load(true)) { + cRecording *r = Recordings.First(); + cRecording *r0 = r; + while (r) { + if (r->start < r0->start) + r0 = r; + r = Recordings.Next(r); + } + if (r0 && r0->Remove()) + return; + } + // No "deleted" files to remove, so let's see if we can delete a recording: + if (Recordings.Load(false)) { + cRecording *r = Recordings.First(); + cRecording *r0 = NULL; + while (r) { + if ((time(NULL) - r->start) / SECSINDAY > r->lifetime) { + if (r0) { + if (r->priority < r0->priority) + r0 = r; + } + else r0 = r; } - else - r0 = r; + r = Recordings.Next(r); } - r = Recordings.Next(r); - } - if (r0 && r0->Delete()) - return; + if (r0 && r0->Delete()) + return; + } + // Unable to free disk space, but there's nothing we can do about that... + esyslog(LOG_ERR, "low disk space, but no recordings to delete"); } - // Unable to free disk space, but there's nothing we can do about that... - //TODO maybe a log entry - but make sure it doesn't come too often } } // --- cRecording ------------------------------------------------------------ -cRecording::cRecording(const char *Name, time_t Start, char Quality, int Priority, int LifeTime) +cRecording::cRecording(const char *Name, time_t Start, int Priority, int LifeTime) { fileName = NULL; name = strdup(Name); start = Start; - quality = Quality; priority = Priority; lifetime = LifeTime; } @@ -112,7 +116,6 @@ cRecording::cRecording(cTimer *Timer) fileName = NULL; name = strdup(Timer->file); start = Timer->StartTime(); - quality = Timer->quality; priority = Timer->priority; lifetime = Timer->lifetime; } @@ -127,7 +130,7 @@ cRecording::cRecording(const char *FileName) if (p) { time_t now = time(NULL); struct tm t = *localtime(&now); // this initializes the time zone in 't' - if (8 == sscanf(p + 1, DATAFORMAT, &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &quality, &priority, &lifetime)) { + if (7 == sscanf(p + 1, DATAFORMAT, &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &priority, &lifetime)) { t.tm_year -= 1900; t.tm_mon--; t.tm_sec = 0; @@ -149,7 +152,7 @@ const char *cRecording::FileName(void) { if (!fileName) { struct tm *t = localtime(&start); - asprintf(&fileName, NAMEFORMAT, BaseDir, name, t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, quality, priority, lifetime); + asprintf(&fileName, NAMEFORMAT, BaseDir, name, t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, priority, lifetime); } return fileName; } @@ -163,7 +166,7 @@ bool cRecording::Delete(void) strncpy(ext, DELEXT, strlen(ext)); isyslog(LOG_INFO, "deleting recording %s", FileName()); if (rename(FileName(), NewName) == -1) { - esyslog(LOG_ERR, "ERROR: %s", strerror(errno)); + esyslog(LOG_ERR, "ERROR: %s: %s", FileName(), strerror(errno)); result = false; } } @@ -173,40 +176,23 @@ bool cRecording::Delete(void) bool cRecording::Remove(void) { - bool result = true; isyslog(LOG_INFO, "removing recording %s", FileName()); - if (remove(FileName()) == -1) { - esyslog(LOG_ERR, "ERROR: %s", strerror(errno)); - result = false; - } - return result; -} - -bool cRecording::AssertRecorder(void) -{ - if (!Recorder || !Recorder->Recording()) { - if (!Recorder) - Recorder = new cDvbRecorder; - return true; - } - Interface.Error("Recorder is in use!"); - return false; + return RemoveFileOrDir(FileName()); } bool cRecording::Record(void) { - return AssertRecorder() && Recorder->Record(FileName(), quality); + return DvbApi.StartRecord(FileName()); } bool cRecording::Play(void) { - return AssertRecorder() && Recorder->Play(FileName()); + return DvbApi.StartReplay(FileName()); } void cRecording::Stop(void) { - if (Recorder) - Recorder->Stop(); + DvbApi.StopRecord(); } // --- cRecordings ----------------------------------------------------------- diff --git a/recording.h b/recording.h index 5b093a04..5a5e8de4 100644 --- a/recording.h +++ b/recording.h @@ -4,7 +4,7 @@ * See the main source file 'osm.c' for copyright information and * how to reach the author. * - * $Id: recording.h 1.1 2000/03/05 15:57:27 kls Exp $ + * $Id: recording.h 1.2 2000/04/14 15:12:42 kls Exp $ */ #ifndef __RECORDING_H @@ -15,21 +15,16 @@ #include "dvbapi.h" #include "tools.h" -extern cDvbRecorder *Recorder; - void AssertFreeDiskSpace(void); class cRecording : public cListObject { -private: - bool AssertRecorder(void); public: char *name; char *fileName; time_t start; - char quality; int priority; int lifetime; - cRecording(const char *Name, time_t Start, char Quality, int Priority, int LifeTime); + cRecording(const char *Name, time_t Start, int Priority, int LifeTime); cRecording(cTimer *Timer); cRecording(const char *FileName); ~cRecording(); diff --git a/remote.c b/remote.c index c2fb36f6..bf5b41f8 100644 --- a/remote.c +++ b/remote.c @@ -4,7 +4,7 @@ * See the main source file 'osm.c' for copyright information and * how to reach the author. * - * $Id: remote.c 1.1 2000/02/19 13:36:48 kls Exp $ + * $Id: remote.c 1.2 2000/04/15 16:00:51 kls Exp $ */ #include "remote.h" @@ -221,7 +221,7 @@ bool cRcIo::String(char *s) } } } - return Number(n, mode == modeH); + return Number(n, true); } bool cRcIo::DetectCode(unsigned char *Code, unsigned short *Address) @@ -244,8 +244,11 @@ bool cRcIo::DetectCode(unsigned char *Code, unsigned short *Address) String(buf); SetCode(*Code, 0); unsigned int Command; - if (GetCommand(&Command, Address)) + if (GetCommand(&Command, Address)) { + SetMode(modeB); + String("----"); return true; + } if (*Code < 'D') { (*Code)++; return false; diff --git a/timers.conf b/timers.conf index de4df9da..1996be4b 100644 --- a/timers.conf +++ b/timers.conf @@ -1,6 +1,9 @@ -1:2:-----S-:2205:2320:H:99:99:Wochenshow -0:15:M------:2125:2205:H:99:99:Neues -1:15:MTWTF--:1828:1901:M:10:5:nano -1:3:M------:2110:2230:H:99:99:SevenDays -1:3:---T---:2215:2300:H:99:99:Switch -1:14:------S:2210:2255:H:99:99:Olli +1:15:MTWTF--:1828:1901:10:5:nano +1:3:M------:2110:2230:99:10:SevenDays +1:10:-T-----:2058:2150:99:10:Quarks +1:3:---T---:2158:2300:99:10:Switch +1:2:----F--:2110:2155:99:10:Anke +1:1:----F--:2210:2325:99:10:7Tage7Koepfe +1:15:-----S-:1358:1435:99:7:Neues +1:1:-----S-:1445:1600:99:10:Hammerman +1:2:-----S-:2205:2320:99:10:Wochenshow diff --git a/tools.c b/tools.c index 5a21a930..e356e0fd 100644 --- a/tools.c +++ b/tools.c @@ -4,18 +4,46 @@ * See the main source file 'osm.c' for copyright information and * how to reach the author. * - * $Id: tools.c 1.2 2000/03/05 14:33:58 kls Exp $ + * $Id: tools.c 1.3 2000/04/15 15:10:05 kls Exp $ */ +#define _GNU_SOURCE #include "tools.h" +#include #include #include #include #include #include +#include #define MaxBuffer 1000 +int SysLogLevel = 3; + +void writechar(int filedes, char c) +{ + write(filedes, &c, sizeof(c)); +} + +void writeint(int filedes, int n) +{ + write(filedes, &n, sizeof(n)); +} + +char readchar(int filedes) +{ + char c; + read(filedes, &c, 1); + return c; +} + +bool readint(int filedes, int &n) +{ + //XXX timeout!! + return read(filedes, &n, sizeof(n)); +} + char *readline(FILE *f) { static char buffer[MaxBuffer]; @@ -30,36 +58,83 @@ char *readline(FILE *f) int time_ms(void) { + static time_t t0 = 0; struct timeval t; - if (gettimeofday(&t, NULL) == 0) - return t.tv_sec * 1000 + t.tv_usec / 1000; + if (gettimeofday(&t, NULL) == 0) { + if (t0 == 0) + t0 = t.tv_sec; // this avoids an overflow (we only work with deltas) + return (t.tv_sec - t0) * 1000 + t.tv_usec / 1000; + } return 0; } -bool MakeDirs(const char *FileName) +void delay_ms(int ms) +{ + int t0 = time_ms(); + while (time_ms() - t0 < ms) + ; +} + +bool MakeDirs(const char *FileName, bool IsDirectory) { bool result = true; char *s = strdup(FileName); char *p = s; if (*p == '/') p++; - while ((p = strchr(p, '/')) != NULL) { - *p = 0; + while ((p = strchr(p, '/')) != NULL || IsDirectory) { + if (p) + *p = 0; struct stat fs; if (stat(s, &fs) != 0 || !S_ISDIR(fs.st_mode)) { isyslog(LOG_INFO, "creating directory %s", s); if (mkdir(s, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == -1) { - esyslog(LOG_ERR, "ERROR while creating directory %s: %s", s, strerror(errno)); + esyslog(LOG_ERR, "ERROR: %s: %s", s, strerror(errno)); result = false; break; } } - *p++ = '/'; + if (p) + *p++ = '/'; + else + break; } delete s; return result; } +bool RemoveFileOrDir(const char *FileName) +{ + struct stat st; + if (stat(FileName, &st) == 0) { + if (S_ISDIR(st.st_mode)) { + DIR *d = opendir(FileName); + if (d) { + struct dirent *e; + while ((e = readdir(d)) != NULL) { + if (strcmp(e->d_name, ".") && strcmp(e->d_name, "..")) { + char *buffer; + asprintf(&buffer, "%s/%s", FileName, e->d_name); + if (remove(buffer) < 0) + esyslog(LOG_ERR, "ERROR: %s: %s", buffer, strerror(errno)); + delete buffer; + } + } + closedir(d); + } + else { + esyslog(LOG_ERR, "ERROR: %s: %s", FileName, strerror(errno)); + return false; + } + } + if (remove(FileName) == 0) + return true; + } + else + esyslog(LOG_ERR, "ERROR: %s: %s", FileName, strerror(errno)); + return false; +} + // --- cListObject ----------------------------------------------------------- cListObject::cListObject(void) diff --git a/tools.h b/tools.h index 9f87bbde..48665a84 100644 --- a/tools.h +++ b/tools.h @@ -4,7 +4,7 @@ * See the main source file 'osm.c' for copyright information and * how to reach the author. * - * $Id: tools.h 1.2 2000/03/05 16:14:05 kls Exp $ + * $Id: tools.h 1.3 2000/04/15 15:09:47 kls Exp $ */ #ifndef __TOOLS_H @@ -13,18 +13,28 @@ #include #include -//TODO -#define dsyslog syslog -#define esyslog syslog -#define isyslog syslog +extern int SysLogLevel; + +#define esyslog if (SysLogLevel > 0) syslog +#define isyslog if (SysLogLevel > 1) syslog +#define dsyslog if (SysLogLevel > 2) syslog + +#define LOG_ERROR esyslog(LOG_ERR, "ERROR (%s,%d): %s", __FILE__, __LINE__, strerror(errno)) +#define LOG_ERROR_STR(s) esyslog(LOG_ERR, "ERROR: %s: %s", s, strerror(errno)); #define SECSINDAY 86400 #define DELETENULL(p) (delete (p), p = NULL) +void writechar(int filedes, char c); +void writeint(int filedes, int n); +char readchar(int filedes); +bool readint(int filedes, int &n); char *readline(FILE *f); int time_ms(void); -bool MakeDirs(const char *FileName); +void delay_ms(int ms); +bool MakeDirs(const char *FileName, bool IsDirectory = false); +bool RemoveFileOrDir(const char *FileName); class cListObject { private: @@ -47,7 +57,7 @@ public: virtual ~cListBase(); void Add(cListObject *Object); void Del(cListObject *Object); - void Move(int From, int To); + virtual void Move(int From, int To); void Move(cListObject *From, cListObject *To); void Clear(void); cListObject *Get(int Index);