diff --git a/Makefile.ps3 b/Makefile.ps3 index 96daa6c994..d290571656 100644 --- a/Makefile.ps3 +++ b/Makefile.ps3 @@ -39,7 +39,7 @@ INCDIRS = -I. -Icommon MKFSELF_NPDRM = $(CELL_SDK)/$(HOST_DIR)/bin/make_fself_npdrm MKPKG_NPDRM = $(CELL_SDK)/$(HOST_DIR)/bin/make_package_npdrm -OBJ = getopt.o ssnes.o driver.o settings.o dynamic.o message.o rewind.o movie.o autosave.o gfx/gfx_common.o gfx/gl.c gfx/shader_cg.c gfx/snes_state.c ups.o bps.o strl.o screenshot.o thread.o audio/hermite.o +OBJ = ps3/buffer.o ps3/ps3_audio.o ps3/resampler.o getopt.o ssnes.o driver.o settings.o dynamic.o message.o rewind.o movie.o autosave.o gfx/gfx_common.o gfx/gl.c gfx/shader_cg.c gfx/snes_state.c ups.o bps.o strl.o screenshot.o thread.o audio/hermite.o LIBS = -ldbgfont -lPSGL -lgcm_cmd -lgcm_sys_stub -lresc_stub libsnes.a -lm -lio_stub -lfs_stub -lsysutil_stub -lsysmodule_stub -laudio_stub -lnet_stub -lpthread diff --git a/config.def.h b/config.def.h index 8e8aa7777a..0df3eb5815 100644 --- a/config.def.h +++ b/config.def.h @@ -50,6 +50,7 @@ #define AUDIO_EXT 15 #define AUDIO_DSOUND 16 #define AUDIO_COREAUDIO 17 +#define AUDIO_PS3 18 //////////////////////// #define INPUT_SDL 7 #define INPUT_X 12 diff --git a/driver.c b/driver.c index 6cf71584e0..e4dde30760 100644 --- a/driver.c +++ b/driver.c @@ -29,6 +29,9 @@ #endif static const audio_driver_t *audio_drivers[] = { +#ifdef __CELLOS_LV2__ + &audio_ps3, +#endif #ifdef HAVE_ALSA &audio_alsa, #endif diff --git a/driver.h b/driver.h index bc66a8dee7..08742596f8 100644 --- a/driver.h +++ b/driver.h @@ -167,6 +167,9 @@ extern const audio_driver_t audio_pulse; extern const audio_driver_t audio_ext; extern const audio_driver_t audio_dsound; extern const audio_driver_t audio_coreaudio; +#ifdef __CELLOS_LV2__ +extern const audio_driver_t audio_ps3; +#endif extern const video_driver_t video_gl; extern const video_driver_t video_xvideo; extern const video_driver_t video_sdl; diff --git a/ps3/buffer.c b/ps3/buffer.c new file mode 100644 index 0000000000..576cd14841 --- /dev/null +++ b/ps3/buffer.c @@ -0,0 +1,101 @@ +/* RSound - A PCM audio client/server + * Copyright (C) 2010 - Hans-Kristian Arntzen + * + * RSound 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 Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RSound 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. + * + * You should have received a copy of the GNU General Public License along with RSound. + * If not, see . + */ + +#include "buffer.h" + +struct fifo_buffer +{ + char *buffer; + size_t bufsize; + size_t first; + size_t end; +}; + +fifo_buffer_t* fifo_new(size_t size) +{ + fifo_buffer_t *buf = calloc(1, sizeof(*buf)); + if (buf == NULL) + return NULL; + + buf->buffer = calloc(1, size + 1); + if (buf->buffer == NULL) + { + free(buf); + return NULL; + } + buf->bufsize = size + 1; + + return buf; +} + +void fifo_free(fifo_buffer_t* buffer) +{ + free(buffer->buffer); + free(buffer); +} + +size_t fifo_read_avail(fifo_buffer_t* buffer) +{ + size_t first = buffer->first; + size_t end = buffer->end; + if (end < first) + end += buffer->bufsize; + return end - first; +} + +size_t fifo_write_avail(fifo_buffer_t* buffer) +{ + size_t first = buffer->first; + size_t end = buffer->end; + if (end < first) + end += buffer->bufsize; + + return (buffer->bufsize - 1) - (end - first); +} + +void fifo_write(fifo_buffer_t* buffer, const void* in_buf, size_t size) +{ + size_t first_write = size; + size_t rest_write = 0; + if (buffer->end + size > buffer->bufsize) + { + first_write = buffer->bufsize - buffer->end; + rest_write = size - first_write; + } + + memcpy(buffer->buffer + buffer->end, in_buf, first_write); + if (rest_write > 0) + memcpy(buffer->buffer, (const char*)in_buf + first_write, rest_write); + + buffer->end = (buffer->end + size) % buffer->bufsize; +} + + +void fifo_read(fifo_buffer_t* buffer, void* in_buf, size_t size) +{ + size_t first_read = size; + size_t rest_read = 0; + if (buffer->first + size > buffer->bufsize) + { + first_read = buffer->bufsize - buffer->first; + rest_read = size - first_read; + } + + memcpy(in_buf, (const char*)buffer->buffer + buffer->first, first_read); + if (rest_read > 0) + memcpy((char*)in_buf + first_read, buffer->buffer, rest_read); + + buffer->first = (buffer->first + size) % buffer->bufsize; +} diff --git a/ps3/buffer.h b/ps3/buffer.h new file mode 100644 index 0000000000..e7cb3497f1 --- /dev/null +++ b/ps3/buffer.h @@ -0,0 +1,32 @@ +/* RSound - A PCM audio client/server + * Copyright (C) 2010 - Hans-Kristian Arntzen + * + * RSound 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 Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RSound 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. + * + * You should have received a copy of the GNU General Public License along with RSound. + * If not, see . + */ + +#ifndef __BUFFER_H +#define __BUFFER_H + +#include +#include +#include + +typedef struct fifo_buffer fifo_buffer_t; + +fifo_buffer_t* fifo_new(size_t size); +void fifo_write(fifo_buffer_t* buffer, const void* in_buf, size_t size); +void fifo_read(fifo_buffer_t* buffer, void* in_buf, size_t size); +void fifo_free(fifo_buffer_t* buffer); +size_t fifo_read_avail(fifo_buffer_t* buffer); +size_t fifo_write_avail(fifo_buffer_t* buffer); + +#endif diff --git a/ps3/ps3_audio.c b/ps3/ps3_audio.c new file mode 100644 index 0000000000..1fe74a7542 --- /dev/null +++ b/ps3/ps3_audio.c @@ -0,0 +1,205 @@ +/* SSNES - A Super Ninteno Entertainment System (SNES) Emulator frontend for libsnes. + * Copyright (C) 2010 - Hans-Kristian Arntzen + * + * Some code herein may be based on code found in BSNES. + * + * SSNES 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 Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * SSNES 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. + * + * You should have received a copy of the GNU General Public License along with SSNES. + * If not, see . + */ + +#include "../driver.h" +#include +#include +#include +#include +#include +#include "buffer.h" +#include "resampler.h" +#include + +#define AUDIO_BLOCKS 8 // 8 or 16. Guess what we choose? :) +#define AUDIO_CHANNELS 2 // All hail glorious stereo! +#define AUDIO_OUT_RATE (48000.0) + +typedef struct +{ + float tmp_data[CELL_AUDIO_BLOCK_SAMPLES * AUDIO_CHANNELS]; + uint32_t audio_port; + bool nonblocking; + volatile bool quit_thread; + fifo_buffer_t *buffer; + uint64_t input_rate; + + pthread_t thread; + pthread_mutex_t lock; + pthread_mutex_t cond_lock; + pthread_cond_t cond; +} ps3_audio_t; + +static size_t drain_fifo(void *cb_data, float **data) +{ + ps3_audio_t *aud = cb_data; + + int16_t tmp[CELL_AUDIO_BLOCK_SAMPLES * AUDIO_CHANNELS]; + + if (fifo_read_avail(aud->buffer) >= sizeof(tmp)) + { + pthread_mutex_lock(&aud->lock); + fifo_read(aud->buffer, tmp, sizeof(tmp)); + pthread_mutex_unlock(&aud->lock); + resampler_s16_to_float(aud->tmp_data, tmp, CELL_AUDIO_BLOCK_SAMPLES * AUDIO_CHANNELS); + } + else + { + memset(aud->tmp_data, 0, sizeof(aud->tmp_data)); + } + *data = aud->tmp_data; + return CELL_AUDIO_BLOCK_SAMPLES; +} + +static void *event_loop(void *data) +{ + ps3_audio_t *aud = data; + sys_event_queue_t id; + sys_ipc_key_t key; + sys_event_t event; + + cellAudioCreateNotifyEventQueue(&id, &key); + cellAudioSetNotifyEventQueue(key); + + resampler_t *resampler = resampler_new(drain_fifo, AUDIO_OUT_RATE/aud->input_rate, 2, data); + + float out_tmp[CELL_AUDIO_BLOCK_SAMPLES * AUDIO_CHANNELS] __attribute__((aligned(16))); + + while (!aud->quit_thread) + { + sys_event_queue_receive(id, &event, SYS_NO_TIMEOUT); + resampler_cb_read(resampler, CELL_AUDIO_BLOCK_SAMPLES, out_tmp); + cellAudioAddData(aud->audio_port, out_tmp, CELL_AUDIO_BLOCK_SAMPLES, 1.0); + pthread_cond_signal(&aud->cond); + } + + cellAudioRemoveNotifyEventQueue(key); + resampler_free(resampler); + pthread_exit(NULL); + return NULL; +} + +static void* __ps3_init(const char* device, int rate, int latency) +{ + (void)latency; + (void)device; + + ps3_audio_t *data = calloc(1, sizeof(*data)); + if (data == NULL) + return NULL; + + CellAudioPortParam params; + + cellAudioInit(); + + params.nChannel = AUDIO_CHANNELS; + params.nBlock = AUDIO_BLOCKS; + params.attr = 0; + + if (cellAudioPortOpen(¶ms, &data->audio_port) != CELL_OK) + { + cellAudioQuit(); + return NULL; + } + + // Create a small fifo buffer. :) + data->buffer = fifo_new(CELL_AUDIO_BLOCK_SAMPLES * AUDIO_CHANNELS * AUDIO_BLOCKS * sizeof(int16_t)); + data->input_rate = rate; + + pthread_mutex_init(&data->lock, NULL); + pthread_mutex_init(&data->cond_lock, NULL); + pthread_cond_init(&data->cond, NULL); + + cellAudioPortStart(data->audio_port); + pthread_create(&data->thread, NULL, event_loop, data); + return data; +} + +// Should make some noise at least. :) +static ssize_t __ps3_write(void* data, const void* buf, size_t size) // Recieve exactly 1024 bytes at a time. +{ + ps3_audio_t *aud = data; + + // We will continuously write slightly more data than we should per second, and rely on blocking mechanisms to ensure we don't write too much. + if (aud->nonblocking) + { + if (fifo_write_avail(aud->buffer) < size) + return 0; + } + else + { + while (fifo_write_avail(aud->buffer) < size) + { + pthread_mutex_lock(&aud->cond_lock); + pthread_cond_wait(&aud->cond, &aud->lock); + pthread_mutex_unlock(&aud->cond_lock); + } + } + + pthread_mutex_lock(&aud->lock); + fifo_write(aud->buffer, buf, size); + pthread_mutex_unlock(&aud->lock); + return size; +} + +static bool __ps3_stop(void *data) +{ + //ps3_audio_t *aud = data; + //cellAudioPortStop(aud->audio_port); + return true; +} + +static bool __ps3_start(void *data) +{ + //ps3_audio_t *aud = data; + //cellAudioPortStart(aud->audio_port); + return false; +} + +static void __ps3_set_nonblock_state(void *data, bool state) +{ + ps3_audio_t *aud = data; + aud->nonblocking = state; +} + +static void __ps3_free(void *data) +{ + ps3_audio_t *aud = data; + + aud->quit_thread = true; + pthread_join(aud->thread, NULL); + + cellAudioPortStop(aud->audio_port); + cellAudioPortClose(aud->audio_port); + cellAudioQuit(); + fifo_free(aud->buffer); + + pthread_mutex_destroy(&aud->lock); + pthread_mutex_destroy(&aud->cond_lock); + pthread_cond_destroy(&aud->cond); + + free(data); +} + +const audio_driver_t audio_ps3 = { + .init = __ps3_init, + .write = __ps3_write, + .stop = __ps3_stop, + .start = __ps3_start, + .set_nonblock_state = __ps3_set_nonblock_state, + .free = __ps3_free +}; diff --git a/ps3/resampler.c b/ps3/resampler.c new file mode 100644 index 0000000000..9d07430272 --- /dev/null +++ b/ps3/resampler.c @@ -0,0 +1,225 @@ +/* RSound - A PCM audio client/server + * Copyright (C) 2010 - Hans-Kristian Arntzen + * + * RSound 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 Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RSound 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. + * + * You should have received a copy of the GNU General Public License along with RSound. + * If not, see . + */ + + +#include "resampler.h" +#include +#include +#include +#include + +#define SAMPLES_TO_FRAMES(x,y) ((x)/(y)->channels) +#define FRAMES_TO_SAMPLES(x,y) ((x)*(y)->channels) + +struct resampler +{ + float *data; + double ratio; + size_t data_ptr; + size_t data_size; + void *cb_data; + int channels; + resampler_cb_t func; + uint64_t sum_output_frames; + uint64_t sum_input_frames; +}; + +resampler_t* resampler_new(resampler_cb_t func, double ratio, int channels, void* cb_data) +{ + if (func == NULL) + return NULL; + + if (channels < 1) + return NULL; + + resampler_t* state = calloc(1, sizeof(resampler_t)); + if (state == NULL) + return NULL; + + state->func = func; + state->ratio = ratio; + state->channels = channels; + state->cb_data = cb_data; + + return state; +} + +void resampler_free(resampler_t* state) +{ + if (state && state->data) + free(state->data); + if (state) + free(state); +} + +void resampler_float_to_s16(int16_t * restrict out, const float * restrict in, size_t samples) +{ + for (int i = 0; i < (int)samples; i++) + { + int32_t temp = in[i] * 0x7FFF; + if (temp > 0x7FFE) + out[i] = 0x7FFE; + else if (temp < -0x7FFF) + out[i] = -0x7FFF; + else + out[i] = (int16_t)temp; + } +} + +void resampler_s16_to_float(float * restrict out, const int16_t * restrict in, size_t samples) +{ + for (int i = 0; i < (int)samples; i++) + out[i] = (float)in[i]/0x7FFF; +} + +static size_t resampler_get_required_frames(resampler_t* state, size_t frames) +{ + assert(state); + + size_t after_sum = state->sum_output_frames + frames; + + size_t min_input_frames = (size_t)((after_sum / state->ratio) + 2.0); + return min_input_frames - state->sum_input_frames; +} + +static void poly_create_3(float *poly, float *y) +{ + poly[2] = (y[0] - 2*y[1] + y[2])/2; + poly[1] = -1.5*y[0] + 2*y[1] - 0.5*y[2]; + poly[0] = y[0]; +} + +static size_t resampler_process(resampler_t *state, size_t frames, float *out_data) +{ + size_t frames_used = 0; + uint64_t pos_out; + double pos_in = 0.0; + +#if 0 + fprintf(stderr, "=========================================\n"); + fprintf(stderr, "Output: %zu frames.\n", frames); + fprintf(stderr, "Output frames: %zu - %zu\n", state->sum_output_frames, state->sum_output_frames + frames); + fprintf(stderr, "Needed input frames: %zu - %zu\n", (size_t)(state->sum_output_frames/state->ratio), (size_t)((state->sum_output_frames + frames)/state->ratio + 1.0)); + fprintf(stderr, "Current input frames: %zu - %zu\n", state->sum_input_frames, state->sum_input_frames + SAMPLES_TO_FRAMES(state->data_ptr, state)); + fprintf(stderr, "=========================================\n"); + + assert(state->sum_input_frames <= (size_t)(state->sum_output_frames/state->ratio)); + assert(state->sum_input_frames + SAMPLES_TO_FRAMES(state->data_ptr, state) - 1 >= (size_t)((state->sum_output_frames + frames - 1)/state->ratio + 1.0)); +#endif + + for (uint64_t x = state->sum_output_frames; x < state->sum_output_frames + frames; x++) + { + pos_out = x - state->sum_output_frames; + pos_in = ((double)x / state->ratio) - (double)state->sum_input_frames; + //pos_in = pos_out / state->ratio; + //fprintf(stderr, "pos_in: %15.7lf\n", pos_in + state->sum_input_frames); + for (int c = 0; c < state->channels; c++) + { + + + float poly[3]; + float data[3]; + float x_val; + + if ((int)pos_in == 0) + { + data[0] = state->data[0 * state->channels + c]; + data[1] = state->data[1 * state->channels + c]; + data[2] = state->data[2 * state->channels + c]; + x_val = pos_in; + } + else + { + data[0] = state->data[((int)pos_in - 1) * state->channels + c]; + data[1] = state->data[((int)pos_in + 0) * state->channels + c]; + data[2] = state->data[((int)pos_in + 1) * state->channels + c]; + x_val = pos_in - (int)pos_in + 1.0; + } + + poly_create_3(poly, data); + + out_data[pos_out * state->channels + c] = poly[2] * x_val * x_val + poly[1] * x_val + poly[0]; + } + } + frames_used = (int)pos_in; + return frames_used; +} + +ssize_t resampler_cb_read(resampler_t *state, size_t frames, float *data) +{ + assert(state); + assert(data); + + + // How many frames must we have to resample? + size_t req_frames = resampler_get_required_frames(state, frames); + + // Do we need to read more data? + if (SAMPLES_TO_FRAMES(state->data_ptr, state) < req_frames) + { + size_t must_read = req_frames - SAMPLES_TO_FRAMES(state->data_ptr, state); + float temp_buf[FRAMES_TO_SAMPLES(must_read, state)]; + + size_t has_read = 0; + + size_t copy_size = 0; + size_t ret = 0; + float *ptr = NULL; + while (has_read < must_read) + { + ret = state->func(state->cb_data, &ptr); + + if (ret == 0 || ptr == NULL) // We're done. + return -1; + + copy_size = (ret > must_read - has_read) ? (must_read - has_read) : ret; + memcpy(temp_buf + FRAMES_TO_SAMPLES(has_read, state), ptr, FRAMES_TO_SAMPLES(copy_size, state) * sizeof(float)); + + has_read += ret; + } + + // We might have gotten a lot of data from the callback. We should realloc our buffer if needed. + size_t req_buffer_frames = SAMPLES_TO_FRAMES(state->data_ptr, state) + has_read; + + if (req_buffer_frames > SAMPLES_TO_FRAMES(state->data_size, state)) + { + state->data = realloc(state->data, FRAMES_TO_SAMPLES(req_buffer_frames, state) * sizeof(float)); + if (state->data == NULL) + return -1; + + state->data_size = FRAMES_TO_SAMPLES(req_buffer_frames, state); + } + + memcpy(state->data + state->data_ptr, temp_buf, FRAMES_TO_SAMPLES(must_read, state) * sizeof(float)); + state->data_ptr += FRAMES_TO_SAMPLES(must_read, state); + + // We have some data from the callback we need to copy over as well. + if (ret > copy_size) + { + memcpy(state->data + state->data_ptr, ptr + FRAMES_TO_SAMPLES(copy_size, state), FRAMES_TO_SAMPLES(ret - copy_size, state) * sizeof(float)); + state->data_ptr += FRAMES_TO_SAMPLES(ret - copy_size, state); + } + } + + // Phew. We should have enough data in our buffer now to be able to process the data we need. + + size_t frames_used = resampler_process(state, frames, data); + state->sum_input_frames += frames_used; + memmove(state->data, state->data + FRAMES_TO_SAMPLES(frames_used, state), (state->data_ptr - FRAMES_TO_SAMPLES(frames_used, state)) * sizeof(float)); + state->data_ptr -= FRAMES_TO_SAMPLES(frames_used, state); + state->sum_output_frames += frames; + + return frames; +} diff --git a/ps3/resampler.h b/ps3/resampler.h new file mode 100644 index 0000000000..e96fb42eda --- /dev/null +++ b/ps3/resampler.h @@ -0,0 +1,42 @@ +/* RSound - A PCM audio client/server + * Copyright (C) 2010 - Hans-Kristian Arntzen + * + * RSound 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 Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * RSound 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. + * + * You should have received a copy of the GNU General Public License along with RSound. + * If not, see . + */ + +#ifndef RSD_RESAMPLER +#define RSD_RESAMPLER + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef size_t (*resampler_cb_t) (void *cb_data, float **data); + +typedef struct resampler resampler_t; + +resampler_t* resampler_new(resampler_cb_t func, double ratio, int channels, void* cb_data); +ssize_t resampler_cb_read(resampler_t *state, size_t frames, float *data); +void resampler_free(resampler_t* state); + +void resampler_float_to_s16(int16_t * restrict out, const float * restrict in, size_t samples); +void resampler_s16_to_float(float * restrict out, const int16_t * restrict in, size_t samples); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/settings.c b/settings.c index 3f55cae17f..3a693fe6b1 100644 --- a/settings.c +++ b/settings.c @@ -62,6 +62,11 @@ static void set_defaults(void) switch (AUDIO_DEFAULT_DRIVER) { + #ifdef __CELLOS_LV2__ + case AUDIO_PS3: + def_audio = "ps3"; + break; + #endif case AUDIO_RSOUND: def_audio = "rsound"; break;