/* Sound module for ALSA project. */ #include #include #include #include #include #include #include #include #include #include #include #include "v9t9_common.h" #include "timer.h" #include "sound.h" #include "command.h" #include "mix_server.h" #include "9901.h" #define WRITE_TO_DISK #define _L LOG_SOUND|LOG_INFO /****************************/ #ifdef WRITE_TO_DISK static int alsa_sndf; static int alsa_paused; static int alsa_ticks; #endif static snd_pcm_t *pcm_handle; static struct snd_pcm_playback_info play_info; static snd_pcm_format_t play_format; static struct snd_pcm_playback_params play_params; static int direction = SND_PCM_OPEN_DUPLEX; static struct snd_pcm_capture_info rec_info; static snd_pcm_format_t rec_format; static struct snd_pcm_capture_params rec_params; /* defaults */ static int card = 0, device = 0, subdevice = 0; static int pcm_hz = 44100; static int pcm_format = SND_PCM_SFMT_S16_LE; static int pcm_samplesize; static s8 *samplebuf; #include "sound_pthread_mixer.h" static void alsa_record_fragment(void *, int); static void sound_module_mix(void *buffer, int bytes) { snd_pcm_write(pcm_handle, buffer, bytes); #ifdef WRITE_TO_DISK alsa_record_fragment(buffer, bytes); #endif } /**************************/ #define TRY(x,y) if (ioctl(sound_fd, x, y) < 0) \ { logger(_L|LOG_ERROR,"ossSound: ioctl failed (" #x "," #y "): %s\n", \ strerror(errno)); close(sound_fd); return 0; } static int setup_playback(void) { int err; memset((void *) &play_format, 0, sizeof(play_format)); play_format.format = pcm_format; play_format.rate = pcm_hz; play_format.channels = 1; if ((err = snd_pcm_playback_info(pcm_handle, &play_info)) < 0) { logger(_L|LOG_USER | LOG_ERROR, "ALSA playback info error: %s\n", snd_strerror(err)); return 0; } /* Verify that our format is legal. */ if (play_info.min_rate > play_format.rate || play_info.max_rate < play_format.rate) { logger(_L|LOG_USER, "ALSA device supports playback range %d to %d Hz," "clipping default rate.\n", play_info.min_rate, play_info.max_rate); if (play_info.min_rate > play_format.rate) play_format.rate = play_info.min_rate; if (play_format.rate > play_info.max_rate) play_format.rate = play_info.max_rate; } if (!(play_info.formats & (1 << play_format.format))) { logger(_L|LOG_USER | LOG_ERROR, "ALSA device does not support desired playback format\n"); //!!! until mix_server.c is more flexible return 0; } if ((err = snd_pcm_playback_format(pcm_handle, &play_format)) < 0) { logger(_L|LOG_USER | LOG_ERROR, "ALSA device cannot set playback format\n", snd_strerror(err)); return 0; } /* Optimize fragments */ pcm_samplesize = 512; samplebuf = (s8 *) malloc(pcm_samplesize); logger(_L|0, "ALSA sample size = %d\n", pcm_samplesize); memset((void *) &play_params, 0, sizeof(play_params)); play_params.fragment_size = pcm_samplesize * sizeof(s8); play_params.fragments_max = 8; play_params.fragments_room = 4; if ((err = snd_pcm_playback_params(pcm_handle, &play_params)) < 0) { logger(_L|LOG_USER | LOG_ERROR, "ALSA device cannot set requested PCM playback parameters:" "%s\n", snd_strerror(err)); return 0; } return 1; } static int setup_recording(void) { int err; if ((err = snd_pcm_capture_info(pcm_handle, &rec_info)) < 0) { logger(_L|LOG_USER | LOG_ERROR, "ALSA record info error: %s\n", snd_strerror(err)); return 0; } /* Verify that our format is legal. */ if (rec_info.min_rate > rec_format.rate || rec_info.max_rate < rec_format.rate) { logger(_L|LOG_USER, "ALSA device supports recording range %d to %d Hz," "clipping default rate.\n", rec_info.min_rate, rec_info.max_rate); if (rec_info.min_rate > rec_format.rate) rec_format.rate = rec_info.min_rate; if (rec_format.rate > rec_info.max_rate) rec_format.rate = rec_info.max_rate; } memset((void *) &rec_format, 0, sizeof(rec_format)); rec_format.rate = pcm_hz; rec_format.channels = 1; if (!(rec_info.formats & (1 << (rec_format.format = SND_PCM_SFMT_S8))) && !(rec_info.formats & (1 << (rec_format.format = SND_PCM_SFMT_U8)))) { logger(_L|LOG_ERROR | LOG_USER, "ALSA device does not support desired eight-bit recording format\n"); //!!! until mix_server.c is more flexible return 0; } if ((err = snd_pcm_capture_format(pcm_handle, &rec_format)) < 0) { logger(_L|LOG_ERROR | LOG_USER, "ALSA device cannot set recording format\n", snd_strerror(err)); return 0; } /* Optimize fragments */ pcm_samplesize = 512; samplebuf = (s8 *) malloc(pcm_samplesize); // log("ALSA sample size = %d", pcm_samplesize); memset((void *) &rec_params, 0, sizeof(rec_params)); rec_params.fragment_size = pcm_samplesize * sizeof(s8); if ((err = snd_pcm_capture_params(pcm_handle, &rec_params)) < 0) { logger(_L|LOG_ERROR | LOG_USER, "ALSA device cannot set requested PCM recording parameters:\n" "%s\n", snd_strerror(err)); return 0; } return 1; } /****************************/ /* Update voice parameters */ static void alsa_update(vmsUpdateMask updated) { pthread_mixer_update(updated); } static void alsa_flush(void) { pthread_mixer_flush(); } /* schedule digital data for playing */ static void alsa_play(vmsPlayMask kind, s8 * data, int len, int hz) { pthread_mixer_play(kind, data, len, hz); } /* read digital data (for cassette) */ static void alsa_read(vmsReadMask kind, u8 * data, int len, int hz) { int x; snd_pcm_read(pcm_handle, data, len); if (rec_format.format == SND_PCM_SFMT_S8) { for (x = 0; x < len; x++) data[x] ^= 0x80; } // for (x=0; x 0) { for (d = 0; !found && d < info.pcmdevs; d++) { s = -1; if ((err = snd_ctl_pcm_info(handle, d, &pcminfo)) == 0 && (pcminfo.flags & SND_PCM_INFO_PLAYBACK) && (err = snd_ctl_pcm_playback_info(handle, d, s, &playinfo)) == 0 && playinfo.max_rate >= 22000 && playinfo.min_channels == 1 && (playinfo.formats & (1 << pcm_format))) { card = c; device = d; subdevice = s; logger(_L|LOG_USER, "ALSA detected suitable device #%d/%d/%d, device '%s', id '%s'\n", card, device, subdevice, info.name, info.id); //!!! is it open? found = 1; } } snd_ctl_close(handle); } } } if (!found) { logger(_L|LOG_USER, "No suitable ALSA PCM devices found\n"); return vmNotAvailable; } else { logger(_L|LOG_USER, "Detected Advanced Linux Sound Architecture\n"); return vmOk; } } /* Interface to command system */ static void do_alsa_stop_recording(void) { if (alsa_sndf) { logger(_L|LOG_USER, "StopSoundRecording: Closing previously recording soundtrack\n"); close(alsa_sndf); alsa_sndf = 0; } } static DECL_SYMBOL_ACTION(alsa_stop_recording) { do_alsa_stop_recording(); return 1; } static DECL_SYMBOL_ACTION(alsa_record_sound) { do_alsa_stop_recording(); alsa_sndf = open(sym->args->u.string.m.mem, O_WRONLY | O_APPEND | O_CREAT, 0666); if (alsa_sndf < 0) { parse_error("RecordSoundToFile: could not open '%s' for appending", sym->args->u.string.m.mem); return 0; } return 1; } static DECL_SYMBOL_ACTION(alsa_pause_sound) { logger(_L|LOG_USER, "PausingSoundRecording: %s\n", alsa_paused ? "paused" : "resuming"); return 1; } static void alsa_record_fragment(void *buffer, int bytes) { if (alsa_sndf && !alsa_paused) { write(alsa_sndf, buffer, bytes); } } static DECL_ARG_ACTION(alsa_add_file_extension) { char *nm; if (!command_arg_get_string(arg, &nm)) return 0; if (strchr(OS_GetFileNamePtr(nm), '.') == NULL) { sprintf(nm + strlen(nm), ".%d.%s", play_format.rate, play_format.format == SND_PCM_SFMT_S8 ? "s8" : play_format.format == SND_PCM_SFMT_U8 ? "u8" : play_format.format == SND_PCM_SFMT_S16_LE ? "s16.le" : play_format.format == SND_PCM_SFMT_S16_BE ? "s16.be" : play_format.format == SND_PCM_SFMT_U16_LE ? "u16.le" : "u16.be"); logger(_L|LOG_USER, "RecordSoundToFile: using '%s'\n", nm); } return 1; } static vmResult alsa_init(void) { command_symbol_table *alsacommands = command_symbol_table_new("ALSA Sound Mixer Options", "These commands control the ALSA 99/4A sound module", command_symbol_new("RecordSoundToFile", "Record soundtrack to a file", c_DONT_SAVE, alsa_record_sound, NULL /* ret */ , command_arg_new_string ("file", "file to receive sound; " "if file exists, new data will be appended to it; " "if no extension is given, one will be added that details " "the RAW sound format used", alsa_add_file_extension /* action */ , NEW_ARG_STR(OS_PATHSIZE), NULL /* next */ ) , command_symbol_new ("PausingSoundRecording", "Pause or resume soundtrack recording (does not close sound file)", c_DONT_SAVE, alsa_pause_sound /* action */ , RET_FIRST_ARG, command_arg_new_num ("state", "whether recording is paused", NULL /* action */ , ARG_NUM(alsa_paused), NULL /* next */ ) , command_symbol_new ("StopSoundRecording", "Stop soundtrack recording and close sound file", c_DONT_SAVE, alsa_stop_recording, NULL /* ret */ , NULL /* args */ , NULL /* next */ ))), NULL /* sub */ , NULL /* next */ ); command_symbol_table_add_subtable(universe, alsacommands); alsa_sndf = 0; alsa_paused = 0; return vmOk; } static vmResult alsa_term(void) { return vmOk; } static vmResult alsa_enable(void) { int err; if ((err = snd_pcm_open(&pcm_handle, card, device, direction = SND_PCM_OPEN_DUPLEX)) < 0 && (err = snd_pcm_open(&pcm_handle, card, device, direction = SND_PCM_OPEN_PLAYBACK)) < 0) { logger(_L|LOG_ERROR | LOG_USER, "Could not open ALSA PCM device (#%d/%d): %s\n", card, device, snd_strerror(err)); return vmNotAvailable; } if (!setup_playback()) return vmInternalError; // allow this to fail if (direction == SND_PCM_OPEN_DUPLEX && !setup_recording()) direction = SND_PCM_OPEN_PLAYBACK; context.soundhz = play_format.rate; pthread_mixer_init(play_format.rate, pcm_samplesize, (play_format.format == SND_PCM_SFMT_S8 || play_format.format == SND_PCM_SFMT_S16_BE || play_format.format == SND_PCM_SFMT_S16_LE), (play_format.format == SND_PCM_SFMT_S8 || play_format.format == SND_PCM_SFMT_U8), (play_format.format == SND_PCM_SFMT_S16_BE || play_format.format == SND_PCM_SFMT_U16_BE)); return pthread_mixer_enable(); } static vmResult alsa_disable(void) { vmResult res; #ifdef WRITE_TO_DISK if (alsa_sndf) close(alsa_sndf); #endif if ((res = pthread_mixer_disable()) != vmOk) return res; pthread_mixer_term(); snd_pcm_close(pcm_handle); return vmOk; } static vmResult alsa_restart(void) { #warning set volume? return pthread_mixer_restart(); } static vmResult alsa_restop(void) { #warning set volume? return pthread_mixer_restop(); } static vmSoundModule alsaSoundModule = { 3, alsa_update, alsa_flush, alsa_play, alsa_read }; vmModule alsaSound = { 3, "Advanced Linux Sound Architecture", "sndALSA", vmTypeSound, 0, alsa_detect, alsa_init, alsa_term, alsa_enable, alsa_disable, alsa_restart, alsa_restop, {(vmGenericModule *) & alsaSoundModule} };