Merge pull request #727 from cjacker/master

Add alsa sound output and I420 Xvideo image format support for unix CLI.
This commit is contained in:
bearoso 2022-01-31 16:10:44 -06:00 committed by GitHub
commit 951de39c87
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 350 additions and 36 deletions

View File

@ -445,6 +445,22 @@ if test "x$enable_xvideo" = "xyes"; then
])
fi
# Check if we can build with libyuv hardware acceleration I420 conversion support
AC_ARG_ENABLE([libyuv],
[AS_HELP_STRING([--enable-libyuv],
[enable libyuv I420 conversion support if available (default: yes)])],
[], [enable_libyuv="yes"])
if test "x$enable_libyuv" = "xyes"; then
enable_libyuv="no"
AC_CHECK_HEADER([libyuv.h],
[
enable_libyuv="yes"
S9XLIBS="$S9XLIBS -lyuv"
S9XDEFS="$S9XDEFS -DUSE_LIBYUV"
])
fi
# Check if we can build with Xinerama multi-monitor support
AC_ARG_ENABLE([xinerama],
[AS_HELP_STRING([--enable-xinerama],
@ -489,6 +505,25 @@ else
S9XDEFS="$S9XDEFS -DNOSOUND"
fi
# Check if we can build with alsa support
AC_ARG_ENABLE([sound-alsa],
[AS_HELP_STRING([--enable-sound-alsa],
[enable Alsa if available (default: yes)])],
[], [enable_sound_alsa="yes"])
if test "x$enable_sound_alsa" = "xyes"; then
enable_sound_alsa="no"
AC_CHECK_HEADER([alsa/asoundlib.h],
[
enable_sound_alsa="yes"
enable_sound="yes"
S9XLIBS="$S9XLIBS -lasound"
#in case '--disable-sound' and 'enable-sound-alsa'
S9XDEFS=$(echo $S9XDEFS|sed 's/-DNOSOUND//g; s/-DUSE_THREADS//g')
S9XDEFS="$S9XDEFS -DALSA"
])
fi
# Output.
S9XFLGS="$CXXFLAGS $CPPFLAGS $LDFLAGS $S9XFLGS"
@ -526,8 +561,10 @@ libs................. $S9XLIBS
features:
Xvideo support....... $enable_xvideo
libyuv support....... $enable_libyuv
Xinerama support..... $enable_xinerama
sound support........ $enable_sound
alsa support......... $enable_sound_alsa
screenshot support... $enable_screenshot
netplay support...... $enable_netplay
gamepad support...... $enable_gamepad

View File

@ -27,10 +27,16 @@
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#ifndef NOSOUND
#ifndef NOSOUND
#ifndef ALSA
#include <sys/soundcard.h>
#include <sys/mman.h>
#else
#include <alsa/asoundlib.h>
#endif
#endif
#ifdef JOYSTICK_SUPPORT
#include <linux/joystick.h>
#endif
@ -114,8 +120,13 @@ struct SUnixSettings
struct SoundStatus
{
#ifndef ALSA
int sound_fd;
uint32 fragment_size;
#else
snd_pcm_t *pcm_handle;
int output_buffer_size;
#endif
};
@ -165,7 +176,7 @@ static long log2 (long num)
namespace {
#if ! defined(NOSOUND)
#if ! defined(NOSOUND) && ! defined(ALSA)
class S9xAudioOutput
{
public:
@ -338,20 +349,27 @@ void S9xExtraUsage (void)
#ifdef JOYSTICK_SUPPORT
S9xMessage(S9X_INFO, S9X_USAGE, "-nogamepad Disable gamepad reading");
S9xMessage(S9X_INFO, S9X_USAGE, "-paddev1 <string> Specify gamepad device 1");
S9xMessage(S9X_INFO, S9X_USAGE, "-paddev1 <string> Specify gamepad device 2");
S9xMessage(S9X_INFO, S9X_USAGE, "-paddev1 <string> Specify gamepad device 3");
S9xMessage(S9X_INFO, S9X_USAGE, "-paddev1 <string> Specify gamepad device 4");
S9xMessage(S9X_INFO, S9X_USAGE, "-paddev2 <string> Specify gamepad device 2");
S9xMessage(S9X_INFO, S9X_USAGE, "-paddev3 <string> Specify gamepad device 3");
S9xMessage(S9X_INFO, S9X_USAGE, "-paddev4 <string> Specify gamepad device 4");
S9xMessage(S9X_INFO, S9X_USAGE, "-paddev5 <string> Specify gamepad device 5");
S9xMessage(S9X_INFO, S9X_USAGE, "-paddev6 <string> Specify gamepad device 6");
S9xMessage(S9X_INFO, S9X_USAGE, "-paddev7 <string> Specify gamepad device 7");
S9xMessage(S9X_INFO, S9X_USAGE, "-paddev8 <string> Specify gamepad device 8");
S9xMessage(S9X_INFO, S9X_USAGE, "");
#endif
#ifdef USE_THREADS
#ifndef NOSOUND
#ifdef USE_THREADS && ! defined(ALSA)
S9xMessage(S9X_INFO, S9X_USAGE, "-threadsound Use a separate thread to output sound");
#endif
S9xMessage(S9X_INFO, S9X_USAGE, "-buffersize Sound generating buffer size in millisecond");
#ifndef ALSA
S9xMessage(S9X_INFO, S9X_USAGE, "-fragmentsize Sound playback buffer fragment size in bytes");
#endif
S9xMessage(S9X_INFO, S9X_USAGE, "-sounddev <string> Specify sound device");
S9xMessage(S9X_INFO, S9X_USAGE, "");
#endif
S9xMessage(S9X_INFO, S9X_USAGE, "-loadsnapshot Load snapshot file at start");
S9xMessage(S9X_INFO, S9X_USAGE, "-playmovie <filename> Start emulator playing the .smv file");
S9xMessage(S9X_INFO, S9X_USAGE, "-recordmovie <filename> Start emulator recording the .smv file");
@ -584,8 +602,11 @@ void S9xParsePortConfig (ConfigFile &conf, int pass)
#endif
unixSettings.SoundBufferSize = conf.GetUInt ("Unix::SoundBufferSize", 100);
unixSettings.SoundFragmentSize = conf.GetUInt ("Unix::SoundFragmentSize", 2048);
#ifndef ALSA
sound_device = conf.GetStringDup("Unix::SoundDevice", "/dev/dsp");
#else
sound_device = conf.GetStringDup("Unix::SoundDevice", "default");
#endif
keymaps.clear();
if (!conf.GetBool("Unix::ClearAllControls", false))
{
@ -1331,9 +1352,25 @@ void S9xSamplesAvailable(void *data)
static uint8 *sound_buffer = NULL;
static int sound_buffer_size = 0;
#ifdef ALSA
snd_pcm_sframes_t frames_written, frames;
frames = snd_pcm_avail(so.pcm_handle);
if (frames < 0)
{
frames = snd_pcm_recover(so.pcm_handle, frames, 1);
return;
}
#endif
if (Settings.DynamicRateControl)
{
#ifndef ALSA
S9xUpdateDynamicRate(s_AudioOutput->GetFreeBufferSize(), so.fragment_size * 4);
#else
S9xUpdateDynamicRate(snd_pcm_frames_to_bytes(so.pcm_handle, frames),
so.output_buffer_size);
#endif
}
samples_to_write = S9xGetSampleCount();
@ -1341,21 +1378,80 @@ void S9xSamplesAvailable(void *data)
if (samples_to_write < 0)
return;
#ifdef ALSA
if (Settings.DynamicRateControl && !Settings.SoundSync)
{
// Using rate control, we should always keep the emulator's sound buffers empty to
// maintain an accurate measurement.
if (frames < samples_to_write/2)
{
S9xClearSamples();
return;
}
}
if (Settings.SoundSync && !Settings.TurboMode && !Settings.Mute)
{
snd_pcm_nonblock(so.pcm_handle, 0);
frames = samples_to_write/2;
} else {
snd_pcm_nonblock(so.pcm_handle, 1);
frames = MIN(frames, samples_to_write/2);
}
int bytes = snd_pcm_frames_to_bytes(so.pcm_handle, frames);
if (bytes <= 0)
return;
if (sound_buffer_size < bytes || sound_buffer == NULL)
{
sound_buffer = (uint8 *)realloc(sound_buffer, bytes);
sound_buffer_size = bytes;
}
#else //OSS
if (sound_buffer_size < samples_to_write * 2)
{
sound_buffer = (uint8 *)realloc(sound_buffer, samples_to_write * 2);
sound_buffer_size = samples_to_write * 2;
}
S9xMixSamples(sound_buffer, samples_to_write);
s_AudioOutput->Write(sound_buffer, samples_to_write * 2);
#endif
#ifndef ALSA
S9xMixSamples(sound_buffer, samples_to_write);
s_AudioOutput->Write(sound_buffer, samples_to_write * 2);
#else
S9xMixSamples(sound_buffer, frames*2);
frames_written = 0;
while (frames_written < frames) {
int result;
result = snd_pcm_writei(so.pcm_handle,
sound_buffer +
snd_pcm_frames_to_bytes(so.pcm_handle, frames_written),
frames - frames_written);
if (result < 0)
{
result = snd_pcm_recover(so.pcm_handle, result, 1);
if (result < 0)
{
break;
}
}
else
{
frames_written += result;
}
}
#endif //ALSA
#endif //NOSOUND
}
bool8 S9xOpenSoundDevice (void)
{
#ifndef NOSOUND
#ifndef ALSA
int J, K;
so.sound_fd = open(sound_device, O_WRONLY | O_NONBLOCK);
@ -1393,8 +1489,103 @@ bool8 S9xOpenSoundDevice (void)
so.fragment_size = J;
printf("fragment size: %d\n", J);
#endif
#else
int err;
unsigned int periods = 8;
unsigned int buffer_size = unixSettings.SoundBufferSize * 1000;
snd_pcm_sw_params_t *sw_params;
snd_pcm_hw_params_t *hw_params;
snd_pcm_uframes_t alsa_buffer_size, alsa_period_size;
unsigned int min = 0;
unsigned int max = 0;
unsigned int rate = Settings.SoundPlaybackRate;
err = snd_pcm_open(&so.pcm_handle, sound_device, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
if (err < 0) {
printf("Failed: %s\n", snd_strerror(err));
return (FALSE);
}
snd_pcm_hw_params_alloca(&hw_params);
snd_pcm_hw_params_any(so.pcm_handle, hw_params);
snd_pcm_hw_params_set_format(so.pcm_handle, hw_params, SND_PCM_FORMAT_S16);
snd_pcm_hw_params_set_access(so.pcm_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
snd_pcm_hw_params_set_rate_resample(so.pcm_handle, hw_params, 0);
snd_pcm_hw_params_set_channels(so.pcm_handle, hw_params, 2);
snd_pcm_hw_params_get_rate_min(hw_params, &min, NULL);
snd_pcm_hw_params_get_rate_max(hw_params, &max, NULL);
fprintf(stderr, "Alsa available rates: %d to %d\n", min, max);
if (rate > max && rate < min)
{
fprintf(stderr, "Rate %d not available. Using %d instead.\n", rate, max);
rate = max;
}
snd_pcm_hw_params_set_rate_near(so.pcm_handle, hw_params, &rate, NULL);
snd_pcm_hw_params_get_buffer_time_min(hw_params, &min, NULL);
snd_pcm_hw_params_get_buffer_time_max(hw_params, &max, NULL);
fprintf(stderr, "Alsa available buffer sizes: %dms to %dms\n", min / 1000, max / 1000);
if (buffer_size < min && buffer_size > max)
{
fprintf(stderr, "Buffer size %dms not available. Using %d instead.\n", buffer_size / 1000, (min + max) / 2000);
buffer_size = (min + max) / 2;
}
snd_pcm_hw_params_set_buffer_time_near(so.pcm_handle, hw_params, &buffer_size, NULL);
snd_pcm_hw_params_get_periods_min(hw_params, &min, NULL);
snd_pcm_hw_params_get_periods_max(hw_params, &max, NULL);
fprintf(stderr, "Alsa period ranges: %d to %d blocks\n", min, max);
if (periods > max)
periods = max;
snd_pcm_hw_params_set_periods_near(so.pcm_handle, hw_params, &periods, NULL);
err = snd_pcm_hw_params(so.pcm_handle, hw_params);
if (err < 0)
{
fprintf(stderr, "Alsa error: unable to install hw params\n");
snd_pcm_drain(so.pcm_handle);
snd_pcm_close(so.pcm_handle);
so.pcm_handle = NULL;
return (FALSE);
}
snd_pcm_sw_params_alloca(&sw_params);
snd_pcm_sw_params_current(so.pcm_handle, sw_params);
snd_pcm_get_params(so.pcm_handle, &alsa_buffer_size, &alsa_period_size);
/* Don't start until we're [nearly] full */
snd_pcm_sw_params_set_start_threshold(so.pcm_handle, sw_params, (alsa_buffer_size / 2));
err = snd_pcm_sw_params(so.pcm_handle, sw_params);
if (err < 0) {
fprintf(stderr, "Alsa error: unable to install sw params\n");
snd_pcm_drain(so.pcm_handle);
snd_pcm_close(so.pcm_handle);
so.pcm_handle = NULL;
return (FALSE);
}
err = snd_pcm_prepare(so.pcm_handle);
if (err < 0) {
fprintf(stderr, "Alsa error: unable to prepare audio: %s\n", snd_strerror(err));
snd_pcm_drain(so.pcm_handle);
snd_pcm_close(so.pcm_handle);
so.pcm_handle = NULL;
return (FALSE);
}
so.output_buffer_size = snd_pcm_frames_to_bytes(so.pcm_handle, alsa_buffer_size);
#endif //ALSA
#endif //NOSOUND
S9xSetSamplesAvailableCallback(S9xSamplesAvailable, NULL);
return (TRUE);
@ -1413,7 +1604,7 @@ void S9xExit (void)
S9xNPDisconnect();
#endif
#ifndef NOSOUND
#if !defined(NOSOUND) && !defined(ALSA)
delete s_AudioOutput;
#endif

View File

@ -25,7 +25,12 @@
#ifdef USE_XVIDEO
#include <X11/extensions/Xvlib.h>
#ifdef USE_LIBYUV
#include <libyuv.h>
#endif
#define FOURCC_YUY2 0x32595559
#define FOURCC_I420 0x30323449
#endif
#ifdef USE_XINERAMA
@ -585,6 +590,12 @@ static bool8 SetupXvideo()
for (int i = 0; i < formats; i++)
{
//prefer I420
if (fo[i].id == FOURCC_I420) {
GUI.xv_format = FOURCC_I420;
break;
}
if (fo[i].id == 0x3 || fo[i].type == XvRGB)
{
if (fo[i].bits_per_pixel < GUI.xv_bpp)
@ -617,32 +628,35 @@ static bool8 SetupXvideo()
}
free (fo);
if (GUI.xv_format != FOURCC_YUY2)
{
if (GUI.xv_format != FOURCC_YUY2 && GUI.xv_format != FOURCC_I420) {
printf("Selected XvRGB format: %d bpp\n",GUI.xv_bpp);
} else {
// use YUY2
printf("Fallback to YUY2 format.\n");
GUI.depth = 15;
// use I420 or YUY2
if(GUI.xv_format == FOURCC_I420) {
printf("Xvideo I420 image format.\n");
} else {
printf("Xvideo YUY2 image format.\n");
GUI.depth = 15;
/* Build a table for yuv conversion */
for (unsigned int color = 0; color < (1 << 15); color++)
{
int r, g, b;
int y, u, v;
/* Build a table for yuv conversion */
for (unsigned int color = 0; color < (1 << 15); color++)
{
int r, g, b;
int y, u, v;
r = (color & 0x7c00) >> 7;
g = (color & 0x03e0) >> 2;
b = (color & 0x001F) << 3;
r = (color & 0x7c00) >> 7;
g = (color & 0x03e0) >> 2;
b = (color & 0x001F) << 3;
y = (int) ((0.257 * ((double) r)) + (0.504 * ((double) g)) + (0.098 * ((double) b)) + 16.0);
u = (int) ((-0.148 * ((double) r)) + (-0.291 * ((double) g)) + (0.439 * ((double) b)) + 128.0);
v = (int) ((0.439 * ((double) r)) + (-0.368 * ((double) g)) + (-0.071 * ((double) b)) + 128.0);
y = (int) ((0.257 * ((double) r)) + (0.504 * ((double) g)) + (0.098 * ((double) b)) + 16.0);
u = (int) ((-0.148 * ((double) r)) + (-0.291 * ((double) g)) + (0.439 * ((double) b)) + 128.0);
v = (int) ((0.439 * ((double) r)) + (-0.368 * ((double) g)) + (-0.071 * ((double) b)) + 128.0);
GUI.y_table[color] = CLAMP (y, 0, 255);
GUI.u_table[color] = CLAMP (u, 0, 255);
GUI.v_table[color] = CLAMP (v, 0, 255);
}
GUI.y_table[color] = CLAMP (y, 0, 255);
GUI.u_table[color] = CLAMP (u, 0, 255);
GUI.v_table[color] = CLAMP (v, 0, 255);
}
}
}
return TRUE;
@ -939,7 +953,7 @@ static void SetupImage (void)
FatalError("Failed to allocate GUI.filter_buffer.");
#ifdef USE_XVIDEO
if ((GUI.depth == 15 || GUI.depth == 16) && GUI.xv_format != FOURCC_YUY2)
if ((GUI.depth == 15 || GUI.depth == 16) && GUI.xv_format != FOURCC_YUY2 && GUI.xv_format != FOURCC_I420)
#else
if (GUI.depth == 15 || GUI.depth == 16)
#endif
@ -1209,6 +1223,55 @@ static void TakedownXvImage (void)
}
#endif
#if defined(USE_XVIDEO) && !defined(USE_LIBYUV)
void rgb565ToRgb24(uint8 *dest, uint8 *s, int width, int height) {
for (int i=0, j=0; i< width * height * 2 ; i+=2) {
uint16 c = s[i] + (s[i+1]<<8);
uint8 r = uint8(((c & 0xF800) >> 11) << 3);
uint8 g = uint8(((c & 0x7E0) >> 5) << 2);
uint8 b = uint8(((c & 0x1F)) << 3);
dest[j++]=r;
dest[j++]=g;
dest[j++]=b;
}
}
void rgb24ToI420(uint8 *dest, uint8 *rgb, int width, int height) {
int image_size = width * height;
int upos = image_size;
int vpos = upos + upos / 4;
int i = 0;
for (int line = 0; line < height; ++line) {
if( !(line % 2) ) {
for ( int x = 0; x < width; x += 2 ) {
uint8 r = rgb[3 * i];
uint8 g = rgb[3 * i + 1];
uint8 b = rgb[3 * i + 2];
dest[i++] = ((66*r + 129*g + 25*b) >> 8) + 16;
dest[upos++] = ((-38*r + -74*g + 112*b) >> 8) + 128;
dest[vpos++] = ((112*r + -94*g + -18*b) >> 8) + 128;
r = rgb[3 * i];
g = rgb[3 * i + 1];
b = rgb[3 * i + 2];
dest[i++] = ((66*r + 129*g + 25*b) >> 8) + 16;
}
} else {
for (int x = 0; x < width; x += 1) {
uint8 r = rgb[3 * i];
uint8 g = rgb[3 * i + 1];
uint8 b = rgb[3 * i + 2];
dest[i++] = ((66*r + 129*g + 25*b) >> 8) + 16;
}
}
}
}
#endif
void S9xPutImage (int width, int height)
{
static int prevWidth = 0, prevHeight = 0;
@ -1307,7 +1370,30 @@ void S9xPutImage (int width, int height)
*d = (GUI.v_table[rgb1] + GUI.v_table[rgb2]) / 2; d++;
}
}
}
} else if (GUI.use_xvideo && (GUI.xv_format == FOURCC_I420)) {
uint8 *s = (uint8 *)GUI.blit_screen;
uint8 *d = (uint8 *)GUI.image->data;
int tw = SNES_WIDTH * 2;
int th = SNES_HEIGHT_EXTENDED * 2;
#ifdef USE_LIBYUV
libyuv::RGB565ToI420(
s, tw*2,
d, tw,
d + tw*th, tw/2,
d + tw*th + tw*th/4, tw/2,
tw, th);
#else
uint8 *ns = (uint8 *) calloc(tw*th*3, 1);
//convert to RGB24
rgb565ToRgb24(ns, s, tw, th);
//convert to I420
rgb24ToI420(d, ns, tw, th);
free(ns);
#endif
GUI.need_convert =0;
}
else
#endif
if (GUI.need_convert)