ref: ac1f0c32193abd006536eb3bfd20dc8b3a6b691a
dir: /pcsound/pcsound_linux.c/
// // Copyright(C) 2005-2014 Simon Howard // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // DESCRIPTION: // PC speaker driver for Linux. // #include "config.h" #ifdef HAVE_LINUX_KD_H #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <linux/kd.h> #include <sys/ioctl.h> #include <unistd.h> #include <fcntl.h> #include "SDL.h" #include "SDL_thread.h" #include "pcsound.h" #include "pcsound_internal.h" #define CONSOLE_DEVICE "/dev/console" static int console_handle; static pcsound_callback_func callback; static int sound_thread_running = 0; static SDL_Thread *sound_thread_handle; static int sleep_adjust = 0; static void AdjustedSleep(unsigned int ms) { unsigned int start_time; unsigned int end_time; unsigned int actual_time; // Adjust based on previous error to keep the tempo right if (sleep_adjust > ms) { sleep_adjust -= ms; return; } else { ms -= sleep_adjust; } // Do the sleep and record how long it takes start_time = SDL_GetTicks(); SDL_Delay(ms); end_time = SDL_GetTicks(); if (end_time > start_time) { actual_time = end_time - start_time; } else { actual_time = ms; } if (actual_time < ms) { actual_time = ms; } // Save sleep_adjust for next time sleep_adjust = actual_time - ms; } static int SoundThread(void *unused) { int current_freq = 0; int frequency; int duration; int cycles; while (sound_thread_running) { callback(&duration, &frequency); if (current_freq != frequency) { current_freq = frequency; if (frequency != 0) { cycles = PCSOUND_8253_FREQUENCY / frequency; } else { cycles = 0; } ioctl(console_handle, KIOCSOUND, cycles); } AdjustedSleep(duration); } return 0; } static int PCSound_Linux_Init(pcsound_callback_func callback_func) { // Try to open the console console_handle = open(CONSOLE_DEVICE, O_WRONLY); if (console_handle == -1) { // Don't have permissions for the console device? fprintf(stderr, "PCSound_Linux_Init: Failed to open '%s': %s\n", CONSOLE_DEVICE, strerror(errno)); return 0; } if (ioctl(console_handle, KIOCSOUND, 0) < 0) { // KIOCSOUND not supported: non-PC linux? close(console_handle); return 0; } // Start a thread up to generate PC speaker output callback = callback_func; sound_thread_running = 1; sound_thread_handle = SDL_CreateThread(SoundThread, "PC speaker thread", NULL); return 1; } static void PCSound_Linux_Shutdown(void) { sound_thread_running = 0; SDL_WaitThread(sound_thread_handle, NULL); close(console_handle); } pcsound_driver_t pcsound_linux_driver = { "Linux", PCSound_Linux_Init, PCSound_Linux_Shutdown, }; #endif /* #ifdef HAVE_LINUX_KD_H */