/* * Sdl sound.c implementation * (c) 2015 */ #include #include #include #include #include #include #include #include "sysconfig.h" #include "sysdeps.h" #include "config.h" #include "uae.h" #include "options.h" #include "memory.h" #include "newcpu.h" #include "custom.h" #include "audio.h" #include "gensound.h" #include "sounddep/sound.h" #include "SDL.h" #ifdef ANDROIDSDL #include #endif // "consumer" means the actual SDL sound output, as opposed to #define SOUND_CONSUMER_BUFFER_LENGTH (SNDBUFFER_LEN * SOUND_BUFFERS_COUNT / 4) extern unsigned long next_sample_evtime; int produce_sound = 0; int changed_produce_sound = 0; // #define SOUND_USE_SEMAPHORES uae_u16 sndbuffer[SOUND_BUFFERS_COUNT][(SNDBUFFER_LEN + 32)*DEFAULT_SOUND_CHANNELS]; unsigned n_callback_sndbuff, n_render_sndbuff; uae_u16 *sndbufpt = sndbuffer[0]; uae_u16 *render_sndbuff = sndbuffer[0]; uae_u16 *finish_sndbuff = sndbuffer[0] + SNDBUFFER_LEN * 2; uae_u16 cdaudio_buffer[CDAUDIO_BUFFERS][(CDAUDIO_BUFFER_LEN + 32) * 2]; uae_u16 *cdbufpt = cdaudio_buffer[0]; uae_u16 *render_cdbuff = cdaudio_buffer[0]; uae_u16 *finish_cdbuff = cdaudio_buffer[0] + CDAUDIO_BUFFER_LEN * 2; bool cdaudio_active = false; static int cdwrcnt = 0; static int cdrdcnt = 0; extern int screen_is_picasso; #ifdef NO_SOUND void finish_sound_buffer(void) {} int setup_sound(void) { sound_available = 0; return 0; } void close_sound(void) {} void amiberry_stop_sound(void) {} int init_sound(void) { return 0; } void pause_sound(void) {} void resume_sound(void) {} void update_sound(double) {} void reset_sound(void) {} void uae4all_init_sound(void) {} void uae4all_play_click(void) {} void uae4all_pause_music(void) {} void uae4all_resume_music(void) {} void restart_sound_buffer(void) {} #else static int have_sound = 0; static int lastfreq; void update_sound(double clk) { float evtime; evtime = clk * CYCLE_UNIT / (float)currprefs.sound_freq; scaled_sample_evtime = evtime; } static int s_oldrate = 0, s_oldbits = 0, s_oldstereo = 0; static int sound_thread_active = 0, sound_thread_exit = 0; static sem_t sound_sem, callback_sem; #define MIN(X,Y) ((X) < (Y) ? (X) : (Y)) static int rdcnt = 0; static int wrcnt = 0; static void sound_copy_produced_block(void *ud, Uint8 *stream, int len) { #ifdef SOUND_USE_SEMAPHORES sem_wait(&sound_sem); #endif //__android_log_print(ANDROID_LOG_INFO, "UAE4ALL2","Sound callback cnt %d buf %d\n", cnt, cnt%SOUND_BUFFERS_COUNT); if (currprefs.sound_stereo) { if (cdaudio_active && currprefs.sound_freq == 44100 && cdrdcnt < cdwrcnt) { for (int i = 0; i < SNDBUFFER_LEN * 2; ++i) sndbuffer[rdcnt % SOUND_BUFFERS_COUNT][i] += cdaudio_buffer[cdrdcnt & (CDAUDIO_BUFFERS - 1)][i]; } memcpy(stream, sndbuffer[rdcnt % SOUND_BUFFERS_COUNT], MIN(SNDBUFFER_LEN * 4, len)); } else memcpy(stream, sndbuffer[rdcnt % SOUND_BUFFERS_COUNT], MIN(SNDBUFFER_LEN * 2, len)); //cdrdcnt = cdwrcnt; // how many smaller "producer buffers" do we have ready to be played? if (wrcnt - rdcnt >= (SOUND_BUFFERS_COUNT / 2)) { rdcnt++; cdrdcnt++; } // if less than half of the production buffers are full, it means that more sound has been // output (by SDL) than the emulation has produced. We solve this by simply not // moving the "read head", until the emulation side has got enough headway. #ifdef SOUND_USE_SEMAPHORES sem_post(&callback_sem); #endif } static void sound_thread_mixer(void *ud, Uint8 *stream, int len) { static int call_count = 0; if (sound_thread_exit) return; sound_thread_active = 1; int sample_size = currprefs.sound_stereo ? 4 : 2; while (len > 0) { int l = MIN(SNDBUFFER_LEN * sample_size, len); sound_copy_produced_block(ud, stream, l); stream += l; len -= l; } // if (call_count % 10 == 0) // printf("wrcnt - rdcnt: %d\n", wrcnt - rdcnt); call_count++; } static void init_soundbuffer_usage(void) { sndbufpt = sndbuffer[0]; render_sndbuff = sndbuffer[0]; finish_sndbuff = sndbuffer[0] + SNDBUFFER_LEN * 2; //output_cnt = 0; rdcnt = 0; wrcnt = 0; cdbufpt = cdaudio_buffer[0]; render_cdbuff = cdaudio_buffer[0]; finish_cdbuff = cdaudio_buffer[0] + CDAUDIO_BUFFER_LEN * 2; cdrdcnt = 0; cdwrcnt = 0; } static int amiberry_start_sound(int rate, int bits, int stereo) { int frag = 0, buffers, ret; unsigned int bsize; static int audioOpened = 0; // if no settings change, we don't need to do anything if (rate == s_oldrate && s_oldbits == bits && s_oldstereo == stereo && audioOpened) return 0; if (audioOpened) amiberry_stop_sound(); // init sem, start sound thread init_soundbuffer_usage(); printf("starting sound thread..\n"); ret = sem_init(&sound_sem, 0, 0); sem_init(&callback_sem, 0, SOUND_BUFFERS_COUNT - 1); if (ret != 0) printf("sem_init() failed: %i, errno=%i\n", ret, errno); SDL_AudioSpec as; memset(&as, 0, sizeof(as)); // __android_log_print(ANDROID_LOG_INFO, "UAE4ALL2", "Opening audio: rate %d bits %d stereo %d", rate, bits, stereo); as.freq = rate; as.format = (bits == 8 ? AUDIO_S8 : AUDIO_S16); as.channels = (stereo ? 2 : 1); as.samples = SOUND_CONSUMER_BUFFER_LENGTH; // as.samples = SNDBUFFER_LEN; as.callback = sound_thread_mixer; if (SDL_OpenAudio(&as, NULL)) printf("Error when opening SDL audio !\n"); audioOpened = 1; s_oldrate = rate; s_oldbits = bits; s_oldstereo = stereo; SDL_PauseAudio(0); return 0; } // this is meant to be called only once on exit void amiberry_stop_sound(void) { int valuesem; if (sound_thread_exit) printf("don't call amiberry_stop_sound more than once!\n"); SDL_PauseAudio(1); if (sound_thread_active) { printf("stopping sound thread..\n"); sound_thread_exit = 1; sem_getvalue(&sound_sem, &valuesem); while (valuesem == 0) { // Produce semaphore until audio thread don't consums them... sem_post(&sound_sem); usleep(200 * 1000); sem_getvalue(&sound_sem, &valuesem); } } sound_thread_exit = 0; SDL_CloseAudio(); sem_destroy(&sound_sem); sem_destroy(&callback_sem); } void finish_sound_buffer(void) { #ifdef DEBUG_SOUND dbg("sound.c : finish_sound_buffer"); #endif //printf("Sound finish %i\n", wrcnt); // "GET NEXT PRODUCER BUFFER FOR WRITING" wrcnt++; sndbufpt = render_sndbuff = sndbuffer[wrcnt % SOUND_BUFFERS_COUNT]; if (currprefs.sound_stereo) finish_sndbuff = sndbufpt + SNDBUFFER_LEN * 2; else finish_sndbuff = sndbufpt + SNDBUFFER_LEN; #ifdef SOUND_USE_SEMAPHORES sem_post(&sound_sem); sem_wait(&callback_sem); #endif while ((wrcnt % SOUND_BUFFERS_COUNT) == (rdcnt % SOUND_BUFFERS_COUNT)) { usleep(500); } #ifdef DEBUG_SOUND dbg(" sound.c : ! finish_sound_buffer"); #endif } void pause_sound_buffer(void) { reset_sound(); } void restart_sound_buffer(void) { sndbufpt = render_sndbuff = sndbuffer[wrcnt % SOUND_BUFFERS_COUNT]; if (currprefs.sound_stereo) finish_sndbuff = sndbufpt + SNDBUFFER_LEN * 2; else finish_sndbuff = sndbufpt + SNDBUFFER_LEN; cdbufpt = render_cdbuff = cdaudio_buffer[cdwrcnt & (CDAUDIO_BUFFERS - 1)]; finish_cdbuff = cdbufpt + CDAUDIO_BUFFER_LEN * 2; } void finish_cdaudio_buffer(void) { cdwrcnt++; cdbufpt = render_cdbuff = cdaudio_buffer[cdwrcnt & (CDAUDIO_BUFFERS - 1)]; finish_cdbuff = cdbufpt + CDAUDIO_BUFFER_LEN * 2; audio_activate(); } bool cdaudio_catchup(void) { while ((cdwrcnt > cdrdcnt + CDAUDIO_BUFFERS - 30) && (sound_thread_active != 0) && (quit_program == 0)) { sleep_millis(10); } return (sound_thread_active != 0); } /* Try to determine whether sound is available. This is only for GUI purposes. */ int setup_sound(void) { #ifdef DEBUG_SOUND dbg("sound.c : setup_sound"); #endif // Android does not like opening sound device several times if (amiberry_start_sound(currprefs.sound_freq, 16, currprefs.sound_stereo) != 0) return 0; sound_available = 1; #ifdef DEBUG_SOUND dbg(" sound.c : ! setup_sound"); #endif return 1; } static int open_sound(void) { #ifdef DEBUG_SOUND dbg("sound.c : open_sound"); #endif // Android does not like opening sound device several times if (amiberry_start_sound(currprefs.sound_freq, 16, currprefs.sound_stereo) != 0) return 0; have_sound = 1; sound_available = 1; if (currprefs.sound_stereo) sample_handler = sample16s_handler; else sample_handler = sample16_handler; #ifdef DEBUG_SOUND dbg(" sound.c : ! open_sound"); #endif return 1; } void close_sound(void) { #ifdef DEBUG_SOUND dbg("sound.c : close_sound"); #endif if (!have_sound) return; // testing shows that reopenning sound device is not a good idea (causes random sound driver crashes) // we will close it on real exit instead #ifdef RASPBERRY //amiberry_stop_sound(); #endif have_sound = 0; #ifdef DEBUG_SOUND dbg(" sound.c : ! close_sound"); #endif } int init_sound(void) { #ifdef DEBUG_SOUND dbg("sound.c : init_sound"); #endif have_sound = open_sound(); #ifdef DEBUG_SOUND dbg(" sound.c : ! init_sound"); #endif return have_sound; } void pause_sound(void) { #ifdef DEBUG_SOUND dbg("sound.c : pause_sound"); #endif SDL_PauseAudio(1); /* nothing to do */ #ifdef DEBUG_SOUND dbg(" sound.c : ! pause_sound"); #endif } void resume_sound(void) { #ifdef DEBUG_SOUND dbg("sound.c : resume_sound"); #endif SDL_PauseAudio(0); /* nothing to do */ #ifdef DEBUG_SOUND dbg(" sound.c : ! resume_sound"); #endif } void uae4all_init_sound(void) { #ifdef DEBUG_SOUND dbg("sound.c : uae4all_init_sound"); #endif #ifdef DEBUG_SOUND dbg(" sound.c : ! uae4all_init_sound"); #endif } void uae4all_pause_music(void) { #ifdef DEBUG_SOUND dbg("sound.c : pause_music"); #endif #ifdef DEBUG_SOUND dbg(" sound.c : ! pause_music"); #endif } void uae4all_resume_music(void) { #ifdef DEBUG_SOUND dbg("sound.c : resume_music"); #endif #ifdef DEBUG_SOUND dbg(" sound.c : ! resume_music"); #endif } void uae4all_play_click(void) { } void reset_sound(void) { if (!have_sound) return; init_soundbuffer_usage(); clear_sound_buffers(); clear_cdaudio_buffers(); } void sound_volume(int dir) { } #endif