1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Mupen64plus-sdl-audio - main.c *
* Mupen64Plus homepage: http://code.google.com/p/mupen64plus/ *
* Copyright (C) 2007-2009 Richard Goedeken *
* Copyright (C) 2007-2008 Ebenblues *
* Copyright (C) 2003 JttL *
* Copyright (C) 2002 Hacktarux *
* *
* 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. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <SDL.h>
#include <SDL_audio.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef USE_SRC
#include <samplerate.h>
#endif
#ifdef USE_SPEEX
#include <speex/speex_resampler.h>
#endif
#ifdef USE_AUDIORESOURCE
#include <audioresource.h>
#include <glib.h>
#endif
#define M64P_PLUGIN_PROTOTYPES 1
#include "m64p_common.h"
#include "m64p_config.h"
#include "m64p_plugin.h"
#include "m64p_types.h"
#include "main.h"
#include "osal_dynamiclib.h"
#include "volume.h"
/* Default start-time size of primary buffer (in equivalent output samples).
This is the buffer where audio is loaded after it's extracted from n64's memory.
This value must be larger than PRIMARY_BUFFER_TARGET */
#define PRIMARY_BUFFER_SIZE 16384
/* this is the buffer fullness level (in equivalent output samples) which is targeted
for the primary audio buffer (by inserting delays) each time data is received from
the running N64 program. This value must be larger than the SECONDARY_BUFFER_SIZE.
Decreasing this value will reduce audio latency but requires a faster PC to avoid
choppiness. Increasing this will increase audio latency but reduce the chance of
drop-outs. The default value 10240 gives a 232ms maximum A/V delay at 44.1khz */
#define PRIMARY_BUFFER_TARGET 10240
/* Size of secondary buffer, in output samples. This is the requested size of SDL's
hardware buffer, and the size of the mix buffer for doing SDL volume control. The
SDL documentation states that this should be a power of two between 512 and 8192. */
#define SECONDARY_BUFFER_SIZE 2048
/* This sets default frequency what is used if rom doesn't want to change it.
Probably only game that needs this is Zelda: Ocarina Of Time Master Quest
*NOTICE* We should try to find out why Demos' frequencies are always wrong
They tend to rely on a default frequency, apparently, never the same one ;)*/
#define DEFAULT_FREQUENCY 33600
/* number of bytes per sample */
#define N64_SAMPLE_BYTES 4
#define SDL_SAMPLE_BYTES 4
/* volume mixer types */
#define VOLUME_TYPE_SDL 1
#define VOLUME_TYPE_OSS 2
/* local variables */
static void (*l_DebugCallback)(void *, int, const char *) = NULL;
static void *l_DebugCallContext = NULL;
static int l_PluginInit = 0;
static int l_PausedForSync = 1; /* Audio is started in paused state after SDL initialization */
static m64p_handle l_ConfigAudio;
#ifdef USE_AUDIORESOURCE
static audioresource_t *l_audioresource = NULL;
static int l_audioresource_acquired = 0;
#endif
enum resampler_type {
RESAMPLER_TRIVIAL,
#ifdef USE_SRC
RESAMPLER_SRC,
#endif
#ifdef USE_SPEEX
RESAMPLER_SPEEX,
#endif
};
/* Read header for type definition */
static AUDIO_INFO AudioInfo;
/* The hardware specifications we are using */
static SDL_AudioSpec *hardware_spec;
/* Pointer to the primary audio buffer */
static unsigned char *primaryBuffer = NULL;
static unsigned int primaryBufferBytes = 0;
/* Pointer to the mixing buffer for voume control*/
static unsigned char *mixBuffer = NULL;
/* Position in buffer array where next audio chunk should be placed */
static unsigned int buffer_pos = 0;
/* Audio frequency, this is usually obtained from the game, but for compatibility we set default value */
static int GameFreq = DEFAULT_FREQUENCY;
/* timestamp for the last time that our audio callback was called */
static unsigned int last_callback_ticks = 0;
/* SpeedFactor is used to increase/decrease game playback speed */
static unsigned int speed_factor = 100;
// If this is true then left and right channels are swapped */
static int SwapChannels = 0;
// Size of Primary audio buffer in equivalent output samples
static unsigned int PrimaryBufferSize = PRIMARY_BUFFER_SIZE;
// Fullness level target for Primary audio buffer, in equivalent output samples
static unsigned int PrimaryBufferTarget = PRIMARY_BUFFER_TARGET;
// Size of Secondary audio buffer in output samples
static unsigned int SecondaryBufferSize = SECONDARY_BUFFER_SIZE;
// Resample type
static enum resampler_type Resample = RESAMPLER_TRIVIAL;
// Resampler specific quality
static int ResampleQuality = 3;
// volume to scale the audio by, range of 0..100
// if muted, this holds the volume when not muted
static int VolPercent = 80;
// how much percent to increment/decrement volume by
static int VolDelta = 5;
// the actual volume passed into SDL, range of 0..SDL_MIX_MAXVOLUME
static int VolSDL = SDL_MIX_MAXVOLUME;
// Muted or not
static int VolIsMuted = 0;
//which type of volume control to use
static int VolumeControlType = VOLUME_TYPE_SDL;
static int OutputFreq;
// Prototype of local functions
static void my_audio_callback(void *userdata, unsigned char *stream, int len);
static void InitializeAudio(int freq);
static void ReadConfig(void);
static void InitializeSDL(void);
static int critical_failure = 0;
/* definitions of pointers to Core config functions */
ptr_ConfigOpenSection ConfigOpenSection = NULL;
ptr_ConfigDeleteSection ConfigDeleteSection = NULL;
ptr_ConfigSaveSection ConfigSaveSection = NULL;
ptr_ConfigSetParameter ConfigSetParameter = NULL;
ptr_ConfigGetParameter ConfigGetParameter = NULL;
ptr_ConfigGetParameterHelp ConfigGetParameterHelp = NULL;
ptr_ConfigSetDefaultInt ConfigSetDefaultInt = NULL;
ptr_ConfigSetDefaultFloat ConfigSetDefaultFloat = NULL;
ptr_ConfigSetDefaultBool ConfigSetDefaultBool = NULL;
ptr_ConfigSetDefaultString ConfigSetDefaultString = NULL;
ptr_ConfigGetParamInt ConfigGetParamInt = NULL;
ptr_ConfigGetParamFloat ConfigGetParamFloat = NULL;
ptr_ConfigGetParamBool ConfigGetParamBool = NULL;
ptr_ConfigGetParamString ConfigGetParamString = NULL;
/* Global functions */
static void DebugMessage(int level, const char *message, ...)
{
char msgbuf[1024];
va_list args;
if (l_DebugCallback == NULL)
return;
va_start(args, message);
vsprintf(msgbuf, message, args);
(*l_DebugCallback)(l_DebugCallContext, level, msgbuf);
va_end(args);
}
#ifdef USE_AUDIORESOURCE
void on_audioresource_acquired(audioresource_t *audioresource, bool acquired, void *user_data)
{
DebugMessage(M64MSG_VERBOSE, "audioresource acquired: %d", acquired);
l_audioresource_acquired = acquired;
}
#endif
/* Mupen64Plus plugin functions */
EXPORT m64p_error CALL PluginStartup(m64p_dynlib_handle CoreLibHandle, void *Context,
void (*DebugCallback)(void *, int, const char *))
{
ptr_CoreGetAPIVersions CoreAPIVersionFunc;
int ConfigAPIVersion, DebugAPIVersion, VidextAPIVersion, bSaveConfig;
float fConfigParamsVersion = 0.0f;
if (l_PluginInit)
return M64ERR_ALREADY_INIT;
/* first thing is to set the callback function for debug info */
l_DebugCallback = DebugCallback;
l_DebugCallContext = Context;
/* attach and call the CoreGetAPIVersions function, check Config API version for compatibility */
CoreAPIVersionFunc = (ptr_CoreGetAPIVersions) osal_dynlib_getproc(CoreLibHandle, "CoreGetAPIVersions");
if (CoreAPIVersionFunc == NULL)
{
DebugMessage(M64MSG_ERROR, "Core emulator broken; no CoreAPIVersionFunc() function found.");
return M64ERR_INCOMPATIBLE;
}
(*CoreAPIVersionFunc)(&ConfigAPIVersion, &DebugAPIVersion, &VidextAPIVersion, NULL);
if ((ConfigAPIVersion & 0xffff0000) != (CONFIG_API_VERSION & 0xffff0000))
{
DebugMessage(M64MSG_ERROR, "Emulator core Config API (v%i.%i.%i) incompatible with plugin (v%i.%i.%i)",
VERSION_PRINTF_SPLIT(ConfigAPIVersion), VERSION_PRINTF_SPLIT(CONFIG_API_VERSION));
return M64ERR_INCOMPATIBLE;
}
/* Get the core config function pointers from the library handle */
ConfigOpenSection = (ptr_ConfigOpenSection) osal_dynlib_getproc(CoreLibHandle, "ConfigOpenSection");
ConfigDeleteSection = (ptr_ConfigDeleteSection) osal_dynlib_getproc(CoreLibHandle, "ConfigDeleteSection");
ConfigSaveSection = (ptr_ConfigSaveSection) osal_dynlib_getproc(CoreLibHandle, "ConfigSaveSection");
ConfigSetParameter = (ptr_ConfigSetParameter) osal_dynlib_getproc(CoreLibHandle, "ConfigSetParameter");
ConfigGetParameter = (ptr_ConfigGetParameter) osal_dynlib_getproc(CoreLibHandle, "ConfigGetParameter");
ConfigSetDefaultInt = (ptr_ConfigSetDefaultInt) osal_dynlib_getproc(CoreLibHandle, "ConfigSetDefaultInt");
ConfigSetDefaultFloat = (ptr_ConfigSetDefaultFloat) osal_dynlib_getproc(CoreLibHandle, "ConfigSetDefaultFloat");
ConfigSetDefaultBool = (ptr_ConfigSetDefaultBool) osal_dynlib_getproc(CoreLibHandle, "ConfigSetDefaultBool");
ConfigSetDefaultString = (ptr_ConfigSetDefaultString) osal_dynlib_getproc(CoreLibHandle, "ConfigSetDefaultString");
ConfigGetParamInt = (ptr_ConfigGetParamInt) osal_dynlib_getproc(CoreLibHandle, "ConfigGetParamInt");
ConfigGetParamFloat = (ptr_ConfigGetParamFloat) osal_dynlib_getproc(CoreLibHandle, "ConfigGetParamFloat");
ConfigGetParamBool = (ptr_ConfigGetParamBool) osal_dynlib_getproc(CoreLibHandle, "ConfigGetParamBool");
ConfigGetParamString = (ptr_ConfigGetParamString) osal_dynlib_getproc(CoreLibHandle, "ConfigGetParamString");
if (!ConfigOpenSection || !ConfigDeleteSection || !ConfigSetParameter || !ConfigGetParameter ||
!ConfigSetDefaultInt || !ConfigSetDefaultFloat || !ConfigSetDefaultBool || !ConfigSetDefaultString ||
!ConfigGetParamInt || !ConfigGetParamFloat || !ConfigGetParamBool || !ConfigGetParamString)
return M64ERR_INCOMPATIBLE;
/* ConfigSaveSection was added in Config API v2.1.0 */
if (ConfigAPIVersion >= 0x020100 && !ConfigSaveSection)
return M64ERR_INCOMPATIBLE;
/* get a configuration section handle */
if (ConfigOpenSection("Audio-SDL", &l_ConfigAudio) != M64ERR_SUCCESS)
{
DebugMessage(M64MSG_ERROR, "Couldn't open config section 'Audio-SDL'");
return M64ERR_INPUT_NOT_FOUND;
}
/* check the section version number */
bSaveConfig = 0;
if (ConfigGetParameter(l_ConfigAudio, "Version", M64TYPE_FLOAT, &fConfigParamsVersion, sizeof(float)) != M64ERR_SUCCESS)
{
DebugMessage(M64MSG_WARNING, "No version number in 'Audio-SDL' config section. Setting defaults.");
ConfigDeleteSection("Audio-SDL");
ConfigOpenSection("Audio-SDL", &l_ConfigAudio);
bSaveConfig = 1;
}
else if (((int) fConfigParamsVersion) != ((int) CONFIG_PARAM_VERSION))
{
DebugMessage(M64MSG_WARNING, "Incompatible version %.2f in 'Audio-SDL' config section: current is %.2f. Setting defaults.", fConfigParamsVersion, (float) CONFIG_PARAM_VERSION);
ConfigDeleteSection("Audio-SDL");
ConfigOpenSection("Audio-SDL", &l_ConfigAudio);
bSaveConfig = 1;
}
else if ((CONFIG_PARAM_VERSION - fConfigParamsVersion) >= 0.0001f)
{
/* handle upgrades */
float fVersion = CONFIG_PARAM_VERSION;
ConfigSetParameter(l_ConfigAudio, "Version", M64TYPE_FLOAT, &fVersion);
DebugMessage(M64MSG_INFO, "Updating parameter set version in 'Audio-SDL' config section to %.2f", fVersion);
bSaveConfig = 1;
}
/* set the default values for this plugin */
ConfigSetDefaultFloat(l_ConfigAudio, "Version", CONFIG_PARAM_VERSION, "Mupen64Plus SDL Audio Plugin config parameter version number");
ConfigSetDefaultInt(l_ConfigAudio, "DEFAULT_FREQUENCY", DEFAULT_FREQUENCY, "Frequency which is used if rom doesn't want to change it");
ConfigSetDefaultBool(l_ConfigAudio, "SWAP_CHANNELS", 0, "Swaps left and right channels");
ConfigSetDefaultInt(l_ConfigAudio, "PRIMARY_BUFFER_SIZE", PRIMARY_BUFFER_SIZE, "Size of primary buffer in output samples. This is where audio is loaded after it's extracted from n64's memory.");
ConfigSetDefaultInt(l_ConfigAudio, "PRIMARY_BUFFER_TARGET", PRIMARY_BUFFER_TARGET, "Fullness level target for Primary audio buffer, in equivalent output samples");
ConfigSetDefaultInt(l_ConfigAudio, "SECONDARY_BUFFER_SIZE", SECONDARY_BUFFER_SIZE, "Size of secondary buffer in output samples. This is SDL's hardware buffer.");
ConfigSetDefaultString(l_ConfigAudio, "RESAMPLE", "trivial", "Audio resampling algorithm. src-sinc-best-quality, src-sinc-medium-quality, src-sinc-fastest, src-zero-order-hold, src-linear, speex-fixed-{10-0}, trivial");
ConfigSetDefaultInt(l_ConfigAudio, "VOLUME_CONTROL_TYPE", VOLUME_TYPE_SDL, "Volume control type: 1 = SDL (only affects Mupen64Plus output) 2 = OSS mixer (adjusts master PC volume)");
ConfigSetDefaultInt(l_ConfigAudio, "VOLUME_ADJUST", 5, "Percentage change each time the volume is increased or decreased");
ConfigSetDefaultInt(l_ConfigAudio, "VOLUME_DEFAULT", 80, "Default volume when a game is started. Only used if VOLUME_CONTROL_TYPE is 1");
if (bSaveConfig && ConfigAPIVersion >= 0x020100)
ConfigSaveSection("Audio-SDL");
#ifdef USE_AUDIORESOURCE
l_audioresource = audioresource_init(AUDIO_RESOURCE_GAME, on_audioresource_acquired, NULL);
audioresource_acquire(l_audioresource);
while(!l_audioresource_acquired)
{
DebugMessage(M64MSG_INFO, "Waiting for audioresource...");
g_main_context_iteration(NULL, false);
}
#endif
l_PluginInit = 1;
return M64ERR_SUCCESS;
}
EXPORT m64p_error CALL PluginShutdown(void)
{
if (!l_PluginInit)
return M64ERR_NOT_INIT;
/* reset some local variables */
l_DebugCallback = NULL;
l_DebugCallContext = NULL;
/* make sure our buffer is freed */
if (mixBuffer != NULL)
{
free(mixBuffer);
mixBuffer = NULL;
}
#ifdef USE_AUDIORESOURCE
audioresource_release(l_audioresource);
audioresource_free(l_audioresource);
#endif
l_PluginInit = 0;
return M64ERR_SUCCESS;
}
EXPORT m64p_error CALL PluginGetVersion(m64p_plugin_type *PluginType, int *PluginVersion, int *APIVersion, const char **PluginNamePtr, int *Capabilities)
{
/* set version info */
if (PluginType != NULL)
*PluginType = M64PLUGIN_AUDIO;
if (PluginVersion != NULL)
*PluginVersion = SDL_AUDIO_PLUGIN_VERSION;
if (APIVersion != NULL)
*APIVersion = AUDIO_PLUGIN_API_VERSION;
if (PluginNamePtr != NULL)
*PluginNamePtr = "Mupen64Plus SDL Audio Plugin";
if (Capabilities != NULL)
{
*Capabilities = 0;
}
return M64ERR_SUCCESS;
}
/* ----------- Audio Functions ------------- */
EXPORT void CALL AiDacrateChanged( int SystemType )
{
int f = GameFreq;
if (!l_PluginInit)
return;
switch (SystemType)
{
case SYSTEM_NTSC:
f = 48681812 / (*AudioInfo.AI_DACRATE_REG + 1);
break;
case SYSTEM_PAL:
f = 49656530 / (*AudioInfo.AI_DACRATE_REG + 1);
break;
case SYSTEM_MPAL:
f = 48628316 / (*AudioInfo.AI_DACRATE_REG + 1);
break;
}
InitializeAudio(f);
}
EXPORT void CALL AiLenChanged( void )
{
unsigned int LenReg;
unsigned char *p;
unsigned int CurrLevel, CurrTime, ExpectedLevel, ExpectedTime;
if (critical_failure == 1)
return;
if (!l_PluginInit)
return;
LenReg = *AudioInfo.AI_LEN_REG;
p = AudioInfo.RDRAM + (*AudioInfo.AI_DRAM_ADDR_REG & 0xFFFFFF);
if (buffer_pos + LenReg < primaryBufferBytes)
{
unsigned int i;
SDL_LockAudio();
for ( i = 0 ; i < LenReg ; i += 4 )
{
if(SwapChannels == 0)
{
// Left channel
primaryBuffer[ buffer_pos + i ] = p[ i + 2 ];
primaryBuffer[ buffer_pos + i + 1 ] = p[ i + 3 ];
// Right channel
primaryBuffer[ buffer_pos + i + 2 ] = p[ i ];
primaryBuffer[ buffer_pos + i + 3 ] = p[ i + 1 ];
} else {
// Left channel
primaryBuffer[ buffer_pos + i ] = p[ i ];
primaryBuffer[ buffer_pos + i + 1 ] = p[ i + 1 ];
// Right channel
primaryBuffer[ buffer_pos + i + 2 ] = p[ i + 2];
primaryBuffer[ buffer_pos + i + 3 ] = p[ i + 3 ];
}
}
buffer_pos += i;
SDL_UnlockAudio();
}
else
{
DebugMessage(M64MSG_WARNING, "AiLenChanged(): Audio buffer overflow.");
}
/* Now we need to handle synchronization, by inserting time delay to keep the emulator running at the correct speed */
/* Start by calculating the current Primary buffer fullness in terms of output samples */
CurrLevel = (unsigned int) (((long long) (buffer_pos/N64_SAMPLE_BYTES) * OutputFreq * 100) / (GameFreq * speed_factor));
/* Next, extrapolate to the buffer level at the expected time of the next audio callback, assuming that the
buffer is filled at the same rate as the output frequency */
CurrTime = SDL_GetTicks();
ExpectedTime = last_callback_ticks + ((1000 * SecondaryBufferSize) / OutputFreq);
ExpectedLevel = CurrLevel;
if (CurrTime < ExpectedTime)
ExpectedLevel += (ExpectedTime - CurrTime) * OutputFreq / 1000;
/* If the expected value of the Primary Buffer Fullness at the time of the next audio callback is more than 10
milliseconds ahead of our target buffer fullness level, then insert a delay now */
DebugMessage(M64MSG_VERBOSE, "%03i New audio bytes: %i Time to next callback: %i Current/Expected buffer level: %i/%i",
CurrTime % 1000, LenReg, (int) (ExpectedTime - CurrTime), CurrLevel, ExpectedLevel);
if (ExpectedLevel >= PrimaryBufferTarget + OutputFreq / 100)
{
unsigned int WaitTime = (ExpectedLevel - PrimaryBufferTarget) * 1000 / OutputFreq;
DebugMessage(M64MSG_VERBOSE, " AiLenChanged(): Waiting %ims", WaitTime);
if (l_PausedForSync)
SDL_PauseAudio(0);
l_PausedForSync = 0;
SDL_Delay(WaitTime);
}
/* Or if the expected level of the primary buffer is less than the secondary buffer size
(ie, predicting an underflow), then pause the audio to let the emulator catch up to speed */
else if (ExpectedLevel < SecondaryBufferSize)
{
DebugMessage(M64MSG_VERBOSE, " AiLenChanged(): Possible underflow at next audio callback; pausing playback");
if (!l_PausedForSync)
SDL_PauseAudio(1);
l_PausedForSync = 1;
}
/* otherwise the predicted buffer level is within our tolerance, so everything is okay */
else
{
if (l_PausedForSync)
SDL_PauseAudio(0);
l_PausedForSync = 0;
}
}
EXPORT int CALL InitiateAudio( AUDIO_INFO Audio_Info )
{
if (!l_PluginInit)
return 0;
AudioInfo = Audio_Info;
return 1;
}
static int underrun_count = 0;
#ifdef USE_SRC
static float *_src = NULL;
static unsigned int _src_len = 0;
static float *_dest = NULL;
static unsigned int _dest_len = 0;
static int error;
static SRC_STATE *src_state;
static SRC_DATA src_data;
#endif
#ifdef USE_SPEEX
SpeexResamplerState* spx_state = NULL;
static int error;
#endif
static int resample(unsigned char *input, int input_avail, int oldsamplerate, unsigned char *output, int output_needed, int newsamplerate)
{
int *psrc = (int*)input;
int *pdest = (int*)output;
int i = 0, j = 0;
#ifdef USE_SPEEX
spx_uint32_t in_len, out_len;
if(Resample == RESAMPLER_SPEEX)
{
if(spx_state == NULL)
{
spx_state = speex_resampler_init(2, oldsamplerate, newsamplerate, ResampleQuality, &error);
if(spx_state == NULL)
{
memset(output, 0, output_needed);
return 0;
}
}
speex_resampler_set_rate(spx_state, oldsamplerate, newsamplerate);
in_len = input_avail / 4;
out_len = output_needed / 4;
if ((error = speex_resampler_process_interleaved_int(spx_state, (const spx_int16_t *)input, &in_len, (spx_int16_t *)output, &out_len)))
{
memset(output, 0, output_needed);
return input_avail; // number of bytes consumed
}
return in_len * 4;
}
#endif
#ifdef USE_SRC
if(Resample == RESAMPLER_SRC)
{
// the high quality resampler needs more input than the samplerate ratio would indicate to work properly
if (input_avail > output_needed * 3 / 2)
input_avail = output_needed * 3 / 2; // just to avoid too much short-float-short conversion time
if (_src_len < input_avail*2 && input_avail > 0)
{
if(_src) free(_src);
_src_len = input_avail*2;
_src = malloc(_src_len);
}
if (_dest_len < output_needed*2 && output_needed > 0)
{
if(_dest) free(_dest);
_dest_len = output_needed*2;
_dest = malloc(_dest_len);
}
memset(_src,0,_src_len);
memset(_dest,0,_dest_len);
if(src_state == NULL)
{
src_state = src_new (ResampleQuality, 2, &error);
if(src_state == NULL)
{
memset(output, 0, output_needed);
return 0;
}
}
src_short_to_float_array ((short *) input, _src, input_avail/2);
src_data.end_of_input = 0;
src_data.data_in = _src;
src_data.input_frames = input_avail/4;
src_data.src_ratio = (float) newsamplerate / oldsamplerate;
src_data.data_out = _dest;
src_data.output_frames = output_needed/4;
if ((error = src_process (src_state, &src_data)))
{
memset(output, 0, output_needed);
return input_avail; // number of bytes consumed
}
src_float_to_short_array (_dest, (short *) output, output_needed/2);
return src_data.input_frames_used * 4;
}
#endif
// RESAMPLE == TRIVIAL
if (newsamplerate >= oldsamplerate)
{
int sldf = oldsamplerate;
int const2 = 2*sldf;
int dldf = newsamplerate;
int const1 = const2 - 2*dldf;
int criteria = const2 - dldf;
for (i = 0; i < output_needed/4; i++)
{
pdest[i] = psrc[j];
if(criteria >= 0)
{
++j;
criteria += const1;
}
else criteria += const2;
}
return j * 4; //number of bytes consumed
}
// newsamplerate < oldsamplerate, this only happens when speed_factor > 1
for (i = 0; i < output_needed/4; i++)
{
j = i * oldsamplerate / newsamplerate;
pdest[i] = psrc[j];
}
return j * 4; //number of bytes consumed
}
static void my_audio_callback(void *userdata, unsigned char *stream, int len)
{
int oldsamplerate, newsamplerate;
if (!l_PluginInit)
return;
/* mark the time, for synchronization on the input side */
last_callback_ticks = SDL_GetTicks();
newsamplerate = OutputFreq * 100 / speed_factor;
oldsamplerate = GameFreq;
if (buffer_pos > (unsigned int) (len * oldsamplerate) / newsamplerate)
{
int input_used;
#if defined(HAS_OSS_SUPPORT)
if (VolumeControlType == VOLUME_TYPE_OSS)
{
input_used = resample(primaryBuffer, buffer_pos, oldsamplerate, stream, len, newsamplerate);
}
else
#endif
{
input_used = resample(primaryBuffer, buffer_pos, oldsamplerate, mixBuffer, len, newsamplerate);
memset(stream, 0, len);
SDL_MixAudio(stream, mixBuffer, len, VolSDL);
}
memmove(primaryBuffer, &primaryBuffer[input_used], buffer_pos - input_used);
buffer_pos -= input_used;
DebugMessage(M64MSG_VERBOSE, "%03i my_audio_callback: used %i samples",
last_callback_ticks % 1000, len / SDL_SAMPLE_BYTES);
}
else
{
unsigned int SamplesNeeded = (len * oldsamplerate) / (newsamplerate * SDL_SAMPLE_BYTES);
unsigned int SamplesPresent = buffer_pos / N64_SAMPLE_BYTES;
underrun_count++;
DebugMessage(M64MSG_VERBOSE, "%03i Buffer underflow (%i). %i samples present, %i needed",
last_callback_ticks % 1000, underrun_count, SamplesPresent, SamplesNeeded);
memset(stream , 0, len);
}
}
EXPORT int CALL RomOpen(void)
{
if (!l_PluginInit)
return 0;
ReadConfig();
InitializeAudio(GameFreq);
return 1;
}
static void InitializeSDL(void)
{
DebugMessage(M64MSG_INFO, "Initializing SDL audio subsystem...");
if(SDL_Init(SDL_INIT_AUDIO | SDL_INIT_TIMER) < 0)
{
DebugMessage(M64MSG_ERROR, "Failed to initialize SDL audio subsystem; forcing exit.\n");
critical_failure = 1;
return;
}
critical_failure = 0;
}
static void CreatePrimaryBuffer(void)
{
unsigned int newPrimaryBytes = (unsigned int) ((long long) PrimaryBufferSize * GameFreq * speed_factor /
(OutputFreq * 100)) * N64_SAMPLE_BYTES;
if (primaryBuffer == NULL)
{
DebugMessage(M64MSG_VERBOSE, "Allocating memory for audio buffer: %i bytes.", newPrimaryBytes);
primaryBuffer = (unsigned char*) malloc(newPrimaryBytes);
memset(primaryBuffer, 0, newPrimaryBytes);
primaryBufferBytes = newPrimaryBytes;
}
else if (newPrimaryBytes > primaryBufferBytes) /* primary buffer only grows; there's no point in shrinking it */
{
unsigned char *newPrimaryBuffer = (unsigned char*) malloc(newPrimaryBytes);
unsigned char *oldPrimaryBuffer = primaryBuffer;
SDL_LockAudio();
memcpy(newPrimaryBuffer, oldPrimaryBuffer, primaryBufferBytes);
memset(newPrimaryBuffer + primaryBufferBytes, 0, newPrimaryBytes - primaryBufferBytes);
primaryBuffer = newPrimaryBuffer;
primaryBufferBytes = newPrimaryBytes;
SDL_UnlockAudio();
free(oldPrimaryBuffer);
}
}
static void InitializeAudio(int freq)
{
SDL_AudioSpec *desired, *obtained;
if(SDL_WasInit(SDL_INIT_AUDIO|SDL_INIT_TIMER) == (SDL_INIT_AUDIO|SDL_INIT_TIMER) )
{
DebugMessage(M64MSG_VERBOSE, "InitializeAudio(): SDL Audio sub-system already initialized.");
SDL_PauseAudio(1);
SDL_CloseAudio();
}
else
{
DebugMessage(M64MSG_VERBOSE, "InitializeAudio(): Initializing SDL Audio");
DebugMessage(M64MSG_VERBOSE, "Primary buffer: %i output samples.", PrimaryBufferSize);
DebugMessage(M64MSG_VERBOSE, "Primary target fullness: %i output samples.", PrimaryBufferTarget);
DebugMessage(M64MSG_VERBOSE, "Secondary buffer: %i output samples.", SecondaryBufferSize);
InitializeSDL();
}
if (critical_failure == 1)
return;
GameFreq = freq; // This is important for the sync
if(hardware_spec != NULL) free(hardware_spec);
// Allocate space for SDL_AudioSpec
desired = malloc(sizeof(SDL_AudioSpec));
obtained = malloc(sizeof(SDL_AudioSpec));
if(freq < 11025) OutputFreq = 11025;
else if(freq < 22050) OutputFreq = 22050;
else OutputFreq = 44100;
desired->freq = OutputFreq;
DebugMessage(M64MSG_VERBOSE, "Requesting frequency: %iHz.", desired->freq);
/* 16-bit signed audio */
desired->format=AUDIO_S16SYS;
DebugMessage(M64MSG_VERBOSE, "Requesting format: %i.", desired->format);
/* Stereo */
desired->channels=2;
/* reload these because they gets re-assigned from SDL data below, and InitializeAudio can be called more than once */
PrimaryBufferSize = ConfigGetParamInt(l_ConfigAudio, "PRIMARY_BUFFER_SIZE");
PrimaryBufferTarget = ConfigGetParamInt(l_ConfigAudio, "PRIMARY_BUFFER_TARGET");
SecondaryBufferSize = ConfigGetParamInt(l_ConfigAudio, "SECONDARY_BUFFER_SIZE");
desired->samples = SecondaryBufferSize;
/* Our callback function */
desired->callback = my_audio_callback;
desired->userdata = NULL;
/* Open the audio device */
l_PausedForSync = 1;
if (SDL_OpenAudio(desired, obtained) < 0)
{
DebugMessage(M64MSG_ERROR, "Couldn't open audio: %s", SDL_GetError());
critical_failure = 1;
return;
}
if (desired->format != obtained->format)
{
DebugMessage(M64MSG_WARNING, "Obtained audio format differs from requested.");
}
if (desired->freq != obtained->freq)
{
DebugMessage(M64MSG_WARNING, "Obtained frequency differs from requested.");
}
/* desired spec is no longer needed */
free(desired);
hardware_spec=obtained;
/* allocate memory for audio buffers */
OutputFreq = hardware_spec->freq;
SecondaryBufferSize = hardware_spec->samples;
if (PrimaryBufferTarget < SecondaryBufferSize)
PrimaryBufferTarget = SecondaryBufferSize;
if (PrimaryBufferSize < PrimaryBufferTarget)
PrimaryBufferSize = PrimaryBufferTarget;
if (PrimaryBufferSize < SecondaryBufferSize * 2)
PrimaryBufferSize = SecondaryBufferSize * 2;
CreatePrimaryBuffer();
if (mixBuffer != NULL)
free(mixBuffer);
mixBuffer = (unsigned char*) malloc(SecondaryBufferSize * SDL_SAMPLE_BYTES);
/* preset the last callback time */
if (last_callback_ticks == 0)
last_callback_ticks = SDL_GetTicks();
DebugMessage(M64MSG_VERBOSE, "Frequency: %i", hardware_spec->freq);
DebugMessage(M64MSG_VERBOSE, "Format: %i", hardware_spec->format);
DebugMessage(M64MSG_VERBOSE, "Channels: %i", hardware_spec->channels);
DebugMessage(M64MSG_VERBOSE, "Silence: %i", hardware_spec->silence);
DebugMessage(M64MSG_VERBOSE, "Samples: %i", hardware_spec->samples);
DebugMessage(M64MSG_VERBOSE, "Size: %i", hardware_spec->size);
/* set playback volume */
#if defined(HAS_OSS_SUPPORT)
if (VolumeControlType == VOLUME_TYPE_OSS)
{
VolPercent = volGet();
}
else
#endif
{
VolSDL = SDL_MIX_MAXVOLUME * VolPercent / 100;
}
}
EXPORT void CALL RomClosed( void )
{
if (!l_PluginInit)
return;
if (critical_failure == 1)
return;
DebugMessage(M64MSG_VERBOSE, "Cleaning up SDL sound plugin...");
// Shut down SDL Audio output
SDL_PauseAudio(1);
SDL_CloseAudio();
// Delete the buffer, as we are done producing sound
if (primaryBuffer != NULL)
{
primaryBufferBytes = 0;
free(primaryBuffer);
primaryBuffer = NULL;
}
if (mixBuffer != NULL)
{
free(mixBuffer);
mixBuffer = NULL;
}
// Delete the hardware spec struct
if(hardware_spec != NULL) free(hardware_spec);
hardware_spec = NULL;
// Shutdown the respective subsystems
if(SDL_WasInit(SDL_INIT_AUDIO) != 0) SDL_QuitSubSystem(SDL_INIT_AUDIO);
if(SDL_WasInit(SDL_INIT_TIMER) != 0) SDL_QuitSubSystem(SDL_INIT_TIMER);
}
EXPORT void CALL ProcessAList(void)
{
}
EXPORT void CALL SetSpeedFactor(int percentage)
{
if (!l_PluginInit)
return;
if (percentage >= 10 && percentage <= 300)
speed_factor = percentage;
// we need a different size primary buffer to store the N64 samples when the speed changes
CreatePrimaryBuffer();
}
static void ReadConfig(void)
{
const char *resampler_id;
/* read the configuration values into our static variables */
GameFreq = ConfigGetParamInt(l_ConfigAudio, "DEFAULT_FREQUENCY");
SwapChannels = ConfigGetParamBool(l_ConfigAudio, "SWAP_CHANNELS");
PrimaryBufferSize = ConfigGetParamInt(l_ConfigAudio, "PRIMARY_BUFFER_SIZE");
PrimaryBufferTarget = ConfigGetParamInt(l_ConfigAudio, "PRIMARY_BUFFER_TARGET");
SecondaryBufferSize = ConfigGetParamInt(l_ConfigAudio, "SECONDARY_BUFFER_SIZE");
resampler_id = ConfigGetParamString(l_ConfigAudio, "RESAMPLE");
VolumeControlType = ConfigGetParamInt(l_ConfigAudio, "VOLUME_CONTROL_TYPE");
VolDelta = ConfigGetParamInt(l_ConfigAudio, "VOLUME_ADJUST");
VolPercent = ConfigGetParamInt(l_ConfigAudio, "VOLUME_DEFAULT");
if (!resampler_id) {
Resample = RESAMPLER_TRIVIAL;
DebugMessage(M64MSG_WARNING, "Could not find RESAMPLE configuration; use trivial resampler");
return;
}
if (strcmp(resampler_id, "trivial") == 0) {
Resample = RESAMPLER_TRIVIAL;
return;
}
#ifdef USE_SPEEX
if (strncmp(resampler_id, "speex-fixed-", strlen("speex-fixed-")) == 0) {
int i;
static const char *speex_quality[] = {
"speex-fixed-0",
"speex-fixed-1",
"speex-fixed-2",
"speex-fixed-3",
"speex-fixed-4",
"speex-fixed-5",
"speex-fixed-6",
"speex-fixed-7",
"speex-fixed-8",
"speex-fixed-9",
"speex-fixed-10",
};
Resample = RESAMPLER_SPEEX;
for (i = 0; i < sizeof(speex_quality) / sizeof(*speex_quality); i++) {
if (strcmp(speex_quality[i], resampler_id) == 0) {
ResampleQuality = i;
return;
}
}
DebugMessage(M64MSG_WARNING, "Unknown RESAMPLE configuration %s; use speex-fixed-4 resampler", resampler_id);
ResampleQuality = 4;
return;
}
#endif
#ifdef USE_SRC
if (strncmp(resampler_id, "src-", strlen("src-")) == 0) {
Resample = RESAMPLER_SRC;
if (strcmp(resampler_id, "src-sinc-best-quality") == 0) {
ResampleQuality = SRC_SINC_BEST_QUALITY;
return;
}
if (strcmp(resampler_id, "src-sinc-medium-quality") == 0) {
ResampleQuality = SRC_SINC_MEDIUM_QUALITY;
return;
}
if (strcmp(resampler_id, "src-sinc-fastest") == 0) {
ResampleQuality = SRC_SINC_FASTEST;
return;
}
if (strcmp(resampler_id, "src-zero-order-hold") == 0) {
ResampleQuality = SRC_ZERO_ORDER_HOLD;
return;
}
if (strcmp(resampler_id, "src-linear") == 0) {
ResampleQuality = SRC_LINEAR;
return;
}
DebugMessage(M64MSG_WARNING, "Unknown RESAMPLE configuration %s; use src-sinc-medium-quality resampler", resampler_id);
ResampleQuality = SRC_SINC_MEDIUM_QUALITY;
return;
}
#endif
DebugMessage(M64MSG_WARNING, "Unknown RESAMPLE configuration %s; use trivial resampler", resampler_id);
Resample = RESAMPLER_TRIVIAL;
}
// Returns the most recent ummuted volume level.
static int VolumeGetUnmutedLevel(void)
{
#if defined(HAS_OSS_SUPPORT)
// reload volume if we're using OSS
if (!VolIsMuted && VolumeControlType == VOLUME_TYPE_OSS)
{
return volGet();
}
#endif
return VolPercent;
}
// Sets the volume level based on the contents of VolPercent and VolIsMuted
static void VolumeCommit(void)
{
int levelToCommit = VolIsMuted ? 0 : VolPercent;
#if defined(HAS_OSS_SUPPORT)
if (VolumeControlType == VOLUME_TYPE_OSS)
{
//OSS mixer volume
volSet(levelToCommit);
}
else
#endif
{
VolSDL = SDL_MIX_MAXVOLUME * levelToCommit / 100;
}
}
EXPORT void CALL VolumeMute(void)
{
if (!l_PluginInit)
return;
// Store the volume level in order to restore it later
if (!VolIsMuted)
VolPercent = VolumeGetUnmutedLevel();
// Toogle mute
VolIsMuted = !VolIsMuted;
VolumeCommit();
}
EXPORT void CALL VolumeUp(void)
{
if (!l_PluginInit)
return;
VolumeSetLevel(VolumeGetUnmutedLevel() + VolDelta);
}
EXPORT void CALL VolumeDown(void)
{
if (!l_PluginInit)
return;
VolumeSetLevel(VolumeGetUnmutedLevel() - VolDelta);
}
EXPORT int CALL VolumeGetLevel(void)
{
return VolIsMuted ? 0 : VolumeGetUnmutedLevel();
}
EXPORT void CALL VolumeSetLevel(int level)
{
if (!l_PluginInit)
return;
//if muted, unmute first
VolIsMuted = 0;
// adjust volume
VolPercent = level;
if (VolPercent < 0)
VolPercent = 0;
else if (VolPercent > 100)
VolPercent = 100;
VolumeCommit();
}
EXPORT const char * CALL VolumeGetString(void)
{
static char VolumeString[32];
if (VolIsMuted)
{
strcpy(VolumeString, "Mute");
}
else
{
sprintf(VolumeString, "%i%%", VolPercent);
}
return VolumeString;
}
|