feat(gx): add incomplete 'CGxDeviceGLSDL' (#2)

* chore(build): add vendored SDL 3.0.0 library

* chore(build): add vendored glew-cmake-2.2.0 library

* feat(console): in the presence of -opengl launch flag, change GxApi to OpenGl

* feat(gx): add uncompleted CGxDeviceGLSDL targeting Windows and Linux

* chore(build): change SDL3 linkage from shared (bad) to to static (good)
This commit is contained in:
phaneron 2023-11-18 10:50:16 -05:00 committed by GitHub
parent 934e0fb600
commit 706c8903a1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2043 changed files with 663533 additions and 5 deletions

2131
vendor/sdl-3.0.0/src/audio/SDL_audio.c vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,27 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_audio_c_h_
#define SDL_audio_c_h_
extern void SDL_UpdateAudio(void);
#endif /* SDL_audio_c_h_ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,544 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
// DO NOT EDIT, THIS FILE WAS GENERATED BY build-scripts/gen_audio_resampler_filter.c
#define RESAMPLER_ZERO_CROSSINGS 5
#define RESAMPLER_BITS_PER_SAMPLE 16
#define RESAMPLER_BITS_PER_ZERO_CROSSING ((RESAMPLER_BITS_PER_SAMPLE / 2) + 1)
#define RESAMPLER_SAMPLES_PER_ZERO_CROSSING (1 << RESAMPLER_BITS_PER_ZERO_CROSSING)
#define RESAMPLER_FILTER_SIZE (RESAMPLER_SAMPLES_PER_ZERO_CROSSING * RESAMPLER_ZERO_CROSSINGS)
static const float ResamplerFilter[RESAMPLER_FILTER_SIZE] = {
1.000000000f, 0.000000000f,-0.000000000f, 0.000000000f,-0.000000000f,
0.999993165f,-0.001679888f, 0.000529080f,-0.000151513f, 0.000027455f,
0.999972661f,-0.003351212f, 0.001055794f,-0.000302183f, 0.000054683f,
0.999938488f,-0.005013955f, 0.001580128f,-0.000452009f, 0.000081685f,
0.999890647f,-0.006668099f, 0.002102071f,-0.000600987f, 0.000108459f,
0.999829139f,-0.008313629f, 0.002621611f,-0.000749115f, 0.000135007f,
0.999753966f,-0.009950528f, 0.003138734f,-0.000896389f, 0.000161328f,
0.999665131f,-0.011578779f, 0.003653429f,-0.001042807f, 0.000187423f,
0.999562634f,-0.013198368f, 0.004165684f,-0.001188367f, 0.000213291f,
0.999446480f,-0.014809279f, 0.004675488f,-0.001333066f, 0.000238933f,
0.999316672f,-0.016411497f, 0.005182828f,-0.001476901f, 0.000264348f,
0.999173212f,-0.018005007f, 0.005687694f,-0.001619871f, 0.000289537f,
0.999016105f,-0.019589795f, 0.006190074f,-0.001761971f, 0.000314501f,
0.998845356f,-0.021165846f, 0.006689957f,-0.001903201f, 0.000339239f,
0.998660968f,-0.022733147f, 0.007187332f,-0.002043558f, 0.000363751f,
0.998462946f,-0.024291684f, 0.007682189f,-0.002183039f, 0.000388037f,
0.998251297f,-0.025841443f, 0.008174516f,-0.002321643f, 0.000412099f,
0.998026026f,-0.027382413f, 0.008664303f,-0.002459367f, 0.000435935f,
0.997787138f,-0.028914579f, 0.009151540f,-0.002596209f, 0.000459547f,
0.997534641f,-0.030437930f, 0.009636217f,-0.002732167f, 0.000482934f,
0.997268542f,-0.031952453f, 0.010118324f,-0.002867240f, 0.000506097f,
0.996988847f,-0.033458137f, 0.010597850f,-0.003001425f, 0.000529036f,
0.996695563f,-0.034954970f, 0.011074786f,-0.003134721f, 0.000551752f,
0.996388700f,-0.036442941f, 0.011549123f,-0.003267125f, 0.000574244f,
0.996068266f,-0.037922039f, 0.012020851f,-0.003398637f, 0.000596512f,
0.995734268f,-0.039392253f, 0.012489961f,-0.003529253f, 0.000618558f,
0.995386717f,-0.040853574f, 0.012956443f,-0.003658973f, 0.000640382f,
0.995025621f,-0.042305990f, 0.013420290f,-0.003787796f, 0.000661984f,
0.994650990f,-0.043749493f, 0.013881491f,-0.003915718f, 0.000683363f,
0.994262835f,-0.045184072f, 0.014340039f,-0.004042740f, 0.000704522f,
0.993861166f,-0.046609719f, 0.014795924f,-0.004168860f, 0.000725459f,
0.993445994f,-0.048026424f, 0.015249139f,-0.004294075f, 0.000746176f,
0.993017331f,-0.049434180f, 0.015699676f,-0.004418386f, 0.000766672f,
0.992575187f,-0.050832978f, 0.016147525f,-0.004541790f, 0.000786949f,
0.992119574f,-0.052222809f, 0.016592680f,-0.004664287f, 0.000807006f,
0.991650506f,-0.053603666f, 0.017035133f,-0.004785875f, 0.000826844f,
0.991167995f,-0.054975543f, 0.017474875f,-0.004906553f, 0.000846464f,
0.990672054f,-0.056338431f, 0.017911900f,-0.005026320f, 0.000865865f,
0.990162696f,-0.057692323f, 0.018346201f,-0.005145175f, 0.000885049f,
0.989639935f,-0.059037214f, 0.018777770f,-0.005263117f, 0.000904016f,
0.989103786f,-0.060373097f, 0.019206599f,-0.005380146f, 0.000922766f,
0.988554262f,-0.061699966f, 0.019632684f,-0.005496260f, 0.000941300f,
0.987991380f,-0.063017815f, 0.020056015f,-0.005611458f, 0.000959619f,
0.987415153f,-0.064326639f, 0.020476588f,-0.005725741f, 0.000977722f,
0.986825598f,-0.065626433f, 0.020894396f,-0.005839106f, 0.000995611f,
0.986222730f,-0.066917192f, 0.021309432f,-0.005951554f, 0.001013285f,
0.985606567f,-0.068198912f, 0.021721690f,-0.006063084f, 0.001030746f,
0.984977124f,-0.069471588f, 0.022131165f,-0.006173695f, 0.001047994f,
0.984334418f,-0.070735217f, 0.022537850f,-0.006283387f, 0.001065030f,
0.983678468f,-0.071989794f, 0.022941741f,-0.006392159f, 0.001081853f,
0.983009290f,-0.073235317f, 0.023342830f,-0.006500011f, 0.001098466f,
0.982326903f,-0.074471782f, 0.023741114f,-0.006606943f, 0.001114868f,
0.981631326f,-0.075699186f, 0.024136587f,-0.006712954f, 0.001131059f,
0.980922577f,-0.076917527f, 0.024529243f,-0.006818044f, 0.001147042f,
0.980200675f,-0.078126804f, 0.024919078f,-0.006922213f, 0.001162815f,
0.979465640f,-0.079327013f, 0.025306087f,-0.007025460f, 0.001178380f,
0.978717491f,-0.080518153f, 0.025690266f,-0.007127786f, 0.001193738f,
0.977956250f,-0.081700223f, 0.026071609f,-0.007229191f, 0.001208889f,
0.977181936f,-0.082873221f, 0.026450113f,-0.007329674f, 0.001223833f,
0.976394570f,-0.084037148f, 0.026825773f,-0.007429235f, 0.001238572f,
0.975594175f,-0.085192002f, 0.027198586f,-0.007527875f, 0.001253106f,
0.974780770f,-0.086337783f, 0.027568547f,-0.007625593f, 0.001267436f,
0.973954379f,-0.087474491f, 0.027935652f,-0.007722391f, 0.001281562f,
0.973115024f,-0.088602126f, 0.028299898f,-0.007818267f, 0.001295485f,
0.972262727f,-0.089720690f, 0.028661282f,-0.007913223f, 0.001309207f,
0.971397512f,-0.090830182f, 0.029019799f,-0.008007258f, 0.001322726f,
0.970519401f,-0.091930604f, 0.029375448f,-0.008100373f, 0.001336045f,
0.969628418f,-0.093021958f, 0.029728224f,-0.008192568f, 0.001349164f,
0.968724588f,-0.094104245f, 0.030078125f,-0.008283845f, 0.001362084f,
0.967807935f,-0.095177467f, 0.030425148f,-0.008374202f, 0.001374806f,
0.966878483f,-0.096241627f, 0.030769290f,-0.008463642f, 0.001387329f,
0.965936258f,-0.097296726f, 0.031110550f,-0.008552163f, 0.001399656f,
0.964981285f,-0.098342768f, 0.031448923f,-0.008639768f, 0.001411786f,
0.964013590f,-0.099379756f, 0.031784409f,-0.008726456f, 0.001423721f,
0.963033199f,-0.100407693f, 0.032117005f,-0.008812229f, 0.001435461f,
0.962040138f,-0.101426582f, 0.032446709f,-0.008897086f, 0.001447008f,
0.961034434f,-0.102436428f, 0.032773519f,-0.008981030f, 0.001458361f,
0.960016114f,-0.103437235f, 0.033097434f,-0.009064060f, 0.001469522f,
0.958985206f,-0.104429007f, 0.033418451f,-0.009146178f, 0.001480492f,
0.957941737f,-0.105411749f, 0.033736571f,-0.009227385f, 0.001491271f,
0.956885736f,-0.106385466f, 0.034051790f,-0.009307680f, 0.001501860f,
0.955817231f,-0.107350163f, 0.034364109f,-0.009387067f, 0.001512261f,
0.954736250f,-0.108305845f, 0.034673526f,-0.009465545f, 0.001522473f,
0.953642823f,-0.109252518f, 0.034980040f,-0.009543115f, 0.001532497f,
0.952536979f,-0.110190189f, 0.035283651f,-0.009619779f, 0.001542336f,
0.951418748f,-0.111118864f, 0.035584357f,-0.009695538f, 0.001551988f,
0.950288160f,-0.112038548f, 0.035882158f,-0.009770393f, 0.001561456f,
0.949145245f,-0.112949250f, 0.036177055f,-0.009844346f, 0.001570741f,
0.947990034f,-0.113850976f, 0.036469046f,-0.009917397f, 0.001579842f,
0.946822559f,-0.114743733f, 0.036758132f,-0.009989548f, 0.001588761f,
0.945642850f,-0.115627529f, 0.037044312f,-0.010060800f, 0.001597498f,
0.944450939f,-0.116502372f, 0.037327588f,-0.010131156f, 0.001606056f,
0.943246858f,-0.117368270f, 0.037607958f,-0.010200615f, 0.001614434f,
0.942030639f,-0.118225231f, 0.037885424f,-0.010269180f, 0.001622633f,
0.940802316f,-0.119073264f, 0.038159985f,-0.010336852f, 0.001630655f,
0.939561921f,-0.119912378f, 0.038431644f,-0.010403633f, 0.001638500f,
0.938309487f,-0.120742582f, 0.038700400f,-0.010469524f, 0.001646169f,
0.937045048f,-0.121563886f, 0.038966254f,-0.010534527f, 0.001653663f,
0.935768638f,-0.122376299f, 0.039229208f,-0.010598644f, 0.001660984f,
0.934480291f,-0.123179830f, 0.039489262f,-0.010661876f, 0.001668131f,
0.933180042f,-0.123974491f, 0.039746418f,-0.010724225f, 0.001675106f,
0.931867925f,-0.124760291f, 0.040000678f,-0.010785693f, 0.001681910f,
0.930543975f,-0.125537242f, 0.040252042f,-0.010846282f, 0.001688544f,
0.929208228f,-0.126305353f, 0.040500513f,-0.010905994f, 0.001695008f,
0.927860720f,-0.127064637f, 0.040746092f,-0.010964829f, 0.001701305f,
0.926501487f,-0.127815104f, 0.040988782f,-0.011022792f, 0.001707433f,
0.925130565f,-0.128556767f, 0.041228583f,-0.011079882f, 0.001713396f,
0.923747991f,-0.129289637f, 0.041465499f,-0.011136103f, 0.001719193f,
0.922353802f,-0.130013726f, 0.041699532f,-0.011191456f, 0.001724825f,
0.920948034f,-0.130729047f, 0.041930683f,-0.011245944f, 0.001730294f,
0.919530726f,-0.131435613f, 0.042158956f,-0.011299568f, 0.001735601f,
0.918101916f,-0.132133435f, 0.042384354f,-0.011352330f, 0.001740746f,
0.916661641f,-0.132822528f, 0.042606878f,-0.011404234f, 0.001745730f,
0.915209940f,-0.133502905f, 0.042826532f,-0.011455280f, 0.001750555f,
0.913746852f,-0.134174578f, 0.043043318f,-0.011505472f, 0.001755221f,
0.912272416f,-0.134837563f, 0.043257241f,-0.011554812f, 0.001759730f,
0.910786671f,-0.135491873f, 0.043468303f,-0.011603301f, 0.001764082f,
0.909289657f,-0.136137522f, 0.043676506f,-0.011650942f, 0.001768278f,
0.907781413f,-0.136774525f, 0.043881856f,-0.011697738f, 0.001772320f,
0.906261980f,-0.137402897f, 0.044084355f,-0.011743690f, 0.001776208f,
0.904731398f,-0.138022653f, 0.044284007f,-0.011788802f, 0.001779944f,
0.903189708f,-0.138633807f, 0.044480816f,-0.011833076f, 0.001783528f,
0.901636952f,-0.139236376f, 0.044674785f,-0.011876514f, 0.001786962f,
0.900073170f,-0.139830375f, 0.044865920f,-0.011919118f, 0.001790246f,
0.898498403f,-0.140415819f, 0.045054222f,-0.011960892f, 0.001793381f,
0.896912695f,-0.140992726f, 0.045239698f,-0.012001838f, 0.001796369f,
0.895316086f,-0.141561111f, 0.045422352f,-0.012041958f, 0.001799211f,
0.893708620f,-0.142120991f, 0.045602186f,-0.012081256f, 0.001801907f,
0.892090339f,-0.142672383f, 0.045779208f,-0.012119733f, 0.001804459f,
0.890461286f,-0.143215304f, 0.045953420f,-0.012157393f, 0.001806868f,
0.888821505f,-0.143749771f, 0.046124828f,-0.012194238f, 0.001809134f,
0.887171038f,-0.144275802f, 0.046293436f,-0.012230270f, 0.001811259f,
0.885509930f,-0.144793414f, 0.046459250f,-0.012265494f, 0.001813243f,
0.883838224f,-0.145302625f, 0.046622274f,-0.012299911f, 0.001815089f,
0.882155965f,-0.145803453f, 0.046782515f,-0.012333524f, 0.001816796f,
0.880463198f,-0.146295917f, 0.046939976f,-0.012366337f, 0.001818366f,
0.878759967f,-0.146780035f, 0.047094664f,-0.012398351f, 0.001819800f,
0.877046317f,-0.147255826f, 0.047246583f,-0.012429571f, 0.001821099f,
0.875322295f,-0.147723309f, 0.047395741f,-0.012459998f, 0.001822264f,
0.873587944f,-0.148182503f, 0.047542141f,-0.012489637f, 0.001823295f,
0.871843312f,-0.148633428f, 0.047685790f,-0.012518489f, 0.001824196f,
0.870088444f,-0.149076103f, 0.047826695f,-0.012546558f, 0.001824965f,
0.868323386f,-0.149510548f, 0.047964860f,-0.012573847f, 0.001825604f,
0.866548186f,-0.149936783f, 0.048100292f,-0.012600359f, 0.001826115f,
0.864762890f,-0.150354828f, 0.048232997f,-0.012626097f, 0.001826498f,
0.862967545f,-0.150764704f, 0.048362981f,-0.012651064f, 0.001826754f,
0.861162199f,-0.151166432f, 0.048490252f,-0.012675264f, 0.001826885f,
0.859346899f,-0.151560031f, 0.048614814f,-0.012698699f, 0.001826891f,
0.857521693f,-0.151945524f, 0.048736676f,-0.012721373f, 0.001826774f,
0.855686629f,-0.152322931f, 0.048855842f,-0.012743288f, 0.001826534f,
0.853841755f,-0.152692274f, 0.048972321f,-0.012764449f, 0.001826173f,
0.851987121f,-0.153053574f, 0.049086119f,-0.012784858f, 0.001825691f,
0.850122774f,-0.153406854f, 0.049197244f,-0.012804518f, 0.001825091f,
0.848248764f,-0.153752135f, 0.049305701f,-0.012823434f, 0.001824372f,
0.846365140f,-0.154089440f, 0.049411498f,-0.012841607f, 0.001823536f,
0.844471951f,-0.154418791f, 0.049514643f,-0.012859042f, 0.001822584f,
0.842569248f,-0.154740210f, 0.049615142f,-0.012875742f, 0.001821517f,
0.840657079f,-0.155053721f, 0.049713003f,-0.012891710f, 0.001820336f,
0.838735496f,-0.155359346f, 0.049808234f,-0.012906950f, 0.001819042f,
0.836804549f,-0.155657108f, 0.049900842f,-0.012921465f, 0.001817636f,
0.834864288f,-0.155947032f, 0.049990834f,-0.012935259f, 0.001816120f,
0.832914765f,-0.156229140f, 0.050078219f,-0.012948334f, 0.001814493f,
0.830956029f,-0.156503456f, 0.050163005f,-0.012960695f, 0.001812758f,
0.828988133f,-0.156770004f, 0.050245198f,-0.012972345f, 0.001810916f,
0.827011128f,-0.157028808f, 0.050324808f,-0.012983287f, 0.001808967f,
0.825025066f,-0.157279893f, 0.050401842f,-0.012993525f, 0.001806912f,
0.823029998f,-0.157523282f, 0.050476308f,-0.013003063f, 0.001804753f,
0.821025977f,-0.157759001f, 0.050548215f,-0.013011904f, 0.001802491f,
0.819013055f,-0.157987074f, 0.050617571f,-0.013020051f, 0.001800126f,
0.816991284f,-0.158207526f, 0.050684384f,-0.013027509f, 0.001797660f,
0.814960718f,-0.158420382f, 0.050748664f,-0.013034280f, 0.001795094f,
0.812921409f,-0.158625668f, 0.050810417f,-0.013040370f, 0.001792428f,
0.810873410f,-0.158823410f, 0.050869654f,-0.013045780f, 0.001789664f,
0.808816775f,-0.159013631f, 0.050926382f,-0.013050515f, 0.001786804f,
0.806751557f,-0.159196360f, 0.050980610f,-0.013054579f, 0.001783847f,
0.804677811f,-0.159371620f, 0.051032348f,-0.013057974f, 0.001780795f,
0.802595589f,-0.159539440f, 0.051081605f,-0.013060706f, 0.001777649f,
0.800504946f,-0.159699844f, 0.051128389f,-0.013062778f, 0.001774411f,
0.798405936f,-0.159852860f, 0.051172709f,-0.013064192f, 0.001771080f,
0.796298614f,-0.159998514f, 0.051214574f,-0.013064954f, 0.001767659f,
0.794183034f,-0.160136832f, 0.051253995f,-0.013065067f, 0.001764147f,
0.792059252f,-0.160267843f, 0.051290979f,-0.013064535f, 0.001760547f,
0.789927322f,-0.160391572f, 0.051325537f,-0.013063361f, 0.001756860f,
0.787787300f,-0.160508047f, 0.051357678f,-0.013061549f, 0.001753085f,
0.785639241f,-0.160617296f, 0.051387412f,-0.013059104f, 0.001749225f,
0.783483200f,-0.160719346f, 0.051414747f,-0.013056029f, 0.001745280f,
0.781319234f,-0.160814225f, 0.051439694f,-0.013052327f, 0.001741252f,
0.779147398f,-0.160901960f, 0.051462263f,-0.013048003f, 0.001737141f,
0.776967749f,-0.160982580f, 0.051482462f,-0.013043061f, 0.001732948f,
0.774780342f,-0.161056113f, 0.051500303f,-0.013037504f, 0.001728675f,
0.772585234f,-0.161122587f, 0.051515795f,-0.013031336f, 0.001724323f,
0.770382481f,-0.161182031f, 0.051528947f,-0.013024562f, 0.001719892f,
0.768172142f,-0.161234473f, 0.051539771f,-0.013017185f, 0.001715383f,
0.765954271f,-0.161279942f, 0.051548275f,-0.013009209f, 0.001710798f,
0.763728927f,-0.161318466f, 0.051554471f,-0.013000638f, 0.001706137f,
0.761496167f,-0.161350075f, 0.051558368f,-0.012991476f, 0.001701402f,
0.759256048f,-0.161374798f, 0.051559977f,-0.012981727f, 0.001696593f,
0.757008627f,-0.161392665f, 0.051559309f,-0.012971395f, 0.001691712f,
0.754753963f,-0.161403704f, 0.051556372f,-0.012960484f, 0.001686760f,
0.752492113f,-0.161407945f, 0.051551179f,-0.012948997f, 0.001681737f,
0.750223135f,-0.161405418f, 0.051543739f,-0.012936940f, 0.001676644f,
0.747947088f,-0.161396153f, 0.051534064f,-0.012924315f, 0.001671483f,
0.745664029f,-0.161380179f, 0.051522163f,-0.012911128f, 0.001666254f,
0.743374018f,-0.161357527f, 0.051508048f,-0.012897381f, 0.001660959f,
0.741077112f,-0.161328227f, 0.051491729f,-0.012883079f, 0.001655598f,
0.738773370f,-0.161292309f, 0.051473217f,-0.012868227f, 0.001650173f,
0.736462852f,-0.161249804f, 0.051452522f,-0.012852827f, 0.001644684f,
0.734145616f,-0.161200742f, 0.051429657f,-0.012836884f, 0.001639133f,
0.731821721f,-0.161145154f, 0.051404631f,-0.012820403f, 0.001633520f,
0.729491227f,-0.161083070f, 0.051377457f,-0.012803386f, 0.001627846f,
0.727154193f,-0.161014523f, 0.051348144f,-0.012785839f, 0.001622112f,
0.724810678f,-0.160939542f, 0.051316704f,-0.012767765f, 0.001616320f,
0.722460743f,-0.160858158f, 0.051283148f,-0.012749169f, 0.001610470f,
0.720104446f,-0.160770404f, 0.051247488f,-0.012730054f, 0.001604563f,
0.717741848f,-0.160676310f, 0.051209734f,-0.012710425f, 0.001598600f,
0.715373009f,-0.160575909f, 0.051169898f,-0.012690285f, 0.001592582f,
0.712997988f,-0.160469230f, 0.051127991f,-0.012669640f, 0.001586510f,
0.710616847f,-0.160356307f, 0.051084025f,-0.012648492f, 0.001580385f,
0.708229645f,-0.160237171f, 0.051038012f,-0.012626846f, 0.001574207f,
0.705836443f,-0.160111854f, 0.050989962f,-0.012604706f, 0.001567979f,
0.703437301f,-0.159980389f, 0.050939887f,-0.012582077f, 0.001561700f,
0.701032280f,-0.159842806f, 0.050887799f,-0.012558961f, 0.001555372f,
0.698621441f,-0.159699138f, 0.050833709f,-0.012535365f, 0.001548995f,
0.696204845f,-0.159549419f, 0.050777630f,-0.012511290f, 0.001542571f,
0.693782552f,-0.159393679f, 0.050719572f,-0.012486743f, 0.001536101f,
0.691354624f,-0.159231952f, 0.050659547f,-0.012461726f, 0.001529584f,
0.688921121f,-0.159064270f, 0.050597568f,-0.012436245f, 0.001523023f,
0.686482106f,-0.158890666f, 0.050533646f,-0.012410302f, 0.001516417f,
0.684037639f,-0.158711173f, 0.050467793f,-0.012383903f, 0.001509769f,
0.681587783f,-0.158525823f, 0.050400021f,-0.012357051f, 0.001503079f,
0.679132597f,-0.158334650f, 0.050330342f,-0.012329751f, 0.001496347f,
0.676672145f,-0.158137687f, 0.050258767f,-0.012302006f, 0.001489575f,
0.674206487f,-0.157934966f, 0.050185310f,-0.012273821f, 0.001482764f,
0.671735685f,-0.157726522f, 0.050109981f,-0.012245200f, 0.001475914f,
0.669259802f,-0.157512387f, 0.050032793f,-0.012216147f, 0.001469026f,
0.666778900f,-0.157292594f, 0.049953758f,-0.012186667f, 0.001462101f,
0.664293039f,-0.157067178f, 0.049872888f,-0.012156762f, 0.001455141f,
0.661802283f,-0.156836172f, 0.049790195f,-0.012126439f, 0.001448145f,
0.659306693f,-0.156599609f, 0.049705692f,-0.012095699f, 0.001441115f,
0.656806333f,-0.156357523f, 0.049619391f,-0.012064549f, 0.001434051f,
0.654301263f,-0.156109948f, 0.049531303f,-0.012032992f, 0.001426955f,
0.651791546f,-0.155856918f, 0.049441442f,-0.012001031f, 0.001419827f,
0.649277246f,-0.155598467f, 0.049349819f,-0.011968672f, 0.001412668f,
0.646758423f,-0.155334628f, 0.049256448f,-0.011935918f, 0.001405479f,
0.644235142f,-0.155065436f, 0.049161340f,-0.011902774f, 0.001398261f,
0.641707464f,-0.154790925f, 0.049064507f,-0.011869243f, 0.001391015f,
0.639175452f,-0.154511129f, 0.048965963f,-0.011835330f, 0.001383741f,
0.636639169f,-0.154226083f, 0.048865719f,-0.011801038f, 0.001376440f,
0.634098677f,-0.153935820f, 0.048763788f,-0.011766373f, 0.001369113f,
0.631554040f,-0.153640376f, 0.048660183f,-0.011731337f, 0.001361761f,
0.629005320f,-0.153339784f, 0.048554915f,-0.011695936f, 0.001354384f,
0.626452580f,-0.153034079f, 0.048447999f,-0.011660173f, 0.001346984f,
0.623895883f,-0.152723296f, 0.048339445f,-0.011624053f, 0.001339561f,
0.621335293f,-0.152407470f, 0.048229267f,-0.011587579f, 0.001332116f,
0.618770871f,-0.152086634f, 0.048117478f,-0.011550756f, 0.001324650f,
0.616202682f,-0.151760824f, 0.048004090f,-0.011513587f, 0.001317164f,
0.613630788f,-0.151430075f, 0.047889115f,-0.011476077f, 0.001309657f,
0.611055252f,-0.151094421f, 0.047772566f,-0.011438230f, 0.001302132f,
0.608476138f,-0.150753897f, 0.047654456f,-0.011400050f, 0.001294589f,
0.605893509f,-0.150408538f, 0.047534798f,-0.011361541f, 0.001287028f,
0.603307427f,-0.150058380f, 0.047413604f,-0.011322706f, 0.001279451f,
0.600717957f,-0.149703457f, 0.047290887f,-0.011283552f, 0.001271857f,
0.598125161f,-0.149343804f, 0.047166660f,-0.011244080f, 0.001264249f,
0.595529103f,-0.148979456f, 0.047040936f,-0.011204296f, 0.001256626f,
0.592929846f,-0.148610450f, 0.046913726f,-0.011164202f, 0.001248990f,
0.590327454f,-0.148236818f, 0.046785045f,-0.011123805f, 0.001241340f,
0.587721989f,-0.147858598f, 0.046654904f,-0.011083106f, 0.001233679f,
0.585113515f,-0.147475824f, 0.046523317f,-0.011042111f, 0.001226005f,
0.582502096f,-0.147088532f, 0.046390297f,-0.011000824f, 0.001218321f,
0.579887795f,-0.146696757f, 0.046255856f,-0.010959248f, 0.001210627f,
0.577270674f,-0.146300533f, 0.046120007f,-0.010917387f, 0.001202924f,
0.574650799f,-0.145899898f, 0.045982762f,-0.010875246f, 0.001195211f,
0.572028231f,-0.145494885f, 0.045844136f,-0.010832828f, 0.001187491f,
0.569403034f,-0.145085532f, 0.045704140f,-0.010790137f, 0.001179764f,
0.566775272f,-0.144671872f, 0.045562787f,-0.010747178f, 0.001172030f,
0.564145009f,-0.144253941f, 0.045420091f,-0.010703955f, 0.001164289f,
0.561512306f,-0.143831776f, 0.045276064f,-0.010660470f, 0.001156544f,
0.558877229f,-0.143405412f, 0.045130719f,-0.010616729f, 0.001148794f,
0.556239839f,-0.142974883f, 0.044984069f,-0.010572735f, 0.001141040f,
0.553600201f,-0.142540227f, 0.044836126f,-0.010528493f, 0.001133283f,
0.550958378f,-0.142101478f, 0.044686904f,-0.010484005f, 0.001125523f,
0.548314433f,-0.141658672f, 0.044536416f,-0.010439276f, 0.001117761f,
0.545668429f,-0.141211844f, 0.044384675f,-0.010394310f, 0.001109997f,
0.543020430f,-0.140761032f, 0.044231692f,-0.010349111f, 0.001102233f,
0.540370499f,-0.140306269f, 0.044077482f,-0.010303683f, 0.001094469f,
0.537718699f,-0.139847592f, 0.043922057f,-0.010258029f, 0.001086705f,
0.535065094f,-0.139385036f, 0.043765430f,-0.010212153f, 0.001078942f,
0.532409746f,-0.138918637f, 0.043607614f,-0.010166060f, 0.001071182f,
0.529752718f,-0.138448432f, 0.043448622f,-0.010119753f, 0.001063423f,
0.527094075f,-0.137974455f, 0.043288466f,-0.010073235f, 0.001055668f,
0.524433878f,-0.137496742f, 0.043127159f,-0.010026512f, 0.001047916f,
0.521772191f,-0.137015329f, 0.042964716f,-0.009979586f, 0.001040168f,
0.519109077f,-0.136530252f, 0.042801147f,-0.009932461f, 0.001032425f,
0.516444599f,-0.136041547f, 0.042636467f,-0.009885141f, 0.001024687f,
0.513778819f,-0.135549248f, 0.042470687f,-0.009837631f, 0.001016955f,
0.511111801f,-0.135053393f, 0.042303821f,-0.009789933f, 0.001009230f,
0.508443608f,-0.134554017f, 0.042135882f,-0.009742051f, 0.001001511f,
0.505774302f,-0.134051154f, 0.041966883f,-0.009693990f, 0.000993800f,
0.503103946f,-0.133544842f, 0.041796836f,-0.009645752f, 0.000986098f,
0.500432602f,-0.133035116f, 0.041625754f,-0.009597342f, 0.000978404f,
0.497760334f,-0.132522012f, 0.041453650f,-0.009548764f, 0.000970719f,
0.495087203f,-0.132005564f, 0.041280536f,-0.009500020f, 0.000963044f,
0.492413273f,-0.131485810f, 0.041106427f,-0.009451116f, 0.000955380f,
0.489738606f,-0.130962784f, 0.040931333f,-0.009402053f, 0.000947726f,
0.487063264f,-0.130436523f, 0.040755269f,-0.009352837f, 0.000940083f,
0.484387309f,-0.129907061f, 0.040578247f,-0.009303470f, 0.000932453f,
0.481710804f,-0.129374435f, 0.040400279f,-0.009253957f, 0.000924835f,
0.479033811f,-0.128838680f, 0.040221378f,-0.009204300f, 0.000917229f,
0.476356392f,-0.128299831f, 0.040041558f,-0.009154505f, 0.000909637f,
0.473678610f,-0.127757925f, 0.039860830f,-0.009104573f, 0.000902059f,
0.471000525f,-0.127212997f, 0.039679208f,-0.009054509f, 0.000894496f,
0.468322201f,-0.126665082f, 0.039496704f,-0.009004316f, 0.000886947f,
0.465643698f,-0.126114216f, 0.039313331f,-0.008953998f, 0.000879414f,
0.462965079f,-0.125560435f, 0.039129101f,-0.008903558f, 0.000871896f,
0.460286405f,-0.125003773f, 0.038944027f,-0.008853000f, 0.000864395f,
0.457607738f,-0.124444266f, 0.038758122f,-0.008802328f, 0.000856910f,
0.454929140f,-0.123881950f, 0.038571399f,-0.008751544f, 0.000849443f,
0.452250671f,-0.123316860f, 0.038383869f,-0.008700653f, 0.000841994f,
0.449572393f,-0.122749031f, 0.038195545f,-0.008649657f, 0.000834562f,
0.446894368f,-0.122178499f, 0.038006440f,-0.008598561f, 0.000827149f,
0.444216656f,-0.121605299f, 0.037816567f,-0.008547367f, 0.000819755f,
0.441539319f,-0.121029465f, 0.037625937f,-0.008496080f, 0.000812381f,
0.438862417f,-0.120451035f, 0.037434564f,-0.008444702f, 0.000805026f,
0.436186011f,-0.119870041f, 0.037242460f,-0.008393237f, 0.000797692f,
0.433510163f,-0.119286520f, 0.037049637f,-0.008341688f, 0.000790378f,
0.430834932f,-0.118700507f, 0.036856108f,-0.008290058f, 0.000783086f,
0.428160380f,-0.118112036f, 0.036661885f,-0.008238352f, 0.000775815f,
0.425486566f,-0.117521144f, 0.036466980f,-0.008186572f, 0.000768565f,
0.422813551f,-0.116927863f, 0.036271406f,-0.008134722f, 0.000761339f,
0.420141396f,-0.116332231f, 0.036075174f,-0.008082804f, 0.000754135f,
0.417470160f,-0.115734281f, 0.035878299f,-0.008030823f, 0.000746954f,
0.414799903f,-0.115134048f, 0.035680790f,-0.007978781f, 0.000739796f,
0.412130686f,-0.114531567f, 0.035482662f,-0.007926681f, 0.000732662f,
0.409462568f,-0.113926872f, 0.035283926f,-0.007874528f, 0.000725553f,
0.406795609f,-0.113320000f, 0.035084593f,-0.007822324f, 0.000718468f,
0.404129868f,-0.112710983f, 0.034884678f,-0.007770072f, 0.000711408f,
0.401465405f,-0.112099857f, 0.034684190f,-0.007717775f, 0.000704373f,
0.398802279f,-0.111486655f, 0.034483143f,-0.007665437f, 0.000697364f,
0.396140548f,-0.110871414f, 0.034281549f,-0.007613061f, 0.000690381f,
0.393480274f,-0.110254166f, 0.034079419f,-0.007560650f, 0.000683424f,
0.390821513f,-0.109634947f, 0.033876767f,-0.007508207f, 0.000676494f,
0.388164325f,-0.109013790f, 0.033673602f,-0.007455735f, 0.000669590f,
0.385508769f,-0.108390730f, 0.033469939f,-0.007403238f, 0.000662714f,
0.382854903f,-0.107765801f, 0.033265787f,-0.007350717f, 0.000655866f,
0.380202785f,-0.107139037f, 0.033061161f,-0.007298177f, 0.000649046f,
0.377552474f,-0.106510472f, 0.032856070f,-0.007245621f, 0.000642253f,
0.374904028f,-0.105880140f, 0.032650528f,-0.007193050f, 0.000635490f,
0.372257505f,-0.105248075f, 0.032444545f,-0.007140469f, 0.000628755f,
0.369612962f,-0.104614312f, 0.032238134f,-0.007087881f, 0.000622049f,
0.366970458f,-0.103978882f, 0.032031306f,-0.007035287f, 0.000615373f,
0.364330050f,-0.103341821f, 0.031824073f,-0.006982692f, 0.000608726f,
0.361691794f,-0.102703162f, 0.031616447f,-0.006930098f, 0.000602110f,
0.359055750f,-0.102062939f, 0.031408439f,-0.006877508f, 0.000595523f,
0.356421972f,-0.101421184f, 0.031200061f,-0.006824926f, 0.000588967f,
0.353790520f,-0.100777932f, 0.030991325f,-0.006772353f, 0.000582442f,
0.351161448f,-0.100133216f, 0.030782241f,-0.006719792f, 0.000575948f,
0.348534815f,-0.099487069f, 0.030572821f,-0.006667247f, 0.000569485f,
0.345910675f,-0.098839524f, 0.030363078f,-0.006614721f, 0.000563054f,
0.343289087f,-0.098190615f, 0.030153021f,-0.006562215f, 0.000556654f,
0.340670105f,-0.097540374f, 0.029942663f,-0.006509734f, 0.000550287f,
0.338053786f,-0.096888834f, 0.029732015f,-0.006457279f, 0.000543951f,
0.335440186f,-0.096236029f, 0.029521089f,-0.006404853f, 0.000537649f,
0.332829360f,-0.095581991f, 0.029309894f,-0.006352460f, 0.000531378f,
0.330221364f,-0.094926753f, 0.029098443f,-0.006300101f, 0.000525141f,
0.327616253f,-0.094270347f, 0.028886747f,-0.006247780f, 0.000518937f,
0.325014082f,-0.093612807f, 0.028674817f,-0.006195500f, 0.000512766f,
0.322414906f,-0.092954163f, 0.028462663f,-0.006143262f, 0.000506629f,
0.319818781f,-0.092294450f, 0.028250298f,-0.006091069f, 0.000500525f,
0.317225760f,-0.091633699f, 0.028037732f,-0.006038925f, 0.000494456f,
0.314635898f,-0.090971943f, 0.027824976f,-0.005986831f, 0.000488420f,
0.312049250f,-0.090309213f, 0.027612040f,-0.005934790f, 0.000482419f,
0.309465869f,-0.089645542f, 0.027398936f,-0.005882806f, 0.000476453f,
0.306885810f,-0.088980961f, 0.027185675f,-0.005830879f, 0.000470521f,
0.304309126f,-0.088315503f, 0.026972268f,-0.005779014f, 0.000464623f,
0.301735870f,-0.087649199f, 0.026758724f,-0.005727211f, 0.000458761f,
0.299166097f,-0.086982081f, 0.026545055f,-0.005675475f, 0.000452934f,
0.296599859f,-0.086314180f, 0.026331272f,-0.005623807f, 0.000447143f,
0.294037209f,-0.085645528f, 0.026117385f,-0.005572209f, 0.000441387f,
0.291478200f,-0.084976157f, 0.025903405f,-0.005520685f, 0.000435666f,
0.288922885f,-0.084306097f, 0.025689341f,-0.005469237f, 0.000429981f,
0.286371316f,-0.083635380f, 0.025475206f,-0.005417866f, 0.000424332f,
0.283823545f,-0.082964036f, 0.025261008f,-0.005366575f, 0.000418720f,
0.281279624f,-0.082292098f, 0.025046759f,-0.005315368f, 0.000413143f,
0.278739606f,-0.081619595f, 0.024832469f,-0.005264245f, 0.000407603f,
0.276203542f,-0.080946559f, 0.024618148f,-0.005213209f, 0.000402099f,
0.273671482f,-0.080273020f, 0.024403807f,-0.005162263f, 0.000396632f,
0.271143479f,-0.079599009f, 0.024189455f,-0.005111409f, 0.000391201f,
0.268619584f,-0.078924556f, 0.023975103f,-0.005060649f, 0.000385807f,
0.266099847f,-0.078249692f, 0.023760760f,-0.005009985f, 0.000380450f,
0.263584318f,-0.077574447f, 0.023546438f,-0.004959420f, 0.000375130f,
0.261073049f,-0.076898851f, 0.023332145f,-0.004908956f, 0.000369847f,
0.258566090f,-0.076222934f, 0.023117893f,-0.004858594f, 0.000364602f,
0.256063490f,-0.075546727f, 0.022903690f,-0.004808337f, 0.000359393f,
0.253565299f,-0.074870258f, 0.022689547f,-0.004758188f, 0.000354222f,
0.251071568f,-0.074193558f, 0.022475473f,-0.004708148f, 0.000349088f,
0.248582344f,-0.073516656f, 0.022261479f,-0.004658219f, 0.000343992f,
0.246097678f,-0.072839582f, 0.022047574f,-0.004608404f, 0.000338934f,
0.243617618f,-0.072162365f, 0.021833767f,-0.004558704f, 0.000333913f,
0.241142213f,-0.071485035f, 0.021620069f,-0.004509122f, 0.000328930f,
0.238671511f,-0.070807621f, 0.021406488f,-0.004459659f, 0.000323984f,
0.236205561f,-0.070130151f, 0.021193035f,-0.004410318f, 0.000319077f,
0.233744411f,-0.069452655f, 0.020979718f,-0.004361100f, 0.000314207f,
0.231288109f,-0.068775161f, 0.020766548f,-0.004312008f, 0.000309375f,
0.228836701f,-0.068097699f, 0.020553533f,-0.004263043f, 0.000304582f,
0.226390237f,-0.067420297f, 0.020340683f,-0.004214207f, 0.000299826f,
0.223948762f,-0.066742982f, 0.020128008f,-0.004165503f, 0.000295108f,
0.221512323f,-0.066065785f, 0.019915515f,-0.004116932f, 0.000290429f,
0.219080968f,-0.065388732f, 0.019703215f,-0.004068496f, 0.000285787f,
0.216654743f,-0.064711852f, 0.019491117f,-0.004020196f, 0.000281184f,
0.214233694f,-0.064035173f, 0.019279229f,-0.003972036f, 0.000276619f,
0.211817866f,-0.063358724f, 0.019067561f,-0.003924015f, 0.000272092f,
0.209407307f,-0.062682530f, 0.018856122f,-0.003876137f, 0.000267604f,
0.207002061f,-0.062006621f, 0.018644920f,-0.003828403f, 0.000263153f,
0.204602173f,-0.061331024f, 0.018433965f,-0.003780814f, 0.000258741f,
0.202207689f,-0.060655766f, 0.018223264f,-0.003733373f, 0.000254367f,
0.199818654f,-0.059980874f, 0.018012827f,-0.003686081f, 0.000250032f,
0.197435111f,-0.059306375f, 0.017802663f,-0.003638940f, 0.000245734f,
0.195057107f,-0.058632298f, 0.017592780f,-0.003591951f, 0.000241475f,
0.192684683f,-0.057958667f, 0.017383186f,-0.003545116f, 0.000237254f,
0.190317885f,-0.057285511f, 0.017173891f,-0.003498437f, 0.000233071f,
0.187956756f,-0.056612855f, 0.016964902f,-0.003451915f, 0.000228927f,
0.185601339f,-0.055940727f, 0.016756228f,-0.003405553f, 0.000224821f,
0.183251678f,-0.055269152f, 0.016547878f,-0.003359350f, 0.000220752f,
0.180907815f,-0.054598157f, 0.016339859f,-0.003313310f, 0.000216722f,
0.178569792f,-0.053927768f, 0.016132180f,-0.003267434f, 0.000212731f,
0.176237654f,-0.053258011f, 0.015924850f,-0.003221722f, 0.000208777f,
0.173911440f,-0.052588912f, 0.015717875f,-0.003176177f, 0.000204861f,
0.171591194f,-0.051920496f, 0.015511264f,-0.003130800f, 0.000200983f,
0.169276957f,-0.051252790f, 0.015305026f,-0.003085592f, 0.000197143f,
0.166968771f,-0.050585817f, 0.015099168f,-0.003040555f, 0.000193341f,
0.164666676f,-0.049919605f, 0.014893698f,-0.002995691f, 0.000189577f,
0.162370713f,-0.049254177f, 0.014688624f,-0.002951000f, 0.000185851f,
0.160080923f,-0.048589560f, 0.014483954f,-0.002906485f, 0.000182163f,
0.157797346f,-0.047925777f, 0.014279695f,-0.002862145f, 0.000178512f,
0.155520023f,-0.047262854f, 0.014075855f,-0.002817984f, 0.000174899f,
0.153248993f,-0.046600815f, 0.013872442f,-0.002774001f, 0.000171324f,
0.150984296f,-0.045939685f, 0.013669462f,-0.002730199f, 0.000167786f,
0.148725971f,-0.045279488f, 0.013466925f,-0.002686579f, 0.000164285f,
0.146474057f,-0.044620249f, 0.013264837f,-0.002643141f, 0.000160822f,
0.144228593f,-0.043961991f, 0.013063205f,-0.002599888f, 0.000157396f,
0.141989618f,-0.043304738f, 0.012862038f,-0.002556820f, 0.000154008f,
0.139757169f,-0.042648514f, 0.012661341f,-0.002513938f, 0.000150656f,
0.137531285f,-0.041993344f, 0.012461123f,-0.002471244f, 0.000147342f,
0.135312004f,-0.041339249f, 0.012261390f,-0.002428739f, 0.000144064f,
0.133099362f,-0.040686254f, 0.012062150f,-0.002386425f, 0.000140824f,
0.130893398f,-0.040034383f, 0.011863409f,-0.002344301f, 0.000137620f,
0.128694148f,-0.039383657f, 0.011665175f,-0.002302370f, 0.000134453f,
0.126501649f,-0.038734100f, 0.011467454f,-0.002260631f, 0.000131323f,
0.124315937f,-0.038085735f, 0.011270254f,-0.002219088f, 0.000128229f,
0.122137048f,-0.037438584f, 0.011073581f,-0.002177740f, 0.000125171f,
0.119965018f,-0.036792671f, 0.010877441f,-0.002136588f, 0.000122150f,
0.117799883f,-0.036148017f, 0.010681842f,-0.002095634f, 0.000119165f,
0.115641679f,-0.035504644f, 0.010486791f,-0.002054878f, 0.000116216f,
0.113490439f,-0.034862575f, 0.010292293f,-0.002014322f, 0.000113304f,
0.111346199f,-0.034221832f, 0.010098355f,-0.001973967f, 0.000110427f,
0.109208994f,-0.033582436f, 0.009904984f,-0.001933813f, 0.000107585f,
0.107078857f,-0.032944409f, 0.009712186f,-0.001893861f, 0.000104780f,
0.104955823f,-0.032307773f, 0.009519967f,-0.001854112f, 0.000102010f,
0.102839925f,-0.031672548f, 0.009328334f,-0.001814568f, 0.000099275f,
0.100731196f,-0.031038757f, 0.009137293f,-0.001775228f, 0.000096576f,
0.098629671f,-0.030406420f, 0.008946849f,-0.001736095f, 0.000093911f,
0.096535380f,-0.029775558f, 0.008757010f,-0.001697168f, 0.000091282f,
0.094448358f,-0.029146192f, 0.008567781f,-0.001658448f, 0.000088688f,
0.092368636f,-0.028518343f, 0.008379168f,-0.001619937f, 0.000086128f,
0.090296247f,-0.027892030f, 0.008191177f,-0.001581635f, 0.000083603f,
0.088231221f,-0.027267275f, 0.008003813f,-0.001543542f, 0.000081113f,
0.086173591f,-0.026644097f, 0.007817084f,-0.001505661f, 0.000078656f,
0.084123388f,-0.026022517f, 0.007630993f,-0.001467990f, 0.000076234f,
0.082080642f,-0.025402554f, 0.007445548f,-0.001430532f, 0.000073846f,
0.080045384f,-0.024784228f, 0.007260753f,-0.001393286f, 0.000071492f,
0.078017645f,-0.024167559f, 0.007076615f,-0.001356254f, 0.000069172f,
0.075997454f,-0.023552566f, 0.006893138f,-0.001319435f, 0.000066885f,
0.073984841f,-0.022939269f, 0.006710328f,-0.001282832f, 0.000064632f,
0.071979836f,-0.022327686f, 0.006528191f,-0.001246443f, 0.000062412f,
0.069982467f,-0.021717836f, 0.006346731f,-0.001210271f, 0.000060225f,
0.067992764f,-0.021109739f, 0.006165955f,-0.001174315f, 0.000058072f,
0.066010755f,-0.020503413f, 0.005985866f,-0.001138576f, 0.000055951f,
0.064036468f,-0.019898876f, 0.005806472f,-0.001103055f, 0.000053862f,
0.062069932f,-0.019296147f, 0.005627775f,-0.001067751f, 0.000051807f,
0.060111174f,-0.018695244f, 0.005449782f,-0.001032667f, 0.000049783f,
0.058160222f,-0.018096185f, 0.005272497f,-0.000997801f, 0.000047792f,
0.056217102f,-0.017498988f, 0.005095926f,-0.000963156f, 0.000045833f,
0.054281842f,-0.016903671f, 0.004920073f,-0.000928730f, 0.000043905f,
0.052354468f,-0.016310251f, 0.004744943f,-0.000894525f, 0.000042010f,
0.050435006f,-0.015718746f, 0.004570540f,-0.000860541f, 0.000040146f,
0.048523483f,-0.015129173f, 0.004396870f,-0.000826779f, 0.000038313f,
0.046619924f,-0.014541549f, 0.004223937f,-0.000793238f, 0.000036511f,
0.044724355f,-0.013955892f, 0.004051745f,-0.000759920f, 0.000034740f,
0.042836800f,-0.013372217f, 0.003880299f,-0.000726824f, 0.000033000f,
0.040957285f,-0.012790543f, 0.003709604f,-0.000693951f, 0.000031291f,
0.039085833f,-0.012210884f, 0.003539663f,-0.000661301f, 0.000029612f,
0.037222470f,-0.011633259f, 0.003370481f,-0.000628876f, 0.000027964f,
0.035367219f,-0.011057682f, 0.003202062f,-0.000596674f, 0.000026345f,
0.033520103f,-0.010484170f, 0.003034411f,-0.000564696f, 0.000024756f,
0.031681147f,-0.009912739f, 0.002867532f,-0.000532943f, 0.000023197f,
0.029850373f,-0.009343404f, 0.002701428f,-0.000501414f, 0.000021668f,
0.028027804f,-0.008776182f, 0.002536104f,-0.000470111f, 0.000020168f,
0.026213462f,-0.008211087f, 0.002371564f,-0.000439032f, 0.000018697f,
0.024407370f,-0.007648135f, 0.002207811f,-0.000408180f, 0.000017255f,
0.022609549f,-0.007087341f, 0.002044849f,-0.000377553f, 0.000015842f,
0.020820021f,-0.006528720f, 0.001882682f,-0.000347151f, 0.000014457f,
0.019038808f,-0.005972286f, 0.001721315f,-0.000316976f, 0.000013101f,
0.017265930f,-0.005418056f, 0.001560750f,-0.000287027f, 0.000011773f,
0.015501408f,-0.004866042f, 0.001400991f,-0.000257305f, 0.000010473f,
0.013745262f,-0.004316260f, 0.001242041f,-0.000227808f, 0.000009201f,
0.011997513f,-0.003768723f, 0.001083905f,-0.000198539f, 0.000007956f,
0.010258180f,-0.003223445f, 0.000926585f,-0.000169495f, 0.000006739f,
0.008527283f,-0.002680442f, 0.000770086f,-0.000140679f, 0.000005549f,
0.006804842f,-0.002139725f, 0.000614409f,-0.000112090f, 0.000004386f,
0.005090874f,-0.001601309f, 0.000459559f,-0.000083727f, 0.000003250f,
0.003385399f,-0.001065208f, 0.000305539f,-0.000055591f, 0.000002140f,
0.001688435f,-0.000531434f, 0.000152351f,-0.000027682f, 0.000001057f,
};

1255
vendor/sdl-3.0.0/src/audio/SDL_audiocvt.c vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,125 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
// Get the name of the audio device we use for output
#if defined(SDL_AUDIO_DRIVER_NETBSD) || defined(SDL_AUDIO_DRIVER_OSS)
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h> // For close()
#include "SDL_audiodev_c.h"
#ifndef SDL_PATH_DEV_DSP
#if defined(__NETBSD__) || defined(__OPENBSD__)
#define SDL_PATH_DEV_DSP "/dev/audio"
#else
#define SDL_PATH_DEV_DSP "/dev/dsp"
#endif
#endif
#ifndef SDL_PATH_DEV_DSP24
#define SDL_PATH_DEV_DSP24 "/dev/sound/dsp"
#endif
#ifndef SDL_PATH_DEV_AUDIO
#define SDL_PATH_DEV_AUDIO "/dev/audio"
#endif
static void test_device(const SDL_bool iscapture, const char *fname, int flags, SDL_bool (*test)(int fd))
{
struct stat sb;
if ((stat(fname, &sb) == 0) && (S_ISCHR(sb.st_mode))) {
const int audio_fd = open(fname, flags | O_CLOEXEC, 0);
if (audio_fd >= 0) {
const SDL_bool okay = test(audio_fd);
close(audio_fd);
if (okay) {
static size_t dummyhandle = 0;
dummyhandle++;
SDL_assert(dummyhandle != 0);
/* Note that spec is NULL; while we are opening the device
* endpoint here, the endpoint does not provide any mix format
* information, making this information inaccessible at
* enumeration time
*/
SDL_AddAudioDevice(iscapture, fname, NULL, (void *)(uintptr_t)dummyhandle);
}
}
}
}
static SDL_bool test_stub(int fd)
{
return SDL_TRUE;
}
static void SDL_EnumUnixAudioDevices_Internal(const SDL_bool iscapture, const SDL_bool classic, SDL_bool (*test)(int))
{
const int flags = iscapture ? OPEN_FLAGS_INPUT : OPEN_FLAGS_OUTPUT;
const char *audiodev;
char audiopath[1024];
if (test == NULL) {
test = test_stub;
}
// Figure out what our audio device is
audiodev = SDL_getenv("SDL_PATH_DSP");
if (audiodev == NULL) {
audiodev = SDL_getenv("AUDIODEV");
}
if (audiodev == NULL) {
if (classic) {
audiodev = SDL_PATH_DEV_AUDIO;
} else {
struct stat sb;
// Added support for /dev/sound/\* in Linux 2.4
if (((stat("/dev/sound", &sb) == 0) && S_ISDIR(sb.st_mode)) && ((stat(SDL_PATH_DEV_DSP24, &sb) == 0) && S_ISCHR(sb.st_mode))) {
audiodev = SDL_PATH_DEV_DSP24;
} else {
audiodev = SDL_PATH_DEV_DSP;
}
}
}
test_device(iscapture, audiodev, flags, test);
if (SDL_strlen(audiodev) < (sizeof(audiopath) - 3)) {
int instance = 0;
while (instance <= 64) {
(void)SDL_snprintf(audiopath, SDL_arraysize(audiopath),
"%s%d", audiodev, instance);
instance++;
test_device(iscapture, audiopath, flags, test);
}
}
}
void SDL_EnumUnixAudioDevices(const SDL_bool classic, SDL_bool (*test)(int))
{
SDL_EnumUnixAudioDevices_Internal(SDL_TRUE, classic, test);
SDL_EnumUnixAudioDevices_Internal(SDL_FALSE, classic, test);
}
#endif // Audio device selection

View file

@ -0,0 +1,41 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_audiodev_c_h_
#define SDL_audiodev_c_h_
#include "SDL_internal.h"
#include "SDL_sysaudio.h"
// Open the audio device for playback, and don't block if busy
//#define USE_BLOCKING_WRITES
#ifdef USE_BLOCKING_WRITES
#define OPEN_FLAGS_OUTPUT O_WRONLY
#define OPEN_FLAGS_INPUT O_RDONLY
#else
#define OPEN_FLAGS_OUTPUT (O_WRONLY | O_NONBLOCK)
#define OPEN_FLAGS_INPUT (O_RDONLY | O_NONBLOCK)
#endif
extern void SDL_EnumUnixAudioDevices(const SDL_bool classic, SDL_bool (*test)(int));
#endif // SDL_audiodev_c_h_

View file

@ -0,0 +1,516 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "SDL_audioqueue.h"
#define AUDIO_SPECS_EQUAL(x, y) (((x).format == (y).format) && ((x).channels == (y).channels) && ((x).freq == (y).freq))
struct SDL_AudioTrack
{
SDL_AudioSpec spec;
SDL_bool flushed;
SDL_AudioTrack *next;
size_t (*avail)(void *ctx);
int (*write)(void *ctx, const Uint8 *buf, size_t len);
size_t (*read)(void *ctx, Uint8 *buf, size_t len, SDL_bool advance);
void (*destroy)(void *ctx);
};
struct SDL_AudioQueue
{
SDL_AudioTrack *head;
SDL_AudioTrack *tail;
size_t chunk_size;
};
typedef struct SDL_AudioChunk SDL_AudioChunk;
struct SDL_AudioChunk
{
SDL_AudioChunk *next;
size_t head;
size_t tail;
Uint8 data[SDL_VARIABLE_LENGTH_ARRAY];
};
typedef struct SDL_ChunkedAudioTrack
{
SDL_AudioTrack track;
size_t chunk_size;
SDL_AudioChunk *head;
SDL_AudioChunk *tail;
size_t queued_bytes;
SDL_AudioChunk *free_chunks;
size_t num_free_chunks;
} SDL_ChunkedAudioTrack;
static void DestroyAudioChunk(SDL_AudioChunk *chunk)
{
SDL_free(chunk);
}
static void DestroyAudioChunks(SDL_AudioChunk *chunk)
{
while (chunk) {
SDL_AudioChunk *next = chunk->next;
DestroyAudioChunk(chunk);
chunk = next;
}
}
static void ResetAudioChunk(SDL_AudioChunk *chunk)
{
chunk->next = NULL;
chunk->head = 0;
chunk->tail = 0;
}
static SDL_AudioChunk *CreateAudioChunk(size_t chunk_size)
{
SDL_AudioChunk *chunk = (SDL_AudioChunk *)SDL_malloc(sizeof(*chunk) + chunk_size);
if (chunk == NULL) {
return NULL;
}
ResetAudioChunk(chunk);
return chunk;
}
static void DestroyAudioTrackChunk(SDL_ChunkedAudioTrack *track, SDL_AudioChunk *chunk)
{
// Keeping a list of free chunks reduces memory allocations,
// But also increases the amount of work to perform when freeing the track.
const size_t max_free_bytes = 64 * 1024;
if (track->chunk_size * track->num_free_chunks < max_free_bytes) {
chunk->next = track->free_chunks;
track->free_chunks = chunk;
++track->num_free_chunks;
} else {
DestroyAudioChunk(chunk);
}
}
static SDL_AudioChunk *CreateAudioTrackChunk(SDL_ChunkedAudioTrack *track)
{
if (track->num_free_chunks > 0) {
SDL_AudioChunk *chunk = track->free_chunks;
track->free_chunks = chunk->next;
--track->num_free_chunks;
ResetAudioChunk(chunk);
return chunk;
}
return CreateAudioChunk(track->chunk_size);
}
static size_t AvailChunkedAudioTrack(void *ctx)
{
SDL_ChunkedAudioTrack *track = ctx;
return track->queued_bytes;
}
static int WriteToChunkedAudioTrack(void *ctx, const Uint8 *data, size_t len)
{
SDL_ChunkedAudioTrack *track = ctx;
SDL_AudioChunk *chunk = track->tail;
// Handle the first chunk
if (chunk == NULL) {
chunk = CreateAudioTrackChunk(track);
if (chunk == NULL) {
return SDL_OutOfMemory();
}
SDL_assert((track->head == NULL) && (track->tail == NULL) && (track->queued_bytes == 0));
track->head = chunk;
track->tail = chunk;
}
size_t total = 0;
size_t old_tail = chunk->tail;
size_t chunk_size = track->chunk_size;
while (chunk) {
size_t to_write = chunk_size - chunk->tail;
to_write = SDL_min(to_write, len - total);
SDL_memcpy(&chunk->data[chunk->tail], &data[total], to_write);
total += to_write;
chunk->tail += to_write;
if (total == len) {
break;
}
SDL_AudioChunk *next = CreateAudioTrackChunk(track);
chunk->next = next;
chunk = next;
}
// Roll back the changes if we couldn't write all the data
if (chunk == NULL) {
chunk = track->tail;
SDL_AudioChunk *next = chunk->next;
chunk->next = NULL;
chunk->tail = old_tail;
DestroyAudioChunks(next);
return SDL_OutOfMemory();
}
track->tail = chunk;
track->queued_bytes += total;
return 0;
}
static size_t ReadFromChunkedAudioTrack(void *ctx, Uint8 *data, size_t len, SDL_bool advance)
{
SDL_ChunkedAudioTrack *track = ctx;
SDL_AudioChunk *chunk = track->head;
size_t total = 0;
size_t head = 0;
while (chunk) {
head = chunk->head;
size_t to_read = chunk->tail - head;
to_read = SDL_min(to_read, len - total);
SDL_memcpy(&data[total], &chunk->data[head], to_read);
total += to_read;
SDL_AudioChunk *next = chunk->next;
if (total == len) {
head += to_read;
break;
}
if (advance) {
DestroyAudioTrackChunk(track, chunk);
}
chunk = next;
}
if (advance) {
if (chunk) {
chunk->head = head;
track->head = chunk;
} else {
track->head = NULL;
track->tail = NULL;
}
track->queued_bytes -= total;
}
return total;
}
static void DestroyChunkedAudioTrack(void *ctx)
{
SDL_ChunkedAudioTrack *track = ctx;
DestroyAudioChunks(track->head);
DestroyAudioChunks(track->free_chunks);
SDL_free(track);
}
static SDL_AudioTrack *CreateChunkedAudioTrack(const SDL_AudioSpec *spec, size_t chunk_size)
{
SDL_ChunkedAudioTrack *track = (SDL_ChunkedAudioTrack *)SDL_calloc(1, sizeof(*track));
if (track == NULL) {
SDL_OutOfMemory();
return NULL;
}
SDL_copyp(&track->track.spec, spec);
track->track.avail = AvailChunkedAudioTrack;
track->track.write = WriteToChunkedAudioTrack;
track->track.read = ReadFromChunkedAudioTrack;
track->track.destroy = DestroyChunkedAudioTrack;
track->chunk_size = chunk_size;
return &track->track;
}
SDL_AudioQueue *SDL_CreateAudioQueue(size_t chunk_size)
{
SDL_AudioQueue *queue = (SDL_AudioQueue *)SDL_calloc(1, sizeof(*queue));
if (queue == NULL) {
SDL_OutOfMemory();
return NULL;
}
queue->chunk_size = chunk_size;
return queue;
}
void SDL_DestroyAudioQueue(SDL_AudioQueue *queue)
{
SDL_ClearAudioQueue(queue);
SDL_free(queue);
}
void SDL_ClearAudioQueue(SDL_AudioQueue *queue)
{
SDL_AudioTrack *track = queue->head;
queue->head = NULL;
queue->tail = NULL;
while (track) {
SDL_AudioTrack *next = track->next;
track->destroy(track);
track = next;
}
}
static void SDL_FlushAudioTrack(SDL_AudioTrack *track)
{
track->flushed = SDL_TRUE;
track->write = NULL;
}
void SDL_FlushAudioQueue(SDL_AudioQueue *queue)
{
SDL_AudioTrack *track = queue->tail;
if (track) {
SDL_FlushAudioTrack(track);
}
}
void SDL_PopAudioQueueHead(SDL_AudioQueue *queue)
{
SDL_AudioTrack *track = queue->head;
for (;;) {
SDL_bool flushed = track->flushed;
SDL_AudioTrack *next = track->next;
track->destroy(track);
track = next;
if (flushed) {
break;
}
}
queue->head = track;
if (track == NULL) {
queue->tail = NULL;
}
}
size_t SDL_GetAudioQueueChunkSize(SDL_AudioQueue *queue)
{
return queue->chunk_size;
}
SDL_AudioTrack *SDL_CreateChunkedAudioTrack(const SDL_AudioSpec *spec, const Uint8 *data, size_t len, size_t chunk_size)
{
SDL_AudioTrack *track = CreateChunkedAudioTrack(spec, chunk_size);
if (track == NULL) {
return NULL;
}
if (track->write(track, data, len) != 0) {
track->destroy(track);
return NULL;
}
return track;
}
void SDL_AddTrackToAudioQueue(SDL_AudioQueue *queue, SDL_AudioTrack *track)
{
SDL_AudioTrack *tail = queue->tail;
if (tail) {
// If the spec has changed, make sure to flush the previous track
if (!AUDIO_SPECS_EQUAL(tail->spec, track->spec)) {
SDL_FlushAudioTrack(tail);
}
tail->next = track;
} else {
queue->head = track;
}
queue->tail = track;
}
int SDL_WriteToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const Uint8 *data, size_t len)
{
if (len == 0) {
return 0;
}
SDL_AudioTrack *track = queue->tail;
if ((track != NULL) && !AUDIO_SPECS_EQUAL(track->spec, *spec)) {
SDL_FlushAudioTrack(track);
}
if ((track == NULL) || (track->write == NULL)) {
SDL_AudioTrack *new_track = CreateChunkedAudioTrack(spec, queue->chunk_size);
if (new_track == NULL) {
return SDL_OutOfMemory();
}
if (track) {
track->next = new_track;
} else {
queue->head = new_track;
}
queue->tail = new_track;
track = new_track;
}
return track->write(track, data, len);
}
void *SDL_BeginAudioQueueIter(SDL_AudioQueue *queue)
{
return queue->head;
}
size_t SDL_NextAudioQueueIter(SDL_AudioQueue *queue, void **inout_iter, SDL_AudioSpec *out_spec, SDL_bool *out_flushed)
{
SDL_AudioTrack *iter = *inout_iter;
SDL_assert(iter != NULL);
SDL_copyp(out_spec, &iter->spec);
SDL_bool flushed = SDL_FALSE;
size_t queued_bytes = 0;
while (iter) {
SDL_AudioTrack *track = iter;
iter = iter->next;
size_t avail = track->avail(track);
if (avail >= SDL_SIZE_MAX - queued_bytes) {
queued_bytes = SDL_SIZE_MAX;
flushed = SDL_FALSE;
break;
}
queued_bytes += avail;
flushed = track->flushed;
if (flushed) {
break;
}
}
*inout_iter = iter;
*out_flushed = flushed;
return queued_bytes;
}
int SDL_ReadFromAudioQueue(SDL_AudioQueue *queue, Uint8 *data, size_t len)
{
size_t total = 0;
SDL_AudioTrack *track = queue->head;
for (;;) {
if (track == NULL) {
return SDL_SetError("Reading past end of queue");
}
total += track->read(track, &data[total], len - total, SDL_TRUE);
if (total == len) {
return 0;
}
if (track->flushed) {
return SDL_SetError("Reading past end of flushed track");
}
SDL_AudioTrack *next = track->next;
if (next == NULL) {
return SDL_SetError("Reading past end of incomplete track");
}
queue->head = next;
track->destroy(track);
track = next;
}
}
int SDL_PeekIntoAudioQueue(SDL_AudioQueue *queue, Uint8 *data, size_t len)
{
size_t total = 0;
SDL_AudioTrack *track = queue->head;
for (;;) {
if (track == NULL) {
return SDL_SetError("Peeking past end of queue");
}
total += track->read(track, &data[total], len - total, SDL_FALSE);
if (total == len) {
return 0;
}
if (track->flushed) {
// If we have run out of data, fill the rest with silence.
SDL_memset(&data[total], SDL_GetSilenceValueForFormat(track->spec.format), len - total);
return 0;
}
track = track->next;
}
}

View file

@ -0,0 +1,77 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_audioqueue_h_
#define SDL_audioqueue_h_
// Internal functions used by SDL_AudioStream for queueing audio.
typedef struct SDL_AudioQueue SDL_AudioQueue;
typedef struct SDL_AudioTrack SDL_AudioTrack;
// Create a new audio queue
SDL_AudioQueue *SDL_CreateAudioQueue(size_t chunk_size);
// Destroy an audio queue
void SDL_DestroyAudioQueue(SDL_AudioQueue *queue);
// Completely clear the queue
void SDL_ClearAudioQueue(SDL_AudioQueue *queue);
// Mark the last track as flushed
void SDL_FlushAudioQueue(SDL_AudioQueue *queue);
// Pop the current head track
// REQUIRES: The head track must exist, and must have been flushed
void SDL_PopAudioQueueHead(SDL_AudioQueue *queue);
// Get the chunk size, mostly for use with SDL_CreateChunkedAudioTrack
// This can be called from any thread
size_t SDL_GetAudioQueueChunkSize(SDL_AudioQueue *queue);
// Write data to the end of queue
// REQUIRES: If the spec has changed, the last track must have been flushed
int SDL_WriteToAudioQueue(SDL_AudioQueue *queue, const SDL_AudioSpec *spec, const Uint8 *data, size_t len);
// Create a track without needing to hold any locks
SDL_AudioTrack *SDL_CreateChunkedAudioTrack(const SDL_AudioSpec *spec, const Uint8 *data, size_t len, size_t chunk_size);
// Add a track to the end of the queue
// REQUIRES: `track != NULL`
void SDL_AddTrackToAudioQueue(SDL_AudioQueue *queue, SDL_AudioTrack *track);
// Iterate over the tracks in the queue
void *SDL_BeginAudioQueueIter(SDL_AudioQueue *queue);
// Query and update the track iterator
// REQUIRES: `*inout_iter != NULL` (a valid iterator)
size_t SDL_NextAudioQueueIter(SDL_AudioQueue *queue, void **inout_iter, SDL_AudioSpec *out_spec, SDL_bool *out_flushed);
// Read data from the start of the queue
// REQUIRES: There must be enough data in the queue
int SDL_ReadFromAudioQueue(SDL_AudioQueue *queue, Uint8 *data, size_t len);
// Peek into the start of the queue
// REQUIRES: There must be enough data in the queue, unless it has been flushed, in which case missing data is filled with silence.
int SDL_PeekIntoAudioQueue(SDL_AudioQueue *queue, Uint8 *data, size_t len);
#endif // SDL_audioqueue_h_

View file

@ -0,0 +1,335 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "SDL_sysaudio.h"
#include "SDL_audioresample.h"
// SDL's resampler uses a "bandlimited interpolation" algorithm:
// https://ccrma.stanford.edu/~jos/resample/
#include "SDL_audio_resampler_filter.h"
// For a given srcpos, `srcpos + frame` are sampled, where `-RESAMPLER_ZERO_CROSSINGS < frame <= RESAMPLER_ZERO_CROSSINGS`.
// Note, when upsampling, it is also possible to start sampling from `srcpos = -1`.
#define RESAMPLER_MAX_PADDING_FRAMES (RESAMPLER_ZERO_CROSSINGS + 1)
#define RESAMPLER_FILTER_INTERP_BITS (32 - RESAMPLER_BITS_PER_ZERO_CROSSING)
#define RESAMPLER_FILTER_INTERP_RANGE (1 << RESAMPLER_FILTER_INTERP_BITS)
#define RESAMPLER_SAMPLES_PER_FRAME (RESAMPLER_ZERO_CROSSINGS * 2)
#define RESAMPLER_FULL_FILTER_SIZE (RESAMPLER_SAMPLES_PER_FRAME * (RESAMPLER_SAMPLES_PER_ZERO_CROSSING + 1))
static void ResampleFrame_Scalar(const float *src, float *dst, const float *raw_filter, float interp, int chans)
{
int i, chan;
float filter[RESAMPLER_SAMPLES_PER_FRAME];
// Interpolate between the nearest two filters
for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; i++) {
filter[i] = (raw_filter[i] * (1.0f - interp)) + (raw_filter[i + RESAMPLER_SAMPLES_PER_FRAME] * interp);
}
if (chans == 2) {
float out[2];
out[0] = 0.0f;
out[1] = 0.0f;
for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; i++) {
const float scale = filter[i];
out[0] += src[i * 2 + 0] * scale;
out[1] += src[i * 2 + 1] * scale;
}
dst[0] = out[0];
dst[1] = out[1];
return;
}
if (chans == 1) {
float out = 0.0f;
for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; i++) {
out += src[i] * filter[i];
}
dst[0] = out;
return;
}
for (chan = 0; chan < chans; chan++) {
float f = 0.0f;
for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; i++) {
f += src[i * chans + chan] * filter[i];
}
dst[chan] = f;
}
}
#ifdef SDL_SSE_INTRINSICS
static void SDL_TARGETING("sse") ResampleFrame_SSE(const float *src, float *dst, const float *raw_filter, float interp, int chans)
{
#if RESAMPLER_SAMPLES_PER_FRAME != 10
#error Invalid samples per frame
#endif
// Load the filter
__m128 f0 = _mm_loadu_ps(raw_filter + 0);
__m128 f1 = _mm_loadu_ps(raw_filter + 4);
__m128 f2 = _mm_loadl_pi(_mm_setzero_ps(), (const __m64 *)(raw_filter + 8));
__m128 g0 = _mm_loadu_ps(raw_filter + 10);
__m128 g1 = _mm_loadu_ps(raw_filter + 14);
__m128 g2 = _mm_loadl_pi(_mm_setzero_ps(), (const __m64 *)(raw_filter + 18));
__m128 interp1 = _mm_set1_ps(interp);
__m128 interp2 = _mm_sub_ps(_mm_set1_ps(1.0f), _mm_set1_ps(interp));
// Linear interpolate the filter
f0 = _mm_add_ps(_mm_mul_ps(f0, interp2), _mm_mul_ps(g0, interp1));
f1 = _mm_add_ps(_mm_mul_ps(f1, interp2), _mm_mul_ps(g1, interp1));
f2 = _mm_add_ps(_mm_mul_ps(f2, interp2), _mm_mul_ps(g2, interp1));
if (chans == 2) {
// Duplicate each of the filter elements
g0 = _mm_unpackhi_ps(f0, f0);
f0 = _mm_unpacklo_ps(f0, f0);
g1 = _mm_unpackhi_ps(f1, f1);
f1 = _mm_unpacklo_ps(f1, f1);
f2 = _mm_unpacklo_ps(f2, f2);
// Multiply the filter by the input
f0 = _mm_mul_ps(f0, _mm_loadu_ps(src + 0));
g0 = _mm_mul_ps(g0, _mm_loadu_ps(src + 4));
f1 = _mm_mul_ps(f1, _mm_loadu_ps(src + 8));
g1 = _mm_mul_ps(g1, _mm_loadu_ps(src + 12));
f2 = _mm_mul_ps(f2, _mm_loadu_ps(src + 16));
// Calculate the sum
f0 = _mm_add_ps(_mm_add_ps(_mm_add_ps(f0, g0), _mm_add_ps(f1, g1)), f2);
f0 = _mm_add_ps(f0, _mm_movehl_ps(f0, f0));
// Store the result
_mm_storel_pi((__m64 *)dst, f0);
return;
}
if (chans == 1) {
// Multiply the filter by the input
f0 = _mm_mul_ps(f0, _mm_loadu_ps(src + 0));
f1 = _mm_mul_ps(f1, _mm_loadu_ps(src + 4));
f2 = _mm_mul_ps(f2, _mm_loadl_pi(_mm_setzero_ps(), (const __m64 *)(src + 8)));
// Calculate the sum
f0 = _mm_add_ps(f0, f1);
f0 = _mm_add_ps(_mm_add_ps(f0, f2), _mm_movehl_ps(f0, f0));
f0 = _mm_add_ss(f0, _mm_shuffle_ps(f0, f0, _MM_SHUFFLE(1, 1, 1, 1)));
// Store the result
_mm_store_ss(dst, f0);
return;
}
float filter[RESAMPLER_SAMPLES_PER_FRAME];
_mm_storeu_ps(filter + 0, f0);
_mm_storeu_ps(filter + 4, f1);
_mm_storel_pi((__m64 *)(filter + 8), f2);
int i, chan = 0;
for (; chan + 4 <= chans; chan += 4) {
f0 = _mm_setzero_ps();
for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; i++) {
f0 = _mm_add_ps(f0, _mm_mul_ps(_mm_loadu_ps(&src[i * chans + chan]), _mm_load1_ps(&filter[i])));
}
_mm_storeu_ps(&dst[chan], f0);
}
for (; chan < chans; chan++) {
f0 = _mm_setzero_ps();
for (i = 0; i < RESAMPLER_SAMPLES_PER_FRAME; i++) {
f0 = _mm_add_ss(f0, _mm_mul_ss(_mm_load_ss(&src[i * chans + chan]), _mm_load_ss(&filter[i])));
}
_mm_store_ss(&dst[chan], f0);
}
}
#endif
static void (*ResampleFrame)(const float *src, float *dst, const float *raw_filter, float interp, int chans);
static float FullResamplerFilter[RESAMPLER_FULL_FILTER_SIZE];
void SDL_SetupAudioResampler(void)
{
static SDL_bool setup = SDL_FALSE;
if (setup) {
return;
}
// Build a table combining the left and right wings, for faster access
int i, j;
for (i = 0; i < RESAMPLER_SAMPLES_PER_ZERO_CROSSING; ++i) {
for (j = 0; j < RESAMPLER_ZERO_CROSSINGS; j++) {
int lwing = (i * RESAMPLER_SAMPLES_PER_FRAME) + (RESAMPLER_ZERO_CROSSINGS - 1) - j;
int rwing = (RESAMPLER_FULL_FILTER_SIZE - 1) - lwing;
float value = ResamplerFilter[(i * RESAMPLER_ZERO_CROSSINGS) + j];
FullResamplerFilter[lwing] = value;
FullResamplerFilter[rwing] = value;
}
}
for (i = 0; i < RESAMPLER_ZERO_CROSSINGS; ++i) {
int rwing = i + RESAMPLER_ZERO_CROSSINGS;
int lwing = (RESAMPLER_FULL_FILTER_SIZE - 1) - rwing;
FullResamplerFilter[lwing] = 0.0f;
FullResamplerFilter[rwing] = 0.0f;
}
ResampleFrame = ResampleFrame_Scalar;
#ifdef SDL_SSE_INTRINSICS
if (SDL_HasSSE()) {
ResampleFrame = ResampleFrame_SSE;
}
#endif
setup = SDL_TRUE;
}
Sint64 SDL_GetResampleRate(int src_rate, int dst_rate)
{
SDL_assert(src_rate > 0);
SDL_assert(dst_rate > 0);
Sint64 sample_rate = ((Sint64)src_rate << 32) / (Sint64)dst_rate;
SDL_assert(sample_rate > 0);
return sample_rate;
}
int SDL_GetResamplerHistoryFrames(void)
{
// Even if we aren't currently resampling, make sure to keep enough history in case we need to later.
return RESAMPLER_MAX_PADDING_FRAMES;
}
int SDL_GetResamplerPaddingFrames(Sint64 resample_rate)
{
// This must always be <= SDL_GetResamplerHistoryFrames()
return resample_rate ? RESAMPLER_MAX_PADDING_FRAMES : 0;
}
// These are not general purpose. They do not check for all possible underflow/overflow
SDL_FORCE_INLINE Sint64 ResamplerAdd(Sint64 a, Sint64 b, Sint64 *ret)
{
if ((b > 0) && (a > SDL_MAX_SINT64 - b)) {
return -1;
}
*ret = a + b;
return 0;
}
SDL_FORCE_INLINE Sint64 ResamplerMul(Sint64 a, Sint64 b, Sint64 *ret)
{
if ((b > 0) && (a > SDL_MAX_SINT64 / b)) {
return -1;
}
*ret = a * b;
return 0;
}
Sint64 SDL_GetResamplerInputFrames(Sint64 output_frames, Sint64 resample_rate, Sint64 resample_offset)
{
// Calculate the index of the last input frame, then add 1.
// ((((output_frames - 1) * resample_rate) + resample_offset) >> 32) + 1
Sint64 output_offset;
if (ResamplerMul(output_frames, resample_rate, &output_offset) ||
ResamplerAdd(output_offset, -resample_rate + resample_offset + 0x100000000, &output_offset)) {
output_offset = SDL_MAX_SINT64;
}
Sint64 input_frames = (Sint64)(Sint32)(output_offset >> 32);
input_frames = SDL_max(input_frames, 0);
return input_frames;
}
Sint64 SDL_GetResamplerOutputFrames(Sint64 input_frames, Sint64 resample_rate, Sint64 *inout_resample_offset)
{
Sint64 resample_offset = *inout_resample_offset;
// input_offset = (input_frames << 32) - resample_offset;
Sint64 input_offset;
if (ResamplerMul(input_frames, 0x100000000, &input_offset) ||
ResamplerAdd(input_offset, -resample_offset, &input_offset)) {
input_offset = SDL_MAX_SINT64;
}
// output_frames = div_ceil(input_offset, resample_rate)
Sint64 output_frames = (input_offset > 0) ? (((input_offset - 1) / resample_rate) + 1) : 0;
*inout_resample_offset = (output_frames * resample_rate) - input_offset;
return output_frames;
}
void SDL_ResampleAudio(int chans, const float *src, int inframes, float *dst, int outframes,
Sint64 resample_rate, Sint64 *inout_resample_offset)
{
int i;
Sint64 srcpos = *inout_resample_offset;
SDL_assert(resample_rate > 0);
for (i = 0; i < outframes; i++) {
int srcindex = (int)(Sint32)(srcpos >> 32);
Uint32 srcfraction = (Uint32)(srcpos & 0xFFFFFFFF);
srcpos += resample_rate;
SDL_assert(srcindex >= -1 && srcindex < inframes);
const float *filter = &FullResamplerFilter[(srcfraction >> RESAMPLER_FILTER_INTERP_BITS) * RESAMPLER_SAMPLES_PER_FRAME];
const float interp = (float)(srcfraction & (RESAMPLER_FILTER_INTERP_RANGE - 1)) * (1.0f / RESAMPLER_FILTER_INTERP_RANGE);
const float *frame = &src[(srcindex - (RESAMPLER_ZERO_CROSSINGS - 1)) * chans];
ResampleFrame(frame, dst, filter, interp, chans);
dst += chans;
}
*inout_resample_offset = srcpos - ((Sint64)inframes << 32);
}

View file

@ -0,0 +1,43 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_audioresample_h_
#define SDL_audioresample_h_
// Internal functions used by SDL_AudioStream for resampling audio.
// The resampler uses 32:32 fixed-point arithmetic to track its position.
Sint64 SDL_GetResampleRate(int src_rate, int dst_rate);
int SDL_GetResamplerHistoryFrames(void);
int SDL_GetResamplerPaddingFrames(Sint64 resample_rate);
Sint64 SDL_GetResamplerInputFrames(Sint64 output_frames, Sint64 resample_rate, Sint64 resample_offset);
Sint64 SDL_GetResamplerOutputFrames(Sint64 input_frames, Sint64 resample_rate, Sint64 *inout_resample_offset);
// Resample some audio.
// REQUIRES: `inframes >= SDL_GetResamplerInputFrames(outframes)`
// REQUIRES: At least `SDL_GetResamplerPaddingFrames(...)` extra frames to the left of src, and right of src+inframes
void SDL_ResampleAudio(int chans, const float *src, int inframes, float *dst, int outframes,
Sint64 resample_rate, Sint64 *inout_resample_offset);
#endif // SDL_audioresample_h_

File diff suppressed because it is too large Load diff

293
vendor/sdl-3.0.0/src/audio/SDL_mixer.c vendored Normal file
View file

@ -0,0 +1,293 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
// This provides the default mixing callback for the SDL audio routines
#include "SDL_sysaudio.h"
/* This table is used to add two sound values together and pin
* the value to avoid overflow. (used with permission from ARDI)
*/
static const Uint8 mix8[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03,
0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24,
0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A,
0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45,
0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50,
0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B,
0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71,
0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C,
0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92,
0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D,
0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8,
0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3,
0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE,
0xBF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9,
0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4,
0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA,
0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5,
0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
// The volume ranges from 0 - 128
#define ADJUST_VOLUME(type, s, v) ((s) = (type)(((s) * (v)) / SDL_MIX_MAXVOLUME))
#define ADJUST_VOLUME_U8(s, v) ((s) = (Uint8)(((((s) - 128) * (v)) / SDL_MIX_MAXVOLUME) + 128))
// !!! FIXME: this needs some SIMD magic.
int SDL_MixAudioFormat(Uint8 *dst, const Uint8 *src, SDL_AudioFormat format,
Uint32 len, int volume)
{
if (volume == 0) {
return 0;
}
switch (format) {
case SDL_AUDIO_U8:
{
Uint8 src_sample;
while (len--) {
src_sample = *src;
ADJUST_VOLUME_U8(src_sample, volume);
*dst = mix8[*dst + src_sample];
++dst;
++src;
}
} break;
case SDL_AUDIO_S8:
{
Sint8 *dst8, *src8;
Sint8 src_sample;
int dst_sample;
const int max_audioval = SDL_MAX_SINT8;
const int min_audioval = SDL_MIN_SINT8;
src8 = (Sint8 *)src;
dst8 = (Sint8 *)dst;
while (len--) {
src_sample = *src8;
ADJUST_VOLUME(Sint8, src_sample, volume);
dst_sample = *dst8 + src_sample;
if (dst_sample > max_audioval) {
dst_sample = max_audioval;
} else if (dst_sample < min_audioval) {
dst_sample = min_audioval;
}
*dst8 = (Sint8)dst_sample;
++dst8;
++src8;
}
} break;
case SDL_AUDIO_S16LE:
{
Sint16 src1, src2;
int dst_sample;
const int max_audioval = SDL_MAX_SINT16;
const int min_audioval = SDL_MIN_SINT16;
len /= 2;
while (len--) {
src1 = SDL_SwapLE16(*(Sint16 *)src);
ADJUST_VOLUME(Sint16, src1, volume);
src2 = SDL_SwapLE16(*(Sint16 *)dst);
src += 2;
dst_sample = src1 + src2;
if (dst_sample > max_audioval) {
dst_sample = max_audioval;
} else if (dst_sample < min_audioval) {
dst_sample = min_audioval;
}
*(Sint16 *)dst = SDL_SwapLE16((Sint16)dst_sample);
dst += 2;
}
} break;
case SDL_AUDIO_S16BE:
{
Sint16 src1, src2;
int dst_sample;
const int max_audioval = SDL_MAX_SINT16;
const int min_audioval = SDL_MIN_SINT16;
len /= 2;
while (len--) {
src1 = SDL_SwapBE16(*(Sint16 *)src);
ADJUST_VOLUME(Sint16, src1, volume);
src2 = SDL_SwapBE16(*(Sint16 *)dst);
src += 2;
dst_sample = src1 + src2;
if (dst_sample > max_audioval) {
dst_sample = max_audioval;
} else if (dst_sample < min_audioval) {
dst_sample = min_audioval;
}
*(Sint16 *)dst = SDL_SwapBE16((Sint16)dst_sample);
dst += 2;
}
} break;
case SDL_AUDIO_S32LE:
{
const Uint32 *src32 = (Uint32 *)src;
Uint32 *dst32 = (Uint32 *)dst;
Sint64 src1, src2;
Sint64 dst_sample;
const Sint64 max_audioval = SDL_MAX_SINT32;
const Sint64 min_audioval = SDL_MIN_SINT32;
len /= 4;
while (len--) {
src1 = (Sint64)((Sint32)SDL_SwapLE32(*src32));
src32++;
ADJUST_VOLUME(Sint64, src1, volume);
src2 = (Sint64)((Sint32)SDL_SwapLE32(*dst32));
dst_sample = src1 + src2;
if (dst_sample > max_audioval) {
dst_sample = max_audioval;
} else if (dst_sample < min_audioval) {
dst_sample = min_audioval;
}
*(dst32++) = SDL_SwapLE32((Uint32)((Sint32)dst_sample));
}
} break;
case SDL_AUDIO_S32BE:
{
const Uint32 *src32 = (Uint32 *)src;
Uint32 *dst32 = (Uint32 *)dst;
Sint64 src1, src2;
Sint64 dst_sample;
const Sint64 max_audioval = SDL_MAX_SINT32;
const Sint64 min_audioval = SDL_MIN_SINT32;
len /= 4;
while (len--) {
src1 = (Sint64)((Sint32)SDL_SwapBE32(*src32));
src32++;
ADJUST_VOLUME(Sint64, src1, volume);
src2 = (Sint64)((Sint32)SDL_SwapBE32(*dst32));
dst_sample = src1 + src2;
if (dst_sample > max_audioval) {
dst_sample = max_audioval;
} else if (dst_sample < min_audioval) {
dst_sample = min_audioval;
}
*(dst32++) = SDL_SwapBE32((Uint32)((Sint32)dst_sample));
}
} break;
case SDL_AUDIO_F32LE:
{
const float fmaxvolume = 1.0f / ((float)SDL_MIX_MAXVOLUME);
const float fvolume = (float)volume;
const float *src32 = (float *)src;
float *dst32 = (float *)dst;
float src1, src2;
double dst_sample;
// !!! FIXME: are these right?
const double max_audioval = 3.402823466e+38F;
const double min_audioval = -3.402823466e+38F;
len /= 4;
while (len--) {
src1 = ((SDL_SwapFloatLE(*src32) * fvolume) * fmaxvolume);
src2 = SDL_SwapFloatLE(*dst32);
src32++;
dst_sample = ((double)src1) + ((double)src2);
if (dst_sample > max_audioval) {
dst_sample = max_audioval;
} else if (dst_sample < min_audioval) {
dst_sample = min_audioval;
}
*(dst32++) = SDL_SwapFloatLE((float)dst_sample);
}
} break;
case SDL_AUDIO_F32BE:
{
const float fmaxvolume = 1.0f / ((float)SDL_MIX_MAXVOLUME);
const float fvolume = (float)volume;
const float *src32 = (float *)src;
float *dst32 = (float *)dst;
float src1, src2;
double dst_sample;
// !!! FIXME: are these right?
const double max_audioval = 3.402823466e+38F;
const double min_audioval = -3.402823466e+38F;
len /= 4;
while (len--) {
src1 = ((SDL_SwapFloatBE(*src32) * fvolume) * fmaxvolume);
src2 = SDL_SwapFloatBE(*dst32);
src32++;
dst_sample = ((double)src1) + ((double)src2);
if (dst_sample > max_audioval) {
dst_sample = max_audioval;
} else if (dst_sample < min_audioval) {
dst_sample = min_audioval;
}
*(dst32++) = SDL_SwapFloatBE((float)dst_sample);
}
} break;
default: // If this happens... FIXME!
return SDL_SetError("SDL_MixAudioFormat(): unknown audio format");
}
return 0;
}

View file

@ -0,0 +1,360 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_sysaudio_h_
#define SDL_sysaudio_h_
#include "../SDL_hashtable.h"
#define DEBUG_AUDIOSTREAM 0
#define DEBUG_AUDIO_CONVERT 0
#if DEBUG_AUDIO_CONVERT
#define LOG_DEBUG_AUDIO_CONVERT(from, to) SDL_Log("SDL_AUDIO_CONVERT: Converting %s to %s.\n", from, to);
#else
#define LOG_DEBUG_AUDIO_CONVERT(from, to)
#endif
// These pointers get set during SDL_ChooseAudioConverters() to various SIMD implementations.
extern void (*SDL_Convert_S8_to_F32)(float *dst, const Sint8 *src, int num_samples);
extern void (*SDL_Convert_U8_to_F32)(float *dst, const Uint8 *src, int num_samples);
extern void (*SDL_Convert_S16_to_F32)(float *dst, const Sint16 *src, int num_samples);
extern void (*SDL_Convert_S32_to_F32)(float *dst, const Sint32 *src, int num_samples);
extern void (*SDL_Convert_F32_to_S8)(Sint8 *dst, const float *src, int num_samples);
extern void (*SDL_Convert_F32_to_U8)(Uint8 *dst, const float *src, int num_samples);
extern void (*SDL_Convert_F32_to_S16)(Sint16 *dst, const float *src, int num_samples);
extern void (*SDL_Convert_F32_to_S32)(Sint32 *dst, const float *src, int num_samples);
// !!! FIXME: These are wordy and unlocalized...
#define DEFAULT_OUTPUT_DEVNAME "System audio output device"
#define DEFAULT_INPUT_DEVNAME "System audio capture device"
// these are used when no better specifics are known. We default to CD audio quality.
#define DEFAULT_AUDIO_OUTPUT_FORMAT SDL_AUDIO_S16
#define DEFAULT_AUDIO_OUTPUT_CHANNELS 2
#define DEFAULT_AUDIO_OUTPUT_FREQUENCY 44100
#define DEFAULT_AUDIO_CAPTURE_FORMAT SDL_AUDIO_S16
#define DEFAULT_AUDIO_CAPTURE_CHANNELS 1
#define DEFAULT_AUDIO_CAPTURE_FREQUENCY 44100
#define AUDIO_SPECS_EQUAL(x, y) (((x).format == (y).format) && ((x).channels == (y).channels) && ((x).freq == (y).freq))
typedef struct SDL_AudioDevice SDL_AudioDevice;
typedef struct SDL_LogicalAudioDevice SDL_LogicalAudioDevice;
// Used by src/SDL.c to initialize a particular audio driver.
extern int SDL_InitAudio(const char *driver_name);
// Used by src/SDL.c to shut down previously-initialized audio.
extern void SDL_QuitAudio(void);
// Function to get a list of audio formats, ordered most similar to `format` to least, 0-terminated. Don't free results.
const SDL_AudioFormat *SDL_ClosestAudioFormats(SDL_AudioFormat format);
// Must be called at least once before using converters.
extern void SDL_ChooseAudioConverters(void);
extern void SDL_SetupAudioResampler(void);
/* Backends should call this as devices are added to the system (such as
a USB headset being plugged in), and should also be called for
for every device found during DetectDevices(). */
extern SDL_AudioDevice *SDL_AddAudioDevice(const SDL_bool iscapture, const char *name, const SDL_AudioSpec *spec, void *handle);
/* Backends should call this if an opened audio device is lost.
This can happen due to i/o errors, or a device being unplugged, etc. */
extern void SDL_AudioDeviceDisconnected(SDL_AudioDevice *device);
// Backends should call this if the system default device changes.
extern void SDL_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device);
// Backends should call this if a device's format is changing (opened or not); SDL will update state and carry on with the new format.
extern int SDL_AudioDeviceFormatChanged(SDL_AudioDevice *device, const SDL_AudioSpec *newspec, int new_sample_frames);
// Same as above, but assume the device is already locked.
extern int SDL_AudioDeviceFormatChangedAlreadyLocked(SDL_AudioDevice *device, const SDL_AudioSpec *newspec, int new_sample_frames);
// Find the SDL_AudioDevice associated with the handle supplied to SDL_AddAudioDevice. NULL if not found. DOES NOT LOCK THE DEVICE.
extern SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByHandle(void *handle);
// Find an SDL_AudioDevice, selected by a callback. NULL if not found. DOES NOT LOCK THE DEVICE.
extern SDL_AudioDevice *SDL_FindPhysicalAudioDeviceByCallback(SDL_bool (*callback)(SDL_AudioDevice *device, void *userdata), void *userdata);
// Backends should call this if they change the device format, channels, freq, or sample_frames to keep other state correct.
extern void SDL_UpdatedAudioDeviceFormat(SDL_AudioDevice *device);
// Backends can call this to get a standardized name for a thread to power a specific audio device.
extern char *SDL_GetAudioThreadName(SDL_AudioDevice *device, char *buf, size_t buflen);
// Backends can call these to change a device's refcount.
extern void RefPhysicalAudioDevice(SDL_AudioDevice *device);
extern void UnrefPhysicalAudioDevice(SDL_AudioDevice *device);
// These functions are the heart of the audio threads. Backends can call them directly if they aren't using the SDL-provided thread.
extern void SDL_OutputAudioThreadSetup(SDL_AudioDevice *device);
extern SDL_bool SDL_OutputAudioThreadIterate(SDL_AudioDevice *device);
extern void SDL_OutputAudioThreadShutdown(SDL_AudioDevice *device);
extern void SDL_CaptureAudioThreadSetup(SDL_AudioDevice *device);
extern SDL_bool SDL_CaptureAudioThreadIterate(SDL_AudioDevice *device);
extern void SDL_CaptureAudioThreadShutdown(SDL_AudioDevice *device);
extern void SDL_AudioThreadFinalize(SDL_AudioDevice *device);
// this gets used from the audio device threads. It has rules, don't use this if you don't know how to use it!
extern void ConvertAudio(int num_frames, const void *src, SDL_AudioFormat src_format, int src_channels,
void *dst, SDL_AudioFormat dst_format, int dst_channels, void* scratch);
// Special case to let something in SDL_audiocvt.c access something in SDL_audio.c. Don't use this.
extern void OnAudioStreamCreated(SDL_AudioStream *stream);
extern void OnAudioStreamDestroy(SDL_AudioStream *stream);
typedef struct SDL_AudioDriverImpl
{
void (*DetectDevices)(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture);
int (*OpenDevice)(SDL_AudioDevice *device);
void (*ThreadInit)(SDL_AudioDevice *device); // Called by audio thread at start
void (*ThreadDeinit)(SDL_AudioDevice *device); // Called by audio thread at end
int (*WaitDevice)(SDL_AudioDevice *device);
int (*PlayDevice)(SDL_AudioDevice *device, const Uint8 *buffer, int buflen); // buffer and buflen are always from GetDeviceBuf, passed here for convenience.
Uint8 *(*GetDeviceBuf)(SDL_AudioDevice *device, int *buffer_size);
int (*WaitCaptureDevice)(SDL_AudioDevice *device);
int (*CaptureFromDevice)(SDL_AudioDevice *device, void *buffer, int buflen);
void (*FlushCapture)(SDL_AudioDevice *device);
void (*CloseDevice)(SDL_AudioDevice *device);
void (*FreeDeviceHandle)(SDL_AudioDevice *device); // SDL is done with this device; free the handle from SDL_AddAudioDevice()
void (*DeinitializeStart)(void); // SDL calls this, then starts destroying objects, then calls Deinitialize. This is a good place to stop hotplug detection.
void (*Deinitialize)(void);
// Some flags to push duplicate code into the core and reduce #ifdefs.
SDL_bool ProvidesOwnCallbackThread; // !!! FIXME: rename this, it's not a callback thread anymore.
SDL_bool HasCaptureSupport;
SDL_bool OnlyHasDefaultOutputDevice;
SDL_bool OnlyHasDefaultCaptureDevice; // !!! FIXME: is there ever a time where you'd have a default output and not a default capture (or vice versa)?
} SDL_AudioDriverImpl;
typedef struct SDL_PendingAudioDeviceEvent
{
Uint32 type;
SDL_AudioDeviceID devid;
struct SDL_PendingAudioDeviceEvent *next;
} SDL_PendingAudioDeviceEvent;
typedef struct SDL_AudioDriver
{
const char *name; // The name of this audio driver
const char *desc; // The description of this audio driver
SDL_AudioDriverImpl impl; // the backend's interface
SDL_RWLock *device_hash_lock; // A rwlock that protects `device_hash`
SDL_HashTable *device_hash; // the collection of currently-available audio devices (capture, playback, logical and physical!)
SDL_AudioStream *existing_streams; // a list of all existing SDL_AudioStreams.
SDL_AudioDeviceID default_output_device_id;
SDL_AudioDeviceID default_capture_device_id;
SDL_PendingAudioDeviceEvent pending_events;
SDL_PendingAudioDeviceEvent *pending_events_tail;
// !!! FIXME: most (all?) of these don't have to be atomic.
SDL_AtomicInt output_device_count;
SDL_AtomicInt capture_device_count;
SDL_AtomicInt last_device_instance_id; // increments on each device add to provide unique instance IDs
SDL_AtomicInt shutting_down; // non-zero during SDL_Quit, so we known not to accept any last-minute device hotplugs.
} SDL_AudioDriver;
struct SDL_AudioQueue; // forward decl.
struct SDL_AudioStream
{
SDL_Mutex* lock;
SDL_PropertiesID props;
SDL_AudioStreamCallback get_callback;
void *get_callback_userdata;
SDL_AudioStreamCallback put_callback;
void *put_callback_userdata;
SDL_AudioSpec src_spec;
SDL_AudioSpec dst_spec;
float freq_ratio;
struct SDL_AudioQueue* queue;
Uint64 total_bytes_queued;
SDL_AudioSpec input_spec; // The spec of input data currently being processed
Sint64 resample_offset;
Uint8 *work_buffer; // used for scratch space during data conversion/resampling.
size_t work_buffer_allocation;
Uint8 *history_buffer; // history for left padding and future sample rate changes.
size_t history_buffer_allocation;
SDL_bool simplified; // SDL_TRUE if created via SDL_OpenAudioDeviceStream
SDL_LogicalAudioDevice *bound_device;
SDL_AudioStream *next_binding;
SDL_AudioStream *prev_binding;
SDL_AudioStream *prev; // linked list of all existing streams (so we can free them on shutdown).
SDL_AudioStream *next; // linked list of all existing streams (so we can free them on shutdown).
};
/* Logical devices are an abstraction in SDL3; you can open the same physical
device multiple times, and each will result in an object with its own set
of bound audio streams, etc, even though internally these are all processed
as a group when mixing the final output for the physical device. */
struct SDL_LogicalAudioDevice
{
// the unique instance ID of this device.
SDL_AudioDeviceID instance_id;
// The physical device associated with this opened device.
SDL_AudioDevice *physical_device;
// If whole logical device is paused (process no streams bound to this device).
SDL_AtomicInt paused;
// double-linked list of all audio streams currently bound to this opened device.
SDL_AudioStream *bound_streams;
// SDL_TRUE if this was opened as a default device.
SDL_bool opened_as_default;
// SDL_TRUE if device was opened with SDL_OpenAudioDeviceStream (so it forbids binding changes, etc).
SDL_bool simplified;
// If non-NULL, callback into the app that lets them access the final postmix buffer.
SDL_AudioPostmixCallback postmix;
// App-supplied pointer for postmix callback.
void *postmix_userdata;
// double-linked list of opened devices on the same physical device.
SDL_LogicalAudioDevice *next;
SDL_LogicalAudioDevice *prev;
};
struct SDL_AudioDevice
{
// A mutex for locking access to this struct
SDL_Mutex *lock;
// Reference count of the device; logical devices, device threads, etc, add to this.
SDL_AtomicInt refcount;
// These are, initially, set from current_audio, but we might swap them out with Zombie versions on disconnect/failure.
int (*WaitDevice)(SDL_AudioDevice *device);
int (*PlayDevice)(SDL_AudioDevice *device, const Uint8 *buffer, int buflen);
Uint8 *(*GetDeviceBuf)(SDL_AudioDevice *device, int *buffer_size);
int (*WaitCaptureDevice)(SDL_AudioDevice *device);
int (*CaptureFromDevice)(SDL_AudioDevice *device, void *buffer, int buflen);
void (*FlushCapture)(SDL_AudioDevice *device);
// human-readable name of the device. ("SoundBlaster Pro 16")
char *name;
// the unique instance ID of this device.
SDL_AudioDeviceID instance_id;
// a way for the backend to identify this device _when not opened_
void *handle;
// The device's current audio specification
SDL_AudioSpec spec;
int buffer_size;
// The device's default audio specification
SDL_AudioSpec default_spec;
// Number of sample frames the devices wants per-buffer.
int sample_frames;
// Value to use for SDL_memset to silence a buffer in this device's format
int silence_value;
// non-zero if we are signaling the audio thread to end.
SDL_AtomicInt shutdown;
// non-zero if this was a disconnected device and we're waiting for it to be decommissioned.
SDL_AtomicInt zombie;
// SDL_TRUE if this is a capture device instead of an output device
SDL_bool iscapture;
// SDL_TRUE if audio thread can skip silence/mix/convert stages and just do a basic memcpy.
SDL_bool simple_copy;
// Scratch buffers used for mixing.
Uint8 *work_buffer;
Uint8 *mix_buffer;
float *postmix_buffer;
// Size of work_buffer (and mix_buffer) in bytes.
int work_buffer_size;
// A thread to feed the audio device
SDL_Thread *thread;
// SDL_TRUE if this physical device is currently opened by the backend.
SDL_bool currently_opened;
// Data private to this driver
struct SDL_PrivateAudioData *hidden;
// All logical devices associated with this physical device.
SDL_LogicalAudioDevice *logical_devices;
};
typedef struct AudioBootStrap
{
const char *name;
const char *desc;
SDL_bool (*init)(SDL_AudioDriverImpl *impl);
SDL_bool demand_only; // if SDL_TRUE: request explicitly, or it won't be available.
} AudioBootStrap;
// Not all of these are available in a given build. Use #ifdefs, etc.
extern AudioBootStrap PIPEWIRE_bootstrap;
extern AudioBootStrap PULSEAUDIO_bootstrap;
extern AudioBootStrap ALSA_bootstrap;
extern AudioBootStrap JACK_bootstrap;
extern AudioBootStrap SNDIO_bootstrap;
extern AudioBootStrap NETBSDAUDIO_bootstrap;
extern AudioBootStrap DSP_bootstrap;
extern AudioBootStrap WASAPI_bootstrap;
extern AudioBootStrap DSOUND_bootstrap;
extern AudioBootStrap WINMM_bootstrap;
extern AudioBootStrap HAIKUAUDIO_bootstrap;
extern AudioBootStrap COREAUDIO_bootstrap;
extern AudioBootStrap DISKAUDIO_bootstrap;
extern AudioBootStrap DUMMYAUDIO_bootstrap;
extern AudioBootStrap AAUDIO_bootstrap;
extern AudioBootStrap OPENSLES_bootstrap;
extern AudioBootStrap ANDROIDAUDIO_bootstrap;
extern AudioBootStrap PS2AUDIO_bootstrap;
extern AudioBootStrap PSPAUDIO_bootstrap;
extern AudioBootStrap VITAAUD_bootstrap;
extern AudioBootStrap N3DSAUDIO_bootstrap;
extern AudioBootStrap EMSCRIPTENAUDIO_bootstrap;
extern AudioBootStrap QSAAUDIO_bootstrap;
#endif // SDL_sysaudio_h_

2129
vendor/sdl-3.0.0/src/audio/SDL_wave.c vendored Normal file

File diff suppressed because it is too large Load diff

151
vendor/sdl-3.0.0/src/audio/SDL_wave.h vendored Normal file
View file

@ -0,0 +1,151 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
/* RIFF WAVE files are little-endian */
/*******************************************/
/* Define values for Microsoft WAVE format */
/*******************************************/
/* FOURCC */
#define RIFF 0x46464952 /* "RIFF" */
#define WAVE 0x45564157 /* "WAVE" */
#define FACT 0x74636166 /* "fact" */
#define LIST 0x5453494c /* "LIST" */
#define BEXT 0x74786562 /* "bext" */
#define JUNK 0x4B4E554A /* "JUNK" */
#define FMT 0x20746D66 /* "fmt " */
#define DATA 0x61746164 /* "data" */
/* Format tags */
#define UNKNOWN_CODE 0x0000
#define PCM_CODE 0x0001
#define MS_ADPCM_CODE 0x0002
#define IEEE_FLOAT_CODE 0x0003
#define ALAW_CODE 0x0006
#define MULAW_CODE 0x0007
#define IMA_ADPCM_CODE 0x0011
#define MPEG_CODE 0x0050
#define MPEGLAYER3_CODE 0x0055
#define EXTENSIBLE_CODE 0xFFFE
/* Stores the WAVE format information. */
typedef struct WaveFormat
{
Uint16 formattag; /* Raw value of the first field in the fmt chunk data. */
Uint16 encoding; /* Actual encoding, possibly from the extensible header. */
Uint16 channels; /* Number of channels. */
Uint32 frequency; /* Sampling rate in Hz. */
Uint32 byterate; /* Average bytes per second. */
Uint16 blockalign; /* Bytes per block. */
Uint16 bitspersample; /* Currently supported are 8, 16, 24, 32, and 4 for ADPCM. */
/* Extra information size. Number of extra bytes starting at byte 18 in the
* fmt chunk data. This is at least 22 for the extensible header.
*/
Uint16 extsize;
/* Extensible WAVE header fields */
Uint16 validsamplebits;
Uint32 samplesperblock; /* For compressed formats. Can be zero. Actually 16 bits in the header. */
Uint32 channelmask;
Uint8 subformat[16]; /* A format GUID. */
} WaveFormat;
/* Stores information on the fact chunk. */
typedef struct WaveFact
{
/* Represents the state of the fact chunk in the WAVE file.
* Set to -1 if the fact chunk is invalid.
* Set to 0 if the fact chunk is not present
* Set to 1 if the fact chunk is present and valid.
* Set to 2 if samplelength is going to be used as the number of sample frames.
*/
Sint32 status;
/* Version 1 of the RIFF specification calls the field in the fact chunk
* dwFileSize. The Standards Update then calls it dwSampleLength and specifies
* that it is 'the length of the data in samples'. WAVE files from Windows
* with this chunk have it set to the samples per channel (sample frames).
* This is useful to truncate compressed audio to a specific sample count
* because a compressed block is usually decoded to a fixed number of
* sample frames.
*/
Uint32 samplelength; /* Raw sample length value from the fact chunk. */
} WaveFact;
/* Generic struct for the chunks in the WAVE file. */
typedef struct WaveChunk
{
Uint32 fourcc; /* FOURCC of the chunk. */
Uint32 length; /* Size of the chunk data. */
Sint64 position; /* Position of the data in the stream. */
Uint8 *data; /* When allocated, this points to the chunk data. length is used for the memory allocation size. */
size_t size; /* Number of bytes in data that could be read from the stream. Can be smaller than length. */
} WaveChunk;
/* Controls how the size of the RIFF chunk affects the loading of a WAVE file. */
typedef enum WaveRiffSizeHint
{
RiffSizeNoHint,
RiffSizeForce,
RiffSizeIgnoreZero,
RiffSizeIgnore,
RiffSizeMaximum
} WaveRiffSizeHint;
/* Controls how a truncated WAVE file is handled. */
typedef enum WaveTruncationHint
{
TruncNoHint,
TruncVeryStrict,
TruncStrict,
TruncDropFrame,
TruncDropBlock
} WaveTruncationHint;
/* Controls how the fact chunk affects the loading of a WAVE file. */
typedef enum WaveFactChunkHint
{
FactNoHint,
FactTruncate,
FactStrict,
FactIgnoreZero,
FactIgnore
} WaveFactChunkHint;
typedef struct WaveFile
{
WaveChunk chunk;
WaveFormat format;
WaveFact fact;
/* Number of sample frames that will be decoded. Calculated either with the
* size of the data chunk or, if the appropriate hint is enabled, with the
* sample length value from the fact chunk.
*/
Sint64 sampleframes;
void *decoderdata; /* Some decoders require extra data for a state. */
WaveRiffSizeHint riffhint;
WaveTruncationHint trunchint;
WaveFactChunkHint facthint;
} WaveFile;

View file

@ -0,0 +1,536 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_AUDIO_DRIVER_AAUDIO
#include "../SDL_sysaudio.h"
#include "SDL_aaudio.h"
#include "../../core/android/SDL_android.h"
#include <stdbool.h>
#include <aaudio/AAudio.h>
#if __ANDROID_API__ < 31
#define AAUDIO_FORMAT_PCM_I32 4
#endif
struct SDL_PrivateAudioData
{
AAudioStream *stream;
int num_buffers;
Uint8 *mixbuf; // Raw mixing buffer
size_t mixbuf_bytes; // num_buffers * device->buffer_size
size_t callback_bytes;
size_t processed_bytes;
SDL_Semaphore *semaphore;
SDL_AtomicInt error_callback_triggered;
SDL_bool resume; // Resume device if it was paused automatically
};
// Debug
#if 0
#define LOGI(...) SDL_Log(__VA_ARGS__);
#else
#define LOGI(...)
#endif
#define LIB_AAUDIO_SO "libaaudio.so"
typedef struct AAUDIO_Data
{
void *handle;
#define SDL_PROC(ret, func, params) ret (*func) params;
#include "SDL_aaudiofuncs.h"
} AAUDIO_Data;
static AAUDIO_Data ctx;
static int AAUDIO_LoadFunctions(AAUDIO_Data *data)
{
#define SDL_PROC(ret, func, params) \
do { \
data->func = (ret (*) params)SDL_LoadFunction(data->handle, #func); \
if (!data->func) { \
return SDL_SetError("Couldn't load AAUDIO function %s: %s", #func, SDL_GetError()); \
} \
} while (0);
#include "SDL_aaudiofuncs.h"
return 0;
}
static void AAUDIO_errorCallback(AAudioStream *stream, void *userData, aaudio_result_t error)
{
LOGI("SDL AAUDIO_errorCallback: %d - %s", error, ctx.AAudio_convertResultToText(error));
// You MUST NOT close the audio stream from this callback, so we cannot call SDL_AudioDeviceDisconnected here.
// Just flag the device so we can kill it in PlayDevice instead.
SDL_AudioDevice *device = (SDL_AudioDevice *) userData;
SDL_AtomicSet(&device->hidden->error_callback_triggered, (int) error); // AAUDIO_OK is zero, so !triggered means no error.
SDL_PostSemaphore(device->hidden->semaphore); // in case we're blocking in WaitDevice.
}
static aaudio_data_callback_result_t AAUDIO_dataCallback(AAudioStream *stream, void *userData, void *audioData, int32_t numFrames)
{
SDL_AudioDevice *device = (SDL_AudioDevice *) userData;
struct SDL_PrivateAudioData *hidden = device->hidden;
size_t framesize = SDL_AUDIO_FRAMESIZE(device->spec);
size_t callback_bytes = numFrames * framesize;
size_t old_buffer_index = hidden->callback_bytes / device->buffer_size;
if (device->iscapture) {
const Uint8 *input = (const Uint8 *)audioData;
size_t available_bytes = hidden->mixbuf_bytes - (hidden->callback_bytes - hidden->processed_bytes);
size_t size = SDL_min(available_bytes, callback_bytes);
size_t offset = hidden->callback_bytes % hidden->mixbuf_bytes;
size_t end = (offset + size) % hidden->mixbuf_bytes;
SDL_assert(size <= hidden->mixbuf_bytes);
//LOGI("Recorded %zu frames, %zu available, %zu max (%zu written, %zu read)\n", callback_bytes / framesize, available_bytes / framesize, hidden->mixbuf_bytes / framesize, hidden->callback_bytes / framesize, hidden->processed_bytes / framesize);
if (offset <= end) {
SDL_memcpy(&hidden->mixbuf[offset], input, size);
} else {
size_t partial = (hidden->mixbuf_bytes - offset);
SDL_memcpy(&hidden->mixbuf[offset], &input[0], partial);
SDL_memcpy(&hidden->mixbuf[0], &input[partial], end);
}
SDL_MemoryBarrierRelease();
hidden->callback_bytes += size;
if (size < callback_bytes) {
LOGI("Audio recording overflow, dropped %zu frames\n", (callback_bytes - size) / framesize);
}
} else {
Uint8 *output = (Uint8 *)audioData;
size_t available_bytes = (hidden->processed_bytes - hidden->callback_bytes);
size_t size = SDL_min(available_bytes, callback_bytes);
size_t offset = hidden->callback_bytes % hidden->mixbuf_bytes;
size_t end = (offset + size) % hidden->mixbuf_bytes;
SDL_assert(size <= hidden->mixbuf_bytes);
//LOGI("Playing %zu frames, %zu available, %zu max (%zu written, %zu read)\n", callback_bytes / framesize, available_bytes / framesize, hidden->mixbuf_bytes / framesize, hidden->processed_bytes / framesize, hidden->callback_bytes / framesize);
SDL_MemoryBarrierAcquire();
if (offset <= end) {
SDL_memcpy(output, &hidden->mixbuf[offset], size);
} else {
size_t partial = (hidden->mixbuf_bytes - offset);
SDL_memcpy(&output[0], &hidden->mixbuf[offset], partial);
SDL_memcpy(&output[partial], &hidden->mixbuf[0], end);
}
hidden->callback_bytes += size;
if (size < callback_bytes) {
LOGI("Audio playback underflow, missed %zu frames\n", (callback_bytes - size) / framesize);
SDL_memset(&output[size], device->silence_value, (callback_bytes - size));
}
}
size_t new_buffer_index = hidden->callback_bytes / device->buffer_size;
while (old_buffer_index < new_buffer_index) {
// Trigger audio processing
SDL_PostSemaphore(hidden->semaphore);
++old_buffer_index;
}
return AAUDIO_CALLBACK_RESULT_CONTINUE;
}
static Uint8 *AAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *bufsize)
{
struct SDL_PrivateAudioData *hidden = device->hidden;
size_t offset = (hidden->processed_bytes % hidden->mixbuf_bytes);
return &hidden->mixbuf[offset];
}
static int AAUDIO_WaitDevice(SDL_AudioDevice *device)
{
SDL_WaitSemaphore(device->hidden->semaphore);
return 0;
}
static int BuildAAudioStream(SDL_AudioDevice *device);
static int RecoverAAudioDevice(SDL_AudioDevice *device)
{
struct SDL_PrivateAudioData *hidden = device->hidden;
// attempt to build a new stream, in case there's a new default device.
ctx.AAudioStream_requestStop(hidden->stream);
ctx.AAudioStream_close(hidden->stream);
hidden->stream = NULL;
SDL_aligned_free(hidden->mixbuf);
hidden->mixbuf = NULL;
SDL_DestroySemaphore(hidden->semaphore);
hidden->semaphore = NULL;
const int prev_sample_frames = device->sample_frames;
SDL_AudioSpec prevspec;
SDL_copyp(&prevspec, &device->spec);
if (BuildAAudioStream(device) < 0) {
return -1; // oh well, we tried.
}
// we don't know the new device spec until we open the new device, so we saved off the old one and force it back
// so SDL_AudioDeviceFormatChanged can set up all the important state if necessary and then set it back to the new spec.
const int new_sample_frames = device->sample_frames;
SDL_AudioSpec newspec;
SDL_copyp(&newspec, &device->spec);
device->sample_frames = prev_sample_frames;
SDL_copyp(&device->spec, &prevspec);
if (SDL_AudioDeviceFormatChangedAlreadyLocked(device, &newspec, new_sample_frames) < 0) {
return -1; // ugh
}
return 0;
}
static int AAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
{
struct SDL_PrivateAudioData *hidden = device->hidden;
// AAUDIO_dataCallback picks up our work and unblocks AAUDIO_WaitDevice. But make sure we didn't fail here.
const aaudio_result_t err = (aaudio_result_t) SDL_AtomicGet(&hidden->error_callback_triggered);
if (err) {
SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "aaudio: Audio device triggered error %d (%s)", (int) err, ctx.AAudio_convertResultToText(err));
if (RecoverAAudioDevice(device) < 0) {
return -1; // oh well, we went down hard.
}
} else {
SDL_MemoryBarrierRelease();
hidden->processed_bytes += buflen;
}
return 0;
}
static int AAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
{
struct SDL_PrivateAudioData *hidden = device->hidden;
// AAUDIO_dataCallback picks up our work and unblocks AAUDIO_WaitDevice. But make sure we didn't fail here.
if (SDL_AtomicGet(&hidden->error_callback_triggered)) {
SDL_AtomicSet(&hidden->error_callback_triggered, 0);
return -1;
}
SDL_assert(buflen == device->buffer_size); // If this isn't true, we need to change semaphore trigger logic and account for wrapping copies here
size_t offset = (hidden->processed_bytes % hidden->mixbuf_bytes);
SDL_MemoryBarrierAcquire();
SDL_memcpy(buffer, &hidden->mixbuf[offset], buflen);
hidden->processed_bytes += buflen;
return buflen;
}
static void AAUDIO_CloseDevice(SDL_AudioDevice *device)
{
struct SDL_PrivateAudioData *hidden = device->hidden;
LOGI(__func__);
if (hidden) {
if (hidden->stream) {
ctx.AAudioStream_requestStop(hidden->stream);
// !!! FIXME: do we have to wait for the state to change to make sure all buffered audio has played, or will close do this (or will the system do this after the close)?
// !!! FIXME: also, will this definitely wait for a running data callback to finish, and then stop the callback from firing again?
ctx.AAudioStream_close(hidden->stream);
}
if (hidden->semaphore) {
SDL_DestroySemaphore(hidden->semaphore);
}
SDL_aligned_free(hidden->mixbuf);
SDL_free(hidden);
device->hidden = NULL;
}
}
static int BuildAAudioStream(SDL_AudioDevice *device)
{
struct SDL_PrivateAudioData *hidden = device->hidden;
const SDL_bool iscapture = device->iscapture;
aaudio_result_t res;
SDL_AtomicSet(&hidden->error_callback_triggered, 0);
AAudioStreamBuilder *builder = NULL;
res = ctx.AAudio_createStreamBuilder(&builder);
if (res != AAUDIO_OK) {
LOGI("SDL Failed AAudio_createStreamBuilder %d", res);
return SDL_SetError("SDL Failed AAudio_createStreamBuilder %d", res);
} else if (builder == NULL) {
LOGI("SDL Failed AAudio_createStreamBuilder - builder NULL");
return SDL_SetError("SDL Failed AAudio_createStreamBuilder - builder NULL");
}
#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES
const int aaudio_device_id = (int) ((size_t) device->handle);
LOGI("Opening device id %d", aaudio_device_id);
ctx.AAudioStreamBuilder_setDeviceId(builder, aaudio_device_id);
#endif
aaudio_format_t format;
#ifdef SET_AUDIO_FORMAT
if ((device->spec.format == SDL_AUDIO_S32) && (SDL_GetAndroidSDKVersion() >= 31)) {
format = AAUDIO_FORMAT_PCM_I32;
} else if (device->spec.format == SDL_AUDIO_F32) {
format = AAUDIO_FORMAT_PCM_FLOAT;
} else {
format = AAUDIO_FORMAT_PCM_I16; // sint16 is a safe bet for everything else.
}
ctx.AAudioStreamBuilder_setFormat(builder, format);
ctx.AAudioStreamBuilder_setSampleRate(builder, device->spec.freq);
ctx.AAudioStreamBuilder_setChannelCount(builder, device->spec.channels);
#endif
const aaudio_direction_t direction = (iscapture ? AAUDIO_DIRECTION_INPUT : AAUDIO_DIRECTION_OUTPUT);
ctx.AAudioStreamBuilder_setDirection(builder, direction);
ctx.AAudioStreamBuilder_setErrorCallback(builder, AAUDIO_errorCallback, device);
ctx.AAudioStreamBuilder_setDataCallback(builder, AAUDIO_dataCallback, device);
// Some devices have flat sounding audio when low latency mode is enabled, but this is a better experience for most people
if (SDL_GetHintBoolean("SDL_ANDROID_LOW_LATENCY_AUDIO", SDL_TRUE)) {
ctx.AAudioStreamBuilder_setPerformanceMode(builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
}
LOGI("AAudio Try to open %u hz %u bit chan %u %s samples %u",
device->spec.freq, SDL_AUDIO_BITSIZE(device->spec.format),
device->spec.channels, SDL_AUDIO_ISBIGENDIAN(device->spec.format) ? "BE" : "LE", device->sample_frames);
res = ctx.AAudioStreamBuilder_openStream(builder, &hidden->stream);
if (res != AAUDIO_OK) {
LOGI("SDL Failed AAudioStreamBuilder_openStream %d", res);
ctx.AAudioStreamBuilder_delete(builder);
return SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
}
ctx.AAudioStreamBuilder_delete(builder);
device->sample_frames = (int)ctx.AAudioStream_getFramesPerDataCallback(hidden->stream);
if (device->sample_frames == AAUDIO_UNSPECIFIED) {
// We'll get variable frames in the callback, make sure we have at least half a buffer available
device->sample_frames = (int)ctx.AAudioStream_getBufferCapacityInFrames(hidden->stream) / 2;
}
device->spec.freq = ctx.AAudioStream_getSampleRate(hidden->stream);
device->spec.channels = ctx.AAudioStream_getChannelCount(hidden->stream);
format = ctx.AAudioStream_getFormat(hidden->stream);
if (format == AAUDIO_FORMAT_PCM_I16) {
device->spec.format = SDL_AUDIO_S16;
} else if (format == AAUDIO_FORMAT_PCM_I32) {
device->spec.format = SDL_AUDIO_S32;
} else if (format == AAUDIO_FORMAT_PCM_FLOAT) {
device->spec.format = SDL_AUDIO_F32;
} else {
return SDL_SetError("Got unexpected audio format %d from AAudioStream_getFormat", (int) format);
}
SDL_UpdatedAudioDeviceFormat(device);
// Allocate a double buffered mixing buffer
hidden->num_buffers = 2;
hidden->mixbuf_bytes = (hidden->num_buffers * device->buffer_size);
hidden->mixbuf = (Uint8 *)SDL_aligned_alloc(SDL_SIMDGetAlignment(), hidden->mixbuf_bytes);
if (hidden->mixbuf == NULL) {
return SDL_OutOfMemory();
}
hidden->processed_bytes = 0;
hidden->callback_bytes = 0;
hidden->semaphore = SDL_CreateSemaphore(iscapture ? 0 : hidden->num_buffers);
if (!hidden->semaphore) {
LOGI("SDL Failed SDL_CreateSemaphore %s iscapture:%d", SDL_GetError(), iscapture);
return -1;
}
LOGI("AAudio Actually opened %u hz %u bit chan %u %s samples %u, buffers %d",
device->spec.freq, SDL_AUDIO_BITSIZE(device->spec.format),
device->spec.channels, SDL_AUDIO_ISBIGENDIAN(device->spec.format) ? "BE" : "LE", device->sample_frames, hidden->num_buffers);
res = ctx.AAudioStream_requestStart(hidden->stream);
if (res != AAUDIO_OK) {
LOGI("SDL Failed AAudioStream_requestStart %d iscapture:%d", res, iscapture);
return SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
}
LOGI("SDL AAudioStream_requestStart OK");
return 0;
}
static int AAUDIO_OpenDevice(SDL_AudioDevice *device)
{
#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES
SDL_assert(device->handle != NULL); // AAUDIO_UNSPECIFIED is zero, so legit devices should all be non-zero.
#endif
LOGI(__func__);
if (device->iscapture) {
if (!Android_JNI_RequestPermission("android.permission.RECORD_AUDIO")) {
LOGI("This app doesn't have RECORD_AUDIO permission");
return SDL_SetError("This app doesn't have RECORD_AUDIO permission");
}
}
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
if (device->hidden == NULL) {
return SDL_OutOfMemory();
}
return BuildAAudioStream(device);
}
static SDL_bool PauseOneDevice(SDL_AudioDevice *device, void *userdata)
{
struct SDL_PrivateAudioData *hidden = (struct SDL_PrivateAudioData *)device->hidden;
if (hidden != NULL) {
if (hidden->stream) {
aaudio_result_t res;
if (device->iscapture) {
// Pause() isn't implemented for 'capture', use Stop()
res = ctx.AAudioStream_requestStop(hidden->stream);
} else {
res = ctx.AAudioStream_requestPause(hidden->stream);
}
if (res != AAUDIO_OK) {
LOGI("SDL Failed AAudioStream_requestPause %d", res);
SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
}
SDL_LockMutex(device->lock);
hidden->resume = SDL_TRUE;
}
}
return SDL_FALSE; // keep enumerating.
}
// Pause (block) all non already paused audio devices by taking their mixer lock
void AAUDIO_PauseDevices(void)
{
if (ctx.handle != NULL) { // AAUDIO driver is used?
(void) SDL_FindPhysicalAudioDeviceByCallback(PauseOneDevice, NULL);
}
}
// Resume (unblock) all non already paused audio devices by releasing their mixer lock
static SDL_bool ResumeOneDevice(SDL_AudioDevice *device, void *userdata)
{
struct SDL_PrivateAudioData *hidden = device->hidden;
if (hidden != NULL) {
if (hidden->resume) {
hidden->resume = SDL_FALSE;
SDL_UnlockMutex(device->lock);
}
if (hidden->stream) {
aaudio_result_t res = ctx.AAudioStream_requestStart(hidden->stream);
if (res != AAUDIO_OK) {
LOGI("SDL Failed AAudioStream_requestStart %d", res);
SDL_SetError("%s : %s", __func__, ctx.AAudio_convertResultToText(res));
}
}
}
return SDL_FALSE; // keep enumerating.
}
void AAUDIO_ResumeDevices(void)
{
if (ctx.handle != NULL) { // AAUDIO driver is used?
(void) SDL_FindPhysicalAudioDeviceByCallback(ResumeOneDevice, NULL);
}
}
static void AAUDIO_Deinitialize(void)
{
Android_StopAudioHotplug();
LOGI(__func__);
if (ctx.handle) {
SDL_UnloadObject(ctx.handle);
}
SDL_zero(ctx);
LOGI("End AAUDIO %s", SDL_GetError());
}
static SDL_bool AAUDIO_Init(SDL_AudioDriverImpl *impl)
{
LOGI(__func__);
/* AAudio was introduced in Android 8.0, but has reference counting crash issues in that release,
* so don't use it until 8.1.
*
* See https://github.com/google/oboe/issues/40 for more information.
*/
if (SDL_GetAndroidSDKVersion() < 27) {
return SDL_FALSE;
}
SDL_zero(ctx);
ctx.handle = SDL_LoadObject(LIB_AAUDIO_SO);
if (ctx.handle == NULL) {
LOGI("SDL couldn't find " LIB_AAUDIO_SO);
return SDL_FALSE;
}
if (AAUDIO_LoadFunctions(&ctx) < 0) {
SDL_UnloadObject(ctx.handle);
SDL_zero(ctx);
return SDL_FALSE;
}
impl->ThreadInit = Android_AudioThreadInit;
impl->Deinitialize = AAUDIO_Deinitialize;
impl->OpenDevice = AAUDIO_OpenDevice;
impl->CloseDevice = AAUDIO_CloseDevice;
impl->WaitDevice = AAUDIO_WaitDevice;
impl->PlayDevice = AAUDIO_PlayDevice;
impl->GetDeviceBuf = AAUDIO_GetDeviceBuf;
impl->WaitCaptureDevice = AAUDIO_WaitDevice;
impl->CaptureFromDevice = AAUDIO_CaptureFromDevice;
impl->HasCaptureSupport = SDL_TRUE;
#if ALLOW_MULTIPLE_ANDROID_AUDIO_DEVICES
impl->DetectDevices = Android_StartAudioHotplug;
#else
impl->OnlyHasDefaultOutputDevice = SDL_TRUE;
impl->OnlyHasDefaultCaptureDevice = SDL_TRUE;
#endif
LOGI("SDL AAUDIO_Init OK");
return SDL_TRUE;
}
AudioBootStrap AAUDIO_bootstrap = {
"AAudio", "AAudio audio driver", AAUDIO_Init, SDL_FALSE
};
#endif // SDL_AUDIO_DRIVER_AAUDIO

View file

@ -0,0 +1,38 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_aaudio_h_
#define SDL_aaudio_h_
#ifdef SDL_AUDIO_DRIVER_AAUDIO
void AAUDIO_ResumeDevices(void);
void AAUDIO_PauseDevices(void);
#else
#define AAUDIO_ResumeDevices()
#define AAUDIO_PauseDevices()
#endif
#endif // SDL_aaudio_h_

View file

@ -0,0 +1,82 @@
/*
Simple DirectMedia Layer
Copyright , (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#define SDL_PROC_UNUSED(ret, func, params)
SDL_PROC(const char *, AAudio_convertResultToText, (aaudio_result_t returnCode))
SDL_PROC(const char *, AAudio_convertStreamStateToText, (aaudio_stream_state_t state))
SDL_PROC(aaudio_result_t, AAudio_createStreamBuilder, (AAudioStreamBuilder * *builder))
SDL_PROC(void, AAudioStreamBuilder_setDeviceId, (AAudioStreamBuilder * builder, int32_t deviceId))
SDL_PROC(void, AAudioStreamBuilder_setSampleRate, (AAudioStreamBuilder * builder, int32_t sampleRate))
SDL_PROC(void, AAudioStreamBuilder_setChannelCount, (AAudioStreamBuilder * builder, int32_t channelCount))
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSamplesPerFrame, (AAudioStreamBuilder * builder, int32_t samplesPerFrame))
SDL_PROC(void, AAudioStreamBuilder_setFormat, (AAudioStreamBuilder * builder, aaudio_format_t format))
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSharingMode, (AAudioStreamBuilder * builder, aaudio_sharing_mode_t sharingMode))
SDL_PROC(void, AAudioStreamBuilder_setDirection, (AAudioStreamBuilder * builder, aaudio_direction_t direction))
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setBufferCapacityInFrames, (AAudioStreamBuilder * builder, int32_t numFrames))
SDL_PROC(void, AAudioStreamBuilder_setPerformanceMode, (AAudioStreamBuilder * builder, aaudio_performance_mode_t mode))
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setUsage, (AAudioStreamBuilder * builder, aaudio_usage_t usage)) // API 28
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setContentType, (AAudioStreamBuilder * builder, aaudio_content_type_t contentType)) // API 28
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setInputPreset, (AAudioStreamBuilder * builder, aaudio_input_preset_t inputPreset)) // API 28
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setAllowedCapturePolicy, (AAudioStreamBuilder * builder, aaudio_allowed_capture_policy_t capturePolicy)) // API 29
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setSessionId, (AAudioStreamBuilder * builder, aaudio_session_id_t sessionId)) // API 28
SDL_PROC_UNUSED(void, AAudioStreamBuilder_setPrivacySensitive, (AAudioStreamBuilder * builder, bool privacySensitive)) // API 30
SDL_PROC(void, AAudioStreamBuilder_setDataCallback, (AAudioStreamBuilder * builder, AAudioStream_dataCallback callback, void *userData))
SDL_PROC(void, AAudioStreamBuilder_setFramesPerDataCallback, (AAudioStreamBuilder * builder, int32_t numFrames))
SDL_PROC(void, AAudioStreamBuilder_setErrorCallback, (AAudioStreamBuilder * builder, AAudioStream_errorCallback callback, void *userData))
SDL_PROC(aaudio_result_t, AAudioStreamBuilder_openStream, (AAudioStreamBuilder * builder, AAudioStream **stream))
SDL_PROC(aaudio_result_t, AAudioStreamBuilder_delete, (AAudioStreamBuilder * builder))
SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_release, (AAudioStream * stream)) // API 30
SDL_PROC(aaudio_result_t, AAudioStream_close, (AAudioStream * stream))
SDL_PROC(aaudio_result_t, AAudioStream_requestStart, (AAudioStream * stream))
SDL_PROC(aaudio_result_t, AAudioStream_requestPause, (AAudioStream * stream))
SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_requestFlush, (AAudioStream * stream))
SDL_PROC(aaudio_result_t, AAudioStream_requestStop, (AAudioStream * stream))
SDL_PROC(aaudio_stream_state_t, AAudioStream_getState, (AAudioStream * stream))
SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_waitForStateChange, (AAudioStream * stream, aaudio_stream_state_t inputState, aaudio_stream_state_t *nextState, int64_t timeoutNanoseconds))
SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_read, (AAudioStream * stream, void *buffer, int32_t numFrames, int64_t timeoutNanoseconds))
SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_write, (AAudioStream * stream, const void *buffer, int32_t numFrames, int64_t timeoutNanoseconds))
SDL_PROC_UNUSED(aaudio_result_t, AAudioStream_setBufferSizeInFrames, (AAudioStream * stream, int32_t numFrames))
SDL_PROC_UNUSED(int32_t, AAudioStream_getBufferSizeInFrames, (AAudioStream * stream))
SDL_PROC_UNUSED(int32_t, AAudioStream_getFramesPerBurst, (AAudioStream * stream))
SDL_PROC(int32_t, AAudioStream_getBufferCapacityInFrames, (AAudioStream * stream))
SDL_PROC(int32_t, AAudioStream_getFramesPerDataCallback, (AAudioStream * stream))
SDL_PROC_UNUSED(int32_t, AAudioStream_getXRunCount, (AAudioStream * stream))
SDL_PROC(int32_t, AAudioStream_getSampleRate, (AAudioStream * stream))
SDL_PROC(int32_t, AAudioStream_getChannelCount, (AAudioStream * stream))
SDL_PROC_UNUSED(int32_t, AAudioStream_getSamplesPerFrame, (AAudioStream * stream))
SDL_PROC_UNUSED(int32_t, AAudioStream_getDeviceId, (AAudioStream * stream))
SDL_PROC(aaudio_format_t, AAudioStream_getFormat, (AAudioStream * stream))
SDL_PROC_UNUSED(aaudio_sharing_mode_t, AAudioStream_getSharingMode, (AAudioStream * stream))
SDL_PROC_UNUSED(aaudio_performance_mode_t, AAudioStream_getPerformanceMode, (AAudioStream * stream))
SDL_PROC_UNUSED(aaudio_direction_t, AAudioStream_getDirection, (AAudioStream * stream))
SDL_PROC_UNUSED(int64_t, AAudioStream_getFramesWritten, (AAudioStream * stream))
SDL_PROC_UNUSED(int64_t, AAudioStream_getFramesRead, (AAudioStream * stream))
SDL_PROC_UNUSED(aaudio_session_id_t, AAudioStream_getSessionId, (AAudioStream * stream)) // API 28
SDL_PROC(aaudio_result_t, AAudioStream_getTimestamp, (AAudioStream * stream, clockid_t clockid, int64_t *framePosition, int64_t *timeNanoseconds))
SDL_PROC_UNUSED(aaudio_usage_t, AAudioStream_getUsage, (AAudioStream * stream)) // API 28
SDL_PROC_UNUSED(aaudio_content_type_t, AAudioStream_getContentType, (AAudioStream * stream)) // API 28
SDL_PROC_UNUSED(aaudio_input_preset_t, AAudioStream_getInputPreset, (AAudioStream * stream)) // API 28
SDL_PROC_UNUSED(aaudio_allowed_capture_policy_t, AAudioStream_getAllowedCapturePolicy, (AAudioStream * stream)) // API 29
SDL_PROC_UNUSED(bool, AAudioStream_isPrivacySensitive, (AAudioStream * stream)) // API 30
#undef SDL_PROC
#undef SDL_PROC_UNUSED

View file

@ -0,0 +1,994 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_AUDIO_DRIVER_ALSA
#ifndef SDL_ALSA_NON_BLOCKING
#define SDL_ALSA_NON_BLOCKING 0
#endif
// without the thread, you will detect devices on startup, but will not get further hotplug events. But that might be okay.
#ifndef SDL_ALSA_HOTPLUG_THREAD
#define SDL_ALSA_HOTPLUG_THREAD 1
#endif
// Allow access to a raw mixing buffer
#include <sys/types.h>
#include <signal.h> // For kill()
#include <string.h>
#include "../SDL_sysaudio.h"
#include "SDL_alsa_audio.h"
#ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
#endif
static int (*ALSA_snd_pcm_open)(snd_pcm_t **, const char *, snd_pcm_stream_t, int);
static int (*ALSA_snd_pcm_close)(snd_pcm_t *pcm);
static int (*ALSA_snd_pcm_start)(snd_pcm_t *pcm);
static snd_pcm_sframes_t (*ALSA_snd_pcm_writei)(snd_pcm_t *, const void *, snd_pcm_uframes_t);
static snd_pcm_sframes_t (*ALSA_snd_pcm_readi)(snd_pcm_t *, void *, snd_pcm_uframes_t);
static int (*ALSA_snd_pcm_recover)(snd_pcm_t *, int, int);
static int (*ALSA_snd_pcm_prepare)(snd_pcm_t *);
static int (*ALSA_snd_pcm_drain)(snd_pcm_t *);
static const char *(*ALSA_snd_strerror)(int);
static size_t (*ALSA_snd_pcm_hw_params_sizeof)(void);
static size_t (*ALSA_snd_pcm_sw_params_sizeof)(void);
static void (*ALSA_snd_pcm_hw_params_copy)(snd_pcm_hw_params_t *, const snd_pcm_hw_params_t *);
static int (*ALSA_snd_pcm_hw_params_any)(snd_pcm_t *, snd_pcm_hw_params_t *);
static int (*ALSA_snd_pcm_hw_params_set_access)(snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_access_t);
static int (*ALSA_snd_pcm_hw_params_set_format)(snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_format_t);
static int (*ALSA_snd_pcm_hw_params_set_channels)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int);
static int (*ALSA_snd_pcm_hw_params_get_channels)(const snd_pcm_hw_params_t *, unsigned int *);
static int (*ALSA_snd_pcm_hw_params_set_rate_near)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *);
static int (*ALSA_snd_pcm_hw_params_set_period_size_near)(snd_pcm_t *, snd_pcm_hw_params_t *, snd_pcm_uframes_t *, int *);
static int (*ALSA_snd_pcm_hw_params_get_period_size)(const snd_pcm_hw_params_t *, snd_pcm_uframes_t *, int *);
static int (*ALSA_snd_pcm_hw_params_set_periods_min)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *);
static int (*ALSA_snd_pcm_hw_params_set_periods_first)(snd_pcm_t *, snd_pcm_hw_params_t *, unsigned int *, int *);
static int (*ALSA_snd_pcm_hw_params_get_periods)(const snd_pcm_hw_params_t *, unsigned int *, int *);
static int (*ALSA_snd_pcm_hw_params_set_buffer_size_near)(snd_pcm_t *pcm, snd_pcm_hw_params_t *, snd_pcm_uframes_t *);
static int (*ALSA_snd_pcm_hw_params_get_buffer_size)(const snd_pcm_hw_params_t *, snd_pcm_uframes_t *);
static int (*ALSA_snd_pcm_hw_params)(snd_pcm_t *, snd_pcm_hw_params_t *);
static int (*ALSA_snd_pcm_sw_params_current)(snd_pcm_t *,
snd_pcm_sw_params_t *);
static int (*ALSA_snd_pcm_sw_params_set_start_threshold)(snd_pcm_t *, snd_pcm_sw_params_t *, snd_pcm_uframes_t);
static int (*ALSA_snd_pcm_sw_params)(snd_pcm_t *, snd_pcm_sw_params_t *);
static int (*ALSA_snd_pcm_nonblock)(snd_pcm_t *, int);
static int (*ALSA_snd_pcm_wait)(snd_pcm_t *, int);
static int (*ALSA_snd_pcm_sw_params_set_avail_min)(snd_pcm_t *, snd_pcm_sw_params_t *, snd_pcm_uframes_t);
static int (*ALSA_snd_pcm_reset)(snd_pcm_t *);
static int (*ALSA_snd_device_name_hint)(int, const char *, void ***);
static char *(*ALSA_snd_device_name_get_hint)(const void *, const char *);
static int (*ALSA_snd_device_name_free_hint)(void **);
static snd_pcm_sframes_t (*ALSA_snd_pcm_avail)(snd_pcm_t *);
#ifdef SND_CHMAP_API_VERSION
static snd_pcm_chmap_t *(*ALSA_snd_pcm_get_chmap)(snd_pcm_t *);
static int (*ALSA_snd_pcm_chmap_print)(const snd_pcm_chmap_t *map, size_t maxlen, char *buf);
#endif
#ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
#define snd_pcm_hw_params_sizeof ALSA_snd_pcm_hw_params_sizeof
#define snd_pcm_sw_params_sizeof ALSA_snd_pcm_sw_params_sizeof
static const char *alsa_library = SDL_AUDIO_DRIVER_ALSA_DYNAMIC;
static void *alsa_handle = NULL;
static int load_alsa_sym(const char *fn, void **addr)
{
*addr = SDL_LoadFunction(alsa_handle, fn);
if (*addr == NULL) {
// Don't call SDL_SetError(): SDL_LoadFunction already did.
return 0;
}
return 1;
}
// cast funcs to char* first, to please GCC's strict aliasing rules.
#define SDL_ALSA_SYM(x) \
if (!load_alsa_sym(#x, (void **)(char *)&ALSA_##x)) \
return -1
#else
#define SDL_ALSA_SYM(x) ALSA_##x = x
#endif
static int load_alsa_syms(void)
{
SDL_ALSA_SYM(snd_pcm_open);
SDL_ALSA_SYM(snd_pcm_close);
SDL_ALSA_SYM(snd_pcm_start);
SDL_ALSA_SYM(snd_pcm_writei);
SDL_ALSA_SYM(snd_pcm_readi);
SDL_ALSA_SYM(snd_pcm_recover);
SDL_ALSA_SYM(snd_pcm_prepare);
SDL_ALSA_SYM(snd_pcm_drain);
SDL_ALSA_SYM(snd_strerror);
SDL_ALSA_SYM(snd_pcm_hw_params_sizeof);
SDL_ALSA_SYM(snd_pcm_sw_params_sizeof);
SDL_ALSA_SYM(snd_pcm_hw_params_copy);
SDL_ALSA_SYM(snd_pcm_hw_params_any);
SDL_ALSA_SYM(snd_pcm_hw_params_set_access);
SDL_ALSA_SYM(snd_pcm_hw_params_set_format);
SDL_ALSA_SYM(snd_pcm_hw_params_set_channels);
SDL_ALSA_SYM(snd_pcm_hw_params_get_channels);
SDL_ALSA_SYM(snd_pcm_hw_params_set_rate_near);
SDL_ALSA_SYM(snd_pcm_hw_params_set_period_size_near);
SDL_ALSA_SYM(snd_pcm_hw_params_get_period_size);
SDL_ALSA_SYM(snd_pcm_hw_params_set_periods_min);
SDL_ALSA_SYM(snd_pcm_hw_params_set_periods_first);
SDL_ALSA_SYM(snd_pcm_hw_params_get_periods);
SDL_ALSA_SYM(snd_pcm_hw_params_set_buffer_size_near);
SDL_ALSA_SYM(snd_pcm_hw_params_get_buffer_size);
SDL_ALSA_SYM(snd_pcm_hw_params);
SDL_ALSA_SYM(snd_pcm_sw_params_current);
SDL_ALSA_SYM(snd_pcm_sw_params_set_start_threshold);
SDL_ALSA_SYM(snd_pcm_sw_params);
SDL_ALSA_SYM(snd_pcm_nonblock);
SDL_ALSA_SYM(snd_pcm_wait);
SDL_ALSA_SYM(snd_pcm_sw_params_set_avail_min);
SDL_ALSA_SYM(snd_pcm_reset);
SDL_ALSA_SYM(snd_device_name_hint);
SDL_ALSA_SYM(snd_device_name_get_hint);
SDL_ALSA_SYM(snd_device_name_free_hint);
SDL_ALSA_SYM(snd_pcm_avail);
#ifdef SND_CHMAP_API_VERSION
SDL_ALSA_SYM(snd_pcm_get_chmap);
SDL_ALSA_SYM(snd_pcm_chmap_print);
#endif
return 0;
}
#undef SDL_ALSA_SYM
#ifdef SDL_AUDIO_DRIVER_ALSA_DYNAMIC
static void UnloadALSALibrary(void)
{
if (alsa_handle != NULL) {
SDL_UnloadObject(alsa_handle);
alsa_handle = NULL;
}
}
static int LoadALSALibrary(void)
{
int retval = 0;
if (alsa_handle == NULL) {
alsa_handle = SDL_LoadObject(alsa_library);
if (alsa_handle == NULL) {
retval = -1;
// Don't call SDL_SetError(): SDL_LoadObject already did.
} else {
retval = load_alsa_syms();
if (retval < 0) {
UnloadALSALibrary();
}
}
}
return retval;
}
#else
static void UnloadALSALibrary(void)
{
}
static int LoadALSALibrary(void)
{
load_alsa_syms();
return 0;
}
#endif // SDL_AUDIO_DRIVER_ALSA_DYNAMIC
typedef struct ALSA_Device
{
char *name;
SDL_bool iscapture;
struct ALSA_Device *next;
} ALSA_Device;
static const ALSA_Device default_output_handle = {
"default",
SDL_FALSE,
NULL
};
static const ALSA_Device default_capture_handle = {
"default",
SDL_TRUE,
NULL
};
static const char *get_audio_device(void *handle, const int channels)
{
SDL_assert(handle != NULL); // SDL2 used NULL to mean "default" but that's not true in SDL3.
ALSA_Device *dev = (ALSA_Device *)handle;
if (SDL_strcmp(dev->name, "default") == 0) {
const char *device = SDL_getenv("AUDIODEV"); // Is there a standard variable name?
if (device != NULL) {
return device;
} else if (channels == 6) {
return "plug:surround51";
} else if (channels == 4) {
return "plug:surround40";
}
return "default";
}
return dev->name;
}
// !!! FIXME: is there a channel swizzler in alsalib instead?
// https://bugzilla.libsdl.org/show_bug.cgi?id=110
// "For Linux ALSA, this is FL-FR-RL-RR-C-LFE
// and for Windows DirectX [and CoreAudio], this is FL-FR-C-LFE-RL-RR"
#define SWIZ6(T) \
static void swizzle_alsa_channels_6_##T(void *buffer, const Uint32 bufferlen) \
{ \
T *ptr = (T *)buffer; \
Uint32 i; \
for (i = 0; i < bufferlen; i++, ptr += 6) { \
T tmp; \
tmp = ptr[2]; \
ptr[2] = ptr[4]; \
ptr[4] = tmp; \
tmp = ptr[3]; \
ptr[3] = ptr[5]; \
ptr[5] = tmp; \
} \
}
// !!! FIXME: is there a channel swizzler in alsalib instead?
// !!! FIXME: this screams for a SIMD shuffle operation.
// https://docs.microsoft.com/en-us/windows-hardware/drivers/audio/mapping-stream-formats-to-speaker-configurations
// For Linux ALSA, this appears to be FL-FR-RL-RR-C-LFE-SL-SR
// and for Windows DirectX [and CoreAudio], this is FL-FR-C-LFE-SL-SR-RL-RR"
#define SWIZ8(T) \
static void swizzle_alsa_channels_8_##T(void *buffer, const Uint32 bufferlen) \
{ \
T *ptr = (T *)buffer; \
Uint32 i; \
for (i = 0; i < bufferlen; i++, ptr += 6) { \
const T center = ptr[2]; \
const T subwoofer = ptr[3]; \
const T side_left = ptr[4]; \
const T side_right = ptr[5]; \
const T rear_left = ptr[6]; \
const T rear_right = ptr[7]; \
ptr[2] = rear_left; \
ptr[3] = rear_right; \
ptr[4] = center; \
ptr[5] = subwoofer; \
ptr[6] = side_left; \
ptr[7] = side_right; \
} \
}
#define CHANNEL_SWIZZLE(x) \
x(Uint64) \
x(Uint32) \
x(Uint16) \
x(Uint8)
CHANNEL_SWIZZLE(SWIZ6)
CHANNEL_SWIZZLE(SWIZ8)
#undef CHANNEL_SWIZZLE
#undef SWIZ6
#undef SWIZ8
// Called right before feeding device->hidden->mixbuf to the hardware. Swizzle
// channels from Windows/Mac order to the format alsalib will want.
static void swizzle_alsa_channels(SDL_AudioDevice *device, void *buffer, Uint32 bufferlen)
{
switch (device->spec.channels) {
#define CHANSWIZ(chans) \
case chans: \
switch ((device->spec.format & (0xFF))) { \
case 8: \
swizzle_alsa_channels_##chans##_Uint8(buffer, bufferlen); \
break; \
case 16: \
swizzle_alsa_channels_##chans##_Uint16(buffer, bufferlen); \
break; \
case 32: \
swizzle_alsa_channels_##chans##_Uint32(buffer, bufferlen); \
break; \
case 64: \
swizzle_alsa_channels_##chans##_Uint64(buffer, bufferlen); \
break; \
default: \
SDL_assert(!"unhandled bitsize"); \
break; \
} \
return;
CHANSWIZ(6);
CHANSWIZ(8);
#undef CHANSWIZ
default:
break;
}
}
#ifdef SND_CHMAP_API_VERSION
// Some devices have the right channel map, no swizzling necessary
static void no_swizzle(SDL_AudioDevice *device, void *buffer, Uint32 bufferlen)
{
}
#endif // SND_CHMAP_API_VERSION
// This function waits until it is possible to write a full sound buffer
static int ALSA_WaitDevice(SDL_AudioDevice *device)
{
const int fulldelay = (int) ((((Uint64) device->sample_frames) * 1000) / device->spec.freq);
const int delay = SDL_max(fulldelay, 10);
while (!SDL_AtomicGet(&device->shutdown)) {
const int rc = ALSA_snd_pcm_wait(device->hidden->pcm_handle, delay);
if (rc < 0 && (rc != -EAGAIN)) {
const int status = ALSA_snd_pcm_recover(device->hidden->pcm_handle, rc, 0);
if (status < 0) {
// Hmm, not much we can do - abort
SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA: snd_pcm_wait failed (unrecoverable): %s", ALSA_snd_strerror(rc));
return -1;
}
continue;
}
if (rc > 0) {
break; // ready to go!
}
// Timed out! Make sure we aren't shutting down and then wait again.
}
return 0;
}
static int ALSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
{
SDL_assert(buffer == device->hidden->mixbuf);
Uint8 *sample_buf = (Uint8 *) buffer; // !!! FIXME: deal with this without casting away constness
const int frame_size = SDL_AUDIO_FRAMESIZE(device->spec);
snd_pcm_uframes_t frames_left = (snd_pcm_uframes_t) (buflen / frame_size);
device->hidden->swizzle_func(device, sample_buf, frames_left);
while ((frames_left > 0) && !SDL_AtomicGet(&device->shutdown)) {
const int rc = ALSA_snd_pcm_writei(device->hidden->pcm_handle, sample_buf, frames_left);
//SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA PLAYDEVICE: WROTE %d of %d bytes", (rc >= 0) ? ((int) (rc * frame_size)) : rc, (int) (frames_left * frame_size));
SDL_assert(rc != 0); // assuming this can't happen if we used snd_pcm_wait and queried for available space.
if (rc < 0) {
SDL_assert(rc != -EAGAIN); // assuming this can't happen if we used snd_pcm_wait and queried for available space. snd_pcm_recover won't handle it!
const int status = ALSA_snd_pcm_recover(device->hidden->pcm_handle, rc, 0);
if (status < 0) {
// Hmm, not much we can do - abort
SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA write failed (unrecoverable): %s", ALSA_snd_strerror(rc));
return -1;
}
continue;
}
sample_buf += rc * frame_size;
frames_left -= rc;
}
return 0;
}
static Uint8 *ALSA_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
snd_pcm_sframes_t rc = ALSA_snd_pcm_avail(device->hidden->pcm_handle);
if (rc <= 0) {
// Wait a bit and try again, maybe the hardware isn't quite ready yet?
SDL_Delay(1);
rc = ALSA_snd_pcm_avail(device->hidden->pcm_handle);
if (rc <= 0) {
// We'll catch it next time
*buffer_size = 0;
return NULL;
}
}
const int requested_frames = SDL_min(device->sample_frames, rc);
const int requested_bytes = requested_frames * SDL_AUDIO_FRAMESIZE(device->spec);
SDL_assert(requested_bytes <= *buffer_size);
//SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA GETDEVICEBUF: NEED %d BYTES", requested_bytes);
*buffer_size = requested_bytes;
return device->hidden->mixbuf;
}
static int ALSA_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
{
const int frame_size = SDL_AUDIO_FRAMESIZE(device->spec);
SDL_assert((buflen % frame_size) == 0);
const snd_pcm_sframes_t total_available = ALSA_snd_pcm_avail(device->hidden->pcm_handle);
const int total_frames = SDL_min(buflen / frame_size, total_available);
const int rc = ALSA_snd_pcm_readi(device->hidden->pcm_handle, buffer, total_frames);
SDL_assert(rc != -EAGAIN); // assuming this can't happen if we used snd_pcm_wait and queried for available space. snd_pcm_recover won't handle it!
if (rc < 0) {
const int status = ALSA_snd_pcm_recover(device->hidden->pcm_handle, rc, 0);
if (status < 0) {
// Hmm, not much we can do - abort
SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "ALSA read failed (unrecoverable): %s", ALSA_snd_strerror(rc));
return -1;
}
return 0; // go back to WaitDevice and try again.
} else if (rc > 0) {
device->hidden->swizzle_func(device, buffer, total_frames - rc);
}
//SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA: captured %d bytes", rc * frame_size);
return rc * frame_size;
}
static void ALSA_FlushCapture(SDL_AudioDevice *device)
{
ALSA_snd_pcm_reset(device->hidden->pcm_handle);
}
static void ALSA_CloseDevice(SDL_AudioDevice *device)
{
if (device->hidden) {
if (device->hidden->pcm_handle) {
// Wait for the submitted audio to drain. ALSA_snd_pcm_drop() can hang, so don't use that.
SDL_Delay(((device->sample_frames * 1000) / device->spec.freq) * 2);
ALSA_snd_pcm_close(device->hidden->pcm_handle);
}
SDL_free(device->hidden->mixbuf);
SDL_free(device->hidden);
}
}
static int ALSA_set_buffer_size(SDL_AudioDevice *device, snd_pcm_hw_params_t *params)
{
int status;
snd_pcm_hw_params_t *hwparams;
snd_pcm_uframes_t persize;
unsigned int periods;
// Copy the hardware parameters for this setup
snd_pcm_hw_params_alloca(&hwparams);
ALSA_snd_pcm_hw_params_copy(hwparams, params);
// Attempt to match the period size to the requested buffer size
persize = device->sample_frames;
status = ALSA_snd_pcm_hw_params_set_period_size_near(
device->hidden->pcm_handle, hwparams, &persize, NULL);
if (status < 0) {
return -1;
}
// Need to at least double buffer
periods = 2;
status = ALSA_snd_pcm_hw_params_set_periods_min(
device->hidden->pcm_handle, hwparams, &periods, NULL);
if (status < 0) {
return -1;
}
status = ALSA_snd_pcm_hw_params_set_periods_first(
device->hidden->pcm_handle, hwparams, &periods, NULL);
if (status < 0) {
return -1;
}
// "set" the hardware with the desired parameters
status = ALSA_snd_pcm_hw_params(device->hidden->pcm_handle, hwparams);
if (status < 0) {
return -1;
}
device->sample_frames = persize;
// This is useful for debugging
if (SDL_getenv("SDL_AUDIO_ALSA_DEBUG")) {
snd_pcm_uframes_t bufsize;
ALSA_snd_pcm_hw_params_get_buffer_size(hwparams, &bufsize);
SDL_LogError(SDL_LOG_CATEGORY_AUDIO,
"ALSA: period size = %ld, periods = %u, buffer size = %lu",
persize, periods, bufsize);
}
return 0;
}
static int ALSA_OpenDevice(SDL_AudioDevice *device)
{
const SDL_bool iscapture = device->iscapture;
int status = 0;
// Initialize all variables that we clean on shutdown
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
if (device->hidden == NULL) {
return SDL_OutOfMemory();
}
// Open the audio device
// Name of device should depend on # channels in spec
snd_pcm_t *pcm_handle = NULL;
status = ALSA_snd_pcm_open(&pcm_handle,
get_audio_device(device->handle, device->spec.channels),
iscapture ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK,
SND_PCM_NONBLOCK);
if (status < 0) {
return SDL_SetError("ALSA: Couldn't open audio device: %s", ALSA_snd_strerror(status));
}
device->hidden->pcm_handle = pcm_handle;
// Figure out what the hardware is capable of
snd_pcm_hw_params_t *hwparams = NULL;
snd_pcm_hw_params_alloca(&hwparams);
status = ALSA_snd_pcm_hw_params_any(pcm_handle, hwparams);
if (status < 0) {
return SDL_SetError("ALSA: Couldn't get hardware config: %s", ALSA_snd_strerror(status));
}
// SDL only uses interleaved sample output
status = ALSA_snd_pcm_hw_params_set_access(pcm_handle, hwparams,
SND_PCM_ACCESS_RW_INTERLEAVED);
if (status < 0) {
return SDL_SetError("ALSA: Couldn't set interleaved access: %s", ALSA_snd_strerror(status));
}
// Try for a closest match on audio format
snd_pcm_format_t format = 0;
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
SDL_AudioFormat test_format;
while ((test_format = *(closefmts++)) != 0) {
switch (test_format) {
case SDL_AUDIO_U8:
format = SND_PCM_FORMAT_U8;
break;
case SDL_AUDIO_S8:
format = SND_PCM_FORMAT_S8;
break;
case SDL_AUDIO_S16LE:
format = SND_PCM_FORMAT_S16_LE;
break;
case SDL_AUDIO_S16BE:
format = SND_PCM_FORMAT_S16_BE;
break;
case SDL_AUDIO_S32LE:
format = SND_PCM_FORMAT_S32_LE;
break;
case SDL_AUDIO_S32BE:
format = SND_PCM_FORMAT_S32_BE;
break;
case SDL_AUDIO_F32LE:
format = SND_PCM_FORMAT_FLOAT_LE;
break;
case SDL_AUDIO_F32BE:
format = SND_PCM_FORMAT_FLOAT_BE;
break;
default:
continue;
}
if (ALSA_snd_pcm_hw_params_set_format(pcm_handle, hwparams, format) >= 0) {
break;
}
}
if (!test_format) {
return SDL_SetError("ALSA: Unsupported audio format");
}
device->spec.format = test_format;
// Validate number of channels and determine if swizzling is necessary.
// Assume original swizzling, until proven otherwise.
device->hidden->swizzle_func = swizzle_alsa_channels;
#ifdef SND_CHMAP_API_VERSION
snd_pcm_chmap_t *chmap = ALSA_snd_pcm_get_chmap(pcm_handle);
if (chmap) {
char chmap_str[64];
if (ALSA_snd_pcm_chmap_print(chmap, sizeof(chmap_str), chmap_str) > 0) {
if (SDL_strcmp("FL FR FC LFE RL RR", chmap_str) == 0 ||
SDL_strcmp("FL FR FC LFE SL SR", chmap_str) == 0) {
device->hidden->swizzle_func = no_swizzle;
}
}
free(chmap); // This should NOT be SDL_free()
}
#endif // SND_CHMAP_API_VERSION
// Set the number of channels
status = ALSA_snd_pcm_hw_params_set_channels(pcm_handle, hwparams,
device->spec.channels);
unsigned int channels = device->spec.channels;
if (status < 0) {
status = ALSA_snd_pcm_hw_params_get_channels(hwparams, &channels);
if (status < 0) {
return SDL_SetError("ALSA: Couldn't set audio channels");
}
device->spec.channels = channels;
}
// Set the audio rate
unsigned int rate = device->spec.freq;
status = ALSA_snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams,
&rate, NULL);
if (status < 0) {
return SDL_SetError("ALSA: Couldn't set audio frequency: %s", ALSA_snd_strerror(status));
}
device->spec.freq = rate;
// Set the buffer size, in samples
status = ALSA_set_buffer_size(device, hwparams);
if (status < 0) {
return SDL_SetError("Couldn't set hardware audio parameters: %s", ALSA_snd_strerror(status));
}
// Set the software parameters
snd_pcm_sw_params_t *swparams = NULL;
snd_pcm_sw_params_alloca(&swparams);
status = ALSA_snd_pcm_sw_params_current(pcm_handle, swparams);
if (status < 0) {
return SDL_SetError("ALSA: Couldn't get software config: %s", ALSA_snd_strerror(status));
}
status = ALSA_snd_pcm_sw_params_set_avail_min(pcm_handle, swparams, device->sample_frames);
if (status < 0) {
return SDL_SetError("Couldn't set minimum available samples: %s", ALSA_snd_strerror(status));
}
status =
ALSA_snd_pcm_sw_params_set_start_threshold(pcm_handle, swparams, 1);
if (status < 0) {
return SDL_SetError("ALSA: Couldn't set start threshold: %s", ALSA_snd_strerror(status));
}
status = ALSA_snd_pcm_sw_params(pcm_handle, swparams);
if (status < 0) {
return SDL_SetError("Couldn't set software audio parameters: %s", ALSA_snd_strerror(status));
}
// Calculate the final parameters for this audio specification
SDL_UpdatedAudioDeviceFormat(device);
// Allocate mixing buffer
if (!iscapture) {
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
if (device->hidden->mixbuf == NULL) {
return SDL_OutOfMemory();
}
SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
}
#if !SDL_ALSA_NON_BLOCKING
if (!iscapture) {
ALSA_snd_pcm_nonblock(pcm_handle, 0);
}
#endif
ALSA_snd_pcm_start(pcm_handle);
return 0; // We're ready to rock and roll. :-)
}
static void add_device(const SDL_bool iscapture, const char *name, void *hint, ALSA_Device **pSeen)
{
ALSA_Device *dev = SDL_malloc(sizeof(ALSA_Device));
char *desc;
char *ptr;
if (dev == NULL) {
return;
}
// Not all alsa devices are enumerable via snd_device_name_get_hint
// (i.e. bluetooth devices). Therefore if hint is passed in to this
// function as NULL, assume name contains desc.
// Make sure not to free the storage associated with desc in this case
if (hint) {
desc = ALSA_snd_device_name_get_hint(hint, "DESC");
if (desc == NULL) {
SDL_free(dev);
return;
}
} else {
desc = (char *)name;
}
SDL_assert(name != NULL);
// some strings have newlines, like "HDA NVidia, HDMI 0\nHDMI Audio Output".
// just chop the extra lines off, this seems to get a reasonable device
// name without extra details.
ptr = SDL_strchr(desc, '\n');
if (ptr != NULL) {
*ptr = '\0';
}
//SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA: adding %s device '%s' (%s)", iscapture ? "capture" : "output", name, desc);
dev->name = SDL_strdup(name);
if (!dev->name) {
if (hint) {
free(desc); // This should NOT be SDL_free()
}
SDL_free(dev->name);
SDL_free(dev);
return;
}
// Note that spec is NULL, because we are required to open the device before
// acquiring the mix format, making this information inaccessible at
// enumeration time
SDL_AddAudioDevice(iscapture, desc, NULL, dev);
if (hint) {
free(desc); // This should NOT be SDL_free()
}
dev->iscapture = iscapture;
dev->next = *pSeen;
*pSeen = dev;
}
static ALSA_Device *hotplug_devices = NULL;
static void ALSA_HotplugIteration(SDL_bool *has_default_output, SDL_bool *has_default_capture)
{
void **hints = NULL;
ALSA_Device *unseen = NULL;
ALSA_Device *seen = NULL;
if (ALSA_snd_device_name_hint(-1, "pcm", &hints) == 0) {
const char *match = NULL;
int bestmatch = 0xFFFF;
int has_default = -1;
size_t match_len = 0;
static const char *const prefixes[] = {
"hw:", "sysdefault:", "default:", NULL
};
unseen = hotplug_devices;
seen = NULL;
// Apparently there are several different ways that ALSA lists
// actual hardware. It could be prefixed with "hw:" or "default:"
// or "sysdefault:" and maybe others. Go through the list and see
// if we can find a preferred prefix for the system.
for (int i = 0; hints[i]; i++) {
char *name = ALSA_snd_device_name_get_hint(hints[i], "NAME");
if (name == NULL) {
continue;
}
if (SDL_strcmp(name, "default") == 0) {
if (has_default < 0) {
has_default = i;
}
} else {
for (int j = 0; prefixes[j]; j++) {
const char *prefix = prefixes[j];
const size_t prefixlen = SDL_strlen(prefix);
if (SDL_strncmp(name, prefix, prefixlen) == 0) {
if (j < bestmatch) {
bestmatch = j;
match = prefix;
match_len = prefixlen;
}
}
}
free(name); // This should NOT be SDL_free()
}
}
// look through the list of device names to find matches
if (match || (has_default >= 0)) { // did we find a device name prefix we like at all...?
for (int i = 0; hints[i]; i++) {
char *name = ALSA_snd_device_name_get_hint(hints[i], "NAME");
if (name == NULL) {
continue;
}
// only want physical hardware interfaces
const SDL_bool is_default = (has_default == i) ? SDL_TRUE : SDL_FALSE;
if (is_default || (match != NULL && SDL_strncmp(name, match, match_len) == 0)) {
char *ioid = ALSA_snd_device_name_get_hint(hints[i], "IOID");
const SDL_bool isoutput = (ioid == NULL) || (SDL_strcmp(ioid, "Output") == 0);
const SDL_bool isinput = (ioid == NULL) || (SDL_strcmp(ioid, "Input") == 0);
SDL_bool have_output = SDL_FALSE;
SDL_bool have_input = SDL_FALSE;
free(ioid); // This should NOT be SDL_free()
if (!isoutput && !isinput) {
free(name); // This should NOT be SDL_free()
continue;
}
if (is_default) {
if (has_default_output && isoutput) {
*has_default_output = SDL_TRUE;
} else if (has_default_capture && isinput) {
*has_default_capture = SDL_TRUE;
}
free(name); // This should NOT be SDL_free()
continue;
}
ALSA_Device *prev = NULL;
ALSA_Device *next;
for (ALSA_Device *dev = unseen; dev; dev = next) {
next = dev->next;
if ((SDL_strcmp(dev->name, name) == 0) && (((isinput) && dev->iscapture) || ((isoutput) && !dev->iscapture))) {
if (prev) {
prev->next = next;
} else {
unseen = next;
}
dev->next = seen;
seen = dev;
if (isinput) {
have_input = SDL_TRUE;
}
if (isoutput) {
have_output = SDL_TRUE;
}
} else {
prev = dev;
}
}
if (isinput && !have_input) {
add_device(SDL_TRUE, name, hints[i], &seen);
}
if (isoutput && !have_output) {
add_device(SDL_FALSE, name, hints[i], &seen);
}
}
free(name); // This should NOT be SDL_free()
}
}
ALSA_snd_device_name_free_hint(hints);
hotplug_devices = seen; // now we have a known-good list of attached devices.
// report anything still in unseen as removed.
ALSA_Device *next = NULL;
for (ALSA_Device *dev = unseen; dev; dev = next) {
//SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA: removing %s device '%s'", dev->iscapture ? "capture" : "output", dev->name);
next = dev->next;
SDL_AudioDeviceDisconnected(SDL_FindPhysicalAudioDeviceByHandle(dev));
SDL_free(dev->name);
SDL_free(dev);
}
}
}
#if SDL_ALSA_HOTPLUG_THREAD
static SDL_AtomicInt ALSA_hotplug_shutdown;
static SDL_Thread *ALSA_hotplug_thread;
static int SDLCALL ALSA_HotplugThread(void *arg)
{
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_LOW);
while (!SDL_AtomicGet(&ALSA_hotplug_shutdown)) {
// Block awhile before checking again, unless we're told to stop.
const Uint64 ticks = SDL_GetTicks() + 5000;
while (!SDL_AtomicGet(&ALSA_hotplug_shutdown) && SDL_GetTicks() < ticks) {
SDL_Delay(100);
}
ALSA_HotplugIteration(NULL, NULL); // run the check.
}
return 0;
}
#endif
static void ALSA_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
{
// ALSA doesn't have a concept of a changeable default device, afaik, so we expose a generic default
// device here. It's the best we can do at this level.
SDL_bool has_default_output = SDL_FALSE, has_default_capture = SDL_FALSE;
ALSA_HotplugIteration(&has_default_output, &has_default_capture); // run once now before a thread continues to check.
if (has_default_output) {
*default_output = SDL_AddAudioDevice(/*iscapture=*/SDL_FALSE, "ALSA default output device", NULL, (void*)&default_output_handle);
}
if (has_default_capture) {
*default_capture = SDL_AddAudioDevice(/*iscapture=*/SDL_TRUE, "ALSA default capture device", NULL, (void*)&default_capture_handle);
}
#if SDL_ALSA_HOTPLUG_THREAD
SDL_AtomicSet(&ALSA_hotplug_shutdown, 0);
ALSA_hotplug_thread = SDL_CreateThread(ALSA_HotplugThread, "SDLHotplugALSA", NULL);
// if the thread doesn't spin, oh well, you just don't get further hotplug events.
#endif
}
static void ALSA_DeinitializeStart(void)
{
ALSA_Device *dev;
ALSA_Device *next;
#if SDL_ALSA_HOTPLUG_THREAD
if (ALSA_hotplug_thread != NULL) {
SDL_AtomicSet(&ALSA_hotplug_shutdown, 1);
SDL_WaitThread(ALSA_hotplug_thread, NULL);
ALSA_hotplug_thread = NULL;
}
#endif
// Shutting down! Clean up any data we've gathered.
for (dev = hotplug_devices; dev; dev = next) {
//SDL_LogInfo(SDL_LOG_CATEGORY_AUDIO, "ALSA: at shutdown, removing %s device '%s'", dev->iscapture ? "capture" : "output", dev->name);
next = dev->next;
SDL_free(dev->name);
SDL_free(dev);
}
hotplug_devices = NULL;
}
static void ALSA_Deinitialize(void)
{
UnloadALSALibrary();
}
static SDL_bool ALSA_Init(SDL_AudioDriverImpl *impl)
{
if (LoadALSALibrary() < 0) {
return SDL_FALSE;
}
impl->DetectDevices = ALSA_DetectDevices;
impl->OpenDevice = ALSA_OpenDevice;
impl->WaitDevice = ALSA_WaitDevice;
impl->GetDeviceBuf = ALSA_GetDeviceBuf;
impl->PlayDevice = ALSA_PlayDevice;
impl->CloseDevice = ALSA_CloseDevice;
impl->DeinitializeStart = ALSA_DeinitializeStart;
impl->Deinitialize = ALSA_Deinitialize;
impl->WaitCaptureDevice = ALSA_WaitDevice;
impl->CaptureFromDevice = ALSA_CaptureFromDevice;
impl->FlushCapture = ALSA_FlushCapture;
impl->HasCaptureSupport = SDL_TRUE;
return SDL_TRUE;
}
AudioBootStrap ALSA_bootstrap = {
"alsa", "ALSA PCM audio", ALSA_Init, SDL_FALSE
};
#endif // SDL_AUDIO_DRIVER_ALSA

View file

@ -0,0 +1,42 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_ALSA_audio_h_
#define SDL_ALSA_audio_h_
#include <alsa/asoundlib.h>
#include "../SDL_sysaudio.h"
struct SDL_PrivateAudioData
{
// The audio device handle
snd_pcm_t *pcm_handle;
// Raw mixing buffer
Uint8 *mixbuf;
// swizzle function
void (*swizzle_func)(SDL_AudioDevice *_this, void *buffer, Uint32 bufferlen);
};
#endif // SDL_ALSA_audio_h_

View file

@ -0,0 +1,191 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_AUDIO_DRIVER_ANDROID
// Output audio to Android (legacy interface)
#include "../SDL_sysaudio.h"
#include "SDL_androidaudio.h"
#include "../../core/android/SDL_android.h"
#include <android/log.h>
struct SDL_PrivateAudioData
{
int resume; // Resume device if it was paused automatically
};
static SDL_AudioDevice *audioDevice = NULL;
static SDL_AudioDevice *captureDevice = NULL;
static int ANDROIDAUDIO_OpenDevice(SDL_AudioDevice *device)
{
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
if (device->hidden == NULL) {
return SDL_OutOfMemory();
}
const SDL_bool iscapture = device->iscapture;
if (iscapture) {
if (captureDevice) {
return SDL_SetError("An audio capture device is already opened");
}
captureDevice = device;
} else {
if (audioDevice) {
return SDL_SetError("An audio playback device is already opened");
}
audioDevice = device;
}
SDL_AudioFormat test_format;
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
while ((test_format = *(closefmts++)) != 0) {
if ((test_format == SDL_AUDIO_U8) ||
(test_format == SDL_AUDIO_S16) ||
(test_format == SDL_AUDIO_F32)) {
device->spec.format = test_format;
break;
}
}
if (!test_format) {
return SDL_SetError("android: Unsupported audio format");
}
if (Android_JNI_OpenAudioDevice(device) < 0) {
return -1;
}
SDL_UpdatedAudioDeviceFormat(device);
return 0;
}
// !!! FIXME: this needs a WaitDevice implementation.
static int ANDROIDAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
{
Android_JNI_WriteAudioBuffer();
return 0;
}
static Uint8 *ANDROIDAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
return Android_JNI_GetAudioBuffer();
}
static int ANDROIDAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
{
return Android_JNI_CaptureAudioBuffer(buffer, buflen);
}
static void ANDROIDAUDIO_FlushCapture(SDL_AudioDevice *device)
{
Android_JNI_FlushCapturedAudio();
}
static void ANDROIDAUDIO_CloseDevice(SDL_AudioDevice *device)
{
/* At this point SDL_CloseAudioDevice via close_audio_device took care of terminating the audio thread
so it's safe to terminate the Java side buffer and AudioTrack
*/
if (device->hidden) {
Android_JNI_CloseAudioDevice(device->iscapture);
if (device->iscapture) {
SDL_assert(captureDevice == device);
captureDevice = NULL;
} else {
SDL_assert(audioDevice == device);
audioDevice = NULL;
}
SDL_free(device->hidden);
device->hidden = NULL;
}
}
// Pause (block) all non already paused audio devices by taking their mixer lock
void ANDROIDAUDIO_PauseDevices(void)
{
// TODO: Handle multiple devices?
struct SDL_PrivateAudioData *hidden;
if (audioDevice != NULL && audioDevice->hidden != NULL) {
hidden = (struct SDL_PrivateAudioData *)audioDevice->hidden;
SDL_LockMutex(audioDevice->lock);
hidden->resume = SDL_TRUE;
}
if (captureDevice != NULL && captureDevice->hidden != NULL) {
hidden = (struct SDL_PrivateAudioData *)captureDevice->hidden;
SDL_LockMutex(captureDevice->lock);
hidden->resume = SDL_TRUE;
}
}
// Resume (unblock) all non already paused audio devices by releasing their mixer lock
void ANDROIDAUDIO_ResumeDevices(void)
{
// TODO: Handle multiple devices?
struct SDL_PrivateAudioData *hidden;
if (audioDevice != NULL && audioDevice->hidden != NULL) {
hidden = (struct SDL_PrivateAudioData *)audioDevice->hidden;
if (hidden->resume) {
hidden->resume = SDL_FALSE;
SDL_UnlockMutex(audioDevice->lock);
}
}
if (captureDevice != NULL && captureDevice->hidden != NULL) {
hidden = (struct SDL_PrivateAudioData *)captureDevice->hidden;
if (hidden->resume) {
hidden->resume = SDL_FALSE;
SDL_UnlockMutex(captureDevice->lock);
}
}
}
static SDL_bool ANDROIDAUDIO_Init(SDL_AudioDriverImpl *impl)
{
// !!! FIXME: if on Android API < 24, DetectDevices and Deinitialize should be NULL and OnlyHasDefaultOutputDevice and OnlyHasDefaultCaptureDevice should be SDL_TRUE, since audio device enum and hotplug appears to require Android 7.0+.
impl->ThreadInit = Android_AudioThreadInit;
impl->DetectDevices = Android_StartAudioHotplug;
impl->DeinitializeStart = Android_StopAudioHotplug;
impl->OpenDevice = ANDROIDAUDIO_OpenDevice;
impl->PlayDevice = ANDROIDAUDIO_PlayDevice;
impl->GetDeviceBuf = ANDROIDAUDIO_GetDeviceBuf;
impl->CloseDevice = ANDROIDAUDIO_CloseDevice;
impl->CaptureFromDevice = ANDROIDAUDIO_CaptureFromDevice;
impl->FlushCapture = ANDROIDAUDIO_FlushCapture;
impl->HasCaptureSupport = SDL_TRUE;
return SDL_TRUE;
}
AudioBootStrap ANDROIDAUDIO_bootstrap = {
"android", "SDL Android audio driver", ANDROIDAUDIO_Init, SDL_FALSE
};
#endif // SDL_AUDIO_DRIVER_ANDROID

View file

@ -0,0 +1,38 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_androidaudio_h_
#define SDL_androidaudio_h_
#ifdef SDL_AUDIO_DRIVER_ANDROID
void ANDROIDAUDIO_ResumeDevices(void);
void ANDROIDAUDIO_PauseDevices(void);
#else
static void ANDROIDAUDIO_ResumeDevices(void) {}
static void ANDROIDAUDIO_PauseDevices(void) {}
#endif
#endif // SDL_androidaudio_h_

View file

@ -0,0 +1,68 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_coreaudio_h_
#define SDL_coreaudio_h_
#include "../SDL_sysaudio.h"
#ifndef __IOS__
#define MACOSX_COREAUDIO
#endif
#ifdef MACOSX_COREAUDIO
#include <CoreAudio/CoreAudio.h>
#else
#import <AVFoundation/AVFoundation.h>
#import <UIKit/UIApplication.h>
#endif
#include <AudioToolbox/AudioToolbox.h>
#include <AudioUnit/AudioUnit.h>
// Things named "Master" were renamed to "Main" in macOS 12.0's SDK.
#ifdef MACOSX_COREAUDIO
#include <AvailabilityMacros.h>
#ifndef MAC_OS_VERSION_12_0
#define kAudioObjectPropertyElementMain kAudioObjectPropertyElementMaster
#endif
#endif
struct SDL_PrivateAudioData
{
SDL_Thread *thread;
AudioQueueRef audioQueue;
int numAudioBuffers;
AudioQueueBufferRef *audioBuffer;
AudioQueueBufferRef current_buffer;
AudioStreamBasicDescription strdesc;
SDL_Semaphore *ready_semaphore;
char *thread_error;
#ifdef MACOSX_COREAUDIO
AudioDeviceID deviceID;
#else
SDL_bool interrupted;
CFTypeRef interruption_listener;
#endif
};
#endif // SDL_coreaudio_h_

View file

@ -0,0 +1,979 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_AUDIO_DRIVER_COREAUDIO
#include "../SDL_sysaudio.h"
#include "SDL_coreaudio.h"
#include "../../thread/SDL_systhread.h"
#define DEBUG_COREAUDIO 0
#if DEBUG_COREAUDIO
#define CHECK_RESULT(msg) \
if (result != noErr) { \
SDL_Log("COREAUDIO: Got error %d from '%s'!\n", (int)result, msg); \
return SDL_SetError("CoreAudio error (%s): %d", msg, (int)result); \
}
#else
#define CHECK_RESULT(msg) \
if (result != noErr) { \
return SDL_SetError("CoreAudio error (%s): %d", msg, (int)result); \
}
#endif
#ifdef MACOSX_COREAUDIO
static const AudioObjectPropertyAddress devlist_address = {
kAudioHardwarePropertyDevices,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMain
};
static const AudioObjectPropertyAddress default_output_device_address = {
kAudioHardwarePropertyDefaultOutputDevice,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMain
};
static const AudioObjectPropertyAddress default_input_device_address = {
kAudioHardwarePropertyDefaultInputDevice,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMain
};
static const AudioObjectPropertyAddress alive_address = {
kAudioDevicePropertyDeviceIsAlive,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMain
};
static OSStatus DeviceAliveNotification(AudioObjectID devid, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data)
{
SDL_AudioDevice *device = (SDL_AudioDevice *)data;
SDL_assert(((AudioObjectID)(size_t)device->handle) == devid);
UInt32 alive = 1;
UInt32 size = sizeof(alive);
const OSStatus error = AudioObjectGetPropertyData(devid, addrs, 0, NULL, &size, &alive);
SDL_bool dead = SDL_FALSE;
if (error == kAudioHardwareBadDeviceError) {
dead = SDL_TRUE; // device was unplugged.
} else if ((error == kAudioHardwareNoError) && (!alive)) {
dead = SDL_TRUE; // device died in some other way.
}
if (dead) {
#if DEBUG_COREAUDIO
SDL_Log("COREAUDIO: device '%s' is lost!", device->name);
#endif
SDL_AudioDeviceDisconnected(device);
}
return noErr;
}
static void COREAUDIO_FreeDeviceHandle(SDL_AudioDevice *device)
{
const AudioDeviceID devid = (AudioDeviceID)(size_t)device->handle;
AudioObjectRemovePropertyListener(devid, &alive_address, DeviceAliveNotification, device);
}
// This only _adds_ new devices. Removal is handled by devices triggering kAudioDevicePropertyDeviceIsAlive property changes.
static void RefreshPhysicalDevices(void)
{
UInt32 size = 0;
AudioDeviceID *devs = NULL;
SDL_bool isstack;
if (AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &devlist_address, 0, NULL, &size) != kAudioHardwareNoError) {
return;
} else if ((devs = (AudioDeviceID *) SDL_small_alloc(Uint8, size, &isstack)) == NULL) {
return;
} else if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &devlist_address, 0, NULL, &size, devs) != kAudioHardwareNoError) {
SDL_small_free(devs, isstack);
return;
}
const UInt32 total_devices = (UInt32) (size / sizeof(AudioDeviceID));
for (UInt32 i = 0; i < total_devices; i++) {
SDL_AudioDevice *device = SDL_FindPhysicalAudioDeviceByHandle((void *)((size_t)devs[i]));
if (device) {
devs[i] = 0; // The system and SDL both agree it's already here, don't check it again.
}
}
// any non-zero items remaining in `devs` are new devices to be added.
for (int iscapture = 0; iscapture < 2; iscapture++) {
const AudioObjectPropertyAddress addr = {
kAudioDevicePropertyStreamConfiguration,
iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
kAudioObjectPropertyElementMain
};
const AudioObjectPropertyAddress nameaddr = {
kAudioObjectPropertyName,
iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
kAudioObjectPropertyElementMain
};
const AudioObjectPropertyAddress freqaddr = {
kAudioDevicePropertyNominalSampleRate,
iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
kAudioObjectPropertyElementMain
};
for (UInt32 i = 0; i < total_devices; i++) {
const AudioDeviceID dev = devs[i];
if (!dev) {
continue; // already added.
}
AudioBufferList *buflist = NULL;
double sampleRate = 0;
if (AudioObjectGetPropertyDataSize(dev, &addr, 0, NULL, &size) != noErr) {
continue;
} else if ((buflist = (AudioBufferList *)SDL_malloc(size)) == NULL) {
continue;
}
OSStatus result = AudioObjectGetPropertyData(dev, &addr, 0, NULL, &size, buflist);
SDL_AudioSpec spec;
SDL_zero(spec);
if (result == noErr) {
for (Uint32 j = 0; j < buflist->mNumberBuffers; j++) {
spec.channels += buflist->mBuffers[j].mNumberChannels;
}
}
SDL_free(buflist);
if (spec.channels == 0) {
continue;
}
size = sizeof(sampleRate);
if (AudioObjectGetPropertyData(dev, &freqaddr, 0, NULL, &size, &sampleRate) == noErr) {
spec.freq = (int)sampleRate;
}
CFStringRef cfstr = NULL;
size = sizeof(CFStringRef);
if (AudioObjectGetPropertyData(dev, &nameaddr, 0, NULL, &size, &cfstr) != kAudioHardwareNoError) {
continue;
}
CFIndex len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfstr), kCFStringEncodingUTF8);
char *name = (char *)SDL_malloc(len + 1);
SDL_bool usable = ((name != NULL) && (CFStringGetCString(cfstr, name, len + 1, kCFStringEncodingUTF8))) ? SDL_TRUE : SDL_FALSE;
CFRelease(cfstr);
if (usable) {
// Some devices have whitespace at the end...trim it.
len = (CFIndex) SDL_strlen(name);
while ((len > 0) && (name[len - 1] == ' ')) {
len--;
}
usable = (len > 0);
}
if (usable) {
name[len] = '\0';
#if DEBUG_COREAUDIO
SDL_Log("COREAUDIO: Found %s device #%d: '%s' (devid %d)\n",
((iscapture) ? "capture" : "output"),
(int)i, name, (int)dev);
#endif
devs[i] = 0; // don't bother checking this one on the next iscapture iteration of the loop
SDL_AudioDevice *device = SDL_AddAudioDevice(iscapture ? SDL_TRUE : SDL_FALSE, name, &spec, (void *)((size_t)dev));
if (device) {
AudioObjectAddPropertyListener(dev, &alive_address, DeviceAliveNotification, device);
}
}
SDL_free(name); // SDL_AddAudioDevice() would have copied the string.
}
}
SDL_small_free(devs, isstack);
}
// this is called when the system's list of available audio devices changes.
static OSStatus DeviceListChangedNotification(AudioObjectID systemObj, UInt32 num_addr, const AudioObjectPropertyAddress *addrs, void *data)
{
RefreshPhysicalDevices();
return noErr;
}
static OSStatus DefaultAudioDeviceChangedNotification(AudioObjectID inObjectID, const AudioObjectPropertyAddress *addr)
{
AudioDeviceID devid;
Uint32 size = sizeof(devid);
if (AudioObjectGetPropertyData(inObjectID, addr, 0, NULL, &size, &devid) == noErr) {
SDL_DefaultAudioDeviceChanged(SDL_FindPhysicalAudioDeviceByHandle((void *)((size_t)devid)));
}
return noErr;
}
static OSStatus DefaultOutputDeviceChangedNotification(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inUserData)
{
#if DEBUG_COREAUDIO
SDL_Log("COREAUDIO: default output device changed!");
#endif
SDL_assert(inNumberAddresses == 1);
return DefaultAudioDeviceChangedNotification(inObjectID, inAddresses);
}
static OSStatus DefaultInputDeviceChangedNotification(AudioObjectID inObjectID, UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses, void *inUserData)
{
#if DEBUG_COREAUDIO
SDL_Log("COREAUDIO: default input device changed!");
#endif
SDL_assert(inNumberAddresses == 1);
return DefaultAudioDeviceChangedNotification(inObjectID, inAddresses);
}
static void COREAUDIO_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
{
RefreshPhysicalDevices();
AudioObjectAddPropertyListener(kAudioObjectSystemObject, &devlist_address, DeviceListChangedNotification, NULL);
// Get the Device ID
UInt32 size;
AudioDeviceID devid;
size = sizeof(AudioDeviceID);
if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &default_output_device_address, 0, NULL, &size, &devid) == noErr) {
SDL_AudioDevice *device = SDL_FindPhysicalAudioDeviceByHandle((void *)((size_t)devid));
if (device) {
*default_output = device;
}
}
AudioObjectAddPropertyListener(kAudioObjectSystemObject, &default_output_device_address, DefaultOutputDeviceChangedNotification, NULL);
size = sizeof(AudioDeviceID);
if (AudioObjectGetPropertyData(kAudioObjectSystemObject, &default_input_device_address, 0, NULL, &size, &devid) == noErr) {
SDL_AudioDevice *device = SDL_FindPhysicalAudioDeviceByHandle((void *)((size_t)devid));
if (device) {
*default_capture = device;
}
}
AudioObjectAddPropertyListener(kAudioObjectSystemObject, &default_input_device_address, DefaultInputDeviceChangedNotification, NULL);
}
#else // iOS-specific section follows.
static SDL_bool session_active = SDL_FALSE;
static SDL_bool PauseOneAudioDevice(SDL_AudioDevice *device, void *userdata)
{
if (device->hidden && device->hidden->audioQueue && !device->hidden->interrupted) {
AudioQueuePause(device->hidden->audioQueue);
}
return SDL_FALSE; // keep enumerating devices until we've paused them all.
}
static void PauseAudioDevices(void)
{
(void) SDL_FindPhysicalAudioDeviceByCallback(PauseOneAudioDevice, NULL);
}
static SDL_bool ResumeOneAudioDevice(SDL_AudioDevice *device, void *userdata)
{
if (device->hidden && device->hidden->audioQueue && !device->hidden->interrupted) {
AudioQueueStart(device->hidden->audioQueue, NULL);
}
return SDL_FALSE; // keep enumerating devices until we've resumed them all.
}
static void ResumeAudioDevices(void)
{
(void) SDL_FindPhysicalAudioDeviceByCallback(ResumeOneAudioDevice, NULL);
}
static void InterruptionBegin(SDL_AudioDevice *device)
{
if (device != NULL && device->hidden->audioQueue != NULL) {
device->hidden->interrupted = SDL_TRUE;
AudioQueuePause(device->hidden->audioQueue);
}
}
static void InterruptionEnd(SDL_AudioDevice *device)
{
if (device != NULL && device->hidden != NULL && device->hidden->audioQueue != NULL && device->hidden->interrupted && AudioQueueStart(device->hidden->audioQueue, NULL) == AVAudioSessionErrorCodeNone) {
device->hidden->interrupted = SDL_FALSE;
}
}
@interface SDLInterruptionListener : NSObject
@property(nonatomic, assign) SDL_AudioDevice *device;
@end
@implementation SDLInterruptionListener
- (void)audioSessionInterruption:(NSNotification *)note
{
@synchronized(self) {
NSNumber *type = note.userInfo[AVAudioSessionInterruptionTypeKey];
if (type.unsignedIntegerValue == AVAudioSessionInterruptionTypeBegan) {
InterruptionBegin(self.device);
} else {
InterruptionEnd(self.device);
}
}
}
- (void)applicationBecameActive:(NSNotification *)note
{
@synchronized(self) {
InterruptionEnd(self.device);
}
}
@end
typedef struct
{
int output;
int capture;
} CountOpenAudioDevicesData;
static SDL_bool CountOpenAudioDevices(SDL_AudioDevice *device, void *userdata)
{
CountOpenAudioDevicesData *data = (CountOpenAudioDevicesData *) userdata;
if (device->hidden != NULL) { // assume it's open if hidden != NULL
if (device->iscapture) {
data->capture++;
} else {
data->output++;
}
}
return SDL_FALSE; // keep enumerating until all devices have been checked.
}
static SDL_bool UpdateAudioSession(SDL_AudioDevice *device, SDL_bool open, SDL_bool allow_playandrecord)
{
@autoreleasepool {
AVAudioSession *session = [AVAudioSession sharedInstance];
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
NSString *category = AVAudioSessionCategoryPlayback;
NSString *mode = AVAudioSessionModeDefault;
NSUInteger options = AVAudioSessionCategoryOptionMixWithOthers;
NSError *err = nil;
const char *hint;
CountOpenAudioDevicesData data;
SDL_zero(data);
(void) SDL_FindPhysicalAudioDeviceByCallback(CountOpenAudioDevices, &data);
hint = SDL_GetHint(SDL_HINT_AUDIO_CATEGORY);
if (hint) {
if (SDL_strcasecmp(hint, "AVAudioSessionCategoryAmbient") == 0) {
category = AVAudioSessionCategoryAmbient;
} else if (SDL_strcasecmp(hint, "AVAudioSessionCategorySoloAmbient") == 0) {
category = AVAudioSessionCategorySoloAmbient;
options &= ~AVAudioSessionCategoryOptionMixWithOthers;
} else if (SDL_strcasecmp(hint, "AVAudioSessionCategoryPlayback") == 0 ||
SDL_strcasecmp(hint, "playback") == 0) {
category = AVAudioSessionCategoryPlayback;
options &= ~AVAudioSessionCategoryOptionMixWithOthers;
} else if (SDL_strcasecmp(hint, "AVAudioSessionCategoryPlayAndRecord") == 0 ||
SDL_strcasecmp(hint, "playandrecord") == 0) {
if (allow_playandrecord) {
category = AVAudioSessionCategoryPlayAndRecord;
}
}
} else if (data.output && data.capture) {
category = AVAudioSessionCategoryPlayAndRecord;
} else if (data.capture) {
category = AVAudioSessionCategoryRecord;
}
#if !TARGET_OS_TV
if (category == AVAudioSessionCategoryPlayAndRecord) {
options |= AVAudioSessionCategoryOptionDefaultToSpeaker;
}
#endif
if (category == AVAudioSessionCategoryRecord ||
category == AVAudioSessionCategoryPlayAndRecord) {
/* AVAudioSessionCategoryOptionAllowBluetooth isn't available in the SDK for
Apple TV but is still needed in order to output to Bluetooth devices.
*/
options |= 0x4; // AVAudioSessionCategoryOptionAllowBluetooth;
}
if (category == AVAudioSessionCategoryPlayAndRecord) {
options |= AVAudioSessionCategoryOptionAllowBluetoothA2DP |
AVAudioSessionCategoryOptionAllowAirPlay;
}
if (category == AVAudioSessionCategoryPlayback ||
category == AVAudioSessionCategoryPlayAndRecord) {
options |= AVAudioSessionCategoryOptionDuckOthers;
}
if ([session respondsToSelector:@selector(setCategory:mode:options:error:)]) {
if (![session.category isEqualToString:category] || session.categoryOptions != options) {
// Stop the current session so we don't interrupt other application audio
PauseAudioDevices();
[session setActive:NO error:nil];
session_active = SDL_FALSE;
if (![session setCategory:category mode:mode options:options error:&err]) {
NSString *desc = err.description;
SDL_SetError("Could not set Audio Session category: %s", desc.UTF8String);
return SDL_FALSE;
}
}
} else {
if (![session.category isEqualToString:category]) {
// Stop the current session so we don't interrupt other application audio
PauseAudioDevices();
[session setActive:NO error:nil];
session_active = SDL_FALSE;
if (![session setCategory:category error:&err]) {
NSString *desc = err.description;
SDL_SetError("Could not set Audio Session category: %s", desc.UTF8String);
return SDL_FALSE;
}
}
}
if ((data.output || data.capture) && !session_active) {
if (![session setActive:YES error:&err]) {
if ([err code] == AVAudioSessionErrorCodeResourceNotAvailable &&
category == AVAudioSessionCategoryPlayAndRecord) {
return UpdateAudioSession(device, open, SDL_FALSE);
}
NSString *desc = err.description;
SDL_SetError("Could not activate Audio Session: %s", desc.UTF8String);
return SDL_FALSE;
}
session_active = SDL_TRUE;
ResumeAudioDevices();
} else if (!data.output && !data.capture && session_active) {
PauseAudioDevices();
[session setActive:NO error:nil];
session_active = SDL_FALSE;
}
if (open) {
SDLInterruptionListener *listener = [SDLInterruptionListener new];
listener.device = device;
[center addObserver:listener
selector:@selector(audioSessionInterruption:)
name:AVAudioSessionInterruptionNotification
object:session];
/* An interruption end notification is not guaranteed to be sent if
we were previously interrupted... resuming if needed when the app
becomes active seems to be the way to go. */
// Note: object: below needs to be nil, as otherwise it filters by the object, and session doesn't send foreground / active notifications.
[center addObserver:listener
selector:@selector(applicationBecameActive:)
name:UIApplicationDidBecomeActiveNotification
object:nil];
[center addObserver:listener
selector:@selector(applicationBecameActive:)
name:UIApplicationWillEnterForegroundNotification
object:nil];
device->hidden->interruption_listener = CFBridgingRetain(listener);
} else {
SDLInterruptionListener *listener = nil;
listener = (SDLInterruptionListener *)CFBridgingRelease(device->hidden->interruption_listener);
[center removeObserver:listener];
@synchronized(listener) {
listener.device = NULL;
}
}
}
return SDL_TRUE;
}
#endif
static int COREAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
{
AudioQueueBufferRef current_buffer = device->hidden->current_buffer;
SDL_assert(current_buffer != NULL); // should have been called from OutputBufferReadyCallback
SDL_assert(buffer == (Uint8 *) current_buffer->mAudioData);
current_buffer->mAudioDataByteSize = current_buffer->mAudioDataBytesCapacity;
device->hidden->current_buffer = NULL;
AudioQueueEnqueueBuffer(device->hidden->audioQueue, current_buffer, 0, NULL);
return 0;
}
static Uint8 *COREAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
AudioQueueBufferRef current_buffer = device->hidden->current_buffer;
SDL_assert(current_buffer != NULL); // should have been called from OutputBufferReadyCallback
SDL_assert(current_buffer->mAudioData != NULL);
*buffer_size = (int) current_buffer->mAudioDataBytesCapacity;
return (Uint8 *) current_buffer->mAudioData;
}
static void OutputBufferReadyCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer)
{
SDL_AudioDevice *device = (SDL_AudioDevice *)inUserData;
SDL_assert(inBuffer != NULL); // ...right?
SDL_assert(device->hidden->current_buffer == NULL); // shouldn't have anything pending
device->hidden->current_buffer = inBuffer;
const SDL_bool okay = SDL_OutputAudioThreadIterate(device);
SDL_assert((device->hidden->current_buffer == NULL) || !okay); // PlayDevice should have enqueued and cleaned it out, unless we failed or shutdown.
// buffer is unexpectedly here? We're probably dying, but try to requeue this buffer with silence.
if (device->hidden->current_buffer) {
AudioQueueBufferRef current_buffer = device->hidden->current_buffer;
device->hidden->current_buffer = NULL;
SDL_memset(current_buffer->mAudioData, device->silence_value, (size_t) current_buffer->mAudioDataBytesCapacity);
AudioQueueEnqueueBuffer(device->hidden->audioQueue, current_buffer, 0, NULL);
}
}
static int COREAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
{
AudioQueueBufferRef current_buffer = device->hidden->current_buffer;
SDL_assert(current_buffer != NULL); // should have been called from InputBufferReadyCallback
SDL_assert(current_buffer->mAudioData != NULL);
SDL_assert(buflen >= (int) current_buffer->mAudioDataByteSize); // `cpy` makes sure this won't overflow a buffer, but we _will_ drop samples if this assertion fails!
const int cpy = SDL_min(buflen, (int) current_buffer->mAudioDataByteSize);
SDL_memcpy(buffer, current_buffer->mAudioData, cpy);
device->hidden->current_buffer = NULL;
AudioQueueEnqueueBuffer(device->hidden->audioQueue, current_buffer, 0, NULL); // requeue for capturing more data later.
return cpy;
}
static void COREAUDIO_FlushCapture(SDL_AudioDevice *device)
{
AudioQueueBufferRef current_buffer = device->hidden->current_buffer;
if (current_buffer != NULL) { // also gets called at shutdown, when no buffer is available.
// just requeue the current buffer without reading from it, so it can be refilled with new data later.
device->hidden->current_buffer = NULL;
AudioQueueEnqueueBuffer(device->hidden->audioQueue, current_buffer, 0, NULL);
}
}
static void InputBufferReadyCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer,
const AudioTimeStamp *inStartTime, UInt32 inNumberPacketDescriptions,
const AudioStreamPacketDescription *inPacketDescs)
{
SDL_AudioDevice *device = (SDL_AudioDevice *)inUserData;
SDL_assert(inAQ == device->hidden->audioQueue);
SDL_assert(inBuffer != NULL); // ...right?
SDL_assert(device->hidden->current_buffer == NULL); // shouldn't have anything pending
device->hidden->current_buffer = inBuffer;
SDL_CaptureAudioThreadIterate(device);
SDL_assert(device->hidden->current_buffer == NULL); // CaptureFromDevice/FlushCapture should have enqueued and cleaned it out.
}
static void COREAUDIO_CloseDevice(SDL_AudioDevice *device)
{
if (!device->hidden) {
return;
}
// dispose of the audio queue before waiting on the thread, or it might stall for a long time!
if (device->hidden->audioQueue) {
AudioQueueFlush(device->hidden->audioQueue);
AudioQueueStop(device->hidden->audioQueue, 0);
AudioQueueDispose(device->hidden->audioQueue, 0);
}
if (device->hidden->thread) {
SDL_assert(SDL_AtomicGet(&device->shutdown) != 0); // should have been set by SDL_audio.c
SDL_WaitThread(device->hidden->thread, NULL);
}
#ifndef MACOSX_COREAUDIO
UpdateAudioSession(device, SDL_FALSE, SDL_TRUE);
#endif
if (device->hidden->ready_semaphore) {
SDL_DestroySemaphore(device->hidden->ready_semaphore);
}
// AudioQueueDispose() frees the actual buffer objects.
SDL_free(device->hidden->audioBuffer);
SDL_free(device->hidden->thread_error);
SDL_free(device->hidden);
}
#ifdef MACOSX_COREAUDIO
static int PrepareDevice(SDL_AudioDevice *device)
{
void *handle = device->handle;
SDL_assert(handle != NULL); // this meant "system default" in SDL2, but doesn't anymore
const AudioDeviceID devid = (AudioDeviceID)((size_t)handle);
OSStatus result = noErr;
UInt32 size = 0;
AudioObjectPropertyAddress addr = {
0,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMain
};
UInt32 alive = 0;
size = sizeof(alive);
addr.mSelector = kAudioDevicePropertyDeviceIsAlive;
addr.mScope = device->iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput;
result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &alive);
CHECK_RESULT("AudioDeviceGetProperty (kAudioDevicePropertyDeviceIsAlive)");
if (!alive) {
return SDL_SetError("CoreAudio: requested device exists, but isn't alive.");
}
// some devices don't support this property, so errors are fine here.
pid_t pid = 0;
size = sizeof(pid);
addr.mSelector = kAudioDevicePropertyHogMode;
result = AudioObjectGetPropertyData(devid, &addr, 0, NULL, &size, &pid);
if ((result == noErr) && (pid != -1)) {
return SDL_SetError("CoreAudio: requested device is being hogged.");
}
device->hidden->deviceID = devid;
return 0;
}
static int AssignDeviceToAudioQueue(SDL_AudioDevice *device)
{
const AudioObjectPropertyAddress prop = {
kAudioDevicePropertyDeviceUID,
device->iscapture ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput,
kAudioObjectPropertyElementMain
};
OSStatus result;
CFStringRef devuid;
UInt32 devuidsize = sizeof(devuid);
result = AudioObjectGetPropertyData(device->hidden->deviceID, &prop, 0, NULL, &devuidsize, &devuid);
CHECK_RESULT("AudioObjectGetPropertyData (kAudioDevicePropertyDeviceUID)");
result = AudioQueueSetProperty(device->hidden->audioQueue, kAudioQueueProperty_CurrentDevice, &devuid, devuidsize);
CHECK_RESULT("AudioQueueSetProperty (kAudioQueueProperty_CurrentDevice)");
// !!! FIXME: do we need to CFRelease(devuid)?
return 0;
}
#endif
static int PrepareAudioQueue(SDL_AudioDevice *device)
{
const AudioStreamBasicDescription *strdesc = &device->hidden->strdesc;
const SDL_bool iscapture = device->iscapture;
OSStatus result;
SDL_assert(CFRunLoopGetCurrent() != NULL);
if (iscapture) {
result = AudioQueueNewInput(strdesc, InputBufferReadyCallback, device, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &device->hidden->audioQueue);
CHECK_RESULT("AudioQueueNewInput");
} else {
result = AudioQueueNewOutput(strdesc, OutputBufferReadyCallback, device, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0, &device->hidden->audioQueue);
CHECK_RESULT("AudioQueueNewOutput");
}
#ifdef MACOSX_COREAUDIO
if (AssignDeviceToAudioQueue(device) < 0) {
return -1;
}
#endif
SDL_UpdatedAudioDeviceFormat(device); // make sure this is correct.
// Set the channel layout for the audio queue
AudioChannelLayout layout;
SDL_zero(layout);
switch (device->spec.channels) {
case 1:
layout.mChannelLayoutTag = kAudioChannelLayoutTag_Mono;
break;
case 2:
layout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
break;
case 3:
layout.mChannelLayoutTag = kAudioChannelLayoutTag_DVD_4;
break;
case 4:
layout.mChannelLayoutTag = kAudioChannelLayoutTag_Quadraphonic;
break;
case 5:
layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_5_0_A;
break;
case 6:
layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_5_1_A;
break;
case 7:
// FIXME: Need to move channel[4] (BC) to channel[6]
layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_6_1_A;
break;
case 8:
layout.mChannelLayoutTag = kAudioChannelLayoutTag_MPEG_7_1_A;
break;
}
if (layout.mChannelLayoutTag != 0) {
result = AudioQueueSetProperty(device->hidden->audioQueue, kAudioQueueProperty_ChannelLayout, &layout, sizeof(layout));
CHECK_RESULT("AudioQueueSetProperty(kAudioQueueProperty_ChannelLayout)");
}
// Make sure we can feed the device a minimum amount of time
double MINIMUM_AUDIO_BUFFER_TIME_MS = 15.0;
#ifdef __IOS__
if (SDL_floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_7_1) {
// Older iOS hardware, use 40 ms as a minimum time
MINIMUM_AUDIO_BUFFER_TIME_MS = 40.0;
}
#endif
int numAudioBuffers = 2;
const double msecs = (device->sample_frames / ((double)device->spec.freq)) * 1000.0;
if (msecs < MINIMUM_AUDIO_BUFFER_TIME_MS) { // use more buffers if we have a VERY small sample set.
numAudioBuffers = ((int)SDL_ceil(MINIMUM_AUDIO_BUFFER_TIME_MS / msecs) * 2);
}
device->hidden->numAudioBuffers = numAudioBuffers;
device->hidden->audioBuffer = SDL_calloc(numAudioBuffers, sizeof(AudioQueueBufferRef));
if (device->hidden->audioBuffer == NULL) {
return SDL_OutOfMemory();
}
#if DEBUG_COREAUDIO
SDL_Log("COREAUDIO: numAudioBuffers == %d\n", numAudioBuffers);
#endif
for (int i = 0; i < numAudioBuffers; i++) {
result = AudioQueueAllocateBuffer(device->hidden->audioQueue, device->buffer_size, &device->hidden->audioBuffer[i]);
CHECK_RESULT("AudioQueueAllocateBuffer");
SDL_memset(device->hidden->audioBuffer[i]->mAudioData, device->silence_value, device->hidden->audioBuffer[i]->mAudioDataBytesCapacity);
device->hidden->audioBuffer[i]->mAudioDataByteSize = device->hidden->audioBuffer[i]->mAudioDataBytesCapacity;
// !!! FIXME: should we use AudioQueueEnqueueBufferWithParameters and specify all frames be "trimmed" so these are immediately ready to refill with SDL callback data?
result = AudioQueueEnqueueBuffer(device->hidden->audioQueue, device->hidden->audioBuffer[i], 0, NULL);
CHECK_RESULT("AudioQueueEnqueueBuffer");
}
result = AudioQueueStart(device->hidden->audioQueue, NULL);
CHECK_RESULT("AudioQueueStart");
return 0; // We're running!
}
static int AudioQueueThreadEntry(void *arg)
{
SDL_AudioDevice *device = (SDL_AudioDevice *)arg;
if (device->iscapture) {
SDL_CaptureAudioThreadSetup(device);
} else {
SDL_OutputAudioThreadSetup(device);
}
if (PrepareAudioQueue(device) < 0) {
device->hidden->thread_error = SDL_strdup(SDL_GetError());
SDL_PostSemaphore(device->hidden->ready_semaphore);
return 0;
}
// init was successful, alert parent thread and start running...
SDL_PostSemaphore(device->hidden->ready_semaphore);
// This would be WaitDevice/WaitCaptureDevice in the normal SDL audio thread, but we get *BufferReadyCallback calls here to know when to iterate.
while (!SDL_AtomicGet(&device->shutdown)) {
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.10, 1);
}
if (device->iscapture) {
SDL_CaptureAudioThreadShutdown(device);
} else {
// Drain off any pending playback.
const CFTimeInterval secs = (((CFTimeInterval)device->sample_frames) / ((CFTimeInterval)device->spec.freq)) * 2.0;
CFRunLoopRunInMode(kCFRunLoopDefaultMode, secs, 0);
SDL_OutputAudioThreadShutdown(device);
}
return 0;
}
static int COREAUDIO_OpenDevice(SDL_AudioDevice *device)
{
// Initialize all variables that we clean on shutdown
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
if (device->hidden == NULL) {
return SDL_OutOfMemory();
}
#ifndef MACOSX_COREAUDIO
if (!UpdateAudioSession(device, SDL_TRUE, SDL_TRUE)) {
return -1;
}
// Stop CoreAudio from doing expensive audio rate conversion
@autoreleasepool {
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setPreferredSampleRate:device->spec.freq error:nil];
device->spec.freq = (int)session.sampleRate;
#if TARGET_OS_TV
if (device->iscapture) {
[session setPreferredInputNumberOfChannels:device->spec.channels error:nil];
device->spec.channels = session.preferredInputNumberOfChannels;
} else {
[session setPreferredOutputNumberOfChannels:device->spec.channels error:nil];
device->spec.channels = session.preferredOutputNumberOfChannels;
}
#else
// Calling setPreferredOutputNumberOfChannels seems to break audio output on iOS
#endif // TARGET_OS_TV
}
#endif
// Setup a AudioStreamBasicDescription with the requested format
AudioStreamBasicDescription *strdesc = &device->hidden->strdesc;
strdesc->mFormatID = kAudioFormatLinearPCM;
strdesc->mFormatFlags = kLinearPCMFormatFlagIsPacked;
strdesc->mChannelsPerFrame = device->spec.channels;
strdesc->mSampleRate = device->spec.freq;
strdesc->mFramesPerPacket = 1;
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
SDL_AudioFormat test_format;
while ((test_format = *(closefmts++)) != 0) {
// CoreAudio handles most of SDL's formats natively.
switch (test_format) {
case SDL_AUDIO_U8:
case SDL_AUDIO_S8:
case SDL_AUDIO_S16LE:
case SDL_AUDIO_S16BE:
case SDL_AUDIO_S32LE:
case SDL_AUDIO_S32BE:
case SDL_AUDIO_F32LE:
case SDL_AUDIO_F32BE:
break;
default:
continue;
}
break;
}
if (!test_format) { // shouldn't happen, but just in case...
return SDL_SetError("%s: Unsupported audio format", "coreaudio");
}
device->spec.format = test_format;
strdesc->mBitsPerChannel = SDL_AUDIO_BITSIZE(test_format);
if (SDL_AUDIO_ISBIGENDIAN(test_format)) {
strdesc->mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
}
if (SDL_AUDIO_ISFLOAT(test_format)) {
strdesc->mFormatFlags |= kLinearPCMFormatFlagIsFloat;
} else if (SDL_AUDIO_ISSIGNED(test_format)) {
strdesc->mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
}
strdesc->mBytesPerFrame = strdesc->mChannelsPerFrame * strdesc->mBitsPerChannel / 8;
strdesc->mBytesPerPacket = strdesc->mBytesPerFrame * strdesc->mFramesPerPacket;
#ifdef MACOSX_COREAUDIO
if (PrepareDevice(device) < 0) {
return -1;
}
#endif
// This has to init in a new thread so it can get its own CFRunLoop. :/
device->hidden->ready_semaphore = SDL_CreateSemaphore(0);
if (!device->hidden->ready_semaphore) {
return -1; // oh well.
}
char threadname[64];
SDL_GetAudioThreadName(device, threadname, sizeof(threadname));
device->hidden->thread = SDL_CreateThreadInternal(AudioQueueThreadEntry, threadname, 0, device);
if (!device->hidden->thread) {
return -1;
}
SDL_WaitSemaphore(device->hidden->ready_semaphore);
SDL_DestroySemaphore(device->hidden->ready_semaphore);
device->hidden->ready_semaphore = NULL;
if ((device->hidden->thread != NULL) && (device->hidden->thread_error != NULL)) {
SDL_WaitThread(device->hidden->thread, NULL);
device->hidden->thread = NULL;
return SDL_SetError("%s", device->hidden->thread_error);
}
return (device->hidden->thread != NULL) ? 0 : -1;
}
static void COREAUDIO_DeinitializeStart(void)
{
#ifdef MACOSX_COREAUDIO
AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &devlist_address, DeviceListChangedNotification, NULL);
AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &default_output_device_address, DefaultOutputDeviceChangedNotification, NULL);
AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &default_input_device_address, DefaultInputDeviceChangedNotification, NULL);
#endif
}
static SDL_bool COREAUDIO_Init(SDL_AudioDriverImpl *impl)
{
impl->OpenDevice = COREAUDIO_OpenDevice;
impl->PlayDevice = COREAUDIO_PlayDevice;
impl->GetDeviceBuf = COREAUDIO_GetDeviceBuf;
impl->CaptureFromDevice = COREAUDIO_CaptureFromDevice;
impl->FlushCapture = COREAUDIO_FlushCapture;
impl->CloseDevice = COREAUDIO_CloseDevice;
impl->DeinitializeStart = COREAUDIO_DeinitializeStart;
#ifdef MACOSX_COREAUDIO
impl->DetectDevices = COREAUDIO_DetectDevices;
impl->FreeDeviceHandle = COREAUDIO_FreeDeviceHandle;
#else
impl->OnlyHasDefaultOutputDevice = SDL_TRUE;
impl->OnlyHasDefaultCaptureDevice = SDL_TRUE;
#endif
impl->ProvidesOwnCallbackThread = SDL_TRUE;
impl->HasCaptureSupport = SDL_TRUE;
return SDL_TRUE;
}
AudioBootStrap COREAUDIO_bootstrap = {
"coreaudio", "CoreAudio", COREAUDIO_Init, SDL_FALSE
};
#endif // SDL_AUDIO_DRIVER_COREAUDIO

View file

@ -0,0 +1,675 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_AUDIO_DRIVER_DSOUND
#include "../SDL_sysaudio.h"
#include "SDL_directsound.h"
#include <mmreg.h>
#ifdef HAVE_MMDEVICEAPI_H
#include "../../core/windows/SDL_immdevice.h"
#endif
#ifndef WAVE_FORMAT_IEEE_FLOAT
#define WAVE_FORMAT_IEEE_FLOAT 0x0003
#endif
// For Vista+, we can enumerate DSound devices with IMMDevice
#ifdef HAVE_MMDEVICEAPI_H
static SDL_bool SupportsIMMDevice = SDL_FALSE;
#endif
// DirectX function pointers for audio
static void *DSoundDLL = NULL;
typedef HRESULT(WINAPI *fnDirectSoundCreate8)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN);
typedef HRESULT(WINAPI *fnDirectSoundEnumerateW)(LPDSENUMCALLBACKW, LPVOID);
typedef HRESULT(WINAPI *fnDirectSoundCaptureCreate8)(LPCGUID, LPDIRECTSOUNDCAPTURE8 *, LPUNKNOWN);
typedef HRESULT(WINAPI *fnDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW, LPVOID);
typedef HRESULT(WINAPI *fnGetDeviceID)(LPCGUID, LPGUID);
static fnDirectSoundCreate8 pDirectSoundCreate8 = NULL;
static fnDirectSoundEnumerateW pDirectSoundEnumerateW = NULL;
static fnDirectSoundCaptureCreate8 pDirectSoundCaptureCreate8 = NULL;
static fnDirectSoundCaptureEnumerateW pDirectSoundCaptureEnumerateW = NULL;
static fnGetDeviceID pGetDeviceID = NULL;
static const GUID SDL_KSDATAFORMAT_SUBTYPE_PCM = { 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
static const GUID SDL_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = { 0x00000003, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } };
static void DSOUND_Unload(void)
{
pDirectSoundCreate8 = NULL;
pDirectSoundEnumerateW = NULL;
pDirectSoundCaptureCreate8 = NULL;
pDirectSoundCaptureEnumerateW = NULL;
pGetDeviceID = NULL;
if (DSoundDLL != NULL) {
SDL_UnloadObject(DSoundDLL);
DSoundDLL = NULL;
}
}
static int DSOUND_Load(void)
{
int loaded = 0;
DSOUND_Unload();
DSoundDLL = SDL_LoadObject("DSOUND.DLL");
if (DSoundDLL == NULL) {
SDL_SetError("DirectSound: failed to load DSOUND.DLL");
} else {
// Now make sure we have DirectX 8 or better...
#define DSOUNDLOAD(f) \
{ \
p##f = (fn##f)SDL_LoadFunction(DSoundDLL, #f); \
if (!p##f) \
loaded = 0; \
}
loaded = 1; // will reset if necessary.
DSOUNDLOAD(DirectSoundCreate8);
DSOUNDLOAD(DirectSoundEnumerateW);
DSOUNDLOAD(DirectSoundCaptureCreate8);
DSOUNDLOAD(DirectSoundCaptureEnumerateW);
DSOUNDLOAD(GetDeviceID);
#undef DSOUNDLOAD
if (!loaded) {
SDL_SetError("DirectSound: System doesn't appear to have DX8.");
}
}
if (!loaded) {
DSOUND_Unload();
}
return loaded;
}
static int SetDSerror(const char *function, int code)
{
const char *error;
switch (code) {
case E_NOINTERFACE:
error = "Unsupported interface -- Is DirectX 8.0 or later installed?";
break;
case DSERR_ALLOCATED:
error = "Audio device in use";
break;
case DSERR_BADFORMAT:
error = "Unsupported audio format";
break;
case DSERR_BUFFERLOST:
error = "Mixing buffer was lost";
break;
case DSERR_CONTROLUNAVAIL:
error = "Control requested is not available";
break;
case DSERR_INVALIDCALL:
error = "Invalid call for the current state";
break;
case DSERR_INVALIDPARAM:
error = "Invalid parameter";
break;
case DSERR_NODRIVER:
error = "No audio device found";
break;
case DSERR_OUTOFMEMORY:
error = "Out of memory";
break;
case DSERR_PRIOLEVELNEEDED:
error = "Caller doesn't have priority";
break;
case DSERR_UNSUPPORTED:
error = "Function not supported";
break;
default:
error = "Unknown DirectSound error";
break;
}
return SDL_SetError("%s: %s (0x%x)", function, error, code);
}
static void DSOUND_FreeDeviceHandle(SDL_AudioDevice *device)
{
#ifdef HAVE_MMDEVICEAPI_H
if (SupportsIMMDevice) {
SDL_IMMDevice_FreeDeviceHandle(device);
} else
#endif
{
SDL_free(device->handle);
}
}
// FindAllDevs is presumably only used on WinXP; Vista and later can use IMMDevice for better results.
typedef struct FindAllDevsData
{
SDL_bool iscapture;
SDL_AudioDevice **default_device;
LPCGUID default_device_guid;
} FindAllDevsData;
static BOOL CALLBACK FindAllDevs(LPGUID guid, LPCWSTR desc, LPCWSTR module, LPVOID userdata)
{
FindAllDevsData *data = (FindAllDevsData *) userdata;
if (guid != NULL) { // skip default device
char *str = WIN_LookupAudioDeviceName(desc, guid);
if (str != NULL) {
LPGUID cpyguid = (LPGUID)SDL_malloc(sizeof(GUID));
if (cpyguid) {
SDL_copyp(cpyguid, guid);
/* Note that spec is NULL, because we are required to connect to the
* device before getting the channel mask and output format, making
* this information inaccessible at enumeration time
*/
SDL_AudioDevice *device = SDL_AddAudioDevice(data->iscapture, str, NULL, cpyguid);
if (device && data->default_device && data->default_device_guid) {
if (SDL_memcmp(cpyguid, data->default_device_guid, sizeof (GUID)) == 0) {
*data->default_device = device;
}
}
}
SDL_free(str); // SDL_AddAudioDevice() makes a copy of this string.
}
}
return TRUE; // keep enumerating.
}
static void DSOUND_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
{
#ifdef HAVE_MMDEVICEAPI_H
if (SupportsIMMDevice) {
SDL_IMMDevice_EnumerateEndpoints(default_output, default_capture);
} else
#endif
{
// Without IMMDevice, you can enumerate devices and figure out the default devices,
// but you won't get device hotplug or default device change notifications. But this is
// only for WinXP; Windows Vista and later should be using IMMDevice.
FindAllDevsData data;
GUID guid;
data.iscapture = SDL_TRUE;
data.default_device = default_capture;
data.default_device_guid = (pGetDeviceID(&DSDEVID_DefaultCapture, &guid) == DS_OK) ? &guid : NULL;
pDirectSoundCaptureEnumerateW(FindAllDevs, &data);
data.iscapture = SDL_FALSE;
data.default_device = default_output;
data.default_device_guid = (pGetDeviceID(&DSDEVID_DefaultPlayback, &guid) == DS_OK) ? &guid : NULL;
pDirectSoundEnumerateW(FindAllDevs, &data);
}
}
static int DSOUND_WaitDevice(SDL_AudioDevice *device)
{
/* Semi-busy wait, since we have no way of getting play notification
on a primary mixing buffer located in hardware (DirectX 5.0)
*/
while (!SDL_AtomicGet(&device->shutdown)) {
DWORD status = 0;
DWORD cursor = 0;
DWORD junk = 0;
HRESULT result = DS_OK;
// Try to restore a lost sound buffer
IDirectSoundBuffer_GetStatus(device->hidden->mixbuf, &status);
if (status & DSBSTATUS_BUFFERLOST) {
IDirectSoundBuffer_Restore(device->hidden->mixbuf);
} else if (!(status & DSBSTATUS_PLAYING)) {
result = IDirectSoundBuffer_Play(device->hidden->mixbuf, 0, 0, DSBPLAY_LOOPING);
} else {
// Find out where we are playing
result = IDirectSoundBuffer_GetCurrentPosition(device->hidden->mixbuf, &junk, &cursor);
if ((result == DS_OK) && ((cursor / device->buffer_size) != device->hidden->lastchunk)) {
break; // ready for next chunk!
}
}
if ((result != DS_OK) && (result != DSERR_BUFFERLOST)) {
return -1;
}
SDL_Delay(1); // not ready yet; sleep a bit.
}
return 0;
}
static int DSOUND_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
{
// Unlock the buffer, allowing it to play
SDL_assert(buflen == device->buffer_size);
if (IDirectSoundBuffer_Unlock(device->hidden->mixbuf, (LPVOID) buffer, buflen, NULL, 0) != DS_OK) {
return -1;
}
return 0;
}
static Uint8 *DSOUND_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
DWORD cursor = 0;
DWORD junk = 0;
HRESULT result = DS_OK;
SDL_assert(*buffer_size == device->buffer_size);
// Figure out which blocks to fill next
device->hidden->locked_buf = NULL;
result = IDirectSoundBuffer_GetCurrentPosition(device->hidden->mixbuf,
&junk, &cursor);
if (result == DSERR_BUFFERLOST) {
IDirectSoundBuffer_Restore(device->hidden->mixbuf);
result = IDirectSoundBuffer_GetCurrentPosition(device->hidden->mixbuf,
&junk, &cursor);
}
if (result != DS_OK) {
SetDSerror("DirectSound GetCurrentPosition", result);
return NULL;
}
cursor /= device->buffer_size;
#ifdef DEBUG_SOUND
// Detect audio dropouts
{
DWORD spot = cursor;
if (spot < device->hidden->lastchunk) {
spot += device->hidden->num_buffers;
}
if (spot > device->hidden->lastchunk + 1) {
fprintf(stderr, "Audio dropout, missed %d fragments\n",
(spot - (device->hidden->lastchunk + 1)));
}
}
#endif
device->hidden->lastchunk = cursor;
cursor = (cursor + 1) % device->hidden->num_buffers;
cursor *= device->buffer_size;
// Lock the audio buffer
DWORD rawlen = 0;
result = IDirectSoundBuffer_Lock(device->hidden->mixbuf, cursor,
device->buffer_size,
(LPVOID *)&device->hidden->locked_buf,
&rawlen, NULL, &junk, 0);
if (result == DSERR_BUFFERLOST) {
IDirectSoundBuffer_Restore(device->hidden->mixbuf);
result = IDirectSoundBuffer_Lock(device->hidden->mixbuf, cursor,
device->buffer_size,
(LPVOID *)&device->hidden->locked_buf, &rawlen, NULL,
&junk, 0);
}
if (result != DS_OK) {
SetDSerror("DirectSound Lock", result);
return NULL;
}
return device->hidden->locked_buf;
}
static int DSOUND_WaitCaptureDevice(SDL_AudioDevice *device)
{
struct SDL_PrivateAudioData *h = device->hidden;
while (!SDL_AtomicGet(&device->shutdown)) {
DWORD junk, cursor;
if (IDirectSoundCaptureBuffer_GetCurrentPosition(h->capturebuf, &junk, &cursor) != DS_OK) {
return -1;
} else if ((cursor / device->buffer_size) != h->lastchunk) {
break;
}
SDL_Delay(1);
}
return 0;
}
static int DSOUND_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
{
struct SDL_PrivateAudioData *h = device->hidden;
DWORD ptr1len, ptr2len;
VOID *ptr1, *ptr2;
SDL_assert(buflen == device->buffer_size);
if (IDirectSoundCaptureBuffer_Lock(h->capturebuf, h->lastchunk * buflen, buflen, &ptr1, &ptr1len, &ptr2, &ptr2len, 0) != DS_OK) {
return -1;
}
SDL_assert(ptr1len == (DWORD)buflen);
SDL_assert(ptr2 == NULL);
SDL_assert(ptr2len == 0);
SDL_memcpy(buffer, ptr1, ptr1len);
if (IDirectSoundCaptureBuffer_Unlock(h->capturebuf, ptr1, ptr1len, ptr2, ptr2len) != DS_OK) {
return -1;
}
h->lastchunk = (h->lastchunk + 1) % h->num_buffers;
return (int) ptr1len;
}
static void DSOUND_FlushCapture(SDL_AudioDevice *device)
{
struct SDL_PrivateAudioData *h = device->hidden;
DWORD junk, cursor;
if (IDirectSoundCaptureBuffer_GetCurrentPosition(h->capturebuf, &junk, &cursor) == DS_OK) {
h->lastchunk = cursor / device->buffer_size;
}
}
static void DSOUND_CloseDevice(SDL_AudioDevice *device)
{
if (device->hidden) {
if (device->hidden->mixbuf != NULL) {
IDirectSoundBuffer_Stop(device->hidden->mixbuf);
IDirectSoundBuffer_Release(device->hidden->mixbuf);
}
if (device->hidden->sound != NULL) {
IDirectSound_Release(device->hidden->sound);
}
if (device->hidden->capturebuf != NULL) {
IDirectSoundCaptureBuffer_Stop(device->hidden->capturebuf);
IDirectSoundCaptureBuffer_Release(device->hidden->capturebuf);
}
if (device->hidden->capture != NULL) {
IDirectSoundCapture_Release(device->hidden->capture);
}
SDL_free(device->hidden);
device->hidden = NULL;
}
}
/* This function tries to create a secondary audio buffer, and returns the
number of audio chunks available in the created buffer. This is for
playback devices, not capture.
*/
static int CreateSecondary(SDL_AudioDevice *device, const DWORD bufsize, WAVEFORMATEX *wfmt)
{
LPDIRECTSOUND sndObj = device->hidden->sound;
LPDIRECTSOUNDBUFFER *sndbuf = &device->hidden->mixbuf;
HRESULT result = DS_OK;
DSBUFFERDESC format;
LPVOID pvAudioPtr1, pvAudioPtr2;
DWORD dwAudioBytes1, dwAudioBytes2;
// Try to create the secondary buffer
SDL_zero(format);
format.dwSize = sizeof(format);
format.dwFlags = DSBCAPS_GETCURRENTPOSITION2;
format.dwFlags |= DSBCAPS_GLOBALFOCUS;
format.dwBufferBytes = bufsize;
format.lpwfxFormat = wfmt;
result = IDirectSound_CreateSoundBuffer(sndObj, &format, sndbuf, NULL);
if (result != DS_OK) {
return SetDSerror("DirectSound CreateSoundBuffer", result);
}
IDirectSoundBuffer_SetFormat(*sndbuf, wfmt);
// Silence the initial audio buffer
result = IDirectSoundBuffer_Lock(*sndbuf, 0, format.dwBufferBytes,
(LPVOID *)&pvAudioPtr1, &dwAudioBytes1,
(LPVOID *)&pvAudioPtr2, &dwAudioBytes2,
DSBLOCK_ENTIREBUFFER);
if (result == DS_OK) {
SDL_memset(pvAudioPtr1, device->silence_value, dwAudioBytes1);
IDirectSoundBuffer_Unlock(*sndbuf,
(LPVOID)pvAudioPtr1, dwAudioBytes1,
(LPVOID)pvAudioPtr2, dwAudioBytes2);
}
return 0; // We're ready to go
}
/* This function tries to create a capture buffer, and returns the
number of audio chunks available in the created buffer. This is for
capture devices, not playback.
*/
static int CreateCaptureBuffer(SDL_AudioDevice *device, const DWORD bufsize, WAVEFORMATEX *wfmt)
{
LPDIRECTSOUNDCAPTURE capture = device->hidden->capture;
LPDIRECTSOUNDCAPTUREBUFFER *capturebuf = &device->hidden->capturebuf;
DSCBUFFERDESC format;
HRESULT result;
SDL_zero(format);
format.dwSize = sizeof(format);
format.dwFlags = DSCBCAPS_WAVEMAPPED;
format.dwBufferBytes = bufsize;
format.lpwfxFormat = wfmt;
result = IDirectSoundCapture_CreateCaptureBuffer(capture, &format, capturebuf, NULL);
if (result != DS_OK) {
return SetDSerror("DirectSound CreateCaptureBuffer", result);
}
result = IDirectSoundCaptureBuffer_Start(*capturebuf, DSCBSTART_LOOPING);
if (result != DS_OK) {
IDirectSoundCaptureBuffer_Release(*capturebuf);
return SetDSerror("DirectSound Start", result);
}
#if 0
// presumably this starts at zero, but just in case...
result = IDirectSoundCaptureBuffer_GetCurrentPosition(*capturebuf, &junk, &cursor);
if (result != DS_OK) {
IDirectSoundCaptureBuffer_Stop(*capturebuf);
IDirectSoundCaptureBuffer_Release(*capturebuf);
return SetDSerror("DirectSound GetCurrentPosition", result);
}
device->hidden->lastchunk = cursor / device->buffer_size;
#endif
return 0;
}
static int DSOUND_OpenDevice(SDL_AudioDevice *device)
{
// Initialize all variables that we clean on shutdown
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
if (device->hidden == NULL) {
return SDL_OutOfMemory();
}
// Open the audio device
LPGUID guid;
#ifdef HAVE_MMDEVICEAPI_H
if (SupportsIMMDevice) {
guid = SDL_IMMDevice_GetDirectSoundGUID(device);
} else
#endif
{
guid = (LPGUID) device->handle;
}
SDL_assert(guid != NULL);
HRESULT result;
if (device->iscapture) {
result = pDirectSoundCaptureCreate8(guid, &device->hidden->capture, NULL);
if (result != DS_OK) {
return SetDSerror("DirectSoundCaptureCreate8", result);
}
} else {
result = pDirectSoundCreate8(guid, &device->hidden->sound, NULL);
if (result != DS_OK) {
return SetDSerror("DirectSoundCreate8", result);
}
result = IDirectSound_SetCooperativeLevel(device->hidden->sound,
GetDesktopWindow(),
DSSCL_NORMAL);
if (result != DS_OK) {
return SetDSerror("DirectSound SetCooperativeLevel", result);
}
}
const DWORD numchunks = 8;
SDL_bool tried_format = SDL_FALSE;
SDL_AudioFormat test_format;
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
while ((test_format = *(closefmts++)) != 0) {
switch (test_format) {
case SDL_AUDIO_U8:
case SDL_AUDIO_S16:
case SDL_AUDIO_S32:
case SDL_AUDIO_F32:
tried_format = SDL_TRUE;
device->spec.format = test_format;
// Update the fragment size as size in bytes
SDL_UpdatedAudioDeviceFormat(device);
const DWORD bufsize = numchunks * device->buffer_size;
if ((bufsize < DSBSIZE_MIN) || (bufsize > DSBSIZE_MAX)) {
SDL_SetError("Sound buffer size must be between %d and %d",
(int)((DSBSIZE_MIN < numchunks) ? 1 : DSBSIZE_MIN / numchunks),
(int)(DSBSIZE_MAX / numchunks));
} else {
WAVEFORMATEXTENSIBLE wfmt;
SDL_zero(wfmt);
if (device->spec.channels > 2) {
wfmt.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
wfmt.Format.cbSize = sizeof(wfmt) - sizeof(WAVEFORMATEX);
if (SDL_AUDIO_ISFLOAT(device->spec.format)) {
SDL_memcpy(&wfmt.SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, sizeof(GUID));
} else {
SDL_memcpy(&wfmt.SubFormat, &SDL_KSDATAFORMAT_SUBTYPE_PCM, sizeof(GUID));
}
wfmt.Samples.wValidBitsPerSample = SDL_AUDIO_BITSIZE(device->spec.format);
switch (device->spec.channels) {
case 3: // 3.0 (or 2.1)
wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER;
break;
case 4: // 4.0
wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
break;
case 5: // 5.0 (or 4.1)
wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
break;
case 6: // 5.1
wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
break;
case 7: // 6.1
wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_BACK_CENTER;
break;
case 8: // 7.1
wfmt.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT;
break;
default:
SDL_assert(0 && "Unsupported channel count!");
break;
}
} else if (SDL_AUDIO_ISFLOAT(device->spec.format)) {
wfmt.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
} else {
wfmt.Format.wFormatTag = WAVE_FORMAT_PCM;
}
wfmt.Format.wBitsPerSample = SDL_AUDIO_BITSIZE(device->spec.format);
wfmt.Format.nChannels = (WORD)device->spec.channels;
wfmt.Format.nSamplesPerSec = device->spec.freq;
wfmt.Format.nBlockAlign = wfmt.Format.nChannels * (wfmt.Format.wBitsPerSample / 8);
wfmt.Format.nAvgBytesPerSec = wfmt.Format.nSamplesPerSec * wfmt.Format.nBlockAlign;
const int rc = device->iscapture ? CreateCaptureBuffer(device, bufsize, (WAVEFORMATEX *)&wfmt) : CreateSecondary(device, bufsize, (WAVEFORMATEX *)&wfmt);
if (rc == 0) {
device->hidden->num_buffers = numchunks;
break;
}
}
continue;
default:
continue;
}
break;
}
if (!test_format) {
if (tried_format) {
return -1; // CreateSecondary() should have called SDL_SetError().
}
return SDL_SetError("%s: Unsupported audio format", "directsound");
}
// Playback buffers will auto-start playing in DSOUND_WaitDevice()
return 0; // good to go.
}
static void DSOUND_DeinitializeStart(void)
{
#ifdef HAVE_MMDEVICEAPI_H
if (SupportsIMMDevice) {
SDL_IMMDevice_Quit();
}
#endif
}
static void DSOUND_Deinitialize(void)
{
DSOUND_Unload();
#ifdef HAVE_MMDEVICEAPI_H
SupportsIMMDevice = SDL_FALSE;
#endif
}
static SDL_bool DSOUND_Init(SDL_AudioDriverImpl *impl)
{
if (!DSOUND_Load()) {
return SDL_FALSE;
}
#ifdef HAVE_MMDEVICEAPI_H
SupportsIMMDevice = !(SDL_IMMDevice_Init(NULL) < 0);
#endif
impl->DetectDevices = DSOUND_DetectDevices;
impl->OpenDevice = DSOUND_OpenDevice;
impl->PlayDevice = DSOUND_PlayDevice;
impl->WaitDevice = DSOUND_WaitDevice;
impl->GetDeviceBuf = DSOUND_GetDeviceBuf;
impl->WaitCaptureDevice = DSOUND_WaitCaptureDevice;
impl->CaptureFromDevice = DSOUND_CaptureFromDevice;
impl->FlushCapture = DSOUND_FlushCapture;
impl->CloseDevice = DSOUND_CloseDevice;
impl->FreeDeviceHandle = DSOUND_FreeDeviceHandle;
impl->DeinitializeStart = DSOUND_DeinitializeStart;
impl->Deinitialize = DSOUND_Deinitialize;
impl->HasCaptureSupport = SDL_TRUE;
return SDL_TRUE;
}
AudioBootStrap DSOUND_bootstrap = {
"directsound", "DirectSound", DSOUND_Init, SDL_FALSE
};
#endif // SDL_AUDIO_DRIVER_DSOUND

View file

@ -0,0 +1,43 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_directsound_h_
#define SDL_directsound_h_
#include "../../core/windows/SDL_directx.h"
#include "../SDL_sysaudio.h"
// The DirectSound objects
struct SDL_PrivateAudioData
{
// !!! FIXME: make this a union with capture/playback sections?
LPDIRECTSOUND sound;
LPDIRECTSOUNDBUFFER mixbuf;
LPDIRECTSOUNDCAPTURE capture;
LPDIRECTSOUNDCAPTUREBUFFER capturebuf;
int num_buffers;
DWORD lastchunk;
Uint8 *locked_buf;
};
#endif // SDL_directsound_h_

View file

@ -0,0 +1,173 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_AUDIO_DRIVER_DISK
// Output raw audio data to a file.
#include "../SDL_sysaudio.h"
#include "SDL_diskaudio.h"
// !!! FIXME: these should be SDL hints, not environment variables.
// environment variables and defaults.
#define DISKENVR_OUTFILE "SDL_DISKAUDIOFILE"
#define DISKDEFAULT_OUTFILE "sdlaudio.raw"
#define DISKENVR_INFILE "SDL_DISKAUDIOFILEIN"
#define DISKDEFAULT_INFILE "sdlaudio-in.raw"
#define DISKENVR_IODELAY "SDL_DISKAUDIODELAY"
static int DISKAUDIO_WaitDevice(SDL_AudioDevice *device)
{
SDL_Delay(device->hidden->io_delay);
return 0;
}
static int DISKAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
{
const int written = (int)SDL_RWwrite(device->hidden->io, buffer, (size_t)buffer_size);
if (written != buffer_size) { // If we couldn't write, assume fatal error for now
return -1;
}
#ifdef DEBUG_AUDIO
SDL_Log("DISKAUDIO: Wrote %d bytes of audio data", (int) written);
#endif
return 0;
}
static Uint8 *DISKAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
return device->hidden->mixbuf;
}
static int DISKAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
{
struct SDL_PrivateAudioData *h = device->hidden;
const int origbuflen = buflen;
if (h->io) {
const int br = (int)SDL_RWread(h->io, buffer, (size_t)buflen);
buflen -= br;
buffer = ((Uint8 *)buffer) + br;
if (buflen > 0) { // EOF (or error, but whatever).
SDL_RWclose(h->io);
h->io = NULL;
}
}
// if we ran out of file, just write silence.
SDL_memset(buffer, device->silence_value, buflen);
return origbuflen;
}
static void DISKAUDIO_FlushCapture(SDL_AudioDevice *device)
{
// no op...we don't advance the file pointer or anything.
}
static void DISKAUDIO_CloseDevice(SDL_AudioDevice *device)
{
if (device->hidden) {
if (device->hidden->io != NULL) {
SDL_RWclose(device->hidden->io);
}
SDL_free(device->hidden->mixbuf);
SDL_free(device->hidden);
device->hidden = NULL;
}
}
static const char *get_filename(const SDL_bool iscapture)
{
const char *devname = SDL_getenv(iscapture ? DISKENVR_INFILE : DISKENVR_OUTFILE);
if (devname == NULL) {
devname = iscapture ? DISKDEFAULT_INFILE : DISKDEFAULT_OUTFILE;
}
return devname;
}
static int DISKAUDIO_OpenDevice(SDL_AudioDevice *device)
{
SDL_bool iscapture = device->iscapture;
const char *fname = get_filename(iscapture);
const char *envr = SDL_getenv(DISKENVR_IODELAY);
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
if (device->hidden == NULL) {
return SDL_OutOfMemory();
}
if (envr != NULL) {
device->hidden->io_delay = SDL_atoi(envr);
} else {
device->hidden->io_delay = ((device->sample_frames * 1000) / device->spec.freq);
}
// Open the "audio device"
device->hidden->io = SDL_RWFromFile(fname, iscapture ? "rb" : "wb");
if (device->hidden->io == NULL) {
return -1;
}
// Allocate mixing buffer
if (!iscapture) {
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
if (device->hidden->mixbuf == NULL) {
return SDL_OutOfMemory();
}
SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
}
SDL_LogCritical(SDL_LOG_CATEGORY_AUDIO, "You are using the SDL disk i/o audio driver!");
SDL_LogCritical(SDL_LOG_CATEGORY_AUDIO, " %s file [%s].\n", iscapture ? "Reading from" : "Writing to", fname);
return 0; // We're ready to rock and roll. :-)
}
static void DISKAUDIO_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
{
*default_output = SDL_AddAudioDevice(SDL_FALSE, DEFAULT_OUTPUT_DEVNAME, NULL, (void *)0x1);
*default_capture = SDL_AddAudioDevice(SDL_TRUE, DEFAULT_INPUT_DEVNAME, NULL, (void *)0x2);
}
static SDL_bool DISKAUDIO_Init(SDL_AudioDriverImpl *impl)
{
impl->OpenDevice = DISKAUDIO_OpenDevice;
impl->WaitDevice = DISKAUDIO_WaitDevice;
impl->WaitCaptureDevice = DISKAUDIO_WaitDevice;
impl->PlayDevice = DISKAUDIO_PlayDevice;
impl->GetDeviceBuf = DISKAUDIO_GetDeviceBuf;
impl->CaptureFromDevice = DISKAUDIO_CaptureFromDevice;
impl->FlushCapture = DISKAUDIO_FlushCapture;
impl->CloseDevice = DISKAUDIO_CloseDevice;
impl->DetectDevices = DISKAUDIO_DetectDevices;
impl->HasCaptureSupport = SDL_TRUE;
return SDL_TRUE;
}
AudioBootStrap DISKAUDIO_bootstrap = {
"disk", "direct-to-disk audio", DISKAUDIO_Init, SDL_TRUE
};
#endif // SDL_AUDIO_DRIVER_DISK

View file

@ -0,0 +1,36 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_diskaudio_h_
#define SDL_diskaudio_h_
#include "../SDL_sysaudio.h"
struct SDL_PrivateAudioData
{
// The file descriptor for the audio device
SDL_RWops *io;
Uint32 io_delay;
Uint8 *mixbuf;
};
#endif // SDL_diskaudio_h_

View file

@ -0,0 +1,304 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
// !!! FIXME: clean out perror and fprintf calls in here.
#ifdef SDL_AUDIO_DRIVER_OSS
#include <stdio.h> // For perror()
#include <string.h> // For strerror()
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/soundcard.h>
#include "../SDL_audiodev_c.h"
#include "../../SDL_utils_c.h"
#include "SDL_dspaudio.h"
static void DSP_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
{
SDL_EnumUnixAudioDevices(SDL_FALSE, NULL);
}
static void DSP_CloseDevice(SDL_AudioDevice *device)
{
if (device->hidden) {
if (device->hidden->audio_fd >= 0) {
close(device->hidden->audio_fd);
}
SDL_free(device->hidden->mixbuf);
SDL_free(device->hidden);
}
}
static int DSP_OpenDevice(SDL_AudioDevice *device)
{
// Make sure fragment size stays a power of 2, or OSS fails.
// (I don't know which of these are actually legal values, though...)
if (device->spec.channels > 8) {
device->spec.channels = 8;
} else if (device->spec.channels > 4) {
device->spec.channels = 4;
} else if (device->spec.channels > 2) {
device->spec.channels = 2;
}
// Initialize all variables that we clean on shutdown
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
if (device->hidden == NULL) {
return SDL_OutOfMemory();
}
// Open the audio device; we hardcode the device path in `device->name` for lack of better info, so use that.
const int flags = ((device->iscapture) ? OPEN_FLAGS_INPUT : OPEN_FLAGS_OUTPUT);
device->hidden->audio_fd = open(device->name, flags | O_CLOEXEC, 0);
if (device->hidden->audio_fd < 0) {
return SDL_SetError("Couldn't open %s: %s", device->name, strerror(errno));
}
// Make the file descriptor use blocking i/o with fcntl()
{
const long ctlflags = fcntl(device->hidden->audio_fd, F_GETFL) & ~O_NONBLOCK;
if (fcntl(device->hidden->audio_fd, F_SETFL, ctlflags) < 0) {
return SDL_SetError("Couldn't set audio blocking mode");
}
}
// Get a list of supported hardware formats
int value;
if (ioctl(device->hidden->audio_fd, SNDCTL_DSP_GETFMTS, &value) < 0) {
perror("SNDCTL_DSP_GETFMTS");
return SDL_SetError("Couldn't get audio format list");
}
// Try for a closest match on audio format
int format = 0;
SDL_AudioFormat test_format;
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
while ((test_format = *(closefmts++)) != 0) {
#ifdef DEBUG_AUDIO
fprintf(stderr, "Trying format 0x%4.4x\n", test_format);
#endif
switch (test_format) {
case SDL_AUDIO_U8:
if (value & AFMT_U8) {
format = AFMT_U8;
}
break;
case SDL_AUDIO_S16LE:
if (value & AFMT_S16_LE) {
format = AFMT_S16_LE;
}
break;
case SDL_AUDIO_S16BE:
if (value & AFMT_S16_BE) {
format = AFMT_S16_BE;
}
break;
default:
continue;
}
break;
}
if (format == 0) {
return SDL_SetError("Couldn't find any hardware audio formats");
}
device->spec.format = test_format;
// Set the audio format
value = format;
if ((ioctl(device->hidden->audio_fd, SNDCTL_DSP_SETFMT, &value) < 0) ||
(value != format)) {
perror("SNDCTL_DSP_SETFMT");
return SDL_SetError("Couldn't set audio format");
}
// Set the number of channels of output
value = device->spec.channels;
if (ioctl(device->hidden->audio_fd, SNDCTL_DSP_CHANNELS, &value) < 0) {
perror("SNDCTL_DSP_CHANNELS");
return SDL_SetError("Cannot set the number of channels");
}
device->spec.channels = value;
// Set the DSP frequency
value = device->spec.freq;
if (ioctl(device->hidden->audio_fd, SNDCTL_DSP_SPEED, &value) < 0) {
perror("SNDCTL_DSP_SPEED");
return SDL_SetError("Couldn't set audio frequency");
}
device->spec.freq = value;
// Calculate the final parameters for this audio specification
SDL_UpdatedAudioDeviceFormat(device);
/* Determine the power of two of the fragment size
Since apps don't control this in SDL3, and this driver only accepts 8, 16
bit formats and 1, 2, 4, 8 channels, this should always be a power of 2 already. */
SDL_assert(SDL_powerof2(device->buffer_size) == device->buffer_size);
int frag_spec = 0;
while ((0x01U << frag_spec) < device->buffer_size) {
frag_spec++;
}
frag_spec |= 0x00020000; // two fragments, for low latency
// Set the audio buffering parameters
#ifdef DEBUG_AUDIO
fprintf(stderr, "Requesting %d fragments of size %d\n",
(frag_spec >> 16), 1 << (frag_spec & 0xFFFF));
#endif
if (ioctl(device->hidden->audio_fd, SNDCTL_DSP_SETFRAGMENT, &frag_spec) < 0) {
perror("SNDCTL_DSP_SETFRAGMENT");
}
#ifdef DEBUG_AUDIO
{
audio_buf_info info;
ioctl(device->hidden->audio_fd, SNDCTL_DSP_GETOSPACE, &info);
fprintf(stderr, "fragments = %d\n", info.fragments);
fprintf(stderr, "fragstotal = %d\n", info.fragstotal);
fprintf(stderr, "fragsize = %d\n", info.fragsize);
fprintf(stderr, "bytes = %d\n", info.bytes);
}
#endif
// Allocate mixing buffer
if (!device->iscapture) {
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
if (device->hidden->mixbuf == NULL) {
return SDL_OutOfMemory();
}
SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
}
return 0; // We're ready to rock and roll. :-)
}
static int DSP_WaitDevice(SDL_AudioDevice *device)
{
const unsigned long ioctlreq = device->iscapture ? SNDCTL_DSP_GETISPACE : SNDCTL_DSP_GETOSPACE;
struct SDL_PrivateAudioData *h = device->hidden;
while (!SDL_AtomicGet(&device->shutdown)) {
audio_buf_info info;
const int rc = ioctl(h->audio_fd, ioctlreq, &info);
if (rc < 0) {
if (errno == EAGAIN) {
continue;
}
// Hmm, not much we can do - abort
fprintf(stderr, "dsp WaitDevice ioctl failed (unrecoverable): %s\n", strerror(errno));
return -1;
} else if (info.bytes < device->buffer_size) {
SDL_Delay(10);
} else {
break; // ready to go!
}
}
return 0;
}
static int DSP_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
{
struct SDL_PrivateAudioData *h = device->hidden;
if (write(h->audio_fd, buffer, buflen) == -1) {
perror("Audio write");
return -1;
}
#ifdef DEBUG_AUDIO
fprintf(stderr, "Wrote %d bytes of audio data\n", h->mixlen);
#endif
return 0;
}
static Uint8 *DSP_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
return device->hidden->mixbuf;
}
static int DSP_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
{
return (int)read(device->hidden->audio_fd, buffer, buflen);
}
static void DSP_FlushCapture(SDL_AudioDevice *device)
{
struct SDL_PrivateAudioData *h = device->hidden;
audio_buf_info info;
if (ioctl(h->audio_fd, SNDCTL_DSP_GETISPACE, &info) == 0) {
while (info.bytes > 0) {
char buf[512];
const size_t len = SDL_min(sizeof(buf), info.bytes);
const ssize_t br = read(h->audio_fd, buf, len);
if (br <= 0) {
break;
}
info.bytes -= br;
}
}
}
static SDL_bool InitTimeDevicesExist = SDL_FALSE;
static SDL_bool look_for_devices_test(int fd)
{
InitTimeDevicesExist = SDL_TRUE; // note that _something_ exists.
// Don't add to the device list, we're just seeing if any devices exist.
return SDL_FALSE;
}
static SDL_bool DSP_Init(SDL_AudioDriverImpl *impl)
{
InitTimeDevicesExist = SDL_FALSE;
SDL_EnumUnixAudioDevices(SDL_FALSE, look_for_devices_test);
if (!InitTimeDevicesExist) {
SDL_SetError("dsp: No such audio device");
return SDL_FALSE; // maybe try a different backend.
}
impl->DetectDevices = DSP_DetectDevices;
impl->OpenDevice = DSP_OpenDevice;
impl->WaitDevice = DSP_WaitDevice;
impl->PlayDevice = DSP_PlayDevice;
impl->GetDeviceBuf = DSP_GetDeviceBuf;
impl->CloseDevice = DSP_CloseDevice;
impl->WaitCaptureDevice = DSP_WaitDevice;
impl->CaptureFromDevice = DSP_CaptureFromDevice;
impl->FlushCapture = DSP_FlushCapture;
impl->HasCaptureSupport = SDL_TRUE;
return SDL_TRUE;
}
AudioBootStrap DSP_bootstrap = {
"dsp", "Open Sound System (/dev/dsp)", DSP_Init, SDL_FALSE
};
#endif // SDL_AUDIO_DRIVER_OSS

View file

@ -0,0 +1,37 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_dspaudio_h_
#define SDL_dspaudio_h_
#include "../SDL_sysaudio.h"
struct SDL_PrivateAudioData
{
// The file descriptor for the audio device
int audio_fd;
// Raw mixing buffer
Uint8 *mixbuf;
};
#endif // SDL_dspaudio_h_

View file

@ -0,0 +1,97 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
// Output audio to nowhere...
#include "../SDL_sysaudio.h"
#include "SDL_dummyaudio.h"
// !!! FIXME: this should be an SDL hint, not an environment variable.
#define DUMMYENVR_IODELAY "SDL_DUMMYAUDIODELAY"
static int DUMMYAUDIO_WaitDevice(SDL_AudioDevice *device)
{
SDL_Delay(device->hidden->io_delay);
return 0;
}
static int DUMMYAUDIO_OpenDevice(SDL_AudioDevice *device)
{
const char *envr = SDL_getenv(DUMMYENVR_IODELAY);
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
if (!device->hidden) {
return SDL_OutOfMemory();
}
if (!device->iscapture) {
device->hidden->mixbuf = (Uint8 *) SDL_malloc(device->buffer_size);
if (!device->hidden->mixbuf) {
return SDL_OutOfMemory();
}
}
device->hidden->io_delay = (Uint32) (envr ? SDL_atoi(envr) : ((device->sample_frames * 1000) / device->spec.freq));
return 0; // we're good; don't change reported device format.
}
static void DUMMYAUDIO_CloseDevice(SDL_AudioDevice *device)
{
if (device->hidden) {
SDL_free(device->hidden->mixbuf);
SDL_free(device->hidden);
device->hidden = NULL;
}
}
static Uint8 *DUMMYAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
return device->hidden->mixbuf;
}
static int DUMMYAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
{
// always return a full buffer of silence.
SDL_memset(buffer, device->silence_value, buflen);
return buflen;
}
static SDL_bool DUMMYAUDIO_Init(SDL_AudioDriverImpl *impl)
{
impl->OpenDevice = DUMMYAUDIO_OpenDevice;
impl->CloseDevice = DUMMYAUDIO_CloseDevice;
impl->WaitDevice = DUMMYAUDIO_WaitDevice;
impl->GetDeviceBuf = DUMMYAUDIO_GetDeviceBuf;
impl->WaitCaptureDevice = DUMMYAUDIO_WaitDevice;
impl->CaptureFromDevice = DUMMYAUDIO_CaptureFromDevice;
impl->OnlyHasDefaultOutputDevice = SDL_TRUE;
impl->OnlyHasDefaultCaptureDevice = SDL_TRUE;
impl->HasCaptureSupport = SDL_TRUE;
return SDL_TRUE;
}
AudioBootStrap DUMMYAUDIO_bootstrap = {
"dummy", "SDL dummy audio driver", DUMMYAUDIO_Init, SDL_TRUE
};

View file

@ -0,0 +1,34 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_dummyaudio_h_
#define SDL_dummyaudio_h_
#include "../SDL_sysaudio.h"
struct SDL_PrivateAudioData
{
Uint8 *mixbuf; // The file descriptor for the audio device
Uint32 io_delay; // miliseconds to sleep in WaitDevice.
};
#endif // SDL_dummyaudio_h_

View file

@ -0,0 +1,351 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_AUDIO_DRIVER_EMSCRIPTEN
#include "../SDL_sysaudio.h"
#include "SDL_emscriptenaudio.h"
#include <emscripten/emscripten.h>
// just turn off clang-format for this whole file, this INDENT_OFF stuff on
// each EM_ASM section is ugly.
/* *INDENT-OFF* */ /* clang-format off */
static Uint8 *EMSCRIPTENAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
return device->hidden->mixbuf;
}
static int EMSCRIPTENAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
{
const int framelen = SDL_AUDIO_FRAMESIZE(device->spec);
MAIN_THREAD_EM_ASM({
var SDL3 = Module['SDL3'];
var numChannels = SDL3.audio.currentOutputBuffer['numberOfChannels'];
for (var c = 0; c < numChannels; ++c) {
var channelData = SDL3.audio.currentOutputBuffer['getChannelData'](c);
if (channelData.length != $1) {
throw 'Web Audio output buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + $1 + ' samples!';
}
for (var j = 0; j < $1; ++j) {
channelData[j] = HEAPF32[$0 + ((j*numChannels + c) << 2) >> 2]; // !!! FIXME: why are these shifts here?
}
}
}, buffer, buffer_size / framelen);
return 0;
}
static void EMSCRIPTENAUDIO_FlushCapture(SDL_AudioDevice *device)
{
// Do nothing, the new data will just be dropped.
}
static int EMSCRIPTENAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
{
MAIN_THREAD_EM_ASM({
var SDL3 = Module['SDL3'];
var numChannels = SDL3.capture.currentCaptureBuffer.numberOfChannels;
for (var c = 0; c < numChannels; ++c) {
var channelData = SDL3.capture.currentCaptureBuffer.getChannelData(c);
if (channelData.length != $1) {
throw 'Web Audio capture buffer length mismatch! Destination size: ' + channelData.length + ' samples vs expected ' + $1 + ' samples!';
}
if (numChannels == 1) { // fastpath this a little for the common (mono) case.
for (var j = 0; j < $1; ++j) {
setValue($0 + (j * 4), channelData[j], 'float');
}
} else {
for (var j = 0; j < $1; ++j) {
setValue($0 + (((j * numChannels) + c) * 4), channelData[j], 'float');
}
}
}
}, buffer, (buflen / sizeof(float)) / device->spec.channels);
return buflen;
}
static void EMSCRIPTENAUDIO_CloseDevice(SDL_AudioDevice *device)
{
if (!device->hidden) {
return;
}
MAIN_THREAD_EM_ASM({
var SDL3 = Module['SDL3'];
if ($0) {
if (SDL3.capture.silenceTimer !== undefined) {
clearInterval(SDL3.capture.silenceTimer);
}
if (SDL3.capture.stream !== undefined) {
var tracks = SDL3.capture.stream.getAudioTracks();
for (var i = 0; i < tracks.length; i++) {
SDL3.capture.stream.removeTrack(tracks[i]);
}
}
if (SDL3.capture.scriptProcessorNode !== undefined) {
SDL3.capture.scriptProcessorNode.onaudioprocess = function(audioProcessingEvent) {};
SDL3.capture.scriptProcessorNode.disconnect();
}
if (SDL3.capture.mediaStreamNode !== undefined) {
SDL3.capture.mediaStreamNode.disconnect();
}
SDL3.capture = undefined;
} else {
if (SDL3.audio.scriptProcessorNode != undefined) {
SDL3.audio.scriptProcessorNode.disconnect();
}
if (SDL3.audio.silenceTimer !== undefined) {
clearInterval(SDL3.audio.silenceTimer);
}
SDL3.audio = undefined;
}
if ((SDL3.audioContext !== undefined) && (SDL3.audio === undefined) && (SDL3.capture === undefined)) {
SDL3.audioContext.close();
SDL3.audioContext = undefined;
}
}, device->iscapture);
SDL_free(device->hidden->mixbuf);
SDL_free(device->hidden);
device->hidden = NULL;
SDL_AudioThreadFinalize(device);
}
EM_JS_DEPS(sdlaudio, "$autoResumeAudioContext,$dynCall");
static int EMSCRIPTENAUDIO_OpenDevice(SDL_AudioDevice *device)
{
// based on parts of library_sdl.js
// create context
const int result = MAIN_THREAD_EM_ASM_INT({
if (typeof(Module['SDL3']) === 'undefined') {
Module['SDL3'] = {};
}
var SDL3 = Module['SDL3'];
if (!$0) {
SDL3.audio = {};
} else {
SDL3.capture = {};
}
if (!SDL3.audioContext) {
if (typeof(AudioContext) !== 'undefined') {
SDL3.audioContext = new AudioContext();
} else if (typeof(webkitAudioContext) !== 'undefined') {
SDL3.audioContext = new webkitAudioContext();
}
if (SDL3.audioContext) {
if ((typeof navigator.userActivation) === 'undefined') { // Firefox doesn't have this (as of August 2023), use autoResumeAudioContext instead.
autoResumeAudioContext(SDL3.audioContext);
}
}
}
return SDL3.audioContext === undefined ? -1 : 0;
}, device->iscapture);
if (result < 0) {
return SDL_SetError("Web Audio API is not available!");
}
device->spec.format = SDL_AUDIO_F32; // web audio only supports floats
// Initialize all variables that we clean on shutdown
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
if (device->hidden == NULL) {
return SDL_OutOfMemory();
}
// limit to native freq
device->spec.freq = EM_ASM_INT({ return Module['SDL3'].audioContext.sampleRate; });
SDL_UpdatedAudioDeviceFormat(device);
if (!device->iscapture) {
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
if (device->hidden->mixbuf == NULL) {
return SDL_OutOfMemory();
}
SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
}
if (device->iscapture) {
/* The idea is to take the capture media stream, hook it up to an
audio graph where we can pass it through a ScriptProcessorNode
to access the raw PCM samples and push them to the SDL app's
callback. From there, we "process" the audio data into silence
and forget about it.
This should, strictly speaking, use MediaRecorder for capture, but
this API is cleaner to use and better supported, and fires a
callback whenever there's enough data to fire down into the app.
The downside is that we are spending CPU time silencing a buffer
that the audiocontext uselessly mixes into any output. On the
upside, both of those things are not only run in native code in
the browser, they're probably SIMD code, too. MediaRecorder
feels like it's a pretty inefficient tapdance in similar ways,
to be honest. */
MAIN_THREAD_EM_ASM({
var SDL3 = Module['SDL3'];
var have_microphone = function(stream) {
//console.log('SDL audio capture: we have a microphone! Replacing silence callback.');
if (SDL3.capture.silenceTimer !== undefined) {
clearInterval(SDL3.capture.silenceTimer);
SDL3.capture.silenceTimer = undefined;
SDL3.capture.silenceBuffer = undefined
}
SDL3.capture.mediaStreamNode = SDL3.audioContext.createMediaStreamSource(stream);
SDL3.capture.scriptProcessorNode = SDL3.audioContext.createScriptProcessor($1, $0, 1);
SDL3.capture.scriptProcessorNode.onaudioprocess = function(audioProcessingEvent) {
if ((SDL3 === undefined) || (SDL3.capture === undefined)) { return; }
audioProcessingEvent.outputBuffer.getChannelData(0).fill(0.0);
SDL3.capture.currentCaptureBuffer = audioProcessingEvent.inputBuffer;
dynCall('vi', $2, [$3]);
};
SDL3.capture.mediaStreamNode.connect(SDL3.capture.scriptProcessorNode);
SDL3.capture.scriptProcessorNode.connect(SDL3.audioContext.destination);
SDL3.capture.stream = stream;
};
var no_microphone = function(error) {
//console.log('SDL audio capture: we DO NOT have a microphone! (' + error.name + ')...leaving silence callback running.');
};
// we write silence to the audio callback until the microphone is available (user approves use, etc).
SDL3.capture.silenceBuffer = SDL3.audioContext.createBuffer($0, $1, SDL3.audioContext.sampleRate);
SDL3.capture.silenceBuffer.getChannelData(0).fill(0.0);
var silence_callback = function() {
SDL3.capture.currentCaptureBuffer = SDL3.capture.silenceBuffer;
dynCall('vi', $2, [$3]);
};
SDL3.capture.silenceTimer = setInterval(silence_callback, ($1 / SDL3.audioContext.sampleRate) * 1000);
if ((navigator.mediaDevices !== undefined) && (navigator.mediaDevices.getUserMedia !== undefined)) {
navigator.mediaDevices.getUserMedia({ audio: true, video: false }).then(have_microphone).catch(no_microphone);
} else if (navigator.webkitGetUserMedia !== undefined) {
navigator.webkitGetUserMedia({ audio: true, video: false }, have_microphone, no_microphone);
}
}, device->spec.channels, device->sample_frames, SDL_CaptureAudioThreadIterate, device);
} else {
// setup a ScriptProcessorNode
MAIN_THREAD_EM_ASM({
var SDL3 = Module['SDL3'];
SDL3.audio.scriptProcessorNode = SDL3.audioContext['createScriptProcessor']($1, 0, $0);
SDL3.audio.scriptProcessorNode['onaudioprocess'] = function (e) {
if ((SDL3 === undefined) || (SDL3.audio === undefined)) { return; }
// if we're actually running the node, we don't need the fake callback anymore, so kill it.
if (SDL3.audio.silenceTimer !== undefined) {
clearInterval(SDL3.audio.silenceTimer);
SDL3.audio.silenceTimer = undefined;
SDL3.audio.silenceBuffer = undefined;
}
SDL3.audio.currentOutputBuffer = e['outputBuffer'];
dynCall('vi', $2, [$3]);
};
SDL3.audio.scriptProcessorNode['connect'](SDL3.audioContext['destination']);
if (SDL3.audioContext.state === 'suspended') { // uhoh, autoplay is blocked.
SDL3.audio.silenceBuffer = SDL3.audioContext.createBuffer($0, $1, SDL3.audioContext.sampleRate);
SDL3.audio.silenceBuffer.getChannelData(0).fill(0.0);
var silence_callback = function() {
if ((typeof navigator.userActivation) !== 'undefined') { // Almost everything modern except Firefox (as of August 2023)
if (navigator.userActivation.hasBeenActive) {
SDL3.audioContext.resume();
}
}
// the buffer that gets filled here just gets ignored, so the app can make progress
// and/or avoid flooding audio queues until we can actually play audio.
SDL3.audio.currentOutputBuffer = SDL3.audio.silenceBuffer;
dynCall('vi', $2, [$3]);
SDL3.audio.currentOutputBuffer = undefined;
};
SDL3.audio.silenceTimer = setInterval(silence_callback, ($1 / SDL3.audioContext.sampleRate) * 1000);
}
}, device->spec.channels, device->sample_frames, SDL_OutputAudioThreadIterate, device);
}
return 0;
}
static SDL_bool EMSCRIPTENAUDIO_Init(SDL_AudioDriverImpl *impl)
{
SDL_bool available, capture_available;
impl->OpenDevice = EMSCRIPTENAUDIO_OpenDevice;
impl->CloseDevice = EMSCRIPTENAUDIO_CloseDevice;
impl->GetDeviceBuf = EMSCRIPTENAUDIO_GetDeviceBuf;
impl->PlayDevice = EMSCRIPTENAUDIO_PlayDevice;
impl->FlushCapture = EMSCRIPTENAUDIO_FlushCapture;
impl->CaptureFromDevice = EMSCRIPTENAUDIO_CaptureFromDevice;
impl->OnlyHasDefaultOutputDevice = SDL_TRUE;
// technically, this is just runs in idle time in the main thread, but it's close enough to a "thread" for our purposes.
impl->ProvidesOwnCallbackThread = SDL_TRUE;
// check availability
available = MAIN_THREAD_EM_ASM_INT({
if (typeof(AudioContext) !== 'undefined') {
return true;
} else if (typeof(webkitAudioContext) !== 'undefined') {
return true;
}
return false;
}) ? SDL_TRUE : SDL_FALSE;
if (!available) {
SDL_SetError("No audio context available");
}
capture_available = available && MAIN_THREAD_EM_ASM_INT({
if ((typeof(navigator.mediaDevices) !== 'undefined') && (typeof(navigator.mediaDevices.getUserMedia) !== 'undefined')) {
return true;
} else if (typeof(navigator.webkitGetUserMedia) !== 'undefined') {
return true;
}
return false;
}) ? SDL_TRUE : SDL_FALSE;
impl->HasCaptureSupport = capture_available ? SDL_TRUE : SDL_FALSE;
impl->OnlyHasDefaultCaptureDevice = capture_available ? SDL_TRUE : SDL_FALSE;
return available;
}
AudioBootStrap EMSCRIPTENAUDIO_bootstrap = {
"emscripten", "SDL emscripten audio driver", EMSCRIPTENAUDIO_Init, SDL_FALSE
};
/* *INDENT-ON* */ /* clang-format on */
#endif // SDL_AUDIO_DRIVER_EMSCRIPTEN

View file

@ -0,0 +1,33 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_emscriptenaudio_h_
#define SDL_emscriptenaudio_h_
#include "../SDL_sysaudio.h"
struct SDL_PrivateAudioData
{
Uint8 *mixbuf;
};
#endif // SDL_emscriptenaudio_h_

View file

@ -0,0 +1,222 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_AUDIO_DRIVER_HAIKU
// Allow access to the audio stream on Haiku
#include <SoundPlayer.h>
#include <signal.h>
#include "../../core/haiku/SDL_BeApp.h"
extern "C"
{
#include "../SDL_sysaudio.h"
#include "SDL_haikuaudio.h"
}
static Uint8 *HAIKUAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
SDL_assert(device->hidden->current_buffer != NULL);
SDL_assert(device->hidden->current_buffer_len > 0);
*buffer_size = device->hidden->current_buffer_len;
return device->hidden->current_buffer;
}
static int HAIKUAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
{
// We already wrote our output right into the BSoundPlayer's callback's stream. Just clean up our stuff.
SDL_assert(device->hidden->current_buffer != NULL);
SDL_assert(device->hidden->current_buffer_len > 0);
device->hidden->current_buffer = NULL;
device->hidden->current_buffer_len = 0;
return 0;
}
// The Haiku callback for handling the audio buffer
static void FillSound(void *data, void *stream, size_t len, const media_raw_audio_format & format)
{
SDL_AudioDevice *device = (SDL_AudioDevice *)data;
SDL_assert(device->hidden->current_buffer == NULL);
SDL_assert(device->hidden->current_buffer_len == 0);
device->hidden->current_buffer = (Uint8 *) stream;
device->hidden->current_buffer_len = (int) len;
SDL_OutputAudioThreadIterate(device);
}
static void HAIKUAUDIO_CloseDevice(SDL_AudioDevice *device)
{
if (device->hidden) {
if (device->hidden->audio_obj) {
device->hidden->audio_obj->Stop();
delete device->hidden->audio_obj;
}
delete device->hidden;
device->hidden = NULL;
SDL_AudioThreadFinalize(device);
}
}
static const int sig_list[] = {
SIGHUP, SIGINT, SIGQUIT, SIGPIPE, SIGALRM, SIGTERM, SIGWINCH, 0
};
static inline void MaskSignals(sigset_t * omask)
{
sigset_t mask;
int i;
sigemptyset(&mask);
for (i = 0; sig_list[i]; ++i) {
sigaddset(&mask, sig_list[i]);
}
sigprocmask(SIG_BLOCK, &mask, omask);
}
static inline void UnmaskSignals(sigset_t * omask)
{
sigprocmask(SIG_SETMASK, omask, NULL);
}
static int HAIKUAUDIO_OpenDevice(SDL_AudioDevice *device)
{
// Initialize all variables that we clean on shutdown
device->hidden = new SDL_PrivateAudioData;
if (device->hidden == NULL) {
return SDL_OutOfMemory();
}
SDL_zerop(device->hidden);
// Parse the audio format and fill the Be raw audio format
media_raw_audio_format format;
SDL_zero(format);
format.byte_order = B_MEDIA_LITTLE_ENDIAN;
format.frame_rate = (float) device->spec.freq;
format.channel_count = device->spec.channels; // !!! FIXME: support > 2?
SDL_AudioFormat test_format;
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
while ((test_format = *(closefmts++)) != 0) {
switch (test_format) {
case SDL_AUDIO_S8:
format.format = media_raw_audio_format::B_AUDIO_CHAR;
break;
case SDL_AUDIO_U8:
format.format = media_raw_audio_format::B_AUDIO_UCHAR;
break;
case SDL_AUDIO_S16LE:
format.format = media_raw_audio_format::B_AUDIO_SHORT;
break;
case SDL_AUDIO_S16BE:
format.format = media_raw_audio_format::B_AUDIO_SHORT;
format.byte_order = B_MEDIA_BIG_ENDIAN;
break;
case SDL_AUDIO_S32LE:
format.format = media_raw_audio_format::B_AUDIO_INT;
break;
case SDL_AUDIO_S32BE:
format.format = media_raw_audio_format::B_AUDIO_INT;
format.byte_order = B_MEDIA_BIG_ENDIAN;
break;
case SDL_AUDIO_F32LE:
format.format = media_raw_audio_format::B_AUDIO_FLOAT;
break;
case SDL_AUDIO_F32BE:
format.format = media_raw_audio_format::B_AUDIO_FLOAT;
format.byte_order = B_MEDIA_BIG_ENDIAN;
break;
default:
continue;
}
break;
}
if (!test_format) { // shouldn't happen, but just in case...
return SDL_SetError("HAIKU: Unsupported audio format");
}
device->spec.format = test_format;
// Calculate the final parameters for this audio specification
SDL_UpdatedAudioDeviceFormat(device);
format.buffer_size = device->buffer_size;
// Subscribe to the audio stream (creates a new thread)
sigset_t omask;
MaskSignals(&omask);
device->hidden->audio_obj = new BSoundPlayer(&format, "SDL Audio",
FillSound, NULL, device);
UnmaskSignals(&omask);
if (device->hidden->audio_obj->Start() == B_NO_ERROR) {
device->hidden->audio_obj->SetHasData(true);
} else {
return SDL_SetError("Unable to start Haiku audio");
}
return 0; // We're running!
}
static void HAIKUAUDIO_Deinitialize(void)
{
SDL_QuitBeApp();
}
static SDL_bool HAIKUAUDIO_Init(SDL_AudioDriverImpl *impl)
{
if (SDL_InitBeApp() < 0) {
return SDL_FALSE;
}
// Set the function pointers
impl->OpenDevice = HAIKUAUDIO_OpenDevice;
impl->GetDeviceBuf = HAIKUAUDIO_GetDeviceBuf;
impl->PlayDevice = HAIKUAUDIO_PlayDevice;
impl->CloseDevice = HAIKUAUDIO_CloseDevice;
impl->Deinitialize = HAIKUAUDIO_Deinitialize;
impl->ProvidesOwnCallbackThread = SDL_TRUE;
impl->OnlyHasDefaultOutputDevice = SDL_TRUE;
return SDL_TRUE;
}
extern "C" { extern AudioBootStrap HAIKUAUDIO_bootstrap; }
AudioBootStrap HAIKUAUDIO_bootstrap = {
"haiku", "Haiku BSoundPlayer", HAIKUAUDIO_Init, SDL_FALSE
};
#endif // SDL_AUDIO_DRIVER_HAIKU

View file

@ -0,0 +1,35 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_haikuaudio_h_
#define SDL_haikuaudio_h_
#include "../SDL_sysaudio.h"
struct SDL_PrivateAudioData
{
BSoundPlayer *audio_obj;
Uint8 *current_buffer;
int current_buffer_len;
};
#endif // SDL_haikuaudio_h_

View file

@ -0,0 +1,443 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_AUDIO_DRIVER_JACK
#include "../SDL_sysaudio.h"
#include "SDL_jackaudio.h"
#include "../../thread/SDL_systhread.h"
static jack_client_t *(*JACK_jack_client_open)(const char *, jack_options_t, jack_status_t *, ...);
static int (*JACK_jack_client_close)(jack_client_t *);
static void (*JACK_jack_on_shutdown)(jack_client_t *, JackShutdownCallback, void *);
static int (*JACK_jack_activate)(jack_client_t *);
static int (*JACK_jack_deactivate)(jack_client_t *);
static void *(*JACK_jack_port_get_buffer)(jack_port_t *, jack_nframes_t);
static int (*JACK_jack_port_unregister)(jack_client_t *, jack_port_t *);
static void (*JACK_jack_free)(void *);
static const char **(*JACK_jack_get_ports)(jack_client_t *, const char *, const char *, unsigned long);
static jack_nframes_t (*JACK_jack_get_sample_rate)(jack_client_t *);
static jack_nframes_t (*JACK_jack_get_buffer_size)(jack_client_t *);
static jack_port_t *(*JACK_jack_port_register)(jack_client_t *, const char *, const char *, unsigned long, unsigned long);
static jack_port_t *(*JACK_jack_port_by_name)(jack_client_t *, const char *);
static const char *(*JACK_jack_port_name)(const jack_port_t *);
static const char *(*JACK_jack_port_type)(const jack_port_t *);
static int (*JACK_jack_connect)(jack_client_t *, const char *, const char *);
static int (*JACK_jack_set_process_callback)(jack_client_t *, JackProcessCallback, void *);
static int (*JACK_jack_set_sample_rate_callback)(jack_client_t *, JackSampleRateCallback, void *);
static int (*JACK_jack_set_buffer_size_callback)(jack_client_t *, JackBufferSizeCallback, void *);
static int load_jack_syms(void);
#ifdef SDL_AUDIO_DRIVER_JACK_DYNAMIC
static const char *jack_library = SDL_AUDIO_DRIVER_JACK_DYNAMIC;
static void *jack_handle = NULL;
// !!! FIXME: this is copy/pasted in several places now
static int load_jack_sym(const char *fn, void **addr)
{
*addr = SDL_LoadFunction(jack_handle, fn);
if (*addr == NULL) {
// Don't call SDL_SetError(): SDL_LoadFunction already did.
return 0;
}
return 1;
}
// cast funcs to char* first, to please GCC's strict aliasing rules.
#define SDL_JACK_SYM(x) \
if (!load_jack_sym(#x, (void **)(char *)&JACK_##x)) \
return -1
static void UnloadJackLibrary(void)
{
if (jack_handle != NULL) {
SDL_UnloadObject(jack_handle);
jack_handle = NULL;
}
}
static int LoadJackLibrary(void)
{
int retval = 0;
if (jack_handle == NULL) {
jack_handle = SDL_LoadObject(jack_library);
if (jack_handle == NULL) {
retval = -1;
// Don't call SDL_SetError(): SDL_LoadObject already did.
} else {
retval = load_jack_syms();
if (retval < 0) {
UnloadJackLibrary();
}
}
}
return retval;
}
#else
#define SDL_JACK_SYM(x) JACK_##x = x
static void UnloadJackLibrary(void)
{
}
static int LoadJackLibrary(void)
{
load_jack_syms();
return 0;
}
#endif // SDL_AUDIO_DRIVER_JACK_DYNAMIC
static int load_jack_syms(void)
{
SDL_JACK_SYM(jack_client_open);
SDL_JACK_SYM(jack_client_close);
SDL_JACK_SYM(jack_on_shutdown);
SDL_JACK_SYM(jack_activate);
SDL_JACK_SYM(jack_deactivate);
SDL_JACK_SYM(jack_port_get_buffer);
SDL_JACK_SYM(jack_port_unregister);
SDL_JACK_SYM(jack_free);
SDL_JACK_SYM(jack_get_ports);
SDL_JACK_SYM(jack_get_sample_rate);
SDL_JACK_SYM(jack_get_buffer_size);
SDL_JACK_SYM(jack_port_register);
SDL_JACK_SYM(jack_port_by_name);
SDL_JACK_SYM(jack_port_name);
SDL_JACK_SYM(jack_port_type);
SDL_JACK_SYM(jack_connect);
SDL_JACK_SYM(jack_set_process_callback);
SDL_JACK_SYM(jack_set_sample_rate_callback);
SDL_JACK_SYM(jack_set_buffer_size_callback);
return 0;
}
static void jackShutdownCallback(void *arg) // JACK went away; device is lost.
{
SDL_AudioDeviceDisconnected((SDL_AudioDevice *)arg);
}
static int jackSampleRateCallback(jack_nframes_t nframes, void *arg)
{
//SDL_Log("JACK Sample Rate Callback! %d", (int) nframes);
SDL_AudioDevice *device = (SDL_AudioDevice *) arg;
SDL_AudioSpec newspec;
SDL_copyp(&newspec, &device->spec);
newspec.freq = (int) nframes;
if (SDL_AudioDeviceFormatChanged(device, &newspec, device->sample_frames) < 0) {
SDL_AudioDeviceDisconnected(device);
}
return 0;
}
static int jackBufferSizeCallback(jack_nframes_t nframes, void *arg)
{
//SDL_Log("JACK Buffer Size Callback! %d", (int) nframes);
SDL_AudioDevice *device = (SDL_AudioDevice *) arg;
SDL_AudioSpec newspec;
SDL_copyp(&newspec, &device->spec);
if (SDL_AudioDeviceFormatChanged(device, &newspec, (int) nframes) < 0) {
SDL_AudioDeviceDisconnected(device);
}
return 0;
}
static int jackProcessPlaybackCallback(jack_nframes_t nframes, void *arg)
{
SDL_assert(nframes == ((SDL_AudioDevice *)arg)->sample_frames);
SDL_OutputAudioThreadIterate((SDL_AudioDevice *)arg);
return 0;
}
static int JACK_PlayDevice(SDL_AudioDevice *device, const Uint8 *ui8buffer, int buflen)
{
const float *buffer = (float *) ui8buffer;
jack_port_t **ports = device->hidden->sdlports;
const int total_channels = device->spec.channels;
const int total_frames = device->sample_frames;
const jack_nframes_t nframes = (jack_nframes_t) device->sample_frames;
for (int channelsi = 0; channelsi < total_channels; channelsi++) {
float *dst = (float *)JACK_jack_port_get_buffer(ports[channelsi], nframes);
if (dst) {
const float *src = buffer + channelsi;
for (int framesi = 0; framesi < total_frames; framesi++) {
*(dst++) = *src;
src += total_channels;
}
}
}
return 0;
}
static Uint8 *JACK_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
return (Uint8 *)device->hidden->iobuffer;
}
static int jackProcessCaptureCallback(jack_nframes_t nframes, void *arg)
{
SDL_assert(nframes == ((SDL_AudioDevice *)arg)->sample_frames);
SDL_CaptureAudioThreadIterate((SDL_AudioDevice *)arg);
return 0;
}
static int JACK_CaptureFromDevice(SDL_AudioDevice *device, void *vbuffer, int buflen)
{
float *buffer = (float *) vbuffer;
jack_port_t **ports = device->hidden->sdlports;
const int total_channels = device->spec.channels;
const int total_frames = device->sample_frames;
const jack_nframes_t nframes = (jack_nframes_t) device->sample_frames;
for (int channelsi = 0; channelsi < total_channels; channelsi++) {
const float *src = (const float *)JACK_jack_port_get_buffer(ports[channelsi], nframes);
if (src) {
float *dst = buffer + channelsi;
for (int framesi = 0; framesi < total_frames; framesi++) {
*dst = *(src++);
dst += total_channels;
}
}
}
return buflen;
}
static void JACK_FlushCapture(SDL_AudioDevice *device)
{
// do nothing, the data will just be replaced next callback.
}
static void JACK_CloseDevice(SDL_AudioDevice *device)
{
if (device->hidden) {
if (device->hidden->client) {
JACK_jack_deactivate(device->hidden->client);
if (device->hidden->sdlports) {
const int channels = device->spec.channels;
int i;
for (i = 0; i < channels; i++) {
JACK_jack_port_unregister(device->hidden->client, device->hidden->sdlports[i]);
}
SDL_free(device->hidden->sdlports);
}
JACK_jack_client_close(device->hidden->client);
}
SDL_free(device->hidden->iobuffer);
SDL_free(device->hidden);
device->hidden = NULL;
SDL_AudioThreadFinalize(device);
}
}
// !!! FIXME: unify this (PulseAudio has a getAppName, Pipewire has a thing, etc
static const char *GetJackAppName(void)
{
const char *retval = SDL_GetHint(SDL_HINT_AUDIO_DEVICE_APP_NAME);
if (retval && *retval) {
return retval;
}
retval = SDL_GetHint(SDL_HINT_APP_NAME);
if (retval && *retval) {
return retval;
}
return "SDL Application";
}
static int JACK_OpenDevice(SDL_AudioDevice *device)
{
/* Note that JACK uses "output" for capture devices (they output audio
data to us) and "input" for playback (we input audio data to them).
Likewise, SDL's playback port will be "output" (we write data out)
and capture will be "input" (we read data in). */
SDL_bool iscapture = device->iscapture;
const unsigned long sysportflags = iscapture ? JackPortIsOutput : JackPortIsInput;
const unsigned long sdlportflags = iscapture ? JackPortIsInput : JackPortIsOutput;
const JackProcessCallback callback = iscapture ? jackProcessCaptureCallback : jackProcessPlaybackCallback;
const char *sdlportstr = iscapture ? "input" : "output";
const char **devports = NULL;
int *audio_ports;
jack_client_t *client = NULL;
jack_status_t status;
int channels = 0;
int ports = 0;
int i;
// Initialize all variables that we clean on shutdown
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
if (device->hidden == NULL) {
return SDL_OutOfMemory();
}
client = JACK_jack_client_open(GetJackAppName(), JackNoStartServer, &status, NULL);
device->hidden->client = client;
if (client == NULL) {
return SDL_SetError("Can't open JACK client");
}
devports = JACK_jack_get_ports(client, NULL, NULL, JackPortIsPhysical | sysportflags);
if (devports == NULL || !devports[0]) {
return SDL_SetError("No physical JACK ports available");
}
while (devports[++ports]) {
// spin to count devports
}
// Filter out non-audio ports
audio_ports = SDL_calloc(ports, sizeof(*audio_ports));
for (i = 0; i < ports; i++) {
const jack_port_t *dport = JACK_jack_port_by_name(client, devports[i]);
const char *type = JACK_jack_port_type(dport);
const int len = SDL_strlen(type);
// See if type ends with "audio"
if (len >= 5 && !SDL_memcmp(type + len - 5, "audio", 5)) {
audio_ports[channels++] = i;
}
}
if (channels == 0) {
SDL_free(audio_ports);
return SDL_SetError("No physical JACK ports available");
}
// Jack pretty much demands what it wants.
device->spec.format = SDL_AUDIO_F32;
device->spec.freq = JACK_jack_get_sample_rate(client);
device->spec.channels = channels;
device->sample_frames = JACK_jack_get_buffer_size(client);
SDL_UpdatedAudioDeviceFormat(device);
if (!device->iscapture) {
device->hidden->iobuffer = (float *)SDL_calloc(1, device->buffer_size);
if (!device->hidden->iobuffer) {
SDL_free(audio_ports);
return SDL_OutOfMemory();
}
}
// Build SDL's ports, which we will connect to the device ports.
device->hidden->sdlports = (jack_port_t **)SDL_calloc(channels, sizeof(jack_port_t *));
if (device->hidden->sdlports == NULL) {
SDL_free(audio_ports);
return SDL_OutOfMemory();
}
for (i = 0; i < channels; i++) {
char portname[32];
(void)SDL_snprintf(portname, sizeof(portname), "sdl_jack_%s_%d", sdlportstr, i);
device->hidden->sdlports[i] = JACK_jack_port_register(client, portname, JACK_DEFAULT_AUDIO_TYPE, sdlportflags, 0);
if (device->hidden->sdlports[i] == NULL) {
SDL_free(audio_ports);
return SDL_SetError("jack_port_register failed");
}
}
if (JACK_jack_set_buffer_size_callback(client, jackBufferSizeCallback, device) != 0) {
SDL_free(audio_ports);
return SDL_SetError("JACK: Couldn't set buffer size callback");
} else if (JACK_jack_set_sample_rate_callback(client, jackSampleRateCallback, device) != 0) {
SDL_free(audio_ports);
return SDL_SetError("JACK: Couldn't set sample rate callback");
} else if (JACK_jack_set_process_callback(client, callback, device) != 0) {
SDL_free(audio_ports);
return SDL_SetError("JACK: Couldn't set process callback");
}
JACK_jack_on_shutdown(client, jackShutdownCallback, device);
if (JACK_jack_activate(client) != 0) {
SDL_free(audio_ports);
return SDL_SetError("Failed to activate JACK client");
}
// once activated, we can connect all the ports.
for (i = 0; i < channels; i++) {
const char *sdlport = JACK_jack_port_name(device->hidden->sdlports[i]);
const char *srcport = iscapture ? devports[audio_ports[i]] : sdlport;
const char *dstport = iscapture ? sdlport : devports[audio_ports[i]];
if (JACK_jack_connect(client, srcport, dstport) != 0) {
SDL_free(audio_ports);
return SDL_SetError("Couldn't connect JACK ports: %s => %s", srcport, dstport);
}
}
// don't need these anymore.
JACK_jack_free(devports);
SDL_free(audio_ports);
// We're ready to rock and roll. :-)
return 0;
}
static void JACK_Deinitialize(void)
{
UnloadJackLibrary();
}
static SDL_bool JACK_Init(SDL_AudioDriverImpl *impl)
{
if (LoadJackLibrary() < 0) {
return SDL_FALSE;
} else {
// Make sure a JACK server is running and available.
jack_status_t status;
jack_client_t *client = JACK_jack_client_open("SDL", JackNoStartServer, &status, NULL);
if (client == NULL) {
UnloadJackLibrary();
return SDL_FALSE;
}
JACK_jack_client_close(client);
}
impl->OpenDevice = JACK_OpenDevice;
impl->GetDeviceBuf = JACK_GetDeviceBuf;
impl->PlayDevice = JACK_PlayDevice;
impl->CloseDevice = JACK_CloseDevice;
impl->Deinitialize = JACK_Deinitialize;
impl->CaptureFromDevice = JACK_CaptureFromDevice;
impl->FlushCapture = JACK_FlushCapture;
impl->OnlyHasDefaultOutputDevice = SDL_TRUE;
impl->OnlyHasDefaultCaptureDevice = SDL_TRUE;
impl->HasCaptureSupport = SDL_TRUE;
impl->ProvidesOwnCallbackThread = SDL_TRUE;
return SDL_TRUE;
}
AudioBootStrap JACK_bootstrap = {
"jack", "JACK Audio Connection Kit", JACK_Init, SDL_FALSE
};
#endif // SDL_AUDIO_DRIVER_JACK

View file

@ -0,0 +1,35 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_jackaudio_h_
#define SDL_jackaudio_h_
#include <jack/jack.h>
#include "../SDL_sysaudio.h"
struct SDL_PrivateAudioData
{
jack_client_t *client;
jack_port_t **sdlports;
float *iobuffer;
};
#endif // SDL_jackaudio_h_

View file

@ -0,0 +1,287 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_AUDIO_DRIVER_N3DS
// N3DS Audio driver
#include "../SDL_sysaudio.h"
#include "SDL_n3dsaudio.h"
#define N3DSAUDIO_DRIVER_NAME "n3ds"
static dspHookCookie dsp_hook;
static SDL_AudioDevice *audio_device;
// fully local functions related to the wavebufs / DSP, not the same as the `device->lock` SDL_Mutex!
static SDL_INLINE void contextLock(SDL_AudioDevice *device)
{
LightLock_Lock(&device->hidden->lock);
}
static SDL_INLINE void contextUnlock(SDL_AudioDevice *device)
{
LightLock_Unlock(&device->hidden->lock);
}
static void N3DSAUD_DspHook(DSP_HookType hook)
{
if (hook == DSPHOOK_ONCANCEL) {
contextLock(audio_device);
audio_device->hidden->isCancelled = SDL_TRUE;
SDL_AudioDeviceDisconnected(audio_device);
CondVar_Broadcast(&audio_device->hidden->cv);
contextUnlock(audio_device);
}
}
static void AudioFrameFinished(void *vdevice)
{
bool shouldBroadcast = false;
unsigned i;
SDL_AudioDevice *device = (SDL_AudioDevice *)vdevice;
contextLock(device);
for (i = 0; i < NUM_BUFFERS; i++) {
if (device->hidden->waveBuf[i].status == NDSP_WBUF_DONE) {
device->hidden->waveBuf[i].status = NDSP_WBUF_FREE;
shouldBroadcast = SDL_TRUE;
}
}
if (shouldBroadcast) {
CondVar_Broadcast(&device->hidden->cv);
}
contextUnlock(device);
}
static int N3DSAUDIO_OpenDevice(SDL_AudioDevice *device)
{
Result ndsp_init_res;
Uint8 *data_vaddr;
float mix[12];
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
if (device->hidden == NULL) {
return SDL_OutOfMemory();
}
// Initialise the DSP service
ndsp_init_res = ndspInit();
if (R_FAILED(ndsp_init_res)) {
if ((R_SUMMARY(ndsp_init_res) == RS_NOTFOUND) && (R_MODULE(ndsp_init_res) == RM_DSP)) {
SDL_SetError("DSP init failed: dspfirm.cdc missing!");
} else {
SDL_SetError("DSP init failed. Error code: 0x%lX", ndsp_init_res);
}
return -1;
}
// Initialise internal state
LightLock_Init(&device->hidden->lock);
CondVar_Init(&device->hidden->cv);
if (device->spec.channels > 2) {
device->spec.channels = 2;
}
Uint32 format = 0;
SDL_AudioFormat test_format;
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
while ((test_format = *(closefmts++)) != 0) {
if (test_format == SDL_AUDIO_S8) { // Signed 8-bit audio supported
format = (device->spec.channels == 2) ? NDSP_FORMAT_STEREO_PCM8 : NDSP_FORMAT_MONO_PCM8;
break;
} else if (test_format == SDL_AUDIO_S16) { // Signed 16-bit audio supported
format = (device->spec.channels == 2) ? NDSP_FORMAT_STEREO_PCM16 : NDSP_FORMAT_MONO_PCM16;
break;
}
}
if (!test_format) { // shouldn't happen, but just in case...
return SDL_SetError("No supported audio format found.");
}
device->spec.format = test_format;
// Update the fragment size as size in bytes
SDL_UpdatedAudioDeviceFormat(device);
// Allocate mixing buffer
if (device->buffer_size >= SDL_MAX_UINT32 / 2) {
return SDL_SetError("Mixing buffer is too large.");
}
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
if (device->hidden->mixbuf == NULL) {
return SDL_OutOfMemory();
}
SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
data_vaddr = (Uint8 *)linearAlloc(device->buffer_size * NUM_BUFFERS);
if (data_vaddr == NULL) {
return SDL_OutOfMemory();
}
SDL_memset(data_vaddr, 0, device->buffer_size * NUM_BUFFERS);
DSP_FlushDataCache(data_vaddr, device->buffer_size * NUM_BUFFERS);
device->hidden->nextbuf = 0;
ndspChnReset(0);
ndspChnSetInterp(0, NDSP_INTERP_LINEAR);
ndspChnSetRate(0, device->spec.freq);
ndspChnSetFormat(0, format);
SDL_zeroa(mix);
mix[0] = mix[1] = 1.0f;
ndspChnSetMix(0, mix);
SDL_memset(device->hidden->waveBuf, 0, sizeof(ndspWaveBuf) * NUM_BUFFERS);
const int sample_frame_size = SDL_AUDIO_FRAMESIZE(device->spec);
for (unsigned i = 0; i < NUM_BUFFERS; i++) {
device->hidden->waveBuf[i].data_vaddr = data_vaddr;
device->hidden->waveBuf[i].nsamples = device->buffer_size / sample_frame_size;
data_vaddr += device->buffer_size;
}
// Setup callback
audio_device = device;
ndspSetCallback(AudioFrameFinished, device);
dspHook(&dsp_hook, N3DSAUD_DspHook);
return 0;
}
static int N3DSAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
{
contextLock(device);
const size_t nextbuf = device->hidden->nextbuf;
if (device->hidden->isCancelled ||
device->hidden->waveBuf[nextbuf].status != NDSP_WBUF_FREE) {
contextUnlock(device);
return 0; // !!! FIXME: is this a fatal error? If so, this should return -1.
}
device->hidden->nextbuf = (nextbuf + 1) % NUM_BUFFERS;
contextUnlock(device);
SDL_memcpy((void *)device->hidden->waveBuf[nextbuf].data_vaddr, buffer, buflen);
DSP_FlushDataCache(device->hidden->waveBuf[nextbuf].data_vaddr, buflen);
ndspChnWaveBufAdd(0, &device->hidden->waveBuf[nextbuf]);
return 0;
}
static int N3DSAUDIO_WaitDevice(SDL_AudioDevice *device)
{
contextLock(device);
while (!device->hidden->isCancelled && !SDL_AtomicGet(&device->shutdown) &&
device->hidden->waveBuf[device->hidden->nextbuf].status != NDSP_WBUF_FREE) {
CondVar_Wait(&device->hidden->cv, &device->hidden->lock);
}
contextUnlock(device);
return 0;
}
static Uint8 *N3DSAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
return device->hidden->mixbuf;
}
static void N3DSAUDIO_CloseDevice(SDL_AudioDevice *device)
{
if (!device->hidden) {
return;
}
contextLock(device);
dspUnhook(&dsp_hook);
ndspSetCallback(NULL, NULL);
if (!device->hidden->isCancelled) {
ndspChnReset(0);
SDL_memset(device->hidden->waveBuf, 0, sizeof(ndspWaveBuf) * NUM_BUFFERS);
CondVar_Broadcast(&device->hidden->cv);
}
contextUnlock(device);
ndspExit();
if (device->hidden->waveBuf[0].data_vaddr) {
linearFree((void *)device->hidden->waveBuf[0].data_vaddr);
}
if (device->hidden->mixbuf) {
SDL_free(device->hidden->mixbuf);
device->hidden->mixbuf = NULL;
}
SDL_free(device->hidden);
device->hidden = NULL;
}
static void N3DSAUDIO_ThreadInit(SDL_AudioDevice *device)
{
s32 current_priority = 0x30;
svcGetThreadPriority(&current_priority, CUR_THREAD_HANDLE);
current_priority--;
// 0x18 is reserved for video, 0x30 is the default for main thread
current_priority = SDL_clamp(current_priority, 0x19, 0x2F);
svcSetThreadPriority(CUR_THREAD_HANDLE, current_priority);
}
static SDL_bool N3DSAUDIO_Init(SDL_AudioDriverImpl *impl)
{
impl->OpenDevice = N3DSAUDIO_OpenDevice;
impl->PlayDevice = N3DSAUDIO_PlayDevice;
impl->WaitDevice = N3DSAUDIO_WaitDevice;
impl->GetDeviceBuf = N3DSAUDIO_GetDeviceBuf;
impl->CloseDevice = N3DSAUDIO_CloseDevice;
impl->ThreadInit = N3DSAUDIO_ThreadInit;
impl->OnlyHasDefaultOutputDevice = SDL_TRUE;
// Should be possible, but micInit would fail
impl->HasCaptureSupport = SDL_FALSE;
return SDL_TRUE;
}
AudioBootStrap N3DSAUDIO_bootstrap = {
N3DSAUDIO_DRIVER_NAME,
"SDL N3DS audio driver",
N3DSAUDIO_Init,
0
};
#endif // SDL_AUDIO_DRIVER_N3DS

View file

@ -0,0 +1,40 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_n3dsaudio_h
#define SDL_n3dsaudio_h
#include <3ds.h>
#define NUM_BUFFERS 3 // -- Minimum 2!
struct SDL_PrivateAudioData
{
// Speaker data
Uint8 *mixbuf;
Uint32 nextbuf;
ndspWaveBuf waveBuf[NUM_BUFFERS];
LightLock lock;
CondVar cv;
SDL_bool isCancelled;
};
#endif // SDL_n3dsaudio_h

View file

@ -0,0 +1,328 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_AUDIO_DRIVER_NETBSD
// Driver for native NetBSD audio(4).
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/audioio.h>
#include "../../core/unix/SDL_poll.h"
#include "../SDL_audiodev_c.h"
#include "SDL_netbsdaudio.h"
//#define DEBUG_AUDIO
static void NETBSDAUDIO_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
{
SDL_EnumUnixAudioDevices(SDL_FALSE, NULL);
}
static void NETBSDAUDIO_Status(SDL_AudioDevice *device)
{
#ifdef DEBUG_AUDIO
/* *INDENT-OFF* */ /* clang-format off */
audio_info_t info;
const struct audio_prinfo *prinfo;
if (ioctl(device->hidden->audio_fd, AUDIO_GETINFO, &info) < 0) {
fprintf(stderr, "AUDIO_GETINFO failed.\n");
return;
}
prinfo = device->iscapture ? &info.record : &info.play;
fprintf(stderr, "\n"
"[%s info]\n"
"buffer size : %d bytes\n"
"sample rate : %i Hz\n"
"channels : %i\n"
"precision : %i-bit\n"
"encoding : 0x%x\n"
"seek : %i\n"
"sample count : %i\n"
"EOF count : %i\n"
"paused : %s\n"
"error occurred : %s\n"
"waiting : %s\n"
"active : %s\n"
"",
device->iscapture ? "record" : "play",
prinfo->buffer_size,
prinfo->sample_rate,
prinfo->channels,
prinfo->precision,
prinfo->encoding,
prinfo->seek,
prinfo->samples,
prinfo->eof,
prinfo->pause ? "yes" : "no",
prinfo->error ? "yes" : "no",
prinfo->waiting ? "yes" : "no",
prinfo->active ? "yes" : "no");
fprintf(stderr, "\n"
"[audio info]\n"
"monitor_gain : %i\n"
"hw block size : %d bytes\n"
"hi watermark : %i\n"
"lo watermark : %i\n"
"audio mode : %s\n"
"",
info.monitor_gain,
info.blocksize,
info.hiwat, info.lowat,
(info.mode == AUMODE_PLAY) ? "PLAY"
: (info.mode == AUMODE_RECORD) ? "RECORD"
: (info.mode == AUMODE_PLAY_ALL ? "PLAY_ALL" : "?"));
fprintf(stderr, "\n"
"[audio spec]\n"
"format : 0x%x\n"
"size : %u\n"
"",
device->spec.format,
device->buffer_size);
/* *INDENT-ON* */ /* clang-format on */
#endif // DEBUG_AUDIO
}
static int NETBSDAUDIO_WaitDevice(SDL_AudioDevice *device)
{
const SDL_bool iscapture = device->iscapture;
while (!SDL_AtomicGet(&device->shutdown)) {
audio_info_t info;
const int rc = ioctl(device->hidden->audio_fd, AUDIO_GETINFO, &info);
if (rc < 0) {
if (errno == EAGAIN) {
continue;
}
// Hmm, not much we can do - abort
fprintf(stderr, "netbsdaudio WaitDevice ioctl failed (unrecoverable): %s\n", strerror(errno));
return -1;
}
const size_t remain = (size_t)((iscapture ? info.record.seek : info.play.seek) * SDL_AUDIO_BYTESIZE(device->spec.format));
if (!iscapture && (remain >= device->buffer_size)) {
SDL_Delay(10);
} else if (iscapture && (remain < device->buffer_size)) {
SDL_Delay(10);
} else {
break; // ready to go!
}
}
return 0;
}
static int NETBSDAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
{
struct SDL_PrivateAudioData *h = device->hidden;
const int written = write(h->audio_fd, buffer, buflen);
if (written != buflen) { // Treat even partial writes as fatal errors.
return -1;
}
#ifdef DEBUG_AUDIO
fprintf(stderr, "Wrote %d bytes of audio data\n", written);
#endif
return 0;
}
static Uint8 *NETBSDAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
return device->hidden->mixbuf;
}
static int NETBSDAUDIO_CaptureFromDevice(SDL_AudioDevice *device, void *vbuffer, int buflen)
{
Uint8 *buffer = (Uint8 *)vbuffer;
const int br = read(device->hidden->audio_fd, buffer, buflen);
if (br == -1) {
// Non recoverable error has occurred. It should be reported!!!
perror("audio");
return -1;
}
#ifdef DEBUG_AUDIO
fprintf(stderr, "Captured %d bytes of audio data\n", br);
#endif
return br;
}
static void NETBSDAUDIO_FlushCapture(SDL_AudioDevice *device)
{
struct SDL_PrivateAudioData *h = device->hidden;
audio_info_t info;
if (ioctl(device->hidden->audio_fd, AUDIO_GETINFO, &info) == 0) {
size_t remain = (size_t)(info.record.seek * SDL_AUDIO_BYTESIZE(device->spec.format));
while (remain > 0) {
char buf[512];
const size_t len = SDL_min(sizeof(buf), remain);
const ssize_t br = read(h->audio_fd, buf, len);
if (br <= 0) {
break;
}
remain -= br;
}
}
}
static void NETBSDAUDIO_CloseDevice(SDL_AudioDevice *device)
{
if (device->hidden) {
if (device->hidden->audio_fd >= 0) {
close(device->hidden->audio_fd);
}
SDL_free(device->hidden->mixbuf);
SDL_free(device->hidden);
device->hidden = NULL;
}
}
static int NETBSDAUDIO_OpenDevice(SDL_AudioDevice *device)
{
const SDL_bool iscapture = device->iscapture;
int encoding = AUDIO_ENCODING_NONE;
audio_info_t info, hwinfo;
struct audio_prinfo *prinfo = iscapture ? &info.record : &info.play;
// Initialize all variables that we clean on shutdown
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
if (device->hidden == NULL) {
return SDL_OutOfMemory();
}
// Open the audio device; we hardcode the device path in `device->name` for lack of better info, so use that.
const int flags = ((device->iscapture) ? O_RDONLY : O_WRONLY);
device->hidden->audio_fd = open(device->name, flags | O_CLOEXEC);
if (device->hidden->audio_fd < 0) {
return SDL_SetError("Couldn't open %s: %s", device->name, strerror(errno));
}
AUDIO_INITINFO(&info);
#ifdef AUDIO_GETFORMAT // Introduced in NetBSD 9.0
if (ioctl(device->hidden->audio_fd, AUDIO_GETFORMAT, &hwinfo) != -1) {
// Use the device's native sample rate so the kernel doesn't have to resample.
device->spec.freq = iscapture ? hwinfo.record.sample_rate : hwinfo.play.sample_rate;
}
#endif
prinfo->sample_rate = device->spec.freq;
prinfo->channels = device->spec.channels;
SDL_AudioFormat test_format;
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
while ((test_format = *(closefmts++)) != 0) {
switch (test_format) {
case SDL_AUDIO_U8:
encoding = AUDIO_ENCODING_ULINEAR;
break;
case SDL_AUDIO_S8:
encoding = AUDIO_ENCODING_SLINEAR;
break;
case SDL_AUDIO_S16LE:
encoding = AUDIO_ENCODING_SLINEAR_LE;
break;
case SDL_AUDIO_S16BE:
encoding = AUDIO_ENCODING_SLINEAR_BE;
break;
case SDL_AUDIO_S32LE:
encoding = AUDIO_ENCODING_SLINEAR_LE;
break;
case SDL_AUDIO_S32BE:
encoding = AUDIO_ENCODING_SLINEAR_BE;
break;
default:
continue;
}
break;
}
if (!test_format) {
return SDL_SetError("%s: Unsupported audio format", "netbsd");
}
prinfo->encoding = encoding;
prinfo->precision = SDL_AUDIO_BITSIZE(test_format);
info.hiwat = 5;
info.lowat = 3;
if (ioctl(device->hidden->audio_fd, AUDIO_SETINFO, &info) < 0) {
return SDL_SetError("AUDIO_SETINFO failed for %s: %s", device->name, strerror(errno));
}
if (ioctl(device->hidden->audio_fd, AUDIO_GETINFO, &info) < 0) {
return SDL_SetError("AUDIO_GETINFO failed for %s: %s", device->name, strerror(errno));
}
// Final spec used for the device.
device->spec.format = test_format;
device->spec.freq = prinfo->sample_rate;
device->spec.channels = prinfo->channels;
SDL_UpdatedAudioDeviceFormat(device);
if (!iscapture) {
// Allocate mixing buffer
device->hidden->mixlen = device->buffer_size;
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->hidden->mixlen);
if (device->hidden->mixbuf == NULL) {
return SDL_OutOfMemory();
}
SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
}
NETBSDAUDIO_Status(device);
return 0; // We're ready to rock and roll. :-)
}
static SDL_bool NETBSDAUDIO_Init(SDL_AudioDriverImpl *impl)
{
impl->DetectDevices = NETBSDAUDIO_DetectDevices;
impl->OpenDevice = NETBSDAUDIO_OpenDevice;
impl->WaitDevice = NETBSDAUDIO_WaitDevice;
impl->PlayDevice = NETBSDAUDIO_PlayDevice;
impl->GetDeviceBuf = NETBSDAUDIO_GetDeviceBuf;
impl->CloseDevice = NETBSDAUDIO_CloseDevice;
impl->WaitCaptureDevice = NETBSDAUDIO_WaitDevice;
impl->CaptureFromDevice = NETBSDAUDIO_CaptureFromDevice;
impl->FlushCapture = NETBSDAUDIO_FlushCapture;
impl->HasCaptureSupport = SDL_TRUE;
return SDL_TRUE;
}
AudioBootStrap NETBSDAUDIO_bootstrap = {
"netbsd", "NetBSD audio", NETBSDAUDIO_Init, SDL_FALSE
};
#endif // SDL_AUDIO_DRIVER_NETBSD

View file

@ -0,0 +1,44 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_netbsdaudio_h_
#define SDL_netbsdaudio_h_
#include "../SDL_sysaudio.h"
struct SDL_PrivateAudioData
{
// The file descriptor for the audio device
int audio_fd;
// Raw mixing buffer
Uint8 *mixbuf;
int mixlen;
// Support for audio timing using a timer, in addition to SDL_IOReady()
float frame_ticks;
float next_frame;
};
#define FUDGE_TICKS 10 // The scheduler overhead ticks per frame
#endif // SDL_netbsdaudio_h_

View file

@ -0,0 +1,782 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_AUDIO_DRIVER_OPENSLES
// For more discussion of low latency audio on Android, see this:
// https://googlesamples.github.io/android-audio-high-performance/guides/opensl_es.html
#include "../SDL_sysaudio.h"
#include "SDL_openslES.h"
#include "../../core/android/SDL_android.h"
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include <android/log.h>
#define NUM_BUFFERS 2 // -- Don't lower this!
struct SDL_PrivateAudioData
{
Uint8 *mixbuff;
int next_buffer;
Uint8 *pmixbuff[NUM_BUFFERS];
SDL_Semaphore *playsem;
};
#if 0
#define LOG_TAG "SDL_openslES"
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
//#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE,LOG_TAG,__VA_ARGS__)
#define LOGV(...)
#else
#define LOGE(...)
#define LOGI(...)
#define LOGV(...)
#endif
/*
#define SL_SPEAKER_FRONT_LEFT ((SLuint32) 0x00000001)
#define SL_SPEAKER_FRONT_RIGHT ((SLuint32) 0x00000002)
#define SL_SPEAKER_FRONT_CENTER ((SLuint32) 0x00000004)
#define SL_SPEAKER_LOW_FREQUENCY ((SLuint32) 0x00000008)
#define SL_SPEAKER_BACK_LEFT ((SLuint32) 0x00000010)
#define SL_SPEAKER_BACK_RIGHT ((SLuint32) 0x00000020)
#define SL_SPEAKER_FRONT_LEFT_OF_CENTER ((SLuint32) 0x00000040)
#define SL_SPEAKER_FRONT_RIGHT_OF_CENTER ((SLuint32) 0x00000080)
#define SL_SPEAKER_BACK_CENTER ((SLuint32) 0x00000100)
#define SL_SPEAKER_SIDE_LEFT ((SLuint32) 0x00000200)
#define SL_SPEAKER_SIDE_RIGHT ((SLuint32) 0x00000400)
#define SL_SPEAKER_TOP_CENTER ((SLuint32) 0x00000800)
#define SL_SPEAKER_TOP_FRONT_LEFT ((SLuint32) 0x00001000)
#define SL_SPEAKER_TOP_FRONT_CENTER ((SLuint32) 0x00002000)
#define SL_SPEAKER_TOP_FRONT_RIGHT ((SLuint32) 0x00004000)
#define SL_SPEAKER_TOP_BACK_LEFT ((SLuint32) 0x00008000)
#define SL_SPEAKER_TOP_BACK_CENTER ((SLuint32) 0x00010000)
#define SL_SPEAKER_TOP_BACK_RIGHT ((SLuint32) 0x00020000)
*/
#define SL_ANDROID_SPEAKER_STEREO (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT)
#define SL_ANDROID_SPEAKER_QUAD (SL_ANDROID_SPEAKER_STEREO | SL_SPEAKER_BACK_LEFT | SL_SPEAKER_BACK_RIGHT)
#define SL_ANDROID_SPEAKER_5DOT1 (SL_ANDROID_SPEAKER_QUAD | SL_SPEAKER_FRONT_CENTER | SL_SPEAKER_LOW_FREQUENCY)
#define SL_ANDROID_SPEAKER_7DOT1 (SL_ANDROID_SPEAKER_5DOT1 | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT)
// engine interfaces
static SLObjectItf engineObject = NULL;
static SLEngineItf engineEngine = NULL;
// output mix interfaces
static SLObjectItf outputMixObject = NULL;
// buffer queue player interfaces
static SLObjectItf bqPlayerObject = NULL;
static SLPlayItf bqPlayerPlay = NULL;
static SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue = NULL;
#if 0
static SLVolumeItf bqPlayerVolume;
#endif
// recorder interfaces
static SLObjectItf recorderObject = NULL;
static SLRecordItf recorderRecord = NULL;
static SLAndroidSimpleBufferQueueItf recorderBufferQueue = NULL;
#if 0
static const char *sldevaudiorecorderstr = "SLES Audio Recorder";
static const char *sldevaudioplayerstr = "SLES Audio Player";
#define SLES_DEV_AUDIO_RECORDER sldevaudiorecorderstr
#define SLES_DEV_AUDIO_PLAYER sldevaudioplayerstr
static void OPENSLES_DetectDevices( int iscapture )
{
LOGI( "openSLES_DetectDevices()" );
if ( iscapture )
addfn( SLES_DEV_AUDIO_RECORDER );
else
addfn( SLES_DEV_AUDIO_PLAYER );
}
#endif
static void OPENSLES_DestroyEngine(void)
{
LOGI("OPENSLES_DestroyEngine()");
// destroy output mix object, and invalidate all associated interfaces
if (outputMixObject != NULL) {
(*outputMixObject)->Destroy(outputMixObject);
outputMixObject = NULL;
}
// destroy engine object, and invalidate all associated interfaces
if (engineObject != NULL) {
(*engineObject)->Destroy(engineObject);
engineObject = NULL;
engineEngine = NULL;
}
}
static int OPENSLES_CreateEngine(void)
{
const SLInterfaceID ids[1] = { SL_IID_VOLUME };
const SLboolean req[1] = { SL_BOOLEAN_FALSE };
SLresult result;
LOGI("openSLES_CreateEngine()");
// create engine
result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
if (SL_RESULT_SUCCESS != result) {
LOGE("slCreateEngine failed: %d", result);
goto error;
}
LOGI("slCreateEngine OK");
// realize the engine
result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
if (SL_RESULT_SUCCESS != result) {
LOGE("RealizeEngine failed: %d", result);
goto error;
}
LOGI("RealizeEngine OK");
// get the engine interface, which is needed in order to create other objects
result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
if (SL_RESULT_SUCCESS != result) {
LOGE("EngineGetInterface failed: %d", result);
goto error;
}
LOGI("EngineGetInterface OK");
// create output mix
result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, ids, req);
if (SL_RESULT_SUCCESS != result) {
LOGE("CreateOutputMix failed: %d", result);
goto error;
}
LOGI("CreateOutputMix OK");
// realize the output mix
result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
if (SL_RESULT_SUCCESS != result) {
LOGE("RealizeOutputMix failed: %d", result);
goto error;
}
return 1;
error:
OPENSLES_DestroyEngine();
return 0;
}
// this callback handler is called every time a buffer finishes recording
static void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
{
struct SDL_PrivateAudioData *audiodata = (struct SDL_PrivateAudioData *)context;
LOGV("SLES: Recording Callback");
SDL_PostSemaphore(audiodata->playsem);
}
static void OPENSLES_DestroyPCMRecorder(SDL_AudioDevice *device)
{
struct SDL_PrivateAudioData *audiodata = device->hidden;
SLresult result;
// stop recording
if (recorderRecord != NULL) {
result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_STOPPED);
if (SL_RESULT_SUCCESS != result) {
LOGE("SetRecordState stopped: %d", result);
}
}
// destroy audio recorder object, and invalidate all associated interfaces
if (recorderObject != NULL) {
(*recorderObject)->Destroy(recorderObject);
recorderObject = NULL;
recorderRecord = NULL;
recorderBufferQueue = NULL;
}
if (audiodata->playsem) {
SDL_DestroySemaphore(audiodata->playsem);
audiodata->playsem = NULL;
}
if (audiodata->mixbuff) {
SDL_free(audiodata->mixbuff);
}
}
static int OPENSLES_CreatePCMRecorder(SDL_AudioDevice *device)
{
struct SDL_PrivateAudioData *audiodata = device->hidden;
SLDataFormat_PCM format_pcm;
SLDataLocator_AndroidSimpleBufferQueue loc_bufq;
SLDataSink audioSnk;
SLDataLocator_IODevice loc_dev;
SLDataSource audioSrc;
const SLInterfaceID ids[1] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE };
const SLboolean req[1] = { SL_BOOLEAN_TRUE };
SLresult result;
int i;
if (!Android_JNI_RequestPermission("android.permission.RECORD_AUDIO")) {
LOGE("This app doesn't have RECORD_AUDIO permission");
return SDL_SetError("This app doesn't have RECORD_AUDIO permission");
}
// Just go with signed 16-bit audio as it's the most compatible
device->spec.format = SDL_AUDIO_S16;
device->spec.channels = 1;
//device->spec.freq = SL_SAMPLINGRATE_16 / 1000;*/
// Update the fragment size as size in bytes
SDL_UpdatedAudioDeviceFormat(device);
LOGI("Try to open %u hz %u bit chan %u %s samples %u",
device->spec.freq, SDL_AUDIO_BITSIZE(device->spec.format),
device->spec.channels, (device->spec.format & 0x1000) ? "BE" : "LE", device->sample_frames);
// configure audio source
loc_dev.locatorType = SL_DATALOCATOR_IODEVICE;
loc_dev.deviceType = SL_IODEVICE_AUDIOINPUT;
loc_dev.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT;
loc_dev.device = NULL;
audioSrc.pLocator = &loc_dev;
audioSrc.pFormat = NULL;
// configure audio sink
loc_bufq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
loc_bufq.numBuffers = NUM_BUFFERS;
format_pcm.formatType = SL_DATAFORMAT_PCM;
format_pcm.numChannels = device->spec.channels;
format_pcm.samplesPerSec = device->spec.freq * 1000; // / kilo Hz to milli Hz
format_pcm.bitsPerSample = SDL_AUDIO_BITSIZE(device->spec.format);
format_pcm.containerSize = SDL_AUDIO_BITSIZE(device->spec.format);
format_pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
format_pcm.channelMask = SL_SPEAKER_FRONT_CENTER;
audioSnk.pLocator = &loc_bufq;
audioSnk.pFormat = &format_pcm;
// create audio recorder
// (requires the RECORD_AUDIO permission)
result = (*engineEngine)->CreateAudioRecorder(engineEngine, &recorderObject, &audioSrc, &audioSnk, 1, ids, req);
if (SL_RESULT_SUCCESS != result) {
LOGE("CreateAudioRecorder failed: %d", result);
goto failed;
}
// realize the recorder
result = (*recorderObject)->Realize(recorderObject, SL_BOOLEAN_FALSE);
if (SL_RESULT_SUCCESS != result) {
LOGE("RealizeAudioPlayer failed: %d", result);
goto failed;
}
// get the record interface
result = (*recorderObject)->GetInterface(recorderObject, SL_IID_RECORD, &recorderRecord);
if (SL_RESULT_SUCCESS != result) {
LOGE("SL_IID_RECORD interface get failed: %d", result);
goto failed;
}
// get the buffer queue interface
result = (*recorderObject)->GetInterface(recorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &recorderBufferQueue);
if (SL_RESULT_SUCCESS != result) {
LOGE("SL_IID_BUFFERQUEUE interface get failed: %d", result);
goto failed;
}
// register callback on the buffer queue
// context is '(SDL_PrivateAudioData *)device->hidden'
result = (*recorderBufferQueue)->RegisterCallback(recorderBufferQueue, bqRecorderCallback, device->hidden);
if (SL_RESULT_SUCCESS != result) {
LOGE("RegisterCallback failed: %d", result);
goto failed;
}
// Create the audio buffer semaphore
audiodata->playsem = SDL_CreateSemaphore(0);
if (!audiodata->playsem) {
LOGE("cannot create Semaphore!");
goto failed;
}
// Create the sound buffers
audiodata->mixbuff = (Uint8 *)SDL_malloc(NUM_BUFFERS * device->buffer_size);
if (audiodata->mixbuff == NULL) {
LOGE("mixbuffer allocate - out of memory");
goto failed;
}
for (i = 0; i < NUM_BUFFERS; i++) {
audiodata->pmixbuff[i] = audiodata->mixbuff + i * device->buffer_size;
}
// in case already recording, stop recording and clear buffer queue
result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_STOPPED);
if (SL_RESULT_SUCCESS != result) {
LOGE("Record set state failed: %d", result);
goto failed;
}
// enqueue empty buffers to be filled by the recorder
for (i = 0; i < NUM_BUFFERS; i++) {
result = (*recorderBufferQueue)->Enqueue(recorderBufferQueue, audiodata->pmixbuff[i], device->buffer_size);
if (SL_RESULT_SUCCESS != result) {
LOGE("Record enqueue buffers failed: %d", result);
goto failed;
}
}
// start recording
result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_RECORDING);
if (SL_RESULT_SUCCESS != result) {
LOGE("Record set state failed: %d", result);
goto failed;
}
return 0;
failed:
return SDL_SetError("Open device failed!");
}
// this callback handler is called every time a buffer finishes playing
static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
{
struct SDL_PrivateAudioData *audiodata = (struct SDL_PrivateAudioData *)context;
LOGV("SLES: Playback Callback");
SDL_PostSemaphore(audiodata->playsem);
}
static void OPENSLES_DestroyPCMPlayer(SDL_AudioDevice *device)
{
struct SDL_PrivateAudioData *audiodata = device->hidden;
// set the player's state to 'stopped'
if (bqPlayerPlay != NULL) {
const SLresult result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_STOPPED);
if (SL_RESULT_SUCCESS != result) {
LOGE("SetPlayState stopped failed: %d", result);
}
}
// destroy buffer queue audio player object, and invalidate all associated interfaces
if (bqPlayerObject != NULL) {
(*bqPlayerObject)->Destroy(bqPlayerObject);
bqPlayerObject = NULL;
bqPlayerPlay = NULL;
bqPlayerBufferQueue = NULL;
}
if (audiodata->playsem) {
SDL_DestroySemaphore(audiodata->playsem);
audiodata->playsem = NULL;
}
if (audiodata->mixbuff) {
SDL_free(audiodata->mixbuff);
}
}
static int OPENSLES_CreatePCMPlayer(SDL_AudioDevice *device)
{
/* If we want to add floating point audio support (requires API level 21)
it can be done as described here:
https://developer.android.com/ndk/guides/audio/opensl/android-extensions.html#floating-point
*/
if (SDL_GetAndroidSDKVersion() >= 21) {
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
SDL_AudioFormat test_format;
while ((test_format = *(closefmts++)) != 0) {
if (SDL_AUDIO_ISSIGNED(test_format)) {
break;
}
}
if (!test_format) {
// Didn't find a compatible format :
LOGI("No compatible audio format, using signed 16-bit audio");
test_format = SDL_AUDIO_S16;
}
device->spec.format = test_format;
} else {
// Just go with signed 16-bit audio as it's the most compatible
device->spec.format = SDL_AUDIO_S16;
}
// Update the fragment size as size in bytes
SDL_UpdatedAudioDeviceFormat(device);
LOGI("Try to open %u hz %s %u bit chan %u %s samples %u",
device->spec.freq, SDL_AUDIO_ISFLOAT(device->spec.format) ? "float" : "pcm", SDL_AUDIO_BITSIZE(device->spec.format),
device->spec.channels, (device->spec.format & 0x1000) ? "BE" : "LE", device->sample_frames);
// configure audio source
SLDataLocator_AndroidSimpleBufferQueue loc_bufq;
loc_bufq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
loc_bufq.numBuffers = NUM_BUFFERS;
SLDataFormat_PCM format_pcm;
format_pcm.formatType = SL_DATAFORMAT_PCM;
format_pcm.numChannels = device->spec.channels;
format_pcm.samplesPerSec = device->spec.freq * 1000; // / kilo Hz to milli Hz
format_pcm.bitsPerSample = SDL_AUDIO_BITSIZE(device->spec.format);
format_pcm.containerSize = SDL_AUDIO_BITSIZE(device->spec.format);
if (SDL_AUDIO_ISBIGENDIAN(device->spec.format)) {
format_pcm.endianness = SL_BYTEORDER_BIGENDIAN;
} else {
format_pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
}
switch (device->spec.channels) {
case 1:
format_pcm.channelMask = SL_SPEAKER_FRONT_LEFT;
break;
case 2:
format_pcm.channelMask = SL_ANDROID_SPEAKER_STEREO;
break;
case 3:
format_pcm.channelMask = SL_ANDROID_SPEAKER_STEREO | SL_SPEAKER_FRONT_CENTER;
break;
case 4:
format_pcm.channelMask = SL_ANDROID_SPEAKER_QUAD;
break;
case 5:
format_pcm.channelMask = SL_ANDROID_SPEAKER_QUAD | SL_SPEAKER_FRONT_CENTER;
break;
case 6:
format_pcm.channelMask = SL_ANDROID_SPEAKER_5DOT1;
break;
case 7:
format_pcm.channelMask = SL_ANDROID_SPEAKER_5DOT1 | SL_SPEAKER_BACK_CENTER;
break;
case 8:
format_pcm.channelMask = SL_ANDROID_SPEAKER_7DOT1;
break;
default:
// Unknown number of channels, fall back to stereo
device->spec.channels = 2;
format_pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
break;
}
SLDataSink audioSnk;
SLDataSource audioSrc;
audioSrc.pFormat = (void *)&format_pcm;
SLAndroidDataFormat_PCM_EX format_pcm_ex;
if (SDL_AUDIO_ISFLOAT(device->spec.format)) {
// Copy all setup into PCM EX structure
format_pcm_ex.formatType = SL_ANDROID_DATAFORMAT_PCM_EX;
format_pcm_ex.endianness = format_pcm.endianness;
format_pcm_ex.channelMask = format_pcm.channelMask;
format_pcm_ex.numChannels = format_pcm.numChannels;
format_pcm_ex.sampleRate = format_pcm.samplesPerSec;
format_pcm_ex.bitsPerSample = format_pcm.bitsPerSample;
format_pcm_ex.containerSize = format_pcm.containerSize;
format_pcm_ex.representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT;
audioSrc.pFormat = (void *)&format_pcm_ex;
}
audioSrc.pLocator = &loc_bufq;
// configure audio sink
SLDataLocator_OutputMix loc_outmix;
loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
loc_outmix.outputMix = outputMixObject;
audioSnk.pLocator = &loc_outmix;
audioSnk.pFormat = NULL;
// create audio player
const SLInterfaceID ids[2] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_VOLUME };
const SLboolean req[2] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE };
SLresult result;
result = (*engineEngine)->CreateAudioPlayer(engineEngine, &bqPlayerObject, &audioSrc, &audioSnk, 2, ids, req);
if (SL_RESULT_SUCCESS != result) {
LOGE("CreateAudioPlayer failed: %d", result);
goto failed;
}
// realize the player
result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE);
if (SL_RESULT_SUCCESS != result) {
LOGE("RealizeAudioPlayer failed: %d", result);
goto failed;
}
// get the play interface
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay);
if (SL_RESULT_SUCCESS != result) {
LOGE("SL_IID_PLAY interface get failed: %d", result);
goto failed;
}
// get the buffer queue interface
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &bqPlayerBufferQueue);
if (SL_RESULT_SUCCESS != result) {
LOGE("SL_IID_BUFFERQUEUE interface get failed: %d", result);
goto failed;
}
// register callback on the buffer queue
// context is '(SDL_PrivateAudioData *)device->hidden'
result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, device->hidden);
if (SL_RESULT_SUCCESS != result) {
LOGE("RegisterCallback failed: %d", result);
goto failed;
}
#if 0
// get the volume interface
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume);
if (SL_RESULT_SUCCESS != result) {
LOGE("SL_IID_VOLUME interface get failed: %d", result);
// goto failed;
}
#endif
struct SDL_PrivateAudioData *audiodata = device->hidden;
// Create the audio buffer semaphore
audiodata->playsem = SDL_CreateSemaphore(NUM_BUFFERS - 1);
if (!audiodata->playsem) {
LOGE("cannot create Semaphore!");
goto failed;
}
// Create the sound buffers
audiodata->mixbuff = (Uint8 *)SDL_malloc(NUM_BUFFERS * device->buffer_size);
if (audiodata->mixbuff == NULL) {
LOGE("mixbuffer allocate - out of memory");
goto failed;
}
for (int i = 0; i < NUM_BUFFERS; i++) {
audiodata->pmixbuff[i] = audiodata->mixbuff + i * device->buffer_size;
}
// set the player's state to playing
result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);
if (SL_RESULT_SUCCESS != result) {
LOGE("Play set state failed: %d", result);
goto failed;
}
return 0;
failed:
return -1;
}
static int OPENSLES_OpenDevice(SDL_AudioDevice *device)
{
device->hidden = (struct SDL_PrivateAudioData *)SDL_calloc(1, sizeof(*device->hidden));
if (device->hidden == NULL) {
return SDL_OutOfMemory();
}
if (device->iscapture) {
LOGI("OPENSLES_OpenDevice() for capture");
return OPENSLES_CreatePCMRecorder(device);
} else {
int ret;
LOGI("OPENSLES_OpenDevice() for playing");
ret = OPENSLES_CreatePCMPlayer(device);
if (ret < 0) {
// Another attempt to open the device with a lower frequency
if (device->spec.freq > 48000) {
OPENSLES_DestroyPCMPlayer(device);
device->spec.freq = 48000;
ret = OPENSLES_CreatePCMPlayer(device);
}
}
if (ret != 0) {
return SDL_SetError("Open device failed!");
}
}
return 0;
}
static int OPENSLES_WaitDevice(SDL_AudioDevice *device)
{
struct SDL_PrivateAudioData *audiodata = device->hidden;
LOGV("OPENSLES_WaitDevice()");
// Wait for an audio chunk to finish
return SDL_WaitSemaphore(audiodata->playsem);
}
static int OPENSLES_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
{
struct SDL_PrivateAudioData *audiodata = device->hidden;
LOGV("======OPENSLES_PlayDevice()======");
// Queue it up
const SLresult result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, buffer, buflen);
audiodata->next_buffer++;
if (audiodata->next_buffer >= NUM_BUFFERS) {
audiodata->next_buffer = 0;
}
// If Enqueue fails, callback won't be called.
// Post the semaphore, not to run out of buffer
if (SL_RESULT_SUCCESS != result) {
SDL_PostSemaphore(audiodata->playsem);
}
return 0;
}
/// n playn sem
// getbuf 0 - 1
// fill buff 0 - 1
// play 0 - 0 1
// wait 1 0 0
// getbuf 1 0 0
// fill buff 1 0 0
// play 0 0 0
// wait
//
// okay..
static Uint8 *OPENSLES_GetDeviceBuf(SDL_AudioDevice *device, int *bufsize)
{
struct SDL_PrivateAudioData *audiodata = device->hidden;
LOGV("OPENSLES_GetDeviceBuf()");
return audiodata->pmixbuff[audiodata->next_buffer];
}
static int OPENSLES_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
{
struct SDL_PrivateAudioData *audiodata = device->hidden;
// Copy it to the output buffer
SDL_assert(buflen == device->buffer_size);
SDL_memcpy(buffer, audiodata->pmixbuff[audiodata->next_buffer], device->buffer_size);
// Re-enqueue the buffer
const SLresult result = (*recorderBufferQueue)->Enqueue(recorderBufferQueue, audiodata->pmixbuff[audiodata->next_buffer], device->buffer_size);
if (SL_RESULT_SUCCESS != result) {
LOGE("Record enqueue buffers failed: %d", result);
return -1;
}
audiodata->next_buffer++;
if (audiodata->next_buffer >= NUM_BUFFERS) {
audiodata->next_buffer = 0;
}
return device->buffer_size;
}
static void OPENSLES_CloseDevice(SDL_AudioDevice *device)
{
// struct SDL_PrivateAudioData *audiodata = device->hidden;
if (device->hidden) {
if (device->iscapture) {
LOGI("OPENSLES_CloseDevice() for capture");
OPENSLES_DestroyPCMRecorder(device);
} else {
LOGI("OPENSLES_CloseDevice() for playing");
OPENSLES_DestroyPCMPlayer(device);
}
SDL_free(device->hidden);
device->hidden = NULL;
}
}
static SDL_bool OPENSLES_Init(SDL_AudioDriverImpl *impl)
{
LOGI("OPENSLES_Init() called");
if (!OPENSLES_CreateEngine()) {
return SDL_FALSE;
}
LOGI("OPENSLES_Init() - set pointers");
// Set the function pointers
// impl->DetectDevices = OPENSLES_DetectDevices;
impl->ThreadInit = Android_AudioThreadInit;
impl->OpenDevice = OPENSLES_OpenDevice;
impl->WaitDevice = OPENSLES_WaitDevice;
impl->PlayDevice = OPENSLES_PlayDevice;
impl->GetDeviceBuf = OPENSLES_GetDeviceBuf;
impl->WaitCaptureDevice = OPENSLES_WaitDevice;
impl->CaptureFromDevice = OPENSLES_CaptureFromDevice;
impl->CloseDevice = OPENSLES_CloseDevice;
impl->Deinitialize = OPENSLES_DestroyEngine;
// and the capabilities
impl->HasCaptureSupport = SDL_TRUE;
impl->OnlyHasDefaultOutputDevice = SDL_TRUE;
impl->OnlyHasDefaultCaptureDevice = SDL_TRUE;
LOGI("OPENSLES_Init() - success");
// this audio target is available.
return SDL_TRUE;
}
AudioBootStrap OPENSLES_bootstrap = {
"openslES", "OpenSL ES audio driver", OPENSLES_Init, SDL_FALSE
};
void OPENSLES_ResumeDevices(void)
{
if (bqPlayerPlay != NULL) {
// set the player's state to 'playing'
SLresult result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);
if (SL_RESULT_SUCCESS != result) {
LOGE("OPENSLES_ResumeDevices failed: %d", result);
}
}
}
void OPENSLES_PauseDevices(void)
{
if (bqPlayerPlay != NULL) {
// set the player's state to 'paused'
SLresult result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PAUSED);
if (SL_RESULT_SUCCESS != result) {
LOGE("OPENSLES_PauseDevices failed: %d", result);
}
}
}
#endif // SDL_AUDIO_DRIVER_OPENSLES

View file

@ -0,0 +1,38 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_openslesaudio_h_
#define SDL_openslesaudio_h_
#ifdef SDL_AUDIO_DRIVER_OPENSLES
void OPENSLES_ResumeDevices(void);
void OPENSLES_PauseDevices(void);
#else
static void OPENSLES_ResumeDevices(void) {}
static void OPENSLES_PauseDevices(void) {}
#endif
#endif // SDL_openslesaudio_h_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,43 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_pipewire_h_
#define SDL_pipewire_h_
#include "../SDL_sysaudio.h"
#include <pipewire/pipewire.h>
struct SDL_PrivateAudioData
{
struct pw_thread_loop *loop;
struct pw_stream *stream;
struct pw_context *context;
Sint32 stride; // Bytes-per-frame
int stream_init_status;
// Set in GetDeviceBuf, filled in AudioThreadIterate, queued in PlayDevice
struct pw_buffer *pw_buf;
};
#endif // SDL_pipewire_h_

View file

@ -0,0 +1,159 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#include "../SDL_sysaudio.h"
#include "SDL_ps2audio.h"
#include <kernel.h>
#include <audsrv.h>
#include <ps2_audio_driver.h>
static int PS2AUDIO_OpenDevice(SDL_AudioDevice *device)
{
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
if (device->hidden == NULL) {
return SDL_OutOfMemory();
}
// These are the native supported audio PS2 configs
switch (device->spec.freq) {
case 11025:
case 12000:
case 22050:
case 24000:
case 32000:
case 44100:
case 48000:
break; // acceptable value, keep it
default:
device->spec.freq = 48000;
break;
}
device->sample_frames = 512;
device->spec.channels = device->spec.channels == 1 ? 1 : 2;
device->spec.format = device->spec.format == SDL_AUDIO_S8 ? SDL_AUDIO_S8 : SDL_AUDIO_S16;
struct audsrv_fmt_t format;
format.bits = device->spec.format == SDL_AUDIO_S8 ? 8 : 16;
format.freq = device->spec.freq;
format.channels = device->spec.channels;
device->hidden->channel = audsrv_set_format(&format);
audsrv_set_volume(MAX_VOLUME);
if (device->hidden->channel < 0) {
return SDL_SetError("Couldn't reserve hardware channel");
}
// Update the fragment size as size in bytes.
SDL_UpdatedAudioDeviceFormat(device);
/* Allocate the mixing buffer. Its size and starting address must
be a multiple of 64 bytes. Our sample count is already a multiple of
64, so spec->size should be a multiple of 64 as well. */
const int mixlen = device->buffer_size * NUM_BUFFERS;
device->hidden->rawbuf = (Uint8 *)SDL_aligned_alloc(64, mixlen);
if (device->hidden->rawbuf == NULL) {
return SDL_SetError("Couldn't allocate mixing buffer");
}
SDL_memset(device->hidden->rawbuf, device->silence_value, mixlen);
for (int i = 0; i < NUM_BUFFERS; i++) {
device->hidden->mixbufs[i] = &device->hidden->rawbuf[i * device->buffer_size];
}
return 0;
}
static int PS2AUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
{
// this returns number of bytes accepted or a negative error. We assume anything other than buflen is a fatal error.
return (audsrv_play_audio((char *)buffer, buflen) != buflen) ? -1 : 0;
}
static int PS2AUDIO_WaitDevice(SDL_AudioDevice *device)
{
audsrv_wait_audio(device->buffer_size);
return 0;
}
static Uint8 *PS2AUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
Uint8 *buffer = device->hidden->mixbufs[device->hidden->next_buffer];
device->hidden->next_buffer = (device->hidden->next_buffer + 1) % NUM_BUFFERS;
return buffer;
}
static void PS2AUDIO_CloseDevice(SDL_AudioDevice *device)
{
if (device->hidden) {
if (device->hidden->channel >= 0) {
audsrv_stop_audio();
device->hidden->channel = -1;
}
if (device->hidden->rawbuf != NULL) {
SDL_aligned_free(device->hidden->rawbuf);
device->hidden->rawbuf = NULL;
}
SDL_free(device->hidden);
device->hidden = NULL;
}
}
static void PS2AUDIO_ThreadInit(SDL_AudioDevice *device)
{
/* Increase the priority of this audio thread by 1 to put it
ahead of other SDL threads. */
const int32_t thid = GetThreadId();
ee_thread_status_t status;
if (ReferThreadStatus(thid, &status) == 0) {
ChangeThreadPriority(thid, status.current_priority - 1);
}
}
static void PS2AUDIO_Deinitialize(void)
{
deinit_audio_driver();
}
static SDL_bool PS2AUDIO_Init(SDL_AudioDriverImpl *impl)
{
if (init_audio_driver() < 0) {
return SDL_FALSE;
}
impl->OpenDevice = PS2AUDIO_OpenDevice;
impl->PlayDevice = PS2AUDIO_PlayDevice;
impl->WaitDevice = PS2AUDIO_WaitDevice;
impl->GetDeviceBuf = PS2AUDIO_GetDeviceBuf;
impl->CloseDevice = PS2AUDIO_CloseDevice;
impl->ThreadInit = PS2AUDIO_ThreadInit;
impl->Deinitialize = PS2AUDIO_Deinitialize;
impl->OnlyHasDefaultOutputDevice = SDL_TRUE;
return SDL_TRUE; // this audio target is available.
}
AudioBootStrap PS2AUDIO_bootstrap = {
"ps2", "PS2 audio driver", PS2AUDIO_Init, SDL_FALSE
};

View file

@ -0,0 +1,42 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_ps2audio_h_
#define SDL_ps2audio_h_
#include "../SDL_sysaudio.h"
#define NUM_BUFFERS 2
struct SDL_PrivateAudioData
{
// The hardware output channel.
int channel;
// The raw allocated mixing buffer.
Uint8 *rawbuf;
// Individual mixing buffers.
Uint8 *mixbufs[NUM_BUFFERS];
// Index of the next available mixing buffer.
int next_buffer;
};
#endif // SDL_ps2audio_h_

View file

@ -0,0 +1,183 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_AUDIO_DRIVER_PSP
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "../SDL_audiodev_c.h"
#include "../SDL_sysaudio.h"
#include "SDL_pspaudio.h"
#include <pspaudio.h>
#include <pspthreadman.h>
static inline SDL_bool isBasicAudioConfig(const SDL_AudioSpec *spec)
{
return spec->freq == 44100;
}
static int PSPAUDIO_OpenDevice(SDL_AudioDevice *device)
{
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
if (device->hidden == NULL) {
return SDL_OutOfMemory();
}
// device only natively supports S16LSB
device->spec.format = SDL_AUDIO_S16LE;
/* PSP has some limitations with the Audio. It fully supports 44.1KHz (Mono & Stereo),
however with frequencies different than 44.1KHz, it just supports Stereo,
so a resampler must be done for these scenarios */
if (isBasicAudioConfig(&device->spec)) {
// The sample count must be a multiple of 64.
device->sample_frames = PSP_AUDIO_SAMPLE_ALIGN(device->sample_frames);
// The number of channels (1 or 2).
device->spec.channels = device->spec.channels == 1 ? 1 : 2;
const int format = (device->spec.channels == 1) ? PSP_AUDIO_FORMAT_MONO : PSP_AUDIO_FORMAT_STEREO;
device->hidden->channel = sceAudioChReserve(PSP_AUDIO_NEXT_CHANNEL, device->sample_frames, format);
} else {
// 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11050, 8000
switch (device->spec.freq) {
case 8000:
case 11025:
case 12000:
case 16000:
case 22050:
case 24000:
case 32000:
case 44100:
case 48000:
break; // acceptable, keep it
default:
device->spec.freq = 48000;
break;
}
// The number of samples to output in one output call (min 17, max 4111).
device->sample_frames = device->sample_frames < 17 ? 17 : (device->sample_frames > 4111 ? 4111 : device->sample_frames);
device->spec.channels = 2; // we're forcing the hardware to stereo.
device->hidden->channel = sceAudioSRCChReserve(device->sample_frames, device->spec.freq, 2);
}
if (device->hidden->channel < 0) {
return SDL_SetError("Couldn't reserve hardware channel");
}
// Update the fragment size as size in bytes.
SDL_UpdatedAudioDeviceFormat(device);
/* Allocate the mixing buffer. Its size and starting address must
be a multiple of 64 bytes. Our sample count is already a multiple of
64, so spec->size should be a multiple of 64 as well. */
const int mixlen = device->buffer_size * NUM_BUFFERS;
device->hidden->rawbuf = (Uint8 *)SDL_aligned_alloc(64, mixlen);
if (device->hidden->rawbuf == NULL) {
return SDL_SetError("Couldn't allocate mixing buffer");
}
SDL_memset(device->hidden->rawbuf, device->silence_value, mixlen);
for (int i = 0; i < NUM_BUFFERS; i++) {
device->hidden->mixbufs[i] = &device->hidden->rawbuf[i * device->buffer_size];
}
return 0;
}
static int PSPAUDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
{
int rc;
if (!isBasicAudioConfig(&device->spec)) {
SDL_assert(device->spec.channels == 2);
rc = sceAudioSRCOutputBlocking(PSP_AUDIO_VOLUME_MAX, (void *) buffer);
} else {
rc = sceAudioOutputPannedBlocking(device->hidden->channel, PSP_AUDIO_VOLUME_MAX, PSP_AUDIO_VOLUME_MAX, (void *) buffer);
}
return (rc == 0) ? 0 : -1;
}
static int PSPAUDIO_WaitDevice(SDL_AudioDevice *device)
{
return 0; // Because we block when sending audio, there's no need for this function to do anything.
}
static Uint8 *PSPAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
Uint8 *buffer = device->hidden->mixbufs[device->hidden->next_buffer];
device->hidden->next_buffer = (device->hidden->next_buffer + 1) % NUM_BUFFERS;
return buffer;
}
static void PSPAUDIO_CloseDevice(SDL_AudioDevice *device)
{
if (device->hidden) {
if (device->hidden->channel >= 0) {
if (!isBasicAudioConfig(&device->spec)) {
sceAudioSRCChRelease();
} else {
sceAudioChRelease(device->hidden->channel);
}
device->hidden->channel = -1;
}
if (device->hidden->rawbuf != NULL) {
SDL_aligned_free(device->hidden->rawbuf);
device->hidden->rawbuf = NULL;
}
SDL_free(device->hidden);
device->hidden = NULL;
}
}
static void PSPAUDIO_ThreadInit(SDL_AudioDevice *device)
{
/* Increase the priority of this audio thread by 1 to put it
ahead of other SDL threads. */
const SceUID thid = sceKernelGetThreadId();
SceKernelThreadInfo status;
status.size = sizeof(SceKernelThreadInfo);
if (sceKernelReferThreadStatus(thid, &status) == 0) {
sceKernelChangeThreadPriority(thid, status.currentPriority - 1);
}
}
static SDL_bool PSPAUDIO_Init(SDL_AudioDriverImpl *impl)
{
impl->OpenDevice = PSPAUDIO_OpenDevice;
impl->PlayDevice = PSPAUDIO_PlayDevice;
impl->WaitDevice = PSPAUDIO_WaitDevice;
impl->GetDeviceBuf = PSPAUDIO_GetDeviceBuf;
impl->CloseDevice = PSPAUDIO_CloseDevice;
impl->ThreadInit = PSPAUDIO_ThreadInit;
impl->OnlyHasDefaultOutputDevice = SDL_TRUE;
//impl->HasCaptureSupport = SDL_TRUE;
//impl->OnlyHasDefaultCaptureDevice = SDL_TRUE;
return SDL_TRUE;
}
AudioBootStrap PSPAUDIO_bootstrap = {
"psp", "PSP audio driver", PSPAUDIO_Init, SDL_FALSE
};
#endif // SDL_AUDIO_DRIVER_PSP

View file

@ -0,0 +1,41 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_pspaudio_h_
#define SDL_pspaudio_h_
#include "../SDL_sysaudio.h"
#define NUM_BUFFERS 2
struct SDL_PrivateAudioData
{
// The hardware output channel.
int channel;
// The raw allocated mixing buffer.
Uint8 *rawbuf;
// Individual mixing buffers.
Uint8 *mixbufs[NUM_BUFFERS];
// Index of the next available mixing buffer.
int next_buffer;
};
#endif // SDL_pspaudio_h_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,44 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_pulseaudio_h_
#define SDL_pulseaudio_h_
#include <pulse/pulseaudio.h>
#include "../SDL_sysaudio.h"
struct SDL_PrivateAudioData
{
// pulseaudio structures
pa_stream *stream;
// Raw mixing buffer
Uint8 *mixbuf;
int bytes_requested; // bytes of data the hardware wants _now_.
const Uint8 *capturebuf;
int capturelen;
};
#endif // SDL_pulseaudio_h_

View file

@ -0,0 +1,450 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2021 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
// !!! FIXME: can this target support hotplugging?
#include "../../SDL_internal.h"
#ifdef SDL_AUDIO_DRIVER_QNX
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sched.h>
#include <sys/select.h>
#include <sys/neutrino.h>
#include <sys/asoundlib.h>
#include "SDL3/SDL_timer.h"
#include "SDL3/SDL_audio.h"
#include "../../core/unix/SDL_poll.h"
#include "../SDL_sysaudio.h"
#include "SDL_qsa_audio.h"
// default channel communication parameters
#define DEFAULT_CPARAMS_RATE 44100
#define DEFAULT_CPARAMS_VOICES 1
#define DEFAULT_CPARAMS_FRAG_SIZE 4096
#define DEFAULT_CPARAMS_FRAGS_MIN 1
#define DEFAULT_CPARAMS_FRAGS_MAX 1
#define QSA_MAX_NAME_LENGTH 81+16 // Hardcoded in QSA, can't be changed
static int QSA_SetError(const char *fn, int status)
{
return SDL_SetError("QSA: %s() failed: %s", fn, snd_strerror(status));
}
// !!! FIXME: does this need to be here? Does the SDL version not work?
static void QSA_ThreadInit(SDL_AudioDevice *device)
{
// Increase default 10 priority to 25 to avoid jerky sound
struct sched_param param;
if (SchedGet(0, 0, &param) != -1) {
param.sched_priority = param.sched_curpriority + 15;
SchedSet(0, 0, SCHED_NOCHANGE, &param);
}
}
// PCM channel parameters initialize function
static void QSA_InitAudioParams(snd_pcm_channel_params_t * cpars)
{
SDL_zerop(cpars);
cpars->channel = SND_PCM_CHANNEL_PLAYBACK;
cpars->mode = SND_PCM_MODE_BLOCK;
cpars->start_mode = SND_PCM_START_DATA;
cpars->stop_mode = SND_PCM_STOP_STOP;
cpars->format.format = SND_PCM_SFMT_S16_LE;
cpars->format.interleave = 1;
cpars->format.rate = DEFAULT_CPARAMS_RATE;
cpars->format.voices = DEFAULT_CPARAMS_VOICES;
cpars->buf.block.frag_size = DEFAULT_CPARAMS_FRAG_SIZE;
cpars->buf.block.frags_min = DEFAULT_CPARAMS_FRAGS_MIN;
cpars->buf.block.frags_max = DEFAULT_CPARAMS_FRAGS_MAX;
}
// This function waits until it is possible to write a full sound buffer
static int QSA_WaitDevice(SDL_AudioDevice *device)
{
// Setup timeout for playing one fragment equal to 2 seconds
// If timeout occurred than something wrong with hardware or driver
// For example, Vortex 8820 audio driver stucks on second DAC because
// it doesn't exist !
const int result = SDL_IOReady(device->hidden->audio_fd,
device->iscapture ? SDL_IOR_READ : SDL_IOR_WRITE,
2 * 1000);
switch (result) {
case -1:
SDL_LogError(SDL_LOG_CATEGORY_AUDIO, "QSA: SDL_IOReady() failed: %s", strerror(errno));
return -1;
case 0:
device->hidden->timeout_on_wait = SDL_TRUE; // !!! FIXME: Should we just disconnect the device in this case?
break;
default:
device->hidden->timeout_on_wait = SDL_FALSE;
break;
}
return 0;
}
static int QSA_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
{
if (SDL_AtomicGet(&device->shutdown) || !device->hidden) {
return 0;
}
int towrite = buflen;
// Write the audio data, checking for EAGAIN (buffer full) and underrun
while ((towrite > 0) && !SDL_AtomicGet(&device->shutdown));
const int bw = snd_pcm_plugin_write(device->hidden->audio_handle, buffer, towrite);
if (bw != towrite) {
// Check if samples playback got stuck somewhere in hardware or in the audio device driver
if ((errno == EAGAIN) && (bw == 0)) {
if (device->hidden->timeout_on_wait) {
return 0; // oh well, try again next time. !!! FIXME: Should we just disconnect the device in this case?
}
}
// Check for errors or conditions
if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {
SDL_Delay(1); // Let a little CPU time go by and try to write again
// if we wrote some data
towrite -= bw;
buffer += bw * device->spec.channels;
continue;
} else if ((errno == EINVAL) || (errno == EIO)) {
snd_pcm_channel_status_t cstatus;
SDL_zero(cstatus);
cstatus.channel = device->iscapture ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK;
int status = snd_pcm_plugin_status(device->hidden->audio_handle, &cstatus);
if (status < 0) {
QSA_SetError("snd_pcm_plugin_status", status);
return -1;
} else if ((cstatus.status == SND_PCM_STATUS_UNDERRUN) || (cstatus.status == SND_PCM_STATUS_READY)) {
status = snd_pcm_plugin_prepare(device->hidden->audio_handle, device->iscapture ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK);
if (status < 0) {
QSA_SetError("snd_pcm_plugin_prepare", status);
return -1;
}
}
continue;
} else {
return -1;
}
} else {
// we wrote all remaining data
towrite -= bw;
buffer += bw * device->spec.channels;
}
}
// If we couldn't write, assume fatal error for now
return (towrite != 0) ? -1 : 0;
}
static Uint8 *QSA_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
return device->hidden->pcm_buf;
}
static void QSA_CloseDevice(SDL_AudioDevice *device)
{
if (device->hidden) {
if (device->hidden->audio_handle != NULL) {
#if _NTO_VERSION < 710
// Finish playing available samples or cancel unread samples during capture
snd_pcm_plugin_flush(device->hidden->audio_handle, device->iscapture ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK);
#endif
snd_pcm_close(device->hidden->audio_handle);
}
SDL_free(device->hidden->pcm_buf);
SDL_free(device->hidden);
device->hidden = NULL;
}
}
static int QSA_OpenDevice(SDL_AudioDevice *device)
{
if (device->iscapture) {
return SDL_SetError("SDL capture support isn't available on QNX atm"); // !!! FIXME: most of this code has support for capture devices, but there's no CaptureFromDevice, etc functions. Fill them in!
}
SDL_assert(device->handle != NULL); // NULL used to mean "system default device" in SDL2; it does not mean that in SDL3.
const Uint32 sdlhandle = (Uint32) ((size_t) device->handle);
const uint32_t cardno = (uint32_t) (sdlhandle & 0xFFFF);
const uint32_t deviceno = (uint32_t) ((sdlhandle >> 16) & 0xFFFF);
const SDL_bool iscapture = device->iscapture;
int status = 0;
// Initialize all variables that we clean on shutdown
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, (sizeof (struct SDL_PrivateAudioData)));
if (device->hidden == NULL) {
return SDL_OutOfMemory();
}
// Initialize channel transfer parameters to default
snd_pcm_channel_params_t cparams;
QSA_InitAudioParams(&cparams);
// Open requested audio device
status = snd_pcm_open(&device->hidden->audio_handle, cardno, deviceno, iscapture ? SND_PCM_OPEN_CAPTURE : SND_PCM_OPEN_PLAYBACK);
if (status < 0) {
device->hidden->audio_handle = NULL;
return QSA_SetError("snd_pcm_open", status);
}
// Try for a closest match on audio format
SDL_AudioFormat test_format = 0;
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
while ((test_format = *(closefmts++)) != 0) {
// if match found set format to equivalent QSA format
switch (test_format) {
#define CHECKFMT(sdlfmt, qsafmt) case SDL_AUDIO_##sdlfmt: cparams.format.format = SND_PCM_SFMT_##qsafmt; break
CHECKFMT(U8, U8);
CHECKFMT(S8, S8);
CHECKFMT(S16LSB, S16_LE);
CHECKFMT(S16MSB, S16_BE);
CHECKFMT(S32LSB, S32_LE);
CHECKFMT(S32MSB, S32_BE);
CHECKFMT(F32LSB, FLOAT_LE);
CHECKFMT(F32MSB, FLOAT_BE);
#undef CHECKFMT
default: continue;
}
break;
}
// assumes test_format not 0 on success
if (test_format == 0) {
return SDL_SetError("QSA: Couldn't find any hardware audio formats");
}
device->spec.format = test_format;
// Set mono/stereo/4ch/6ch/8ch audio
cparams.format.voices = device->spec.channels;
// Set rate
cparams.format.rate = device->spec.freq;
// Setup the transfer parameters according to cparams
status = snd_pcm_plugin_params(device->hidden->audio_handle, &cparams);
if (status < 0) {
return QSA_SetError("snd_pcm_plugin_params", status);
}
// Make sure channel is setup right one last time
snd_pcm_channel_setup_t csetup;
SDL_zero(csetup);
csetup.channel = iscapture ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK;
if (snd_pcm_plugin_setup(device->hidden->audio_handle, &csetup) < 0) {
return SDL_SetError("QSA: Unable to setup channel");
}
device->sample_frames = csetup.buf.block.frag_size;
// Calculate the final parameters for this audio specification
SDL_UpdatedAudioDeviceFormat(device);
device->hidden->pcm_buf = (Uint8 *) SDL_malloc(device->buffer_size);
if (device->hidden->pcm_buf == NULL) {
return SDL_OutOfMemory();
}
SDL_memset(device->hidden->pcm_buf, device->silence_value, device->buffer_size);
// get the file descriptor
device->hidden->audio_fd = snd_pcm_file_descriptor(device->hidden->audio_handle, csetup.channel);
if (device->hidden->audio_fd < 0) {
return QSA_SetError("snd_pcm_file_descriptor", device->hidden->audio_fd);
}
// Prepare an audio channel
status = snd_pcm_plugin_prepare(device->hidden->audio_handle, csetup.channel)
if (status < 0) {
return QSA_SetError("snd_pcm_plugin_prepare", status);
}
return 0; // We're really ready to rock and roll. :-)
}
static SDL_AudioFormat QnxFormatToSDLFormat(const int32_t qnxfmt)
{
switch (qnxfmt) {
#define CHECKFMT(sdlfmt, qsafmt) case SND_PCM_SFMT_##qsafmt: return SDL_AUDIO_##sdlfmt
CHECKFMT(U8, U8);
CHECKFMT(S8, S8);
CHECKFMT(S16LSB, S16_LE);
CHECKFMT(S16MSB, S16_BE);
CHECKFMT(S32LSB, S32_LE);
CHECKFMT(S32MSB, S32_BE);
CHECKFMT(F32LSB, FLOAT_LE);
CHECKFMT(F32MSB, FLOAT_BE);
#undef CHECKFMT
default: break;
}
return SDL_AUDIO_S16; // oh well.
}
static void QSA_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
{
// Detect amount of available devices
// this value can be changed in the runtime
int num_cards = 0;
(void) snd_cards_list(NULL, 0, &alloc_num_cards);
SDL_bool isstack = SDL_FALSE;
int *cards = SDL_small_alloc(int, num_cards, &isstack);
if (!cards) {
return; // we're in trouble.
}
int overflow_cards = 0;
const int total_num_cards = snd_cards_list(cards, num_cards, &overflow_cards);
// if overflow_cards > 0 or total_num_cards > num_cards, it changed at the last moment; oh well, we lost some.
num_cards = SDL_min(num_cards, total_num_cards); // ...but make sure it didn't _shrink_.
// If io-audio manager is not running we will get 0 as number of available audio devices
if (num_cards == 0) { // not any available audio devices?
SDL_small_free(cards, isstack);
return;
}
// Find requested devices by type
for (int it = 0; it < num_cards; it++) {
const int card = cards[it];
for (uint32_t deviceno = 0; ; deviceno++) {
int32_t status;
char name[QSA_MAX_NAME_LENGTH];
status = snd_card_get_longname(card, name, sizeof (name));
if (status == EOK) {
snd_pcm_t *handle;
// Add device number to device name
char fullname[QSA_MAX_NAME_LENGTH + 32];
SDL_snprintf(fullname, sizeof (fullname), "%s d%d", name, (int) deviceno);
// Check if this device id could play anything
SDL_bool iscapture = SDL_FALSE;
status = snd_pcm_open(&handle, card, deviceno, SND_PCM_OPEN_PLAYBACK);
if (status != EOK) { // no? See if it's a capture device instead.
#if 0 // !!! FIXME: most of this code has support for capture devices, but there's no CaptureFromDevice, etc functions. Fill them in!
status = snd_pcm_open(&handle, card, deviceno, SND_PCM_OPEN_CAPTURE);
if (status == EOK) {
iscapture = SDL_TRUE;
}
#endif
}
if (status == EOK) {
SDL_AudioSpec spec;
SDL_AudioSpec *pspec = &spec;
snd_pcm_channel_setup_t csetup;
SDL_zero(csetup);
csetup.channel = iscapture ? SND_PCM_CHANNEL_CAPTURE : SND_PCM_CHANNEL_PLAYBACK;
if (snd_pcm_plugin_setup(device->hidden->audio_handle, &csetup) < 0) {
pspec = NULL; // go on without spec info.
} else {
spec.format = QnxFormatToSDLFormat(csetup.format.format);
spec.channels = csetup.format.channels;
spec.freq = csetup.format.rate;
}
status = snd_pcm_close(handle);
if (status == EOK) {
// !!! FIXME: I'm assuming each of these values are way less than 0xFFFF. Fix this if not.
SDL_assert(card <= 0xFFFF);
SDL_assert(deviceno <= 0xFFFF);
const Uint32 sdlhandle = ((Uint32) card) | (((Uint32) deviceno) << 16);
SDL_AddAudioDevice(iscapture, fullname, pspec, (void *) ((size_t) sdlhandle));
}
} else {
// Check if we got end of devices list
if (status == -ENOENT) {
break;
}
}
} else {
break;
}
}
}
SDL_small_free(cards, isstack);
// Try to open the "preferred" devices, which will tell us the card/device pairs for the default devices.
snd_pcm_t handle;
int cardno, deviceno;
if (snd_pcm_open_preferred(&handle, &cardno, &deviceno, SND_PCM_OPEN_PLAYBACK) == 0) {
snd_pcm_close(handle);
// !!! FIXME: I'm assuming each of these values are way less than 0xFFFF. Fix this if not.
SDL_assert(cardno <= 0xFFFF);
SDL_assert(deviceno <= 0xFFFF);
const Uint32 sdlhandle = ((Uint32) card) | (((Uint32) deviceno) << 16);
*default_output = SDL_FindPhysicalAudioDeviceByHandle((void *) ((size_t) sdlhandle));
}
if (snd_pcm_open_preferred(&handle, &cardno, &deviceno, SND_PCM_OPEN_CAPTURE) == 0) {
snd_pcm_close(handle);
// !!! FIXME: I'm assuming each of these values are way less than 0xFFFF. Fix this if not.
SDL_assert(cardno <= 0xFFFF);
SDL_assert(deviceno <= 0xFFFF);
const Uint32 sdlhandle = ((Uint32) card) | (((Uint32) deviceno) << 16);
*default_capture = SDL_FindPhysicalAudioDeviceByHandle((void *) ((size_t) sdlhandle));
}
}
static void QSA_Deinitialize(void)
{
// nothing to do here atm.
}
static SDL_bool QSA_Init(SDL_AudioDriverImpl * impl)
{
impl->DetectDevices = QSA_DetectDevices;
impl->OpenDevice = QSA_OpenDevice;
impl->ThreadInit = QSA_ThreadInit;
impl->WaitDevice = QSA_WaitDevice;
impl->PlayDevice = QSA_PlayDevice;
impl->GetDeviceBuf = QSA_GetDeviceBuf;
impl->CloseDevice = QSA_CloseDevice;
impl->Deinitialize = QSA_Deinitialize;
// !!! FIXME: most of this code has support for capture devices, but there's no CaptureFromDevice, etc functions. Fill them in!
//impl->HasCaptureSupport = SDL_TRUE;
return SDL_TRUE;
}
AudioBootStrap QSAAUDIO_bootstrap = {
"qsa", "QNX QSA Audio", QSA_Init, 0
};
#endif // SDL_AUDIO_DRIVER_QNX

View file

@ -0,0 +1,40 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2021 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "../../SDL_internal.h"
#ifndef __SDL_QSA_AUDIO_H__
#define __SDL_QSA_AUDIO_H__
#include <sys/asoundlib.h>
#include "../SDL_sysaudio.h"
struct SDL_PrivateAudioData
{
snd_pcm_t *audio_handle; // The audio device handle
int audio_fd; // The audio file descriptor, for selecting on
SDL_bool timeout_on_wait; // Select timeout status
Uint8 *pcm_buf; // Raw mixing buffer
};
#endif // __SDL_QSA_AUDIO_H__

View file

@ -0,0 +1,359 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_AUDIO_DRIVER_SNDIO
// OpenBSD sndio target
#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif
#ifdef HAVE_SIGNAL_H
#include <signal.h>
#endif
#include <poll.h>
#include <unistd.h>
#include "../SDL_sysaudio.h"
#include "SDL_sndioaudio.h"
#ifdef SDL_AUDIO_DRIVER_SNDIO_DYNAMIC
#endif
#ifndef INFTIM
#define INFTIM -1
#endif
#ifndef SIO_DEVANY
#define SIO_DEVANY "default"
#endif
static struct sio_hdl *(*SNDIO_sio_open)(const char *, unsigned int, int);
static void (*SNDIO_sio_close)(struct sio_hdl *);
static int (*SNDIO_sio_setpar)(struct sio_hdl *, struct sio_par *);
static int (*SNDIO_sio_getpar)(struct sio_hdl *, struct sio_par *);
static int (*SNDIO_sio_start)(struct sio_hdl *);
static int (*SNDIO_sio_stop)(struct sio_hdl *);
static size_t (*SNDIO_sio_read)(struct sio_hdl *, void *, size_t);
static size_t (*SNDIO_sio_write)(struct sio_hdl *, const void *, size_t);
static int (*SNDIO_sio_nfds)(struct sio_hdl *);
static int (*SNDIO_sio_pollfd)(struct sio_hdl *, struct pollfd *, int);
static int (*SNDIO_sio_revents)(struct sio_hdl *, struct pollfd *);
static int (*SNDIO_sio_eof)(struct sio_hdl *);
static void (*SNDIO_sio_initpar)(struct sio_par *);
#ifdef SDL_AUDIO_DRIVER_SNDIO_DYNAMIC
static const char *sndio_library = SDL_AUDIO_DRIVER_SNDIO_DYNAMIC;
static void *sndio_handle = NULL;
static int load_sndio_sym(const char *fn, void **addr)
{
*addr = SDL_LoadFunction(sndio_handle, fn);
if (*addr == NULL) {
return 0; // Don't call SDL_SetError(): SDL_LoadFunction already did.
}
return 1;
}
// cast funcs to char* first, to please GCC's strict aliasing rules.
#define SDL_SNDIO_SYM(x) \
if (!load_sndio_sym(#x, (void **)(char *)&SNDIO_##x)) \
return -1
#else
#define SDL_SNDIO_SYM(x) SNDIO_##x = x
#endif
static int load_sndio_syms(void)
{
SDL_SNDIO_SYM(sio_open);
SDL_SNDIO_SYM(sio_close);
SDL_SNDIO_SYM(sio_setpar);
SDL_SNDIO_SYM(sio_getpar);
SDL_SNDIO_SYM(sio_start);
SDL_SNDIO_SYM(sio_stop);
SDL_SNDIO_SYM(sio_read);
SDL_SNDIO_SYM(sio_write);
SDL_SNDIO_SYM(sio_nfds);
SDL_SNDIO_SYM(sio_pollfd);
SDL_SNDIO_SYM(sio_revents);
SDL_SNDIO_SYM(sio_eof);
SDL_SNDIO_SYM(sio_initpar);
return 0;
}
#undef SDL_SNDIO_SYM
#ifdef SDL_AUDIO_DRIVER_SNDIO_DYNAMIC
static void UnloadSNDIOLibrary(void)
{
if (sndio_handle != NULL) {
SDL_UnloadObject(sndio_handle);
sndio_handle = NULL;
}
}
static int LoadSNDIOLibrary(void)
{
int retval = 0;
if (sndio_handle == NULL) {
sndio_handle = SDL_LoadObject(sndio_library);
if (sndio_handle == NULL) {
retval = -1; // Don't call SDL_SetError(): SDL_LoadObject already did.
} else {
retval = load_sndio_syms();
if (retval < 0) {
UnloadSNDIOLibrary();
}
}
}
return retval;
}
#else
static void UnloadSNDIOLibrary(void)
{
}
static int LoadSNDIOLibrary(void)
{
load_sndio_syms();
return 0;
}
#endif // SDL_AUDIO_DRIVER_SNDIO_DYNAMIC
static int SNDIO_WaitDevice(SDL_AudioDevice *device)
{
const SDL_bool iscapture = device->iscapture;
while (!SDL_AtomicGet(&device->shutdown)) {
if (SNDIO_sio_eof(device->hidden->dev)) {
return -1;
}
const int nfds = SNDIO_sio_pollfd(device->hidden->dev, device->hidden->pfd, iscapture ? POLLIN : POLLOUT);
if (nfds <= 0 || poll(device->hidden->pfd, nfds, 10) < 0) {
return -1;
}
const int revents = SNDIO_sio_revents(device->hidden->dev, device->hidden->pfd);
if (iscapture && (revents & POLLIN)) {
break;
} else if (!iscapture && (revents & POLLOUT)) {
break;
} else if (revents & POLLHUP) {
return -1;
}
}
return 0;
}
static int SNDIO_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
{
// !!! FIXME: this should be non-blocking so we can check device->shutdown.
// this is set to blocking, because we _have_ to send the entire buffer down, but hopefully WaitDevice took most of the delay time.
if (SNDIO_sio_write(device->hidden->dev, buffer, buflen) != buflen) {
return -1; // If we couldn't write, assume fatal error for now
}
#ifdef DEBUG_AUDIO
fprintf(stderr, "Wrote %d bytes of audio data\n", written);
#endif
return 0;
}
static int SNDIO_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
{
// We set capture devices non-blocking; this can safely return 0 in SDL3, but we'll check for EOF to cause a device disconnect.
const size_t br = SNDIO_sio_read(device->hidden->dev, buffer, buflen);
if ((br == 0) && SNDIO_sio_eof(device->hidden->dev)) {
return -1;
}
return (int) br;
}
static void SNDIO_FlushCapture(SDL_AudioDevice *device)
{
char buf[512];
while (!SDL_AtomicGet(&device->shutdown) && (SNDIO_sio_read(device->hidden->dev, buf, sizeof(buf)) > 0)) {
// do nothing
}
}
static Uint8 *SNDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
return device->hidden->mixbuf;
}
static void SNDIO_CloseDevice(SDL_AudioDevice *device)
{
if (device->hidden) {
if (device->hidden->dev != NULL) {
SNDIO_sio_stop(device->hidden->dev);
SNDIO_sio_close(device->hidden->dev);
}
SDL_free(device->hidden->pfd);
SDL_free(device->hidden->mixbuf);
SDL_free(device->hidden);
device->hidden = NULL;
}
}
static int SNDIO_OpenDevice(SDL_AudioDevice *device)
{
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
if (device->hidden == NULL) {
return SDL_OutOfMemory();
}
// !!! FIXME: we really should standardize this on a specific SDL hint.
const char *audiodev = SDL_getenv("AUDIODEV");
// Capture devices must be non-blocking for SNDIO_FlushCapture
device->hidden->dev = SNDIO_sio_open(audiodev != NULL ? audiodev : SIO_DEVANY,
device->iscapture ? SIO_REC : SIO_PLAY, device->iscapture);
if (device->hidden->dev == NULL) {
return SDL_SetError("sio_open() failed");
}
device->hidden->pfd = SDL_malloc(sizeof(struct pollfd) * SNDIO_sio_nfds(device->hidden->dev));
if (device->hidden->pfd == NULL) {
return SDL_OutOfMemory();
}
struct sio_par par;
SNDIO_sio_initpar(&par);
par.rate = device->spec.freq;
par.pchan = device->spec.channels;
par.round = device->sample_frames;
par.appbufsz = par.round * 2;
// Try for a closest match on audio format
SDL_AudioFormat test_format;
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
while ((test_format = *(closefmts++)) != 0) {
if (!SDL_AUDIO_ISFLOAT(test_format)) {
par.le = SDL_AUDIO_ISLITTLEENDIAN(test_format) ? 1 : 0;
par.sig = SDL_AUDIO_ISSIGNED(test_format) ? 1 : 0;
par.bits = SDL_AUDIO_BITSIZE(test_format);
if (SNDIO_sio_setpar(device->hidden->dev, &par) == 0) {
continue;
}
if (SNDIO_sio_getpar(device->hidden->dev, &par) == 0) {
return SDL_SetError("sio_getpar() failed");
}
if (par.bps != SIO_BPS(par.bits)) {
continue;
}
if ((par.bits == 8 * par.bps) || (par.msb)) {
break;
}
}
}
if (!test_format) {
return SDL_SetError("sndio: Unsupported audio format");
}
if ((par.bps == 4) && (par.sig) && (par.le)) {
device->spec.format = SDL_AUDIO_S32LE;
} else if ((par.bps == 4) && (par.sig) && (!par.le)) {
device->spec.format = SDL_AUDIO_S32BE;
} else if ((par.bps == 2) && (par.sig) && (par.le)) {
device->spec.format = SDL_AUDIO_S16LE;
} else if ((par.bps == 2) && (par.sig) && (!par.le)) {
device->spec.format = SDL_AUDIO_S16BE;
} else if ((par.bps == 1) && (par.sig)) {
device->spec.format = SDL_AUDIO_S8;
} else if ((par.bps == 1) && (!par.sig)) {
device->spec.format = SDL_AUDIO_U8;
} else {
return SDL_SetError("sndio: Got unsupported hardware audio format.");
}
device->spec.freq = par.rate;
device->spec.channels = par.pchan;
device->sample_frames = par.round;
// Calculate the final parameters for this audio specification
SDL_UpdatedAudioDeviceFormat(device);
// Allocate mixing buffer
device->hidden->mixbuf = (Uint8 *)SDL_malloc(device->buffer_size);
if (device->hidden->mixbuf == NULL) {
return SDL_OutOfMemory();
}
SDL_memset(device->hidden->mixbuf, device->silence_value, device->buffer_size);
if (!SNDIO_sio_start(device->hidden->dev)) {
return SDL_SetError("sio_start() failed");
}
return 0; // We're ready to rock and roll. :-)
}
static void SNDIO_Deinitialize(void)
{
UnloadSNDIOLibrary();
}
static void SNDIO_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
{
*default_output = SDL_AddAudioDevice(SDL_FALSE, DEFAULT_OUTPUT_DEVNAME, NULL, (void *)0x1);
*default_capture = SDL_AddAudioDevice(SDL_TRUE, DEFAULT_INPUT_DEVNAME, NULL, (void *)0x2);
}
static SDL_bool SNDIO_Init(SDL_AudioDriverImpl *impl)
{
if (LoadSNDIOLibrary() < 0) {
return SDL_FALSE;
}
impl->OpenDevice = SNDIO_OpenDevice;
impl->WaitDevice = SNDIO_WaitDevice;
impl->PlayDevice = SNDIO_PlayDevice;
impl->GetDeviceBuf = SNDIO_GetDeviceBuf;
impl->CloseDevice = SNDIO_CloseDevice;
impl->WaitCaptureDevice = SNDIO_WaitDevice;
impl->CaptureFromDevice = SNDIO_CaptureFromDevice;
impl->FlushCapture = SNDIO_FlushCapture;
impl->Deinitialize = SNDIO_Deinitialize;
impl->DetectDevices = SNDIO_DetectDevices;
impl->HasCaptureSupport = SDL_TRUE;
return SDL_TRUE;
}
AudioBootStrap SNDIO_bootstrap = {
"sndio", "OpenBSD sndio", SNDIO_Init, SDL_FALSE
};
#endif // SDL_AUDIO_DRIVER_SNDIO

View file

@ -0,0 +1,38 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_sndioaudio_h_
#define SDL_sndioaudio_h_
#include <poll.h>
#include <sndio.h>
#include "../SDL_sysaudio.h"
struct SDL_PrivateAudioData
{
struct sio_hdl *dev; // The audio device handle
Uint8 *mixbuf; // Raw mixing buffer
struct pollfd *pfd; // Polling structures for non-blocking sndio devices
};
#endif // SDL_sndioaudio_h_

View file

@ -0,0 +1,238 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_AUDIO_DRIVER_VITA
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "../SDL_audiodev_c.h"
#include "../SDL_sysaudio.h"
#include "SDL_vitaaudio.h"
#include <psp2/kernel/threadmgr.h>
#include <psp2/audioout.h>
#include <psp2/audioin.h>
#define SCE_AUDIO_SAMPLE_ALIGN(s) (((s) + 63) & ~63)
#define SCE_AUDIO_MAX_VOLUME 0x8000
static int VITAAUD_OpenCaptureDevice(SDL_AudioDevice *device)
{
device->spec.freq = 16000;
device->spec.channels = 1;
device->sample_frames = 512;
SDL_UpdatedAudioDeviceFormat(device);
device->hidden->port = sceAudioInOpenPort(SCE_AUDIO_IN_PORT_TYPE_VOICE, 512, 16000, SCE_AUDIO_IN_PARAM_FORMAT_S16_MONO);
if (device->hidden->port < 0) {
return SDL_SetError("Couldn't open audio in port: %x", device->hidden->port);
}
return 0;
}
static int VITAAUD_OpenDevice(SDL_AudioDevice *device)
{
int format, mixlen, i, port = SCE_AUDIO_OUT_PORT_TYPE_MAIN;
int vols[2] = { SCE_AUDIO_MAX_VOLUME, SCE_AUDIO_MAX_VOLUME };
SDL_AudioFormat test_format;
const SDL_AudioFormat *closefmts;
device->hidden = (struct SDL_PrivateAudioData *)
SDL_malloc(sizeof(*device->hidden));
if (device->hidden == NULL) {
return SDL_OutOfMemory();
}
SDL_memset(device->hidden, 0, sizeof(*device->hidden));
closefmts = SDL_ClosestAudioFormats(device->spec.format);
while ((test_format = *(closefmts++)) != 0) {
if (test_format == SDL_AUDIO_S16LE) {
device->spec.format = test_format;
break;
}
}
if (!test_format) {
return SDL_SetError("Unsupported audio format");
}
if (device->iscapture) {
return VITAAUD_OpenCaptureDevice(device);
}
// The sample count must be a multiple of 64.
device->sample_frames = SCE_AUDIO_SAMPLE_ALIGN(device->sample_frames);
// Update the fragment size as size in bytes.
SDL_UpdatedAudioDeviceFormat(device);
/* Allocate the mixing buffer. Its size and starting address must
be a multiple of 64 bytes. Our sample count is already a multiple of
64, so spec->size should be a multiple of 64 as well. */
mixlen = device->buffer_size * NUM_BUFFERS;
device->hidden->rawbuf = (Uint8 *)SDL_aligned_alloc(64, mixlen);
if (device->hidden->rawbuf == NULL) {
return SDL_SetError("Couldn't allocate mixing buffer");
}
// Setup the hardware channel.
if (device->spec.channels == 1) {
format = SCE_AUDIO_OUT_MODE_MONO;
} else {
format = SCE_AUDIO_OUT_MODE_STEREO;
}
// the main port requires 48000Hz audio, so this drops to the background music port if necessary
if (device->spec.freq < 48000) {
port = SCE_AUDIO_OUT_PORT_TYPE_BGM;
}
device->hidden->port = sceAudioOutOpenPort(port, device->sample_frames, device->spec.freq, format);
if (device->hidden->port < 0) {
SDL_aligned_free(device->hidden->rawbuf);
device->hidden->rawbuf = NULL;
return SDL_SetError("Couldn't open audio out port: %x", device->hidden->port);
}
sceAudioOutSetVolume(device->hidden->port, SCE_AUDIO_VOLUME_FLAG_L_CH | SCE_AUDIO_VOLUME_FLAG_R_CH, vols);
SDL_memset(device->hidden->rawbuf, 0, mixlen);
for (i = 0; i < NUM_BUFFERS; i++) {
device->hidden->mixbufs[i] = &device->hidden->rawbuf[i * device->buffer_size];
}
device->hidden->next_buffer = 0;
return 0;
}
static int VITAAUD_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buffer_size)
{
return (sceAudioOutOutput(device->hidden->port, buffer) == 0) ? 0 : -1;
}
// This function waits until it is possible to write a full sound buffer
static int VITAAUD_WaitDevice(SDL_AudioDevice *device)
{
// !!! FIXME: we might just need to sleep roughly as long as playback buffers take to process, based on sample rate, etc.
while (!SDL_AtomicGet(&device->shutdown) && (sceAudioOutGetRestSample(device->hidden->port) >= device->buffer_size)) {
SDL_Delay(1);
}
return 0;
}
static Uint8 *VITAAUD_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
Uint8 *retval = device->hidden->mixbufs[device->hidden->next_buffer];
device->hidden->next_buffer = (device->hidden->next_buffer + 1) % NUM_BUFFERS;
return retval;
}
static void VITAAUD_CloseDevice(SDL_AudioDevice *device)
{
if (device->hidden) {
if (device->hidden->port >= 0) {
if (device->iscapture) {
sceAudioInReleasePort(device->hidden->port);
} else {
sceAudioOutReleasePort(device->hidden->port);
}
device->hidden->port = -1;
}
if (!device->iscapture && device->hidden->rawbuf != NULL) {
SDL_aligned_free(device->hidden->rawbuf); // this uses SDL_aligned_alloc(), not SDL_malloc()
device->hidden->rawbuf = NULL;
}
SDL_free(device->hidden);
device->hidden = NULL;
}
}
static int VITAAUD_WaitCaptureDevice(SDL_AudioDevice *device)
{
// there's only a blocking call to obtain more data, so we'll just sleep as
// long as a buffer would run.
const Uint64 endticks = SDL_GetTicks() + ((device->sample_frames * 1000) / device->spec.freq);
while (!SDL_AtomicGet(&device->shutdown) && (SDL_GetTicks() < endticks)) {
SDL_Delay(1);
}
return 0;
}
static int VITAAUD_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
{
int ret;
SDL_assert(buflen == device->buffer_size);
ret = sceAudioInInput(device->hidden->port, buffer);
if (ret < 0) {
return SDL_SetError("Failed to capture from device: %x", ret);
}
return device->buffer_size;
}
static void VITAAUD_FlushCapture(SDL_AudioDevice *device)
{
// just grab the latest and dump it.
sceAudioInInput(device->hidden->port, device->work_buffer);
}
static void VITAAUD_ThreadInit(SDL_AudioDevice *device)
{
// Increase the priority of this audio thread by 1 to put it ahead of other SDL threads.
SceUID thid;
SceKernelThreadInfo info;
thid = sceKernelGetThreadId();
info.size = sizeof(SceKernelThreadInfo);
if (sceKernelGetThreadInfo(thid, &info) == 0) {
sceKernelChangeThreadPriority(thid, info.currentPriority - 1);
}
}
static SDL_bool VITAAUD_Init(SDL_AudioDriverImpl *impl)
{
impl->OpenDevice = VITAAUD_OpenDevice;
impl->PlayDevice = VITAAUD_PlayDevice;
impl->WaitDevice = VITAAUD_WaitDevice;
impl->GetDeviceBuf = VITAAUD_GetDeviceBuf;
impl->CloseDevice = VITAAUD_CloseDevice;
impl->ThreadInit = VITAAUD_ThreadInit;
impl->WaitCaptureDevice = VITAAUD_WaitCaptureDevice;
impl->FlushCapture = VITAAUD_FlushCapture;
impl->CaptureFromDevice = VITAAUD_CaptureFromDevice;
impl->HasCaptureSupport = SDL_TRUE;
impl->OnlyHasDefaultOutputDevice = SDL_TRUE;
impl->OnlyHasDefaultCaptureDevice = SDL_TRUE;
return SDL_TRUE;
}
AudioBootStrap VITAAUD_bootstrap = {
"vita", "VITA audio driver", VITAAUD_Init, SDL_FALSE
};
#endif // SDL_AUDIO_DRIVER_VITA

View file

@ -0,0 +1,41 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_vitaaudio_h
#define SDL_vitaaudio_h
#include "../SDL_sysaudio.h"
#define NUM_BUFFERS 2
struct SDL_PrivateAudioData
{
// The hardware input/output port.
int port;
// The raw allocated mixing buffer.
Uint8 *rawbuf;
// Individual mixing buffers.
Uint8 *mixbufs[NUM_BUFFERS];
// Index of the next available mixing buffer.
int next_buffer;
};
#endif // SDL_vitaaudio_h

View file

@ -0,0 +1,765 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_AUDIO_DRIVER_WASAPI
#include "../../core/windows/SDL_windows.h"
#include "../../core/windows/SDL_immdevice.h"
#include "../../thread/SDL_systhread.h"
#include "../SDL_sysaudio.h"
#define COBJMACROS
#include <audioclient.h>
#include "SDL_wasapi.h"
// These constants aren't available in older SDKs
#ifndef AUDCLNT_STREAMFLAGS_RATEADJUST
#define AUDCLNT_STREAMFLAGS_RATEADJUST 0x00100000
#endif
#ifndef AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY
#define AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY 0x08000000
#endif
#ifndef AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM
#define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000
#endif
// Some GUIDs we need to know without linking to libraries that aren't available before Vista.
static const IID SDL_IID_IAudioRenderClient = { 0xf294acfc, 0x3146, 0x4483, { 0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2 } };
static const IID SDL_IID_IAudioCaptureClient = { 0xc8adbd64, 0xe71e, 0x48a0, { 0xa4, 0xde, 0x18, 0x5c, 0x39, 0x5c, 0xd3, 0x17 } };
// WASAPI is _really_ particular about various things happening on the same thread, for COM and such,
// so we proxy various stuff to a single background thread to manage.
typedef struct ManagementThreadPendingTask
{
ManagementThreadTask fn;
void *userdata;
int result;
SDL_Semaphore *task_complete_sem;
char *errorstr;
struct ManagementThreadPendingTask *next;
} ManagementThreadPendingTask;
static SDL_Thread *ManagementThread = NULL;
static ManagementThreadPendingTask *ManagementThreadPendingTasks = NULL;
static SDL_Mutex *ManagementThreadLock = NULL;
static SDL_Condition *ManagementThreadCondition = NULL;
static SDL_AtomicInt ManagementThreadShutdown;
static void ManagementThreadMainloop(void)
{
SDL_LockMutex(ManagementThreadLock);
ManagementThreadPendingTask *task;
while (((task = SDL_AtomicGetPtr((void **) &ManagementThreadPendingTasks)) != NULL) || !SDL_AtomicGet(&ManagementThreadShutdown)) {
if (!task) {
SDL_WaitCondition(ManagementThreadCondition, ManagementThreadLock); // block until there's something to do.
} else {
SDL_AtomicSetPtr((void **) &ManagementThreadPendingTasks, task->next); // take task off the pending list.
SDL_UnlockMutex(ManagementThreadLock); // let other things add to the list while we chew on this task.
task->result = task->fn(task->userdata); // run this task.
if (task->task_complete_sem) { // something waiting on result?
task->errorstr = SDL_strdup(SDL_GetError());
SDL_PostSemaphore(task->task_complete_sem);
} else { // nothing waiting, we're done, free it.
SDL_free(task);
}
SDL_LockMutex(ManagementThreadLock); // regrab the lock so we can get the next task; if nothing to do, we'll release the lock in SDL_WaitCondition.
}
}
SDL_UnlockMutex(ManagementThreadLock); // told to shut down and out of tasks, let go of the lock and return.
}
int WASAPI_ProxyToManagementThread(ManagementThreadTask task, void *userdata, int *wait_on_result)
{
// We want to block for a result, but we are already running from the management thread! Just run the task now so we don't deadlock.
if ((wait_on_result != NULL) && (SDL_ThreadID() == SDL_GetThreadID(ManagementThread))) {
*wait_on_result = task(userdata);
return 0; // completed!
}
if (SDL_AtomicGet(&ManagementThreadShutdown)) {
return SDL_SetError("Can't add task, we're shutting down");
}
ManagementThreadPendingTask *pending = SDL_calloc(1, sizeof(ManagementThreadPendingTask));
if (!pending) {
return SDL_OutOfMemory();
}
pending->fn = task;
pending->userdata = userdata;
if (wait_on_result) {
pending->task_complete_sem = SDL_CreateSemaphore(0);
if (!pending->task_complete_sem) {
SDL_free(pending);
return -1;
}
}
pending->next = NULL;
SDL_LockMutex(ManagementThreadLock);
// add to end of task list.
ManagementThreadPendingTask *prev = NULL;
for (ManagementThreadPendingTask *i = SDL_AtomicGetPtr((void **) &ManagementThreadPendingTasks); i != NULL; i = i->next) {
prev = i;
}
if (prev != NULL) {
prev->next = pending;
} else {
SDL_AtomicSetPtr((void **) &ManagementThreadPendingTasks, pending);
}
// task is added to the end of the pending list, let management thread rip!
SDL_SignalCondition(ManagementThreadCondition);
SDL_UnlockMutex(ManagementThreadLock);
if (wait_on_result) {
SDL_WaitSemaphore(pending->task_complete_sem);
SDL_DestroySemaphore(pending->task_complete_sem);
*wait_on_result = pending->result;
if (pending->errorstr) {
SDL_SetError("%s", pending->errorstr);
SDL_free(pending->errorstr);
}
SDL_free(pending);
}
return 0; // successfully added (and possibly executed)!
}
static int ManagementThreadPrepare(void)
{
if (WASAPI_PlatformInit() == -1) {
return -1;
}
ManagementThreadLock = SDL_CreateMutex();
if (!ManagementThreadLock) {
WASAPI_PlatformDeinit();
return -1;
}
ManagementThreadCondition = SDL_CreateCondition();
if (!ManagementThreadCondition) {
SDL_DestroyMutex(ManagementThreadLock);
ManagementThreadLock = NULL;
WASAPI_PlatformDeinit();
return -1;
}
return 0;
}
typedef struct
{
char *errorstr;
SDL_Semaphore *ready_sem;
} ManagementThreadEntryData;
static int ManagementThreadEntry(void *userdata)
{
ManagementThreadEntryData *data = (ManagementThreadEntryData *)userdata;
if (ManagementThreadPrepare() < 0) {
data->errorstr = SDL_strdup(SDL_GetError());
SDL_PostSemaphore(data->ready_sem); // unblock calling thread.
return 0;
}
SDL_PostSemaphore(data->ready_sem); // unblock calling thread.
ManagementThreadMainloop();
WASAPI_PlatformDeinit();
return 0;
}
static int InitManagementThread(void)
{
ManagementThreadEntryData mgmtdata;
SDL_zero(mgmtdata);
mgmtdata.ready_sem = SDL_CreateSemaphore(0);
if (!mgmtdata.ready_sem) {
return -1;
}
SDL_AtomicSetPtr((void **) &ManagementThreadPendingTasks, NULL);
SDL_AtomicSet(&ManagementThreadShutdown, 0);
ManagementThread = SDL_CreateThreadInternal(ManagementThreadEntry, "SDLWASAPIMgmt", 256 * 1024, &mgmtdata); // !!! FIXME: maybe even smaller stack size?
if (!ManagementThread) {
return -1;
}
SDL_WaitSemaphore(mgmtdata.ready_sem);
SDL_DestroySemaphore(mgmtdata.ready_sem);
if (mgmtdata.errorstr) {
SDL_WaitThread(ManagementThread, NULL);
ManagementThread = NULL;
SDL_SetError("%s", mgmtdata.errorstr);
SDL_free(mgmtdata.errorstr);
return -1;
}
return 0;
}
static void DeinitManagementThread(void)
{
if (ManagementThread) {
SDL_AtomicSet(&ManagementThreadShutdown, 1);
SDL_LockMutex(ManagementThreadLock);
SDL_SignalCondition(ManagementThreadCondition);
SDL_UnlockMutex(ManagementThreadLock);
SDL_WaitThread(ManagementThread, NULL);
ManagementThread = NULL;
}
SDL_assert(SDL_AtomicGetPtr((void **) &ManagementThreadPendingTasks) == NULL);
SDL_DestroyCondition(ManagementThreadCondition);
SDL_DestroyMutex(ManagementThreadLock);
ManagementThreadCondition = NULL;
ManagementThreadLock = NULL;
SDL_AtomicSet(&ManagementThreadShutdown, 0);
}
typedef struct
{
SDL_AudioDevice **default_output;
SDL_AudioDevice **default_capture;
} mgmtthrtask_DetectDevicesData;
static int mgmtthrtask_DetectDevices(void *userdata)
{
mgmtthrtask_DetectDevicesData *data = (mgmtthrtask_DetectDevicesData *)userdata;
WASAPI_EnumerateEndpoints(data->default_output, data->default_capture);
return 0;
}
static void WASAPI_DetectDevices(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
{
int rc;
// this blocks because it needs to finish before the audio subsystem inits
mgmtthrtask_DetectDevicesData data = { default_output, default_capture };
WASAPI_ProxyToManagementThread(mgmtthrtask_DetectDevices, &data, &rc);
}
static int mgmtthrtask_DisconnectDevice(void *userdata)
{
SDL_AudioDeviceDisconnected((SDL_AudioDevice *)userdata);
return 0;
}
void WASAPI_DisconnectDevice(SDL_AudioDevice *device)
{
int rc; // block on this; don't disconnect while holding the device lock!
WASAPI_ProxyToManagementThread(mgmtthrtask_DisconnectDevice, device, &rc);
}
static SDL_bool WasapiFailed(SDL_AudioDevice *device, const HRESULT err)
{
if (err == S_OK) {
return SDL_FALSE;
} else if (err == AUDCLNT_E_DEVICE_INVALIDATED) {
device->hidden->device_lost = SDL_TRUE;
} else {
device->hidden->device_dead = SDL_TRUE;
}
return SDL_TRUE;
}
static int mgmtthrtask_ReleaseCaptureClient(void *userdata)
{
IAudioCaptureClient_Release((IAudioCaptureClient *)userdata);
return 0;
}
static int mgmtthrtask_ReleaseRenderClient(void *userdata)
{
IAudioRenderClient_Release((IAudioRenderClient *)userdata);
return 0;
}
static int mgmtthrtask_ResetWasapiDevice(void *userdata)
{
SDL_AudioDevice *device = (SDL_AudioDevice *)userdata;
if (!device || !device->hidden) {
return 0;
}
if (device->hidden->client) {
IAudioClient_Stop(device->hidden->client);
IAudioClient_Release(device->hidden->client);
device->hidden->client = NULL;
}
if (device->hidden->render) {
// this is silly, but this will block indefinitely if you call it from SDLMMNotificationClient_OnDefaultDeviceChanged, so
// proxy this to the management thread to be released later.
WASAPI_ProxyToManagementThread(mgmtthrtask_ReleaseRenderClient, device->hidden->render, NULL);
device->hidden->render = NULL;
}
if (device->hidden->capture) {
// this is silly, but this will block indefinitely if you call it from SDLMMNotificationClient_OnDefaultDeviceChanged, so
// proxy this to the management thread to be released later.
WASAPI_ProxyToManagementThread(mgmtthrtask_ReleaseCaptureClient, device->hidden->capture, NULL);
device->hidden->capture = NULL;
}
if (device->hidden->waveformat) {
CoTaskMemFree(device->hidden->waveformat);
device->hidden->waveformat = NULL;
}
if (device->hidden->activation_handler) {
WASAPI_PlatformDeleteActivationHandler(device->hidden->activation_handler);
device->hidden->activation_handler = NULL;
}
if (device->hidden->event) {
CloseHandle(device->hidden->event);
device->hidden->event = NULL;
}
return 0;
}
static void ResetWasapiDevice(SDL_AudioDevice *device)
{
int rc;
WASAPI_ProxyToManagementThread(mgmtthrtask_ResetWasapiDevice, device, &rc);
}
static int mgmtthrtask_ActivateDevice(void *userdata)
{
return WASAPI_ActivateDevice((SDL_AudioDevice *)userdata);
}
static int ActivateWasapiDevice(SDL_AudioDevice *device)
{
// this blocks because we're either being notified from a background thread or we're running during device open,
// both of which won't deadlock vs the device thread.
int rc = -1;
return ((WASAPI_ProxyToManagementThread(mgmtthrtask_ActivateDevice, device, &rc) < 0) || (rc < 0)) ? -1 : 0;
}
// do not call when holding the device lock!
static SDL_bool RecoverWasapiDevice(SDL_AudioDevice *device)
{
ResetWasapiDevice(device); // dump the lost device's handles.
// This handles a non-default device that simply had its format changed in the Windows Control Panel.
if (ActivateWasapiDevice(device) == -1) {
WASAPI_DisconnectDevice(device);
return SDL_FALSE;
}
device->hidden->device_lost = SDL_FALSE;
return SDL_TRUE; // okay, carry on with new device details!
}
// do not call when holding the device lock!
static SDL_bool RecoverWasapiIfLost(SDL_AudioDevice *device)
{
if (SDL_AtomicGet(&device->shutdown)) {
return SDL_FALSE; // already failed.
} else if (device->hidden->device_dead) { // had a fatal error elsewhere, clean up and quit
IAudioClient_Stop(device->hidden->client);
WASAPI_DisconnectDevice(device);
SDL_assert(SDL_AtomicGet(&device->shutdown)); // so we don't come back through here.
return SDL_FALSE; // already failed.
} else if (SDL_AtomicGet(&device->zombie)) {
return SDL_FALSE; // we're already dead, so just leave and let the Zombie implementations take over.
} else if (!device->hidden->client) {
return SDL_TRUE; // still waiting for activation.
}
return device->hidden->device_lost ? RecoverWasapiDevice(device) : SDL_TRUE;
}
static Uint8 *WASAPI_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
// get an endpoint buffer from WASAPI.
BYTE *buffer = NULL;
if (device->hidden->render) {
if (WasapiFailed(device, IAudioRenderClient_GetBuffer(device->hidden->render, device->sample_frames, &buffer))) {
SDL_assert(buffer == NULL);
if (device->hidden->device_lost) { // just use an available buffer, we won't be playing it anyhow.
*buffer_size = 0; // we'll recover during WaitDevice and try again.
}
}
}
return (Uint8 *)buffer;
}
static int WASAPI_PlayDevice(SDL_AudioDevice *device, const Uint8 *buffer, int buflen)
{
if (device->hidden->render != NULL) { // definitely activated?
// WasapiFailed() will mark the device for reacquisition or removal elsewhere.
WasapiFailed(device, IAudioRenderClient_ReleaseBuffer(device->hidden->render, device->sample_frames, 0));
}
return 0;
}
static int WASAPI_WaitDevice(SDL_AudioDevice *device)
{
// WaitDevice does not hold the device lock, so check for recovery/disconnect details here.
while (RecoverWasapiIfLost(device) && device->hidden->client && device->hidden->event) {
DWORD waitResult = WaitForSingleObjectEx(device->hidden->event, 200, FALSE);
if (waitResult == WAIT_OBJECT_0) {
const UINT32 maxpadding = device->sample_frames;
UINT32 padding = 0;
if (!WasapiFailed(device, IAudioClient_GetCurrentPadding(device->hidden->client, &padding))) {
//SDL_Log("WASAPI EVENT! padding=%u maxpadding=%u", (unsigned int)padding, (unsigned int)maxpadding);*/
if (device->iscapture && (padding > 0)) {
break;
} else if (!device->iscapture && (padding <= maxpadding)) {
break;
}
}
} else if (waitResult != WAIT_TIMEOUT) {
//SDL_Log("WASAPI FAILED EVENT!");*/
IAudioClient_Stop(device->hidden->client);
return -1;
}
}
return 0;
}
static int WASAPI_CaptureFromDevice(SDL_AudioDevice *device, void *buffer, int buflen)
{
BYTE *ptr = NULL;
UINT32 frames = 0;
DWORD flags = 0;
while (device->hidden->capture) {
const HRESULT ret = IAudioCaptureClient_GetBuffer(device->hidden->capture, &ptr, &frames, &flags, NULL, NULL);
if (ret == AUDCLNT_S_BUFFER_EMPTY) {
return 0; // in theory we should have waited until there was data, but oh well, we'll go back to waiting. Returning 0 is safe in SDL3.
}
WasapiFailed(device, ret); // mark device lost/failed if necessary.
if (ret == S_OK) {
const int total = ((int)frames) * device->hidden->framesize;
const int cpy = SDL_min(buflen, total);
const int leftover = total - cpy;
const SDL_bool silent = (flags & AUDCLNT_BUFFERFLAGS_SILENT) ? SDL_TRUE : SDL_FALSE;
SDL_assert(leftover == 0); // according to MSDN, this isn't everything available, just one "packet" of data per-GetBuffer call.
if (silent) {
SDL_memset(buffer, device->silence_value, cpy);
} else {
SDL_memcpy(buffer, ptr, cpy);
}
WasapiFailed(device, IAudioCaptureClient_ReleaseBuffer(device->hidden->capture, frames));
return cpy;
}
}
return -1; // unrecoverable error.
}
static void WASAPI_FlushCapture(SDL_AudioDevice *device)
{
BYTE *ptr = NULL;
UINT32 frames = 0;
DWORD flags = 0;
// just read until we stop getting packets, throwing them away.
while (!SDL_AtomicGet(&device->shutdown) && device->hidden->capture) {
const HRESULT ret = IAudioCaptureClient_GetBuffer(device->hidden->capture, &ptr, &frames, &flags, NULL, NULL);
if (ret == AUDCLNT_S_BUFFER_EMPTY) {
break; // no more buffered data; we're done.
} else if (WasapiFailed(device, ret)) {
break; // failed for some other reason, abort.
} else if (WasapiFailed(device, IAudioCaptureClient_ReleaseBuffer(device->hidden->capture, frames))) {
break; // something broke.
}
}
}
static void WASAPI_CloseDevice(SDL_AudioDevice *device)
{
if (device->hidden) {
ResetWasapiDevice(device);
SDL_free(device->hidden->devid);
SDL_free(device->hidden);
device->hidden = NULL;
}
}
static int mgmtthrtask_PrepDevice(void *userdata)
{
SDL_AudioDevice *device = (SDL_AudioDevice *)userdata;
/* !!! FIXME: we could request an exclusive mode stream, which is lower latency;
!!! it will write into the kernel's audio buffer directly instead of
!!! shared memory that a user-mode mixer then writes to the kernel with
!!! everything else. Doing this means any other sound using this device will
!!! stop playing, including the user's MP3 player and system notification
!!! sounds. You'd probably need to release the device when the app isn't in
!!! the foreground, to be a good citizen of the system. It's doable, but it's
!!! more work and causes some annoyances, and I don't know what the latency
!!! wins actually look like. Maybe add a hint to force exclusive mode at
!!! some point. To be sure, defaulting to shared mode is the right thing to
!!! do in any case. */
const AUDCLNT_SHAREMODE sharemode = AUDCLNT_SHAREMODE_SHARED;
IAudioClient *client = device->hidden->client;
SDL_assert(client != NULL);
#if defined(__WINRT__) || defined(__GDK__) // CreateEventEx() arrived in Vista, so we need an #ifdef for XP.
device->hidden->event = CreateEventEx(NULL, NULL, 0, EVENT_ALL_ACCESS);
#else
device->hidden->event = CreateEventW(NULL, 0, 0, NULL);
#endif
if (device->hidden->event == NULL) {
return WIN_SetError("WASAPI can't create an event handle");
}
HRESULT ret;
WAVEFORMATEX *waveformat = NULL;
ret = IAudioClient_GetMixFormat(client, &waveformat);
if (FAILED(ret)) {
return WIN_SetErrorFromHRESULT("WASAPI can't determine mix format", ret);
}
SDL_assert(waveformat != NULL);
device->hidden->waveformat = waveformat;
SDL_AudioSpec newspec;
newspec.channels = (Uint8)waveformat->nChannels;
// Make sure we have a valid format that we can convert to whatever WASAPI wants.
const SDL_AudioFormat wasapi_format = SDL_WaveFormatExToSDLFormat(waveformat);
SDL_AudioFormat test_format;
const SDL_AudioFormat *closefmts = SDL_ClosestAudioFormats(device->spec.format);
while ((test_format = *(closefmts++)) != 0) {
if (test_format == wasapi_format) {
newspec.format = test_format;
break;
}
}
if (!test_format) {
return SDL_SetError("%s: Unsupported audio format", "wasapi");
}
REFERENCE_TIME default_period = 0;
ret = IAudioClient_GetDevicePeriod(client, &default_period, NULL);
if (FAILED(ret)) {
return WIN_SetErrorFromHRESULT("WASAPI can't determine minimum device period", ret);
}
DWORD streamflags = 0;
/* we've gotten reports that WASAPI's resampler introduces distortions, but in the short term
it fixes some other WASAPI-specific quirks we haven't quite tracked down.
Refer to bug #6326 for the immediate concern. */
#if 1
// favor WASAPI's resampler over our own
if ((DWORD)device->spec.freq != waveformat->nSamplesPerSec) {
streamflags |= (AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY);
waveformat->nSamplesPerSec = device->spec.freq;
waveformat->nAvgBytesPerSec = waveformat->nSamplesPerSec * waveformat->nChannels * (waveformat->wBitsPerSample / 8);
}
#endif
newspec.freq = waveformat->nSamplesPerSec;
streamflags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
ret = IAudioClient_Initialize(client, sharemode, streamflags, 0, 0, waveformat, NULL);
if (FAILED(ret)) {
return WIN_SetErrorFromHRESULT("WASAPI can't initialize audio client", ret);
}
ret = IAudioClient_SetEventHandle(client, device->hidden->event);
if (FAILED(ret)) {
return WIN_SetErrorFromHRESULT("WASAPI can't set event handle", ret);
}
UINT32 bufsize = 0; // this is in sample frames, not samples, not bytes.
ret = IAudioClient_GetBufferSize(client, &bufsize);
if (FAILED(ret)) {
return WIN_SetErrorFromHRESULT("WASAPI can't determine buffer size", ret);
}
/* Match the callback size to the period size to cut down on the number of
interrupts waited for in each call to WaitDevice */
const float period_millis = default_period / 10000.0f;
const float period_frames = period_millis * newspec.freq / 1000.0f;
const int new_sample_frames = (int) SDL_ceilf(period_frames);
// Update the fragment size as size in bytes
if (SDL_AudioDeviceFormatChangedAlreadyLocked(device, &newspec, new_sample_frames) < 0) {
return -1;
}
device->hidden->framesize = SDL_AUDIO_FRAMESIZE(device->spec);
if (device->iscapture) {
IAudioCaptureClient *capture = NULL;
ret = IAudioClient_GetService(client, &SDL_IID_IAudioCaptureClient, (void **)&capture);
if (FAILED(ret)) {
return WIN_SetErrorFromHRESULT("WASAPI can't get capture client service", ret);
}
SDL_assert(capture != NULL);
device->hidden->capture = capture;
ret = IAudioClient_Start(client);
if (FAILED(ret)) {
return WIN_SetErrorFromHRESULT("WASAPI can't start capture", ret);
}
WASAPI_FlushCapture(device); // MSDN says you should flush capture endpoint right after startup.
} else {
IAudioRenderClient *render = NULL;
ret = IAudioClient_GetService(client, &SDL_IID_IAudioRenderClient, (void **)&render);
if (FAILED(ret)) {
return WIN_SetErrorFromHRESULT("WASAPI can't get render client service", ret);
}
SDL_assert(render != NULL);
device->hidden->render = render;
ret = IAudioClient_Start(client);
if (FAILED(ret)) {
return WIN_SetErrorFromHRESULT("WASAPI can't start playback", ret);
}
}
return 0; // good to go.
}
// This is called once a device is activated, possibly asynchronously.
int WASAPI_PrepDevice(SDL_AudioDevice *device)
{
int rc = 0;
return (WASAPI_ProxyToManagementThread(mgmtthrtask_PrepDevice, device, &rc) < 0) ? -1 : rc;
}
static int WASAPI_OpenDevice(SDL_AudioDevice *device)
{
// Initialize all variables that we clean on shutdown
device->hidden = (struct SDL_PrivateAudioData *) SDL_calloc(1, sizeof(*device->hidden));
if (device->hidden == NULL) {
return SDL_OutOfMemory();
} else if (ActivateWasapiDevice(device) < 0) {
return -1; // already set error.
}
/* Ready, but possibly waiting for async device activation.
Until activation is successful, we will report silence from capture
devices and ignore data on playback devices. Upon activation, we'll make
sure any bound audio streams are adjusted for the final device format. */
return 0;
}
static void WASAPI_ThreadInit(SDL_AudioDevice *device)
{
WASAPI_PlatformThreadInit(device);
}
static void WASAPI_ThreadDeinit(SDL_AudioDevice *device)
{
WASAPI_PlatformThreadDeinit(device);
}
static int mgmtthrtask_FreeDeviceHandle(void *userdata)
{
WASAPI_PlatformFreeDeviceHandle((SDL_AudioDevice *)userdata);
return 0;
}
static void WASAPI_FreeDeviceHandle(SDL_AudioDevice *device)
{
int rc;
WASAPI_ProxyToManagementThread(mgmtthrtask_FreeDeviceHandle, device, &rc);
}
static int mgmtthrtask_DeinitializeStart(void *userdata)
{
WASAPI_PlatformDeinitializeStart();
return 0;
}
static void WASAPI_DeinitializeStart(void)
{
int rc;
WASAPI_ProxyToManagementThread(mgmtthrtask_DeinitializeStart, NULL, &rc);
}
static void WASAPI_Deinitialize(void)
{
DeinitManagementThread();
}
static SDL_bool WASAPI_Init(SDL_AudioDriverImpl *impl)
{
if (InitManagementThread() < 0) {
return SDL_FALSE;
}
impl->DetectDevices = WASAPI_DetectDevices;
impl->ThreadInit = WASAPI_ThreadInit;
impl->ThreadDeinit = WASAPI_ThreadDeinit;
impl->OpenDevice = WASAPI_OpenDevice;
impl->PlayDevice = WASAPI_PlayDevice;
impl->WaitDevice = WASAPI_WaitDevice;
impl->GetDeviceBuf = WASAPI_GetDeviceBuf;
impl->WaitCaptureDevice = WASAPI_WaitDevice;
impl->CaptureFromDevice = WASAPI_CaptureFromDevice;
impl->FlushCapture = WASAPI_FlushCapture;
impl->CloseDevice = WASAPI_CloseDevice;
impl->DeinitializeStart = WASAPI_DeinitializeStart;
impl->Deinitialize = WASAPI_Deinitialize;
impl->FreeDeviceHandle = WASAPI_FreeDeviceHandle;
impl->HasCaptureSupport = SDL_TRUE;
return SDL_TRUE;
}
AudioBootStrap WASAPI_bootstrap = {
"wasapi", "WASAPI", WASAPI_Init, SDL_FALSE
};
#endif // SDL_AUDIO_DRIVER_WASAPI

View file

@ -0,0 +1,73 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_wasapi_h_
#define SDL_wasapi_h_
#ifdef __cplusplus
extern "C" {
#endif
#include "../SDL_sysaudio.h"
struct SDL_PrivateAudioData
{
WCHAR *devid;
WAVEFORMATEX *waveformat;
IAudioClient *client;
IAudioRenderClient *render;
IAudioCaptureClient *capture;
HANDLE event;
HANDLE task;
SDL_bool coinitialized;
int framesize;
SDL_bool device_lost;
SDL_bool device_dead;
void *activation_handler;
};
// win32 and winrt implementations call into these.
int WASAPI_PrepDevice(SDL_AudioDevice *device);
void WASAPI_DisconnectDevice(SDL_AudioDevice *device); // don't hold the device lock when calling this!
// BE CAREFUL: if you are holding the device lock and proxy to the management thread with wait_until_complete, and grab the lock again, you will deadlock.
typedef int (*ManagementThreadTask)(void *userdata);
int WASAPI_ProxyToManagementThread(ManagementThreadTask task, void *userdata, int *wait_until_complete);
// These are functions that are implemented differently for Windows vs WinRT.
// UNLESS OTHERWISE NOTED THESE ALL HAPPEN ON THE MANAGEMENT THREAD.
int WASAPI_PlatformInit(void);
void WASAPI_PlatformDeinit(void);
void WASAPI_PlatformDeinitializeStart(void);
void WASAPI_EnumerateEndpoints(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture);
int WASAPI_ActivateDevice(SDL_AudioDevice *device);
void WASAPI_PlatformThreadInit(SDL_AudioDevice *device); // this happens on the audio device thread, not the management thread.
void WASAPI_PlatformThreadDeinit(SDL_AudioDevice *device); // this happens on the audio device thread, not the management thread.
void WASAPI_PlatformDeleteActivationHandler(void *handler);
void WASAPI_PlatformFreeDeviceHandle(SDL_AudioDevice *device);
#ifdef __cplusplus
}
#endif
#endif // SDL_wasapi_h_

View file

@ -0,0 +1,195 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
/* This is code that Windows uses to talk to WASAPI-related system APIs.
This is for non-WinRT desktop apps. The C++/CX implementation of these
functions, exclusive to WinRT, are in SDL_wasapi_winrt.cpp.
The code in SDL_wasapi.c is used by both standard Windows and WinRT builds
to deal with audio and calls into these functions. */
#if defined(SDL_AUDIO_DRIVER_WASAPI) && !defined(__WINRT__)
#include "../../core/windows/SDL_windows.h"
#include "../../core/windows/SDL_immdevice.h"
#include "../SDL_sysaudio.h"
#include <audioclient.h>
#include "SDL_wasapi.h"
// handle to Avrt.dll--Vista and later!--for flagging the callback thread as "Pro Audio" (low latency).
static HMODULE libavrt = NULL;
typedef HANDLE(WINAPI *pfnAvSetMmThreadCharacteristicsW)(LPCWSTR, LPDWORD);
typedef BOOL(WINAPI *pfnAvRevertMmThreadCharacteristics)(HANDLE);
static pfnAvSetMmThreadCharacteristicsW pAvSetMmThreadCharacteristicsW = NULL;
static pfnAvRevertMmThreadCharacteristics pAvRevertMmThreadCharacteristics = NULL;
static SDL_bool immdevice_initialized = SDL_FALSE;
// Some GUIDs we need to know without linking to libraries that aren't available before Vista.
static const IID SDL_IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32, { 0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2 } };
static int mgmtthrtask_AudioDeviceDisconnected(void *userdata)
{
SDL_AudioDeviceDisconnected((SDL_AudioDevice *)userdata);
return 0;
}
static void WASAPI_AudioDeviceDisconnected(SDL_AudioDevice *device)
{
// don't wait on this, IMMDevice's own thread needs to return or everything will deadlock.
WASAPI_ProxyToManagementThread(mgmtthrtask_AudioDeviceDisconnected, device, NULL);
}
static int mgmtthrtask_DefaultAudioDeviceChanged(void *userdata)
{
SDL_DefaultAudioDeviceChanged((SDL_AudioDevice *) userdata);
return 0;
}
static void WASAPI_DefaultAudioDeviceChanged(SDL_AudioDevice *new_default_device)
{
// don't wait on this, IMMDevice's own thread needs to return or everything will deadlock.
WASAPI_ProxyToManagementThread(mgmtthrtask_DefaultAudioDeviceChanged, new_default_device, NULL);
}
int WASAPI_PlatformInit(void)
{
const SDL_IMMDevice_callbacks callbacks = { WASAPI_AudioDeviceDisconnected, WASAPI_DefaultAudioDeviceChanged };
if (FAILED(WIN_CoInitialize())) {
return SDL_SetError("CoInitialize() failed");
} else if (SDL_IMMDevice_Init(&callbacks) < 0) {
return -1; // Error string is set by SDL_IMMDevice_Init
}
immdevice_initialized = SDL_TRUE;
libavrt = LoadLibrary(TEXT("avrt.dll")); // this library is available in Vista and later. No WinXP, so have to LoadLibrary to use it for now!
if (libavrt) {
pAvSetMmThreadCharacteristicsW = (pfnAvSetMmThreadCharacteristicsW)GetProcAddress(libavrt, "AvSetMmThreadCharacteristicsW");
pAvRevertMmThreadCharacteristics = (pfnAvRevertMmThreadCharacteristics)GetProcAddress(libavrt, "AvRevertMmThreadCharacteristics");
}
return 0;
}
static void StopWasapiHotplug(void)
{
if (immdevice_initialized) {
SDL_IMMDevice_Quit();
immdevice_initialized = SDL_FALSE;
}
}
void WASAPI_PlatformDeinit(void)
{
if (libavrt) {
FreeLibrary(libavrt);
libavrt = NULL;
}
pAvSetMmThreadCharacteristicsW = NULL;
pAvRevertMmThreadCharacteristics = NULL;
StopWasapiHotplug();
WIN_CoUninitialize();
}
void WASAPI_PlatformDeinitializeStart(void)
{
StopWasapiHotplug();
}
void WASAPI_PlatformThreadInit(SDL_AudioDevice *device)
{
// this thread uses COM.
if (SUCCEEDED(WIN_CoInitialize())) { // can't report errors, hope it worked!
device->hidden->coinitialized = SDL_TRUE;
}
// Set this thread to very high "Pro Audio" priority.
if (pAvSetMmThreadCharacteristicsW) {
DWORD idx = 0;
device->hidden->task = pAvSetMmThreadCharacteristicsW(L"Pro Audio", &idx);
} else {
SDL_SetThreadPriority(device->iscapture ? SDL_THREAD_PRIORITY_HIGH : SDL_THREAD_PRIORITY_TIME_CRITICAL);
}
}
void WASAPI_PlatformThreadDeinit(SDL_AudioDevice *device)
{
// Set this thread back to normal priority.
if (device->hidden->task && pAvRevertMmThreadCharacteristics) {
pAvRevertMmThreadCharacteristics(device->hidden->task);
device->hidden->task = NULL;
}
if (device->hidden->coinitialized) {
WIN_CoUninitialize();
device->hidden->coinitialized = SDL_FALSE;
}
}
int WASAPI_ActivateDevice(SDL_AudioDevice *device)
{
IMMDevice *immdevice = NULL;
if (SDL_IMMDevice_Get(device, &immdevice, device->iscapture) < 0) {
device->hidden->client = NULL;
return -1; // This is already set by SDL_IMMDevice_Get
}
// this is _not_ async in standard win32, yay!
HRESULT ret = IMMDevice_Activate(immdevice, &SDL_IID_IAudioClient, CLSCTX_ALL, NULL, (void **)&device->hidden->client);
IMMDevice_Release(immdevice);
if (FAILED(ret)) {
SDL_assert(device->hidden->client == NULL);
return WIN_SetErrorFromHRESULT("WASAPI can't activate audio endpoint", ret);
}
SDL_assert(device->hidden->client != NULL);
if (WASAPI_PrepDevice(device) == -1) { // not async, fire it right away.
return -1;
}
return 0; // good to go.
}
void WASAPI_EnumerateEndpoints(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
{
SDL_IMMDevice_EnumerateEndpoints(default_output, default_capture);
}
void WASAPI_PlatformDeleteActivationHandler(void *handler)
{
// not asynchronous.
SDL_assert(!"This function should have only been called on WinRT.");
}
void WASAPI_PlatformFreeDeviceHandle(SDL_AudioDevice *device)
{
SDL_IMMDevice_FreeDeviceHandle(device);
}
#endif // SDL_AUDIO_DRIVER_WASAPI && !defined(__WINRT__)

View file

@ -0,0 +1,360 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
// This is C++/CX code that the WinRT port uses to talk to WASAPI-related
// system APIs. The C implementation of these functions, for non-WinRT apps,
// is in SDL_wasapi_win32.c. The code in SDL_wasapi.c is used by both standard
// Windows and WinRT builds to deal with audio and calls into these functions.
#if defined(SDL_AUDIO_DRIVER_WASAPI) && defined(__WINRT__)
#include <Windows.h>
#include <windows.ui.core.h>
#include <windows.devices.enumeration.h>
#include <windows.media.devices.h>
#include <wrl/implements.h>
#include <collection.h>
extern "C" {
#include "../../core/windows/SDL_windows.h"
#include "../SDL_sysaudio.h"
}
#define COBJMACROS
#include <mmdeviceapi.h>
#include <audioclient.h>
#include "SDL_wasapi.h"
using namespace Windows::Devices::Enumeration;
using namespace Windows::Media::Devices;
using namespace Windows::Foundation;
using namespace Microsoft::WRL;
static Platform::String ^ SDL_PKEY_AudioEngine_DeviceFormat = L"{f19f064d-082c-4e27-bc73-6882a1bb8e4c} 0";
static SDL_bool FindWinRTAudioDeviceCallback(SDL_AudioDevice *device, void *userdata)
{
return (SDL_wcscmp((LPCWSTR) device->handle, (LPCWSTR) userdata) == 0) ? SDL_TRUE : SDL_FALSE;
}
static SDL_AudioDevice *FindWinRTAudioDevice(LPCWSTR devid)
{
return SDL_FindPhysicalAudioDeviceByCallback(FindWinRTAudioDeviceCallback, (void *) devid);
}
class SDL_WasapiDeviceEventHandler
{
public:
SDL_WasapiDeviceEventHandler(const SDL_bool _iscapture);
~SDL_WasapiDeviceEventHandler();
void OnDeviceAdded(DeviceWatcher ^ sender, DeviceInformation ^ args);
void OnDeviceRemoved(DeviceWatcher ^ sender, DeviceInformationUpdate ^ args);
void OnDeviceUpdated(DeviceWatcher ^ sender, DeviceInformationUpdate ^ args);
void OnEnumerationCompleted(DeviceWatcher ^ sender, Platform::Object ^ args);
void OnDefaultRenderDeviceChanged(Platform::Object ^ sender, DefaultAudioRenderDeviceChangedEventArgs ^ args);
void OnDefaultCaptureDeviceChanged(Platform::Object ^ sender, DefaultAudioCaptureDeviceChangedEventArgs ^ args);
void WaitForCompletion();
private:
SDL_Semaphore *completed_semaphore;
const SDL_bool iscapture;
DeviceWatcher ^ watcher;
Windows::Foundation::EventRegistrationToken added_handler;
Windows::Foundation::EventRegistrationToken removed_handler;
Windows::Foundation::EventRegistrationToken updated_handler;
Windows::Foundation::EventRegistrationToken completed_handler;
Windows::Foundation::EventRegistrationToken default_changed_handler;
};
SDL_WasapiDeviceEventHandler::SDL_WasapiDeviceEventHandler(const SDL_bool _iscapture)
: iscapture(_iscapture), completed_semaphore(SDL_CreateSemaphore(0))
{
if (!completed_semaphore) {
return; // uhoh.
}
Platform::String ^ selector = _iscapture ? MediaDevice::GetAudioCaptureSelector() : MediaDevice::GetAudioRenderSelector();
Platform::Collections::Vector<Platform::String ^> properties;
properties.Append(SDL_PKEY_AudioEngine_DeviceFormat);
watcher = DeviceInformation::CreateWatcher(selector, properties.GetView());
if (!watcher)
return; // uhoh.
// !!! FIXME: this doesn't need a lambda here, I think, if I make SDL_WasapiDeviceEventHandler a proper C++/CX class. --ryan.
added_handler = watcher->Added += ref new TypedEventHandler<DeviceWatcher ^, DeviceInformation ^>([this](DeviceWatcher ^ sender, DeviceInformation ^ args) { OnDeviceAdded(sender, args); });
removed_handler = watcher->Removed += ref new TypedEventHandler<DeviceWatcher ^, DeviceInformationUpdate ^>([this](DeviceWatcher ^ sender, DeviceInformationUpdate ^ args) { OnDeviceRemoved(sender, args); });
updated_handler = watcher->Updated += ref new TypedEventHandler<DeviceWatcher ^, DeviceInformationUpdate ^>([this](DeviceWatcher ^ sender, DeviceInformationUpdate ^ args) { OnDeviceUpdated(sender, args); });
completed_handler = watcher->EnumerationCompleted += ref new TypedEventHandler<DeviceWatcher ^, Platform::Object ^>([this](DeviceWatcher ^ sender, Platform::Object ^ args) { OnEnumerationCompleted(sender, args); });
if (iscapture) {
default_changed_handler = MediaDevice::DefaultAudioCaptureDeviceChanged += ref new TypedEventHandler<Platform::Object ^, DefaultAudioCaptureDeviceChangedEventArgs ^>([this](Platform::Object ^ sender, DefaultAudioCaptureDeviceChangedEventArgs ^ args) { OnDefaultCaptureDeviceChanged(sender, args); });
} else {
default_changed_handler = MediaDevice::DefaultAudioRenderDeviceChanged += ref new TypedEventHandler<Platform::Object ^, DefaultAudioRenderDeviceChangedEventArgs ^>([this](Platform::Object ^ sender, DefaultAudioRenderDeviceChangedEventArgs ^ args) { OnDefaultRenderDeviceChanged(sender, args); });
}
watcher->Start();
}
SDL_WasapiDeviceEventHandler::~SDL_WasapiDeviceEventHandler()
{
if (watcher) {
watcher->Added -= added_handler;
watcher->Removed -= removed_handler;
watcher->Updated -= updated_handler;
watcher->EnumerationCompleted -= completed_handler;
watcher->Stop();
watcher = nullptr;
}
if (completed_semaphore) {
SDL_DestroySemaphore(completed_semaphore);
completed_semaphore = nullptr;
}
if (iscapture) {
MediaDevice::DefaultAudioCaptureDeviceChanged -= default_changed_handler;
} else {
MediaDevice::DefaultAudioRenderDeviceChanged -= default_changed_handler;
}
}
void SDL_WasapiDeviceEventHandler::OnDeviceAdded(DeviceWatcher ^ sender, DeviceInformation ^ info)
{
/* You can have multiple endpoints on a device that are mutually exclusive ("Speakers" vs "Line Out" or whatever).
In a perfect world, things that are unplugged won't be in this collection. The only gotcha is probably for
phones and tablets, where you might have an internal speaker and a headphone jack and expect both to be
available and switch automatically. (!!! FIXME...?) */
SDL_assert(sender == this->watcher);
char *utf8dev = WIN_StringToUTF8(info->Name->Data());
if (utf8dev) {
SDL_AudioSpec spec;
SDL_zero(spec);
Platform::Object ^ obj = info->Properties->Lookup(SDL_PKEY_AudioEngine_DeviceFormat);
if (obj) {
IPropertyValue ^ property = (IPropertyValue ^) obj;
Platform::Array<unsigned char> ^ data;
property->GetUInt8Array(&data);
WAVEFORMATEXTENSIBLE fmt;
SDL_zero(fmt);
SDL_memcpy(&fmt, data->Data, SDL_min(data->Length, sizeof(WAVEFORMATEXTENSIBLE)));
spec.channels = (Uint8)fmt.Format.nChannels;
spec.freq = fmt.Format.nSamplesPerSec;
spec.format = SDL_WaveFormatExToSDLFormat((WAVEFORMATEX *)&fmt);
}
LPWSTR devid = SDL_wcsdup(info->Id->Data());
if (devid) {
SDL_AddAudioDevice(this->iscapture, utf8dev, spec.channels ? &spec : NULL, devid);
}
SDL_free(utf8dev);
}
}
void SDL_WasapiDeviceEventHandler::OnDeviceRemoved(DeviceWatcher ^ sender, DeviceInformationUpdate ^ info)
{
SDL_assert(sender == this->watcher);
WASAPI_DisconnectDevice(FindWinRTAudioDevice(info->Id->Data()));
}
void SDL_WasapiDeviceEventHandler::OnDeviceUpdated(DeviceWatcher ^ sender, DeviceInformationUpdate ^ args)
{
SDL_assert(sender == this->watcher);
}
void SDL_WasapiDeviceEventHandler::OnEnumerationCompleted(DeviceWatcher ^ sender, Platform::Object ^ args)
{
SDL_assert(sender == this->watcher);
if (this->completed_semaphore) {
SDL_PostSemaphore(this->completed_semaphore);
}
}
void SDL_WasapiDeviceEventHandler::OnDefaultRenderDeviceChanged(Platform::Object ^ sender, DefaultAudioRenderDeviceChangedEventArgs ^ args)
{
SDL_assert(!this->iscapture);
SDL_DefaultAudioDeviceChanged(FindWinRTAudioDevice(args->Id->Data()));
}
void SDL_WasapiDeviceEventHandler::OnDefaultCaptureDeviceChanged(Platform::Object ^ sender, DefaultAudioCaptureDeviceChangedEventArgs ^ args)
{
SDL_assert(this->iscapture);
SDL_DefaultAudioDeviceChanged(FindWinRTAudioDevice(args->Id->Data()));
}
void SDL_WasapiDeviceEventHandler::WaitForCompletion()
{
if (this->completed_semaphore) {
SDL_WaitSemaphore(this->completed_semaphore);
SDL_DestroySemaphore(this->completed_semaphore);
this->completed_semaphore = nullptr;
}
}
static SDL_WasapiDeviceEventHandler *playback_device_event_handler;
static SDL_WasapiDeviceEventHandler *capture_device_event_handler;
int WASAPI_PlatformInit(void)
{
return 0;
}
static void StopWasapiHotplug(void)
{
delete playback_device_event_handler;
playback_device_event_handler = nullptr;
delete capture_device_event_handler;
capture_device_event_handler = nullptr;
}
void WASAPI_PlatformDeinit(void)
{
StopWasapiHotplug();
}
void WASAPI_PlatformDeinitializeStart(void)
{
StopWasapiHotplug();
}
void WASAPI_EnumerateEndpoints(SDL_AudioDevice **default_output, SDL_AudioDevice **default_capture)
{
Platform::String ^ defdevid;
// DeviceWatchers will fire an Added event for each existing device at
// startup, so we don't need to enumerate them separately before
// listening for updates.
playback_device_event_handler = new SDL_WasapiDeviceEventHandler(SDL_FALSE);
playback_device_event_handler->WaitForCompletion();
defdevid = MediaDevice::GetDefaultAudioRenderId(AudioDeviceRole::Default);
if (defdevid) {
*default_output = FindWinRTAudioDevice(defdevid->Data());
}
capture_device_event_handler = new SDL_WasapiDeviceEventHandler(SDL_TRUE);
capture_device_event_handler->WaitForCompletion();
defdevid = MediaDevice::GetDefaultAudioCaptureId(AudioDeviceRole::Default);
if (defdevid) {
*default_capture = FindWinRTAudioDevice(defdevid->Data());
}
}
class SDL_WasapiActivationHandler : public RuntimeClass<RuntimeClassFlags<ClassicCom>, FtmBase, IActivateAudioInterfaceCompletionHandler>
{
public:
SDL_WasapiActivationHandler() : completion_semaphore(SDL_CreateSemaphore(0)) { SDL_assert(completion_semaphore != NULL); }
STDMETHOD(ActivateCompleted)(IActivateAudioInterfaceAsyncOperation *operation);
void WaitForCompletion();
private:
SDL_Semaphore *completion_semaphore;
};
void SDL_WasapiActivationHandler::WaitForCompletion()
{
if (completion_semaphore) {
SDL_WaitSemaphore(completion_semaphore);
SDL_DestroySemaphore(completion_semaphore);
completion_semaphore = NULL;
}
}
HRESULT
SDL_WasapiActivationHandler::ActivateCompleted(IActivateAudioInterfaceAsyncOperation *async)
{
// Just set a flag, since we're probably in a different thread. We'll pick it up and init everything on our own thread to prevent races.
SDL_PostSemaphore(completion_semaphore);
return S_OK;
}
void WASAPI_PlatformDeleteActivationHandler(void *handler)
{
((SDL_WasapiActivationHandler *)handler)->Release();
}
int WASAPI_ActivateDevice(SDL_AudioDevice *device)
{
LPCWSTR devid = (LPCWSTR) device->handle;
SDL_assert(devid != NULL);
ComPtr<SDL_WasapiActivationHandler> handler = Make<SDL_WasapiActivationHandler>();
if (handler == nullptr) {
return SDL_SetError("Failed to allocate WASAPI activation handler");
}
handler.Get()->AddRef(); // we hold a reference after ComPtr destructs on return, causing a Release, and Release ourselves in WASAPI_PlatformDeleteActivationHandler(), etc.
device->hidden->activation_handler = handler.Get();
IActivateAudioInterfaceAsyncOperation *async = nullptr;
const HRESULT ret = ActivateAudioInterfaceAsync(devid, __uuidof(IAudioClient), nullptr, handler.Get(), &async);
if (FAILED(ret) || async == nullptr) {
if (async != nullptr) {
async->Release();
}
handler.Get()->Release();
return WIN_SetErrorFromHRESULT("WASAPI can't activate requested audio endpoint", ret);
}
// !!! FIXME: the problems in SDL2 that needed this to be synchronous are _probably_ solved by SDL3, and this can block indefinitely if a user prompt is shown to get permission to use a microphone.
handler.Get()->WaitForCompletion(); // block here until we have an answer, so this is synchronous to us after all.
HRESULT activateRes = S_OK;
IUnknown *iunknown = nullptr;
const HRESULT getActivateRes = async->GetActivateResult(&activateRes, &iunknown);
async->Release();
if (FAILED(getActivateRes)) {
return WIN_SetErrorFromHRESULT("Failed to get WASAPI activate result", getActivateRes);
} else if (FAILED(activateRes)) {
return WIN_SetErrorFromHRESULT("Failed to activate WASAPI device", activateRes);
}
iunknown->QueryInterface(IID_PPV_ARGS(&device->hidden->client));
if (!device->hidden->client) {
return SDL_SetError("Failed to query WASAPI client interface");
}
if (WASAPI_PrepDevice(device) == -1) {
return -1;
}
return 0;
}
void WASAPI_PlatformThreadInit(SDL_AudioDevice *device)
{
// !!! FIXME: set this thread to "Pro Audio" priority.
SDL_SetThreadPriority(device->iscapture ? SDL_THREAD_PRIORITY_HIGH : SDL_THREAD_PRIORITY_TIME_CRITICAL);
}
void WASAPI_PlatformThreadDeinit(SDL_AudioDevice *device)
{
// !!! FIXME: set this thread to "Pro Audio" priority.
}
void WASAPI_PlatformFreeDeviceHandle(SDL_AudioDevice *device)
{
SDL_free(device->handle);
}
#endif // SDL_AUDIO_DRIVER_WASAPI && defined(__WINRT__)