// INCLUDE HEADERS /////////////////////////////////////////////////////// // See header file for licensing info #include "saa.h" #include #include #include #include #include #include #include #include #include #include "saadriver/kmod/saa713x_ioctl.h" #include // GLOBAL VARS /////////////////////////////////////////////////////////// int fd, fdiic, res; int curfreq = 0; int muted = 0; int asrc = 0; int sleeptime = 10000; char addr_byte = TUNER_ADDRESS; char ctrl_byte = CTRL_BYTE; char aux_byte = AUX_BYTE; char bsw_lo = CMD_LO_PHILIPS; char bsw_mid = CMD_MID_PHILIPS; char bsw_hi = CMD_HI_PHILIPS; float f_vif = FREQ_VIF_PAL; float f_lo = FREQ_LOBAND_PAL; float f_hi = FREQ_HIBAND_PAL; float f_max = FREQ_HIGHEST_PAL; struct saa_video_opt vo; static struct saa_audio_spec audio_spec = { // saa/tvv/FLTK/audioopt.c .sas_audsel = AUDSEL_SIF, .sas_sfs = SFS_SIF_32KHZ, .sas_icsch = 1, .sas_cap_sampcount = 16384, .sas_audfmt = AUDFMT_16BIT, .sas_audsamp = AUDSAMP_STEREO, .sas_flag_samp2complement = 1, .sas_flags = 0, }; // The following are for the SDL viewer caddr_t buf; int paused = 0; int rows, cols, size, i, bpp, empty; struct saa_video_std std; struct saa_video_spec spec; SDL_Surface *screen; SDL_Overlay *overlay; // SDL_Event event; SDL_Rect rect = { 0 }; // TUNER FUNCTIONS /////////////////////////////////////////////////////// // Video source int tuner_tunerdev() { // Fixed value, always 1 return 1; } int tuner_videosource_set(int videosource) { /* VSRC MAPPING (like bt848) MODE_CVBS_1 = 3 DEV3 (ext3) MODE_CVBS_2 = 1 DEV1 (tuner) MODE_CVBS_3 = 2 DEV2 (ext2) MODE_CVBS_4 = 0 DEV0 (ext0) MODE_CVBS_5 ignored MODE_SVIDEOFIXGC_1 DEV4/SVID (svideo / fixed gain) MODE_SVIDEOFIXGC_2 ignored MODE_SVIDEOAGC_1 ignored MODE_SVIDEOAGC_2 ignored */ if (videosource < 0 || videosource > 4) return -1; if ((fd = open(SAADEV, O_RDWR, 0)) < 0) return -1; res = ioctl(fd, GETVIDEOOPT, &vo); if (res < 0) { close(fd); return -1; } if (videosource == 0) { vo.svo_input_mode = MODE_CVBS_4; asrc = 1; } if (videosource == 1) { // tuner vo.svo_input_mode = MODE_CVBS_2; asrc = 0; } if (videosource == 2) { vo.svo_input_mode = MODE_CVBS_3; asrc = 1; } if (videosource == 3) { vo.svo_input_mode = MODE_CVBS_1; asrc = 1; } if (videosource == 4) { // svideo (first) vo.svo_input_mode = MODE_SVIDEOFIXGC_1; asrc = 1; } res = ioctl(fd, SETVIDEOOPT, &vo); close(fd); tuner_audiosource_set(asrc); return res; } int tuner_videosource() { if ((fd = open(SAADEV, O_RDWR, 0)) < 0) return -1; res = ioctl(fd, GETVIDEOOPT, &vo); close(fd); if (res < 0) return -1; if (vo.svo_input_mode == MODE_CVBS_1) return 3; if (vo.svo_input_mode == MODE_CVBS_2) return 1; if (vo.svo_input_mode == MODE_CVBS_3) return 2; if (vo.svo_input_mode == MODE_CVBS_4) return 0; if (vo.svo_input_mode == MODE_SVIDEOFIXGC_1) return 4; return -1; } // Audio init int tuner_audio_init() { int foo; if ((fd = open(SAUDEV, O_RDWR, 0)) < 0) return -1; if (ioctl(fd, ACAPTUREON, &audio_spec) != 0) { close(fd); return -1; } usleep(100000); if (ioctl(fd, ACAPTUREOFF, &foo) != 0) { close(fd); return -1; } close(fd); return 0; } // Audio source int tuner_audiosource_set(int audiosource) { /* saa context bt848 mapto OCS_DACLEFT_DACRIGHT tuner to soundcard AUDIO_TUNER tuner to sc 0 OCS_INLEFT1_INRIGHT1 from line-in AUDIO_EXTERN line-in to sc 1 OCS_INLEFT2_INRIGHT2 other line-in AUDIO_INTERN ... 2 others are ignored Emulate bt muting behaviour, if muted the asrc returned must be 0x80 */ if ((fd = open(SAUDEV, O_RDWR, 0)) < 0) return -1; if (audiosource == 0) { audiosource = OCS_DACLEFT_DACRIGHT; asrc = audiosource; } else if (audiosource == 1) { audiosource = OCS_INLEFT1_INRIGHT1; asrc = audiosource; } else if (audiosource == 2) { audiosource = OCS_INLEFT2_INRIGHT2; asrc = audiosource; } else { if (audiosource == 0x80) { // res = ioctl(fd, SETAUDIOMUTE, ); // THIS NEEDS SOME WORK // Fake mute by using OCS_INLEFT1_INRIGHT1 that's hopefully unused // OCS_INLEFT2_INRIGHT2 used before but may not be present audiosource = OCS_INLEFT1_INRIGHT1; muted = 1; } else if (audiosource == 0x81) { audiosource = asrc; muted = 0; } else { close(fd); return -1; } } res = ioctl(fd, SETAUDIOMIXER, &audiosource); close(fd); return res; } int tuner_audiosource() { enum OCS audiosource; if (muted == 1) return 0x80; if ((fd = open(SAUDEV, O_RDWR, 0)) < 0) return -1; if (ioctl(fd, GETAUDIOMIXER, &audiosource) != 0) { close(fd); return -1; } close(fd); if (audiosource == OCS_DACLEFT_DACRIGHT) return 0; if (audiosource == OCS_INLEFT1_INRIGHT1) return 1; if (audiosource == OCS_INLEFT2_INRIGHT2) return 2; return -1; } // AFC /* * TODO: At least the MK3 type tuners do support AFC with programmable * IF section. It may be worth figuring this out. * Seems that it AFC's automatically between -0.1875 < f < + 0.1875 MHz * per AFC window, and how much it has AFC'd can be read out */ int tuner_afc_set(int afcbool) { // No/auto AFC return -1; } int tuner_afc() { // No/auto AFC return 0; } // Tuner init, determine i2c tuner write address, control byte, aux byte, .. /* * Control byte must have test bits set appropriately on MK3 hardware, and * Auxiliary byte must be used and set to the correct AGC TOP value * See "FQ1200MK3/FM1200MK3 Family Application Note" and saa_reg.h */ int tuner_init() { struct iiccmd cmd; char buf[5]; uint16_t f_div; if ((fdiic = open(IICDEV, O_RDWR, 0)) < 0) return -1; cmd.slave = TUNER_ADDRESS; cmd.count = 1; cmd.buf = buf; buf[0] = buf[1] = buf[2] = buf[3] = 0; ioctl(fdiic, I2CRSTCARD, &cmd); usleep(sleeptime); if (ioctl(fdiic, I2CSTART, &cmd) != 0) { cmd.slave = TUNER_ADDRESS_ALT; ioctl(fdiic, I2CSTOP); usleep(sleeptime); ioctl(fdiic, I2CRSTCARD, &cmd); usleep(sleeptime); if (ioctl(fdiic, I2CSTART, &cmd) == 0) addr_byte = TUNER_ADDRESS_ALT; else return -1; // neither address detected! } if (TV_STD == TV_STD_PAL_LACCENT || TV_STD == TV_STD_SECAM_LACCENT) { f_vif = FREQ_VIF_PAL_LACCENT; } if (TV_STD == TV_STD_NTSC) { f_vif = FREQ_VIF_NTSC; } if (TV_STD == TV_STD_NTSC_JAPAN) { f_vif = FREQ_VIF_NTSC_JAPAN; } if (TV_STD == TV_STD_NTSC || TV_STD == TV_STD_NTSC_JAPAN) { f_lo = FREQ_LOBAND_NTSC; f_hi = FREQ_HIBAND_NTSC; f_max = FREQ_HIGHEST_NTSC; } if (TUNER_API == TUNER_API_PHILIPS_MK3) { bsw_lo = CMD_LO_PHILIPS_MK3; bsw_mid = CMD_MID_PHILIPS_MK3; bsw_hi = CMD_HI_PHILIPS_MK3; // Special case, aux byte must be written (once), change ctrl byte ctrl_byte = CTRL_BYTE_MK3; if (TV_STD == TV_STD_PAL_L || TV_STD == TV_STD_PAL_LACCENT || TV_STD == TV_STD_SECAM_L || TV_STD == TV_STD_SECAM_LACCENT) { aux_byte = AUX_BYTE_L_LACCENT; } } if (TUNER_API == TUNER_API_LG) { bsw_lo = CMD_LO_LG; bsw_mid = CMD_MID_LG; bsw_hi = CMD_HI_LG; } if (TUNER_API == TUNER_API_ALPS) { bsw_lo = CMD_LO_ALPS; bsw_mid = CMD_MID_ALPS; bsw_hi = CMD_HI_ALPS; } if (TUNER_API == TUNER_API_TEMIC) { bsw_lo = CMD_LO_TEMIC; bsw_mid = CMD_MID_TEMIC; bsw_hi = CMD_HI_TEMIC; } // Write ctrl and aux byte if MK3, then set it to the usual ctrl byte if (TUNER_API == TUNER_API_PHILIPS_MK3) { // BB must be set for CB to deliver AB (expected after BB) f_div = (f_lo + f_vif) * 16; // 0.0625 = 1/16 buf[0] = (f_div >> 8) & 0x7f; // Upper divider byte DB1 buf[1] = f_div & 0xff; // Lower divider byte DB2 buf[2] = ctrl_byte; buf[3] = bsw_lo; buf[4] = aux_byte; cmd.count = 5; cmd.slave = addr_byte; cmd.buf = buf; ctrl_byte = CTRL_BYTE; // For consecutive tuning if ((res = ioctl(fdiic, I2CWRITE, &cmd)) != 0) return res; else curfreq = (int)f_lo; ioctl(fdiic, I2CSTOP); usleep(sleeptime); } return 0; } // Tuner quit, to close the device int tuner_quit() { return close(fdiic); } // IF (TDA988x) init, needed if present. MK3 tuners, maybe others too. int tuner_if_init() { struct iiccmd cmd; char buf[4]; res = -1; cmd.slave = TDA988X_ADDRESS; cmd.count = 1; cmd.buf = buf; buf[0] = buf[1] = buf[2] = buf[3] = 0; ioctl(fdiic, I2CRSTCARD, &cmd); usleep(sleeptime); if (ioctl(fdiic, I2CSTART, &cmd) == 0) { // MK3 specs buf[0] = 0; if (TV_STD == TV_STD_NTSC_JAPAN) { buf[1] = B_DATA_NTSC_JAPAN; } else if (TV_STD == TV_STD_PAL_L || TV_STD == TV_STD_SECAM_L) { buf[1] = B_DATA_PAL_L; } else if (TV_STD == TV_STD_PAL_LACCENT || TV_STD == TV_STD_SECAM_LACCENT) { buf[1] = B_DATA_PAL_LACCENT; } else buf[1] = B_DATA_PAL_NTSC; if (TV_STD == TV_STD_PAL_L || TV_STD == TV_STD_PAL_LACCENT || TV_STD == TV_STD_SECAM_L || TV_STD == TV_STD_SECAM_LACCENT) { buf[2] = C_DATA_PAL_L; } else if (TV_STD == TV_STD_NTSC || TV_STD == TV_STD_NTSC_JAPAN) { buf[2] = C_DATA_NTSC; } else buf[2] = C_DATA_PAL; if (TV_STD == TV_STD_PAL_B_G || TV_STD == TV_STD_SECAM_B_G) { buf[3] = E_DATA_PAL_B_G; } else if (TV_STD == TV_STD_PAL_I) { buf[3] = E_DATA_PAL_I; } else if (TV_STD == TV_STD_PAL_LACCENT || TV_STD == TV_STD_SECAM_LACCENT) { buf[3] = E_DATA_PAL_LACCENT; } else if (TV_STD == TV_STD_NTSC || TV_STD == TV_STD_NTSC_JAPAN) { buf[3] = E_DATA_NTSC; } else buf[3] = E_DATA_PAL_D_K_L; // PAL or SECAM D/K/L cmd.count = 4; cmd.buf = buf; res = ioctl(fdiic, I2CWRITE, &cmd); usleep(sleeptime); ioctl(fdiic, I2CSTOP); usleep(sleeptime); } return res; } // Frequency MHz int tuner_frequency_set(int frequency) { struct iiccmd cmd; char buf[4]; uint16_t f_div; res = -1; if (frequency == curfreq) return 0; if (frequency > f_max) frequency = (int)f_max; if (frequency < f_lo) frequency = (int)f_lo; buf[0] = buf[1] = buf[2] = buf[3] = 0; cmd.slave = addr_byte; cmd.count = 1; if ((res = ioctl(fdiic, I2CSTART, &cmd)) != 0) return res; // Write divider bytes, control byte, bandswitch byte f_div = (frequency + f_vif) * 16; // 0.0625 = 1/16 buf[0] = (f_div >> 8) & 0x7f; // Upper divider byte DB1 buf[1] = f_div & 0xff; // Lower divider byte DB2 buf[2] = ctrl_byte; if (frequency > FREQ_MIDBAND && frequency <= f_hi) buf[3] = bsw_mid; else if (frequency > f_hi && frequency <= f_max) buf[3] = bsw_hi; else buf[3] = bsw_lo; cmd.count = 4; cmd.buf = buf; res = ioctl(fdiic, I2CWRITE, &cmd); usleep(sleeptime); ioctl(fdiic, I2CSTOP); usleep(sleeptime); if (res == 0) curfreq = frequency; return res; } int tuner_frequency() { // There's no frequency retrieval, so we keep track with curfreq // Actually, at least with MK3 you could retrieve freq from 0x87 return curfreq; } // Brightness, contrast, color, saturation % int tuner_brightness_set(int brightness) { if (brightness > MAXPERC) brightness = MAXPERC; if (brightness < MINPERC) brightness = MINPERC; brightness = brightness * SAA_BRIGHTMAX / MAXPERC; if ((fd = open(SAADEV, O_RDWR, 0)) < 0) return -1; res = ioctl(fd, GETVIDEOOPT, &vo); if (res < 0) { close(fd); return -1; } vo.svo_brightness = brightness; res = ioctl(fd, SETVIDEOOPT, &vo); close(fd); return res; } int tuner_brightness() { if ((fd = open(SAADEV, O_RDWR, 0)) < 0) return -1; res = ioctl(fd, GETVIDEOOPT, &vo); close(fd); if (res < 0) return -1; return vo.svo_brightness * MAXPERC / SAA_BRIGHTMAX; } int tuner_contrast_set(int contrast) { if (contrast > MAXPERC) contrast = MAXPERC; if (contrast < MINPERC) contrast = MINPERC; contrast = contrast * SAA_CONTMAX / MAXPERC; if ((fd = open(SAADEV, O_RDWR, 0)) < 0) return -1; res = ioctl(fd, GETVIDEOOPT, &vo); if (res < 0) { close(fd); return -1; } vo.svo_contrast = contrast; res = ioctl(fd, SETVIDEOOPT, &vo); close(fd); return res; } int tuner_contrast() { if ((fd = open(SAADEV, O_RDWR, 0)) < 0) return -1; res = ioctl(fd, GETVIDEOOPT, &vo); close(fd); if (res < 0) return -1; return vo.svo_contrast * MAXPERC / SAA_CONTMAX; } int tuner_color_set(int color) { if (color >= MAXPERC || color < MINPERC) color = MINPERC; if (color <= 50) color = SAA_HUEMAX * (50 - color) / MAXPERC; else color = SAA_HUEMAX * (color - 50) / MAXPERC; if ((fd = open(SAADEV, O_RDWR, 0)) < 0) return -1; res = ioctl(fd, GETVIDEOOPT, &vo); if (res < 0) { close(fd); return -1; } vo.svo_hue = color; res = ioctl(fd, SETVIDEOOPT, &vo); close(fd); return res; } int tuner_color() { if ((fd = open(SAADEV, O_RDWR, 0)) < 0) return -1; res = ioctl(fd, GETVIDEOOPT, &vo); close(fd); if (res < 0) return -1; if (vo.svo_hue <= 128) return (50 - vo.svo_hue * MAXPERC / SAA_HUEMAX); return vo.svo_hue * MAXPERC / SAA_HUEMAX; } int tuner_saturation_set(int saturation) { if (saturation > MAXPERC) saturation = MAXPERC; if (saturation < MINPERC) saturation = MINPERC; saturation = saturation * SAA_SATMAX / MAXPERC; if ((fd = open(SAADEV, O_RDWR, 0)) < 0) return -1; res = ioctl(fd, GETVIDEOOPT, &vo); if (res < 0) { close(fd); return -1; } vo.svo_saturation = saturation; res = ioctl(fd, SETVIDEOOPT, &vo); close(fd); return res; } int tuner_saturation() { int saturation; if ((fd = open(SAADEV, O_RDWR, 0)) < 0) return -1; res = ioctl(fd, GETVIDEOOPT, &vo); close(fd); if (res < 0) return -1; close(fd); return vo.svo_saturation * MAXPERC / SAA_SATMAX; } // VIEWER FUNCTIONS ////////////////////////////////////////////////////// int viewer_init(int width, int height) { if ((i = open(SAADEV, O_RDWR)) < 0) { return 1; } // Get videostd and opt if (ioctl(i, GETVIDEOSTD, &std) < 0) { close(i); return 2; } if (ioctl(i, GETVIDEOOPT, &vo) < 0) { close(i); return 3; } if (TV_STD == TV_STD_NTSC || TV_STD == TV_STD_NTSC_JAPAN) { // NTSC std.std_video_yinstart = NTSC_LINE_OFFSET; std.std_video_yinstop = std.std_video_yinstart + NTSC_LINE_COUNT - 1; vo.svo_cstd = CSTD_NTSC; } else { // PAL, SECAM std.std_video_yinstart = PAL_LINE_OFFSET; std.std_video_yinstop = std.std_video_yinstart + PAL_LINE_COUNT - 1; if (TV_STD == TV_STD_SECAM_B_G || TV_STD == TV_STD_SECAM_D_K || TV_STD == TV_STD_SECAM_L || TV_STD == TV_STD_SECAM_LACCENT) { // SECAM color standard vo.svo_cstd = CSTD_SECAM; } else { vo.svo_cstd = CSTD_PAL; } } // Set videostd and opt if (ioctl(fd, SETVIDEOSTD, &std) < 0) { close(i); return 4; } if (ioctl(fd, SETVIDEOOPT, &vo) < 0) { close(i); return 5; } // SDTV PAL B/G/I..: 720x576i NTSCM/PALM: 640x480i // Capture at full frame size, regardless of display size rows = (std.std_video_yinstop - std.std_video_yinstart + 1) * 2; cols = std.std_video_xinstop - std.std_video_xinstart + 1; size = rows * cols * 2; // Video spec spec.svs_cap_nrtasks = 1; spec.svs_vfmt = VFMT_YUV422; spec.svs_cap_swapbytes = SWAPBYTES_NONE; spec.svs_flags = VSPEC_FLAG_CAPTURE_SIGNAL; spec.svs_cap_bufsz = SAA_VCAP_FRAME_MAX_SZ; spec.svs_buf_base = 0; spec.svs_buf_width = cols; spec.svs_buf_height = rows * 2; spec.svs_buf_offset[0][0] = 0; bpp = (spec.svs_vfmt & VFMT_BPP_MASK) >> VFMT_BPP_SHIFT; spec.svs_buf_pitch[0] = spec.svs_buf_width * bpp / 8; // SDL init and such rect.w = width; rect.h = height; if (SDL_Init(SDL_INIT_VIDEO) < 0) { SDL_Quit(); close(i); return 6; } if ((screen = SDL_SetVideoMode(rect.w, rect.h, 0, SDL_RESIZABLE)) == NULL) { SDL_Quit(); close(i); return 7; } if ((overlay = SDL_CreateYUVOverlay(cols, rows, SDL_UYVY_OVERLAY, screen)) == NULL) { SDL_Quit(); close(i); return 8; } SDL_DisplayYUVOverlay(overlay, &rect); // CAPTUREON will cause SIGUSR1 to be emitted with every frame paused = 1; signal(SIGUSR1, frame_handler); // Capture on if (ioctl(i, CAPTUREON, &spec) < 0) { signal(SIGUSR1, SIG_DFL); SDL_Quit(); close(i); return 9; } // Framebuffer buf = mmap((caddr_t)0, size, PROT_READ, 0, i, (off_t)SAA_MMAP_T0_OFFSET); if (buf == (caddr_t)MAP_FAILED) { signal(SIGUSR1, SIG_DFL); SDL_Quit(); close(i); return 10; } // All succeeded return 0; } void viewer_start() { paused = 0; } void viewer_pause() { paused = 1; } void viewer_resize(int width, int height) { rect.w = width; rect.h = height; SDL_SetVideoMode(rect.w, rect.h, 0, SDL_RESIZABLE); if (paused) { SDL_DisplayYUVOverlay(overlay, &rect); } } void viewer_quit() { ioctl(i, CAPTUREOFF, &empty); signal(SIGUSR1, SIG_DFL); SDL_Quit(); munmap(buf, size); close(i); } void frame_handler() { SDL_LockYUVOverlay(overlay); memcpy(overlay->pixels[0], buf, size); SDL_UnlockYUVOverlay(overlay); SDL_DisplayYUVOverlay(overlay, &rect); } // END ///////////////////////////////////////////////////////////////////