New upstream release
Stephen Kitt
2 years ago
8 | 8 | option(BUILD_TESTS "Build tests/ folder for unit tests to be executed on the host against FAudio" OFF) |
9 | 9 | if(WIN32) |
10 | 10 | option(BUILD_CPP "Build cpp/ folder (COM wrapper for XAudio2)" OFF) |
11 | option(PLATFORM_WIN32 "Enable native Win32 platform instead of SDL2" OFF) | |
11 | 12 | endif() |
12 | 13 | option(XNASONG "Build with XNA_Song.c" ON) |
13 | 14 | option(LOG_ASSERTIONS "Bind FAudio_assert to log, instead of platform's assert" OFF) |
29 | 30 | # Version |
30 | 31 | SET(LIB_MAJOR_VERSION "0") |
31 | 32 | SET(LIB_MINOR_VERSION "21") |
32 | SET(LIB_REVISION "02") | |
33 | SET(LIB_REVISION "07") | |
33 | 34 | SET(LIB_VERSION "${LIB_MAJOR_VERSION}.${LIB_MINOR_VERSION}.${LIB_REVISION}") |
34 | 35 | |
35 | 36 | # Build Type |
97 | 98 | src/FAudio_internal_simd.c |
98 | 99 | src/FAudio_operationset.c |
99 | 100 | src/FAudio_platform_sdl2.c |
101 | src/FAudio_platform_win32.c | |
100 | 102 | # Optional source files |
101 | 103 | src/XNA_Song.c |
102 | 104 | src/FAudio_gstreamer.c |
103 | 105 | ) |
106 | ||
107 | if(PLATFORM_WIN32) | |
108 | target_link_libraries(FAudio PRIVATE -ldxguid -luuid -lwinmm -lole32 -ladvapi32 -luser32 -lmfplat -lmfreadwrite -lmfuuid -lpropsys) | |
109 | target_compile_definitions(FAudio PUBLIC FAUDIO_WIN32_PLATFORM) | |
110 | target_compile_definitions(FAudio PRIVATE HAVE_WMADEC=1) | |
111 | set(PLATFORM_CFLAGS "-DFAUDIO_WIN32_PLATFORM") | |
112 | set(GSTREAMER OFF) | |
113 | set(XNASONG OFF) | |
114 | else() | |
115 | set(PLATFORM_CFLAGS) | |
116 | endif() | |
104 | 117 | |
105 | 118 | # Only disable DebugConfiguration in release builds |
106 | 119 | if(NOT FORCE_ENABLE_DEBUGCONFIGURATION) |
136 | 149 | |
137 | 150 | # GStreamer Support |
138 | 151 | if(GSTREAMER) |
152 | # Requires for gstreamer in the pkgconfig file | |
153 | set(FAUDIO_GSTREAMER_DEPS " gstreamer-app-1.0 gstreamer-audio-1.0") | |
154 | ||
139 | 155 | # Add the extra definition... |
140 | target_compile_definitions(FAudio PRIVATE HAVE_GSTREAMER=1) | |
156 | target_compile_definitions(FAudio PRIVATE HAVE_WMADEC=1 HAVE_GSTREAMER=1) | |
141 | 157 | |
142 | 158 | # Find GStreamer |
143 | 159 | find_package(PkgConfig) |
156 | 172 | ${GSTAUDIO_LDFLAGS} |
157 | 173 | ${GSTAPP_LDFLAGS} |
158 | 174 | ) |
175 | else() | |
176 | set(FAUDIO_GSTREAMER_DEPS "") | |
159 | 177 | endif(GSTREAMER) |
160 | 178 | |
161 | 179 | # Dump source voices to RIFF WAVE files |
164 | 182 | endif() |
165 | 183 | |
166 | 184 | # SDL2 Dependency |
167 | if (DEFINED SDL2_INCLUDE_DIRS AND DEFINED SDL2_LIBRARIES) | |
185 | if (PLATFORM_WIN32) | |
186 | message(STATUS "not using SDL2") | |
187 | elseif (DEFINED SDL2_INCLUDE_DIRS AND DEFINED SDL2_LIBRARIES) | |
168 | 188 | message(STATUS "using pre-defined SDL2 variables SDL2_INCLUDE_DIRS and SDL2_LIBRARIES") |
169 | 189 | target_include_directories(FAudio PUBLIC "$<BUILD_INTERFACE:${SDL2_INCLUDE_DIRS}>") |
170 | 190 | target_link_libraries(FAudio PUBLIC ${SDL2_LIBRARIES}) |
8 | 8 | Version: @LIB_VERSION@ |
9 | 9 | |
10 | 10 | Libs: -L${libdir} -l@PROJECT_NAME@ |
11 | Cflags: -I${includedir} | |
11 | Requires:@FAUDIO_GSTREAMER_DEPS@ | |
12 | Cflags: -I${includedir} @PLATFORM_CFLAGS@ |
78 | 78 | |
79 | 79 | public const uint FAUDIO_ABI_VERSION = 0; |
80 | 80 | public const uint FAUDIO_MAJOR_VERSION = 21; |
81 | public const uint FAUDIO_MINOR_VERSION = 2; | |
81 | public const uint FAUDIO_MINOR_VERSION = 7; | |
82 | 82 | public const uint FAUDIO_PATCH_VERSION = 0; |
83 | 83 | |
84 | 84 | public const uint FAUDIO_COMPILED_VERSION = ( |
175 | 175 | */ |
176 | 176 | } |
177 | 177 | |
178 | public struct FAudioXMA2WaveFormatEx | |
179 | { | |
180 | public FAudioWaveFormatEx wfx; | |
181 | public ushort wNumStreams; | |
182 | public uint dwChannelMask; | |
183 | public uint dwSamplesEncoded; | |
184 | public uint dwBytesPerBlock; | |
185 | public uint dwPlayBegin; | |
186 | public uint dwPlayLength; | |
187 | public uint dwLoopBegin; | |
188 | public uint dwLoopLength; | |
189 | public byte bLoopCount; | |
190 | public byte bEncoderVersion; | |
191 | public ushort wBlockCount; | |
192 | }; | |
193 | ||
178 | 194 | [StructLayout(LayoutKind.Sequential, Pack = 1)] |
179 | 195 | public unsafe struct FAudioDeviceDetails |
180 | 196 | { |
405 | 421 | ); |
406 | 422 | |
407 | 423 | [DllImport(nativeLibName, CallingConvention = CallingConvention.Cdecl)] |
424 | public static extern uint FAudio_CreateSourceVoice( | |
425 | IntPtr audio, /* FAudio* */ | |
426 | out IntPtr ppSourceVoice, /* FAudioSourceVoice** */ | |
427 | IntPtr pSourceFormat, /* FAudioWaveFormatEx* */ | |
428 | uint Flags, | |
429 | float MaxFrequencyRatio, | |
430 | IntPtr pCallback, /* FAudioVoiceCallback* */ | |
431 | IntPtr pSendList, /* FAudioVoiceSends* */ | |
432 | IntPtr pEffectChain /* FAudioEffectChain* */ | |
433 | ); | |
434 | ||
435 | [DllImport(nativeLibName, CallingConvention = CallingConvention.Cdecl)] | |
408 | 436 | public static extern uint FAudio_CreateSubmixVoice( |
409 | 437 | IntPtr audio, /* FAudio* */ |
410 | 438 | out IntPtr ppSubmixVoice, /* FAudioSubmixVoice** */ |
622 | 650 | IntPtr voice, /* FAudioSourceVoice* */ |
623 | 651 | ref FAudioBuffer pBuffer, |
624 | 652 | IntPtr pBufferWMA /* const FAudioBufferWMA* */ |
653 | ); | |
654 | ||
655 | [DllImport(nativeLibName, CallingConvention = CallingConvention.Cdecl)] | |
656 | public static extern uint FAudioSourceVoice_SubmitSourceBuffer( | |
657 | IntPtr voice, /* FAudioSourceVoice* */ | |
658 | ref FAudioBuffer pBuffer, | |
659 | ref FAudioBufferWMA pBufferWMA | |
625 | 660 | ); |
626 | 661 | |
627 | 662 | [DllImport(nativeLibName, CallingConvention = CallingConvention.Cdecl)] |
0 | faudio (21.02-2) UNRELEASED; urgency=medium | |
0 | faudio (21.07-1) UNRELEASED; urgency=medium | |
1 | 1 | |
2 | [ Jens Reyer ] | |
2 | 3 | * Remove myself from Uploaders. |
3 | 4 | |
4 | -- Jens Reyer <jre.winesim@gmail.com> Sun, 07 Feb 2021 18:23:26 +0100 | |
5 | [ Stephen Kitt ] | |
6 | * New upstream release. | |
7 | ||
8 | -- Stephen Kitt <skitt@debian.org> Wed, 07 Jul 2021 21:36:36 +0200 | |
5 | 9 | |
6 | 10 | faudio (21.02-1) unstable; urgency=medium |
7 | 11 |
189 | 189 | FAudio_CreateMasteringVoice@Base 19.02 |
190 | 190 | FAudio_CreateSourceVoice@Base 19.02 |
191 | 191 | FAudio_CreateSubmixVoice@Base 19.02 |
192 | FAudio_GSTREAMER_end_buffer@Base 20.11 | |
193 | FAudio_GSTREAMER_free@Base 20.11 | |
194 | FAudio_GSTREAMER_init@Base 20.11 | |
195 | 192 | FAudio_GetDeviceCount@Base 19.02 |
196 | 193 | FAudio_GetDeviceDetails@Base 19.02 |
197 | 194 | FAudio_GetPerformanceData@Base 19.02 |
236 | 233 | FAudio_StopEngine@Base 19.02 |
237 | 234 | FAudio_UTF8_To_UTF16@Base 19.02 |
238 | 235 | FAudio_UnregisterForCallbacks@Base 19.02 |
236 | FAudio_WMADEC_end_buffer@Base 21.07 | |
237 | FAudio_WMADEC_free@Base 21.07 | |
238 | FAudio_WMADEC_init@Base 21.07 | |
239 | 239 | FAudio_close@Base 19.02 |
240 | 240 | FAudio_fopen@Base 19.02 |
241 | 241 | FAudio_memopen@Base 19.02 |
2 | 2 | |
3 | 3 | --- a/CMakeLists.txt |
4 | 4 | +++ b/CMakeLists.txt |
5 | @@ -462,12 +462,3 @@ | |
5 | @@ -482,12 +482,3 @@ | |
6 | 6 | ${CMAKE_CURRENT_BINARY_DIR}/generated/${PROJECT_NAME}-config.cmake |
7 | 7 | INSTALL_DESTINATION ${FAudio_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} |
8 | 8 | ) |
2 | 2 | |
3 | 3 | --- a/CMakeLists.txt |
4 | 4 | +++ b/CMakeLists.txt |
5 | @@ -102,6 +102,7 @@ | |
5 | @@ -104,6 +104,7 @@ | |
6 | 6 | src/XNA_Song.c |
7 | 7 | src/FAudio_gstreamer.c |
8 | 8 | ) |
9 | 9 | +target_link_libraries(FAudio PRIVATE m) |
10 | 10 | |
11 | # Only disable DebugConfiguration in release builds | |
12 | if(NOT FORCE_ENABLE_DEBUGCONFIGURATION) | |
11 | if(PLATFORM_WIN32) | |
12 | target_link_libraries(FAudio PRIVATE -ldxguid -luuid -lwinmm -lole32 -ladvapi32 -luser32 -lmfplat -lmfreadwrite -lmfuuid -lpropsys) |
2 | 2 | |
3 | 3 | --- a/CMakeLists.txt |
4 | 4 | +++ b/CMakeLists.txt |
5 | @@ -78,7 +78,6 @@ | |
5 | @@ -79,7 +79,6 @@ | |
6 | 6 | # Internal Headers |
7 | 7 | src/FACT_internal.h |
8 | 8 | src/FAudio_internal.h |
10 | 10 | src/stb_vorbis.h |
11 | 11 | # Source Files |
12 | 12 | src/F3DAudio.c |
13 | @@ -104,6 +103,9 @@ | |
14 | ) | |
15 | target_link_libraries(FAudio PRIVATE m) | |
13 | @@ -117,6 +116,9 @@ | |
14 | set(PLATFORM_CFLAGS) | |
15 | endif() | |
16 | 16 | |
17 | 17 | +add_definitions(-DSTB_VORBIS_HEADER_ONLY -D_DEFAULT_SOURCE) |
18 | 18 | +target_link_libraries(FAudio PRIVATE stb) |
2 | 2 | |
3 | 3 | --- a/CMakeLists.txt |
4 | 4 | +++ b/CMakeLists.txt |
5 | @@ -423,6 +423,7 @@ | |
5 | @@ -443,6 +443,7 @@ | |
6 | 6 | if(BUILD_TESTS) |
7 | 7 | add_executable(faudio_tests tests/xaudio2.c) |
8 | 8 | target_link_libraries(faudio_tests PRIVATE FAudio) |
310 | 310 | } FAudioDebugConfiguration; |
311 | 311 | |
312 | 312 | #pragma pack(pop) |
313 | ||
314 | /* This ISN'T packed. Strictly speaking it wouldn't have mattered anyway but eh. | |
315 | * See https://github.com/microsoft/DirectXTK/issues/256 | |
316 | */ | |
317 | typedef struct FAudioXMA2WaveFormatEx | |
318 | { | |
319 | FAudioWaveFormatEx wfx; | |
320 | uint16_t wNumStreams; | |
321 | uint32_t dwChannelMask; | |
322 | uint32_t dwSamplesEncoded; | |
323 | uint32_t dwBytesPerBlock; | |
324 | uint32_t dwPlayBegin; | |
325 | uint32_t dwPlayLength; | |
326 | uint32_t dwLoopBegin; | |
327 | uint32_t dwLoopLength; | |
328 | uint8_t bLoopCount; | |
329 | uint8_t bEncoderVersion; | |
330 | uint16_t wBlockCount; | |
331 | } FAudioXMA2WaveFormat; | |
313 | 332 | |
314 | 333 | /* Constants */ |
315 | 334 | |
465 | 484 | |
466 | 485 | #define FAUDIO_ABI_VERSION 0 |
467 | 486 | #define FAUDIO_MAJOR_VERSION 21 |
468 | #define FAUDIO_MINOR_VERSION 2 | |
487 | #define FAUDIO_MINOR_VERSION 7 | |
469 | 488 | #define FAUDIO_PATCH_VERSION 0 |
470 | 489 | |
471 | 490 | #define FAUDIO_COMPILED_VERSION ( \ |
88 | 88 | FAudio_PlatformDestroyMutex(pEngine->wbLock); |
89 | 89 | FAudio_PlatformUnlockMutex(pEngine->apiLock); |
90 | 90 | FAudio_PlatformDestroyMutex(pEngine->apiLock); |
91 | if (pEngine->settings != NULL) | |
92 | { | |
93 | pEngine->pFree(pEngine->settings); | |
94 | } | |
91 | 95 | pEngine->pFree(pEngine); |
92 | 96 | FAudio_PlatformRelease(); |
93 | 97 | return 0; |
1635 | 1639 | ((entry->Format.wBlockAlign + 16) * 2) |
1636 | 1640 | ); |
1637 | 1641 | } |
1642 | else | |
1643 | { | |
1644 | FAudio_assert(0 && "Unrecognized wFormatTag!"); | |
1645 | } | |
1638 | 1646 | |
1639 | 1647 | pWaveProperties->loopRegion = entry->LoopRegion; |
1640 | 1648 | pWaveProperties->streaming = pWaveBank->streaming; |
1655 | 1663 | FAudioBufferWMA bufferWMA; |
1656 | 1664 | FAudioVoiceSends sends; |
1657 | 1665 | FAudioSendDescriptor send; |
1658 | FAudioADPCMWaveFormat format; | |
1666 | union | |
1667 | { | |
1668 | FAudioWaveFormatEx pcm; | |
1669 | FAudioADPCMWaveFormat adpcm; | |
1670 | FAudioXMA2WaveFormat xma2; | |
1671 | } format; | |
1659 | 1672 | FACTWaveBankEntry *entry; |
1673 | FACTSeekTable *seek; | |
1660 | 1674 | if (pWaveBank == NULL) |
1661 | 1675 | { |
1662 | 1676 | *ppWave = NULL; |
1701 | 1715 | send.pOutputVoice = pWaveBank->parentEngine->master; |
1702 | 1716 | sends.SendCount = 1; |
1703 | 1717 | sends.pSends = &send; |
1704 | format.wfx.nChannels = entry->Format.nChannels; | |
1705 | format.wfx.nSamplesPerSec = entry->Format.nSamplesPerSec; | |
1718 | format.pcm.nChannels = entry->Format.nChannels; | |
1719 | format.pcm.nSamplesPerSec = entry->Format.nSamplesPerSec; | |
1706 | 1720 | if (entry->Format.wFormatTag == 0x0) |
1707 | 1721 | { |
1708 | format.wfx.wFormatTag = FAUDIO_FORMAT_PCM; | |
1709 | format.wfx.wBitsPerSample = 8 << entry->Format.wBitsPerSample; | |
1710 | format.wfx.nBlockAlign = format.wfx.nChannels * format.wfx.wBitsPerSample / 8; | |
1711 | format.wfx.nAvgBytesPerSec = format.wfx.nBlockAlign * format.wfx.nSamplesPerSec; | |
1712 | format.wfx.cbSize = 0; | |
1722 | format.pcm.wFormatTag = FAUDIO_FORMAT_PCM; | |
1723 | format.pcm.wBitsPerSample = 8 << entry->Format.wBitsPerSample; | |
1724 | format.pcm.nBlockAlign = format.pcm.nChannels * format.pcm.wBitsPerSample / 8; | |
1725 | format.pcm.nAvgBytesPerSec = format.pcm.nBlockAlign * format.pcm.nSamplesPerSec; | |
1726 | format.pcm.cbSize = 0; | |
1713 | 1727 | } |
1714 | 1728 | else if (entry->Format.wFormatTag == 0x1) |
1715 | 1729 | { |
1716 | /* XMA2 is quite similar to WMA Pro. */ | |
1730 | /* XMA2 is quite similar to WMA Pro... is what everyone thought. | |
1731 | * What a great way to start this comment. | |
1732 | * | |
1733 | * Let's reconstruct the extra data because who knows what decoder we're dealing with in <present year>. | |
1734 | * It's also a good exercise in understanding XMA2 metadata and feeding blocks into the decoder properly. | |
1735 | * At the time of writing this patch, it's FFmpeg via gstreamer which doesn't even respect most of this. | |
1736 | * ... which means: good luck to whoever ends up finding inaccuracies here in the future! | |
1737 | * | |
1738 | * dwLoopLength seems to match dwPlayLength in everything I've seen that had bLoopCount == 0. | |
1739 | * dwLoopBegin can be > 0 even with bLoopCount == 0 because why not. Let's ignore that. | |
1740 | * | |
1741 | * dwSamplesEncoded is usually close to dwPlayLength but not always (if ever?) equal. Let's assume equality. | |
1742 | * The XMA2 seek table uses sample indices as opposed to WMA's byte index seek table. | |
1743 | * | |
1744 | * nBlockAlign uses aWMABlockAlign given the entire WMA Pro thing BUT it's expected to be the block size for decoding. | |
1745 | * The XMA2 block size MUST be a multiple of 2048 BUT entry->PlayRegion.dwLength / seek->entryCount doesn't respect that. | |
1746 | * And even when correctly guesstimating the block size, we sometimes end up with block sizes >= 64k BYTES. nBlockAlign IS 16-BIT! | |
1747 | * Scrap nBlockAlign. I've given up and made all FAudio gstreamer functions use dwBytesPerBlock if available. | |
1748 | * Still though, if we don't want FAudio_INTERNAL_DecodeGSTREAMER to hang, the total data length must match (see SoundEffect.cs in FNA). | |
1749 | * As such, we round up when guessing the block size, feed GStreamer with zeroes^Wundersized blocks and hope for the best. | |
1750 | * | |
1751 | * This is FUN. | |
1752 | * -ade | |
1753 | */ | |
1717 | 1754 | FAudio_assert(entry->Format.wBitsPerSample != 0); |
1718 | 1755 | |
1719 | format.wfx.wFormatTag = FAUDIO_FORMAT_XMAUDIO2; | |
1720 | format.wfx.nAvgBytesPerSec = aWMAAvgBytesPerSec[entry->Format.wBlockAlign >> 5]; | |
1721 | format.wfx.nBlockAlign = aWMABlockAlign[entry->Format.wBlockAlign & 0x1F]; | |
1722 | format.wfx.wBitsPerSample = 16; | |
1723 | format.wfx.cbSize = 0; | |
1756 | seek = &pWaveBank->seekTables[nWaveIndex]; | |
1757 | format.pcm.wFormatTag = FAUDIO_FORMAT_XMAUDIO2; | |
1758 | format.pcm.wBitsPerSample = 16; | |
1759 | format.pcm.nAvgBytesPerSec = aWMAAvgBytesPerSec[entry->Format.wBlockAlign >> 5]; | |
1760 | format.pcm.nBlockAlign = aWMABlockAlign[entry->Format.wBlockAlign & 0x1F]; | |
1761 | format.pcm.cbSize = ( | |
1762 | sizeof(FAudioXMA2WaveFormat) - | |
1763 | sizeof(FAudioWaveFormatEx) | |
1764 | ); | |
1765 | format.xma2.wNumStreams = (format.pcm.nChannels + 1) / 2; | |
1766 | format.xma2.dwChannelMask = format.pcm.nChannels > 1 ? 0xFFFFFFFF >> (32 - format.pcm.nChannels) : 0; | |
1767 | format.xma2.dwSamplesEncoded = seek->entries[seek->entryCount - 1]; | |
1768 | format.xma2.dwBytesPerBlock = (uint16_t) FAudio_ceil( | |
1769 | (double) entry->PlayRegion.dwLength / | |
1770 | (double) seek->entryCount / | |
1771 | 2048.0 | |
1772 | ) * 2048; | |
1773 | format.xma2.dwPlayBegin = format.xma2.dwLoopBegin = 0; | |
1774 | format.xma2.dwPlayLength = format.xma2.dwLoopLength = format.xma2.dwSamplesEncoded; | |
1775 | format.xma2.bLoopCount = 0; | |
1776 | format.xma2.bEncoderVersion = 4; | |
1777 | format.xma2.wBlockCount = seek->entryCount; | |
1724 | 1778 | } |
1725 | 1779 | else if (entry->Format.wFormatTag == 0x2) |
1726 | 1780 | { |
1727 | format.wfx.wFormatTag = FAUDIO_FORMAT_MSADPCM; | |
1728 | format.wfx.nBlockAlign = (entry->Format.wBlockAlign + 22) * format.wfx.nChannels; | |
1729 | format.wfx.wBitsPerSample = 16; | |
1730 | format.wfx.cbSize = ( | |
1781 | format.pcm.wFormatTag = FAUDIO_FORMAT_MSADPCM; | |
1782 | format.pcm.nBlockAlign = (entry->Format.wBlockAlign + 22) * format.pcm.nChannels; | |
1783 | format.pcm.wBitsPerSample = 16; | |
1784 | format.pcm.cbSize = ( | |
1731 | 1785 | sizeof(FAudioADPCMWaveFormat) - |
1732 | 1786 | sizeof(FAudioWaveFormatEx) |
1733 | 1787 | ); |
1734 | format.wSamplesPerBlock = ( | |
1735 | ((format.wfx.nBlockAlign / format.wfx.nChannels) - 6) * 2 | |
1788 | format.adpcm.wSamplesPerBlock = ( | |
1789 | ((format.pcm.nBlockAlign / format.pcm.nChannels) - 6) * 2 | |
1736 | 1790 | ); |
1737 | 1791 | } |
1738 | 1792 | else if (entry->Format.wFormatTag == 0x3) |
1740 | 1794 | /* Apparently this is used to detect WMA Pro...? */ |
1741 | 1795 | FAudio_assert(entry->Format.wBitsPerSample == 0); |
1742 | 1796 | |
1743 | format.wfx.wFormatTag = FAUDIO_FORMAT_WMAUDIO2; | |
1744 | format.wfx.nAvgBytesPerSec = aWMAAvgBytesPerSec[entry->Format.wBlockAlign >> 5]; | |
1745 | format.wfx.nBlockAlign = aWMABlockAlign[entry->Format.wBlockAlign & 0x1F]; | |
1746 | format.wfx.wBitsPerSample = 16; | |
1747 | format.wfx.cbSize = 0; | |
1797 | format.pcm.wFormatTag = FAUDIO_FORMAT_WMAUDIO2; | |
1798 | format.pcm.nAvgBytesPerSec = aWMAAvgBytesPerSec[entry->Format.wBlockAlign >> 5]; | |
1799 | format.pcm.nBlockAlign = aWMABlockAlign[entry->Format.wBlockAlign & 0x1F]; | |
1800 | format.pcm.wBitsPerSample = 16; | |
1801 | format.pcm.cbSize = 0; | |
1748 | 1802 | } |
1749 | 1803 | else |
1750 | 1804 | { |
1760 | 1814 | (*ppWave)->callback.callback.OnVoiceProcessingPassEnd = NULL; |
1761 | 1815 | (*ppWave)->callback.callback.OnVoiceProcessingPassStart = NULL; |
1762 | 1816 | (*ppWave)->callback.wave = *ppWave; |
1763 | (*ppWave)->srcChannels = format.wfx.nChannels; | |
1817 | (*ppWave)->srcChannels = format.pcm.nChannels; | |
1764 | 1818 | FAudio_CreateSourceVoice( |
1765 | 1819 | pWaveBank->parentEngine->audio, |
1766 | 1820 | &(*ppWave)->voice, |
1767 | &format.wfx, | |
1821 | &format.pcm, | |
1768 | 1822 | FAUDIO_VOICE_USEFILTER, /* FIXME: Can this be optional? */ |
1769 | 1823 | 4.0f, |
1770 | 1824 | (FAudioVoiceCallback*) &(*ppWave)->callback, |
1774 | 1828 | if (pWaveBank->streaming) |
1775 | 1829 | { |
1776 | 1830 | /* Init stream cache info */ |
1777 | if (format.wfx.wFormatTag == FAUDIO_FORMAT_PCM) | |
1831 | if (format.pcm.wFormatTag == FAUDIO_FORMAT_PCM) | |
1778 | 1832 | { |
1779 | 1833 | (*ppWave)->streamSize = ( |
1780 | format.wfx.nSamplesPerSec * | |
1781 | format.wfx.nBlockAlign | |
1834 | format.pcm.nSamplesPerSec * | |
1835 | format.pcm.nBlockAlign | |
1782 | 1836 | ); |
1783 | 1837 | } |
1784 | else if (format.wfx.wFormatTag == FAUDIO_FORMAT_MSADPCM) | |
1838 | else if (format.pcm.wFormatTag == FAUDIO_FORMAT_MSADPCM) | |
1785 | 1839 | { |
1786 | 1840 | (*ppWave)->streamSize = ( |
1787 | format.wfx.nSamplesPerSec / | |
1788 | format.wSamplesPerBlock * | |
1789 | format.wfx.nBlockAlign | |
1841 | format.pcm.nSamplesPerSec / | |
1842 | format.adpcm.wSamplesPerBlock * | |
1843 | format.pcm.nBlockAlign | |
1790 | 1844 | ); |
1791 | 1845 | } |
1792 | 1846 | else |
1796 | 1850 | |
1797 | 1851 | /* XACT does NOT support loop subregions for these formats */ |
1798 | 1852 | FAudio_assert(entry->LoopRegion.dwStartSample == 0); |
1799 | FAudio_assert(entry->LoopRegion.dwTotalSamples == entry->Duration); | |
1853 | FAudio_assert(entry->LoopRegion.dwTotalSamples == 0 || entry->LoopRegion.dwTotalSamples == entry->Duration); | |
1800 | 1854 | } |
1801 | 1855 | (*ppWave)->streamCache = (uint8_t*) pWaveBank->parentEngine->pMalloc( |
1802 | 1856 | (*ppWave)->streamSize |
1831 | 1885 | buffer.LoopCount = nLoopCount; |
1832 | 1886 | } |
1833 | 1887 | buffer.pContext = NULL; |
1834 | if ( format.wfx.wFormatTag == FAUDIO_FORMAT_WMAUDIO2 || | |
1835 | format.wfx.wFormatTag == FAUDIO_FORMAT_XMAUDIO2 ) | |
1888 | if (format.pcm.wFormatTag == FAUDIO_FORMAT_WMAUDIO2) | |
1836 | 1889 | { |
1837 | 1890 | bufferWMA.pDecodedPacketCumulativeBytes = |
1838 | 1891 | pWaveBank->seekTables[nWaveIndex].entries; |
2315 | 2368 | FACT_STATE_STOPPING | |
2316 | 2369 | FACT_STATE_PAUSED |
2317 | 2370 | ); |
2371 | ||
2372 | FACT_INTERNAL_SendCueNotification(pCue, NOTIFY_CUESTOP, FACTNOTIFICATIONTYPE_CUESTOP); | |
2373 | ||
2318 | 2374 | FAudio_PlatformUnlockMutex( |
2319 | 2375 | pCue->parentBank->parentEngine->apiLock |
2320 | 2376 | ); |
2413 | 2469 | FACT_STATE_STOPPED | |
2414 | 2470 | FACT_STATE_PREPARED |
2415 | 2471 | ); |
2472 | ||
2473 | FACT_INTERNAL_SendCueNotification(pCue, NOTIFY_CUEPLAY, FACTNOTIFICATIONTYPE_CUEPLAY); | |
2474 | ||
2416 | 2475 | pCue->start = FAudio_timems(); |
2417 | 2476 | |
2418 | 2477 | /* If it's a simple wave, just play it! */ |
2511 | 2570 | pCue->maxRpcReleaseTime |
2512 | 2571 | ); |
2513 | 2572 | } |
2514 | ||
2515 | pCue->state |= FACT_STATE_STOPPING; | |
2573 | else | |
2574 | { | |
2575 | /* Pretty sure this doesn't happen, but just in case? */ | |
2576 | pCue->state |= FACT_STATE_STOPPING; | |
2577 | } | |
2516 | 2578 | } |
2517 | 2579 | |
2518 | 2580 | FAudio_PlatformUnlockMutex(pCue->parentBank->parentEngine->apiLock); |
37 | 37 | |
38 | 38 | #define FACT_CONTENT_VERSION_3_4 45 |
39 | 39 | #define FACT_CONTENT_VERSION_3_1 44 |
40 | #define FACT_CONTENT_VERSION_3_0 43 | |
40 | 41 | |
41 | 42 | static inline int FACT_INTERNAL_SupportedContent(uint16_t version) |
42 | 43 | { |
43 | 44 | return ( version == FACT_CONTENT_VERSION || |
44 | 45 | version == FACT_CONTENT_VERSION_3_4 || |
45 | version == FACT_CONTENT_VERSION_3_1 ); | |
46 | version == FACT_CONTENT_VERSION_3_1 || | |
47 | version == FACT_CONTENT_VERSION_3_0 ); | |
46 | 48 | } |
47 | 49 | |
48 | 50 | #define WAVEBANK_HEADER_VERSION 44 |
61 | 63 | static inline float FACT_INTERNAL_CalculateAmplitudeRatio(float decibel) |
62 | 64 | { |
63 | 65 | return (float) FAudio_pow(10.0, decibel / 2000.0); |
66 | } | |
67 | ||
68 | static inline float FACT_INTERNAL_CalculateFilterFrequency( | |
69 | float desiredFrequency, | |
70 | uint32_t sampleRate | |
71 | ) { | |
72 | /* This is needed to convert linear frequencies to the value | |
73 | * FAudio_INTERNAL_FilterVoice expects, in order for it to actually | |
74 | * filter at the correct frequency. | |
75 | * | |
76 | * The formula is... | |
77 | * | |
78 | * (2 * sin(pi * (desired filter cutoff frequency) / sampleRate)) | |
79 | * | |
80 | * ... but it behaves badly as the filter frequency gets too high as a | |
81 | * fraction of the sample rate, hence the mins. | |
82 | * | |
83 | * -@Woflox | |
84 | */ | |
85 | float freq = 2 * FAudio_sin( | |
86 | F3DAUDIO_PI * | |
87 | FAudio_min(desiredFrequency / sampleRate, 0.5f) | |
88 | ); | |
89 | return FAudio_min(freq, 1.0f); | |
64 | 90 | } |
65 | 91 | |
66 | 92 | static inline void FACT_INTERNAL_ReadFile( |
387 | 413 | { |
388 | 414 | const float rngQFactor = 1.0f / ( |
389 | 415 | FACT_INTERNAL_rng() * |
390 | (evt->wave.maxQFactor - evt->wave.minQFactor) | |
416 | (evt->wave.maxQFactor - evt->wave.minQFactor) + | |
417 | evt->wave.minQFactor | |
391 | 418 | ); |
392 | const float rngFrequency = ( | |
393 | FACT_INTERNAL_rng() * | |
394 | (evt->wave.maxFrequency - evt->wave.minFrequency) | |
395 | ) / 20000.0f; | |
419 | const float rngFrequency = FACT_INTERNAL_CalculateFilterFrequency( | |
420 | ( | |
421 | FACT_INTERNAL_rng() * | |
422 | (evt->wave.maxFrequency - evt->wave.minFrequency) + | |
423 | evt->wave.minFrequency | |
424 | ), | |
425 | cue->parentBank->parentEngine->audio->master->master.inputSampleRate | |
426 | ); | |
396 | 427 | if (trackInst->activeWave.wave != NULL) |
397 | 428 | { |
398 | 429 | /* Variation on Loop */ |
426 | 457 | } |
427 | 458 | else |
428 | 459 | { |
429 | trackInst->upcomingWave.baseQFactor = 1.0f / (float) track->qfactor; | |
430 | trackInst->upcomingWave.baseFrequency = track->frequency / 20000.0f; | |
460 | trackInst->upcomingWave.baseQFactor = 1.0f / (track->qfactor / 3.0f); | |
461 | trackInst->upcomingWave.baseFrequency = FACT_INTERNAL_CalculateFilterFrequency( | |
462 | track->frequency, | |
463 | cue->parentBank->parentEngine->audio->master->master.inputSampleRate | |
464 | ); | |
431 | 465 | } |
432 | 466 | |
433 | 467 | /* Try to change loop counter at the very end */ |
834 | 868 | return 1; |
835 | 869 | } |
836 | 870 | |
871 | void FACT_INTERNAL_SendCueNotification(FACTCue *cue, FACTNoticationsFlags flag, uint8_t type) | |
872 | { | |
873 | if (cue->parentBank->parentEngine->notifications & flag) | |
874 | { | |
875 | FACTNotification note; | |
876 | ||
877 | note.type = type; | |
878 | note.pvContext = cue->parentBank->parentEngine->cue_context; | |
879 | note.cue.cueIndex = cue->index; | |
880 | note.cue.pSoundBank = cue->parentBank; | |
881 | note.cue.pCue = cue; | |
882 | ||
883 | cue->parentBank->parentEngine->notificationCallback(¬e); | |
884 | } | |
885 | } | |
886 | ||
837 | 887 | void FACT_INTERNAL_DestroySound(FACTSoundInstance *sound) |
838 | 888 | { |
839 | 889 | uint8_t i; |
871 | 921 | sound->parentCue->state |= FACT_STATE_STOPPED; |
872 | 922 | sound->parentCue->state &= ~(FACT_STATE_PLAYING | FACT_STATE_STOPPING); |
873 | 923 | sound->parentCue->data->instanceCount -= 1; |
924 | ||
925 | FACT_INTERNAL_SendCueNotification(sound->parentCue, NOTIFY_CUESTOP, FACTNOTIFICATIONTYPE_CUESTOP); | |
874 | 926 | } |
875 | 927 | sound->parentCue->parentBank->parentEngine->pFree(sound); |
876 | 928 | } |
887 | 939 | sound->fadeType = 2; /* Out */ |
888 | 940 | sound->fadeStart = FAudio_timems(); |
889 | 941 | sound->fadeTarget = fadeOutMS; |
942 | ||
943 | sound->parentCue->state |= FACT_STATE_STOPPING; | |
890 | 944 | } |
891 | 945 | |
892 | 946 | void FACT_INTERNAL_BeginReleaseRPC(FACTSoundInstance *sound, uint16_t releaseMS) |
901 | 955 | sound->fadeType = 3; /* Release RPC */ |
902 | 956 | sound->fadeStart = FAudio_timems(); |
903 | 957 | sound->fadeTarget = releaseMS; |
958 | ||
959 | sound->parentCue->state |= FACT_STATE_STOPPING; | |
904 | 960 | } |
905 | 961 | |
906 | 962 | /* RPC Helper Functions */ |
949 | 1005 | result = rpc->points[i].y; |
950 | 1006 | if (var >= rpc->points[i].x && var <= rpc->points[i + 1].x) |
951 | 1007 | { |
952 | /* TODO: Non-linear curves! Check rpc->points[i].type! | |
953 | * 0 - Linear | |
954 | * 1 - "Fast", a logarithmic curve? | |
955 | * 2 - "Slow", an exponential curve? | |
956 | * 3 - "SinCos", looks like log if higher/exp if lower? | |
957 | * flibit absolutely does not know math, he is useless! | |
958 | */ | |
959 | ||
960 | /* y += mx */ | |
961 | result += | |
962 | ((rpc->points[i + 1].y - rpc->points[i].y) / | |
963 | (rpc->points[i + 1].x - rpc->points[i].x)) * | |
964 | (var - rpc->points[i].x); | |
965 | ||
966 | /* Pre-algebra, rockin'! */ | |
1008 | const float maxX = rpc->points[i + 1].x - rpc->points[i].x; | |
1009 | const float maxY = rpc->points[i + 1].y - rpc->points[i].y; | |
1010 | const float deltaX = (var - rpc->points[i].x); | |
1011 | const float deltaXNormalized = deltaX / maxX; | |
1012 | ||
1013 | if (rpc->points[i].type == 0) /* Linear */ | |
1014 | { | |
1015 | result += maxY * deltaXNormalized; | |
1016 | } | |
1017 | else if (rpc->points[i].type == 1) /* Fast */ | |
1018 | { | |
1019 | result += maxY * (1.0f - FAudio_pow(1.0f - FAudio_pow(deltaXNormalized, 1.0f / 1.5f), 1.5f)); | |
1020 | } | |
1021 | else if (rpc->points[i].type == 2) /* Slow */ | |
1022 | { | |
1023 | result += maxY * (1.0f - FAudio_pow(1.0f - FAudio_pow(deltaXNormalized, 1.5f), 1.0f / 1.5f)); | |
1024 | } | |
1025 | else if (rpc->points[i].type == 3) /* SinCos */ | |
1026 | { | |
1027 | if (maxY > 0.0f) | |
1028 | { | |
1029 | result += maxY * (1.0f - FAudio_pow(1.0f - FAudio_sqrtf(deltaXNormalized), 2.0f)); | |
1030 | } | |
1031 | else | |
1032 | { | |
1033 | result += maxY * (1.0f - FAudio_sqrtf(1.0f - FAudio_pow(deltaXNormalized, 2.0f))); | |
1034 | } | |
1035 | } | |
1036 | else | |
1037 | { | |
1038 | FAudio_assert(0 && "Unrecognized curve type!"); | |
1039 | } | |
1040 | ||
967 | 1041 | break; |
968 | 1042 | } |
969 | 1043 | } |
986 | 1060 | |
987 | 1061 | if (codeCount > 0) |
988 | 1062 | { |
989 | /* Do NOT overwrite Frequency! */ | |
1063 | /* Do NOT overwrite Frequency/QFactor! */ | |
990 | 1064 | data->rpcVolume = 0.0f; |
991 | 1065 | data->rpcPitch = 0.0f; |
992 | 1066 | data->rpcReverbSend = 0.0f; |
993 | data->rpcFilterQFactor = FAUDIO_DEFAULT_FILTER_ONEOVERQ; | |
994 | 1067 | for (i = 0; i < codeCount; i += 1) |
995 | 1068 | { |
996 | 1069 | rpc = FACT_INTERNAL_GetRPC( |
1050 | 1123 | else if (rpc->parameter == RPC_PARAMETER_FILTERFREQUENCY) |
1051 | 1124 | { |
1052 | 1125 | /* Yes, just overwrite... */ |
1053 | data->rpcFilterFreq = rpcResult / 20000.0f; | |
1126 | data->rpcFilterFreq = FACT_INTERNAL_CalculateFilterFrequency( | |
1127 | rpcResult, | |
1128 | engine->audio->master->master.inputSampleRate | |
1129 | ); | |
1054 | 1130 | } |
1055 | 1131 | else if (rpc->parameter == RPC_PARAMETER_FILTERQFACTOR) |
1056 | 1132 | { |
1057 | /* TODO: How do we combine these? */ | |
1058 | data->rpcFilterQFactor += 1.0f / rpcResult; | |
1133 | /* Yes, just overwrite... */ | |
1134 | data->rpcFilterQFactor = 1.0f / rpcResult; | |
1059 | 1135 | } |
1060 | 1136 | else |
1061 | 1137 | { |
1215 | 1291 | sound->parentCue->maxRpcReleaseTime |
1216 | 1292 | ); |
1217 | 1293 | } |
1218 | ||
1219 | sound->parentCue->state |= FACT_STATE_STOPPING; | |
1294 | else | |
1295 | { | |
1296 | /* Pretty sure this doesn't happen, but just in case? */ | |
1297 | sound->parentCue->state |= FACT_STATE_STOPPING; | |
1298 | } | |
1220 | 1299 | } |
1221 | 1300 | } |
1222 | 1301 | |
1429 | 1508 | |
1430 | 1509 | /* RPC updates */ |
1431 | 1510 | sound->rpcData.rpcFilterFreq = -1.0f; |
1511 | sound->rpcData.rpcFilterQFactor = -1.0f; | |
1432 | 1512 | FACT_INTERNAL_UpdateRPCs( |
1433 | 1513 | sound->parentCue, |
1434 | 1514 | sound->sound->rpcCodeCount, |
1440 | 1520 | for (i = 0; i < sound->sound->trackCount; i += 1) |
1441 | 1521 | { |
1442 | 1522 | sound->tracks[i].rpcData.rpcFilterFreq = sound->rpcData.rpcFilterFreq; |
1523 | sound->tracks[i].rpcData.rpcFilterQFactor = sound->rpcData.rpcFilterQFactor; | |
1443 | 1524 | FACT_INTERNAL_UpdateRPCs( |
1444 | 1525 | sound->parentCue, |
1445 | 1526 | sound->sound->tracks[i].rpcCodeCount, |
1541 | 1622 | { |
1542 | 1623 | filterParams.Frequency = sound->tracks[i].activeWave.baseFrequency; |
1543 | 1624 | } |
1544 | filterParams.OneOverQ = ( | |
1545 | sound->tracks[i].activeWave.baseQFactor + | |
1546 | sound->rpcData.rpcFilterQFactor + | |
1547 | sound->tracks[i].rpcData.rpcFilterQFactor | |
1548 | ) / 3.0f; /* FIXME: How do we combine QFactor params? */ | |
1625 | if (sound->tracks[i].rpcData.rpcFilterQFactor >= 0.0f) | |
1626 | { | |
1627 | filterParams.OneOverQ = sound->tracks[i].rpcData.rpcFilterQFactor; | |
1628 | } | |
1629 | else | |
1630 | { | |
1631 | filterParams.OneOverQ = sound->tracks[i].activeWave.baseQFactor; | |
1632 | } | |
1549 | 1633 | FAudioVoice_SetFilterParameters( |
1550 | 1634 | sound->tracks[i].activeWave.wave->voice, |
1551 | 1635 | &filterParams, |
1838 | 1922 | buffer.pContext = NULL; |
1839 | 1923 | |
1840 | 1924 | /* Submit, finally. */ |
1841 | if ( entry->Format.wFormatTag == 0x1 || | |
1842 | entry->Format.wFormatTag == 0x3 ) | |
1925 | if (entry->Format.wFormatTag == 0x3) | |
1843 | 1926 | { |
1844 | 1927 | bufferWMA.pDecodedPacketCumulativeBytes = |
1845 | 1928 | c->wave->parentBank->seekTables[c->wave->index].entries; |
2130 | 2213 | { |
2131 | 2214 | pEngine->dspPresetCodes[i] = (uint32_t) (ptr - start); |
2132 | 2215 | pEngine->dspPresets[i].accessibility = read_u8(&ptr); |
2133 | pEngine->dspPresets[i].parameterCount = read_u32(&ptr, se); | |
2216 | pEngine->dspPresets[i].parameterCount = read_u16(&ptr, se); | |
2217 | ptr += 2; /* Unknown value */ | |
2134 | 2218 | pEngine->dspPresets[i].parameters = (FACTDSPParameter*) pEngine->pMalloc( |
2135 | 2219 | sizeof(FACTDSPParameter) * |
2136 | 2220 | pEngine->dspPresets[i].parameterCount |
2202 | 2286 | pEngine->sb_context = NULL; |
2203 | 2287 | pEngine->wb_context = NULL; |
2204 | 2288 | pEngine->wave_context = NULL; |
2289 | ||
2290 | /* Store this pointer in case we're asked to free it */ | |
2291 | if (pParams->globalSettingsFlags & FACT_FLAG_MANAGEDATA) | |
2292 | { | |
2293 | pEngine->settings = pParams->pGlobalSettingsBuffer; | |
2294 | } | |
2205 | 2295 | |
2206 | 2296 | /* Finally. */ |
2207 | 2297 | FAudio_assert((ptr - start) == pParams->globalSettingsBufferSize); |
2265 | 2355 | track->events[i].wave.angle = read_u16(ptr, se); |
2266 | 2356 | |
2267 | 2357 | /* Track Variation */ |
2268 | track->events[i].wave.complex.trackCount = read_u16(ptr, se); | |
2269 | track->events[i].wave.complex.variation = read_u16(ptr, se); | |
2358 | evtInfo = read_u32(ptr, se); | |
2359 | track->events[i].wave.complex.trackCount = evtInfo & 0xFFFF; | |
2360 | track->events[i].wave.complex.variation = (evtInfo >> 16) & 0xFFFF; | |
2270 | 2361 | *ptr += 4; /* Unknown values */ |
2271 | 2362 | track->events[i].wave.complex.tracks = (uint16_t*) pMalloc( |
2272 | 2363 | sizeof(uint16_t) * |
2337 | 2428 | track->events[i].wave.variationFlags = read_u16(ptr, se); |
2338 | 2429 | |
2339 | 2430 | /* Track Variation */ |
2340 | track->events[i].wave.complex.trackCount = read_u16(ptr, se); | |
2341 | track->events[i].wave.complex.variation = read_u16(ptr, se); | |
2431 | evtInfo = read_u32(ptr, se); | |
2432 | track->events[i].wave.complex.trackCount = evtInfo & 0xFFFF; | |
2433 | track->events[i].wave.complex.variation = (evtInfo >> 16) & 0xFFFF; | |
2342 | 2434 | *ptr += 4; /* Unknown values */ |
2343 | 2435 | track->events[i].wave.complex.tracks = (uint16_t*) pMalloc( |
2344 | 2436 | sizeof(uint16_t) * |
2428 | 2520 | FACTSoundBank **ppSoundBank |
2429 | 2521 | ) { |
2430 | 2522 | FACTSoundBank *sb; |
2431 | uint16_t cueSimpleCount, | |
2523 | uint16_t contentVersion, | |
2524 | cueSimpleCount, | |
2432 | 2525 | cueComplexCount, |
2433 | 2526 | cueTotalAlign; |
2434 | 2527 | int32_t cueSimpleOffset, |
2440 | 2533 | cueHashOffset, |
2441 | 2534 | cueNameIndexOffset, |
2442 | 2535 | soundOffset; |
2536 | uint32_t entryCountAndFlags; | |
2537 | uint16_t filterData; | |
2443 | 2538 | uint8_t platform; |
2444 | 2539 | size_t memsize; |
2445 | uint16_t i, j, cur, tool; | |
2540 | uint16_t i, j, k, cur, tool; | |
2446 | 2541 | uint8_t *ptrBookmark; |
2447 | 2542 | |
2448 | 2543 | uint8_t *ptr = (uint8_t*) pvBuffer; |
2456 | 2551 | return -1; /* TODO: NOT XACT FILE */ |
2457 | 2552 | } |
2458 | 2553 | |
2459 | if (!FACT_INTERNAL_SupportedContent(read_u16(&ptr, se))) | |
2554 | contentVersion = read_u16(&ptr, se); | |
2555 | if (!FACT_INTERNAL_SupportedContent(contentVersion)) | |
2460 | 2556 | { |
2461 | 2557 | return -2; |
2462 | 2558 | } |
2599 | 2695 | loc.rpcCodeCount = read_u8(&ptr); \ |
2600 | 2696 | memsize = sizeof(uint32_t) * loc.rpcCodeCount; \ |
2601 | 2697 | loc.rpcCodes = (uint32_t*) pEngine->pMalloc(memsize); \ |
2602 | FAudio_memcpy(loc.rpcCodes, ptr, memsize); \ | |
2603 | ptr += memsize; | |
2698 | for (k = 0; k < loc.rpcCodeCount; k += 1) \ | |
2699 | { \ | |
2700 | loc.rpcCodes[k] = read_u32(&ptr, se); \ | |
2701 | } \ | |
2604 | 2702 | |
2605 | 2703 | /* Sound has attached RPCs */ |
2606 | 2704 | if (sb->sounds[i].flags & 0x02) |
2655 | 2753 | sb->sounds[i].dspCodeCount = read_u8(&ptr); |
2656 | 2754 | memsize = sizeof(uint32_t) * sb->sounds[i].dspCodeCount; |
2657 | 2755 | sb->sounds[i].dspCodes = (uint32_t*) pEngine->pMalloc(memsize); |
2658 | FAudio_memcpy(sb->sounds[i].dspCodes, ptr, memsize); | |
2659 | ptr += memsize; | |
2756 | for (j = 0; j < sb->sounds[i].dspCodeCount; j += 1) | |
2757 | { | |
2758 | sb->sounds[i].dspCodes[j] = read_u32(&ptr, se); | |
2759 | } | |
2660 | 2760 | } |
2661 | 2761 | else |
2662 | 2762 | { |
2673 | 2773 | |
2674 | 2774 | sb->sounds[i].tracks[j].code = read_u32(&ptr, se); |
2675 | 2775 | |
2676 | sb->sounds[i].tracks[j].filter = read_u8(&ptr); | |
2677 | if (sb->sounds[i].tracks[j].filter & 0x01) | |
2776 | if (contentVersion == FACT_CONTENT_VERSION_3_0) | |
2777 | { | |
2778 | /* 3.0 doesn't have track filter data */ | |
2779 | sb->sounds[i].tracks[j].filter = 0xFF; | |
2780 | sb->sounds[i].tracks[j].qfactor = 0; | |
2781 | sb->sounds[i].tracks[j].frequency = 0; | |
2782 | continue; | |
2783 | } | |
2784 | ||
2785 | filterData = read_u16(&ptr, se); | |
2786 | if (filterData & 0x0001) | |
2678 | 2787 | { |
2679 | 2788 | sb->sounds[i].tracks[j].filter = |
2680 | (sb->sounds[i].tracks[j].filter >> 1) & 0x02; | |
2789 | (filterData >> 1) & 0x02; | |
2681 | 2790 | } |
2682 | 2791 | else |
2683 | 2792 | { |
2684 | 2793 | /* Huh...? */ |
2685 | 2794 | sb->sounds[i].tracks[j].filter = 0xFF; |
2686 | 2795 | } |
2687 | ||
2688 | sb->sounds[i].tracks[j].qfactor = read_u8(&ptr); | |
2796 | sb->sounds[i].tracks[j].qfactor = (filterData >> 8) & 0xFF; | |
2689 | 2797 | sb->sounds[i].tracks[j].frequency = read_u16(&ptr, se); |
2690 | 2798 | } |
2691 | 2799 | |
2777 | 2885 | for (i = 0; i < sb->variationCount; i += 1) |
2778 | 2886 | { |
2779 | 2887 | sb->variationCodes[i] = (uint32_t) (ptr - start); |
2780 | sb->variations[i].entryCount = read_u16(&ptr, se); | |
2781 | sb->variations[i].flags = (read_u16(&ptr, se) >> 3) & 0x07; | |
2888 | entryCountAndFlags = read_u32(&ptr, se); | |
2889 | sb->variations[i].entryCount = entryCountAndFlags & 0xFFFF; | |
2890 | sb->variations[i].flags = (entryCountAndFlags >> (16 + 3)) & 0x07; | |
2782 | 2891 | ptr += 2; /* Unknown value */ |
2783 | 2892 | sb->variations[i].variable = read_s16(&ptr, se); |
2784 | 2893 | memsize = sizeof(FACTVariation) * sb->variations[i].entryCount; |
2959 | 3068 | uint32_t fileOffset; |
2960 | 3069 | uint8_t *packetBuffer = NULL; |
2961 | 3070 | uint32_t packetBufferLen = 0; |
3071 | uint16_t *pcm; | |
2962 | 3072 | |
2963 | 3073 | #define SEEKSET(loc) \ |
2964 | 3074 | fileOffset = offset + loc; |
3116 | 3226 | } |
3117 | 3227 | wb->entries[i].PlayRegion.dwOffset += |
3118 | 3228 | header.Segments[FACT_WAVEBANK_SEGIDX_ENTRYWAVEDATA].dwOffset; |
3229 | ||
3230 | /* If it's in-memory big-endian PCM, swap! */ | |
3231 | if ( se && | |
3232 | !wb->streaming && | |
3233 | wb->entries[i].Format.wFormatTag == 0x0 && | |
3234 | wb->entries[i].Format.wBitsPerSample == 1 ) | |
3235 | { | |
3236 | pcm = (uint16_t*) FAudio_memptr( | |
3237 | (FAudioIOStream*) wb->io, | |
3238 | wb->entries[i].PlayRegion.dwOffset | |
3239 | ); | |
3240 | for (j = 0; j < wb->entries[i].PlayRegion.dwLength; j += 2, pcm += 1) | |
3241 | { | |
3242 | DOSWAP_16(*pcm); | |
3243 | } | |
3244 | } | |
3119 | 3245 | } |
3120 | 3246 | |
3121 | 3247 | /* FIXME: This is a bit hacky. */ |
88 | 88 | typedef struct FACTDSPPreset |
89 | 89 | { |
90 | 90 | uint8_t accessibility; |
91 | uint32_t parameterCount; | |
91 | uint16_t parameterCount; | |
92 | 92 | FACTDSPParameter *parameters; |
93 | 93 | } FACTDSPPreset; |
94 | 94 | |
439 | 439 | void *sb_context; |
440 | 440 | void *wb_context; |
441 | 441 | void *wave_context; |
442 | ||
443 | /* Settings handle */ | |
444 | void *settings; | |
442 | 445 | }; |
443 | 446 | |
444 | 447 | struct FACTSoundBank |
580 | 583 | void FACT_INTERNAL_DestroySound(FACTSoundInstance *sound); |
581 | 584 | void FACT_INTERNAL_BeginFadeOut(FACTSoundInstance *sound, uint16_t fadeOutMS); |
582 | 585 | void FACT_INTERNAL_BeginReleaseRPC(FACTSoundInstance *sound, uint16_t releaseMS); |
586 | ||
587 | void FACT_INTERNAL_SendCueNotification(FACTCue *cue, FACTNoticationsFlags flag, uint8_t type); | |
583 | 588 | |
584 | 589 | /* RPC Helper Functions */ |
585 | 590 |
292 | 292 | |
293 | 293 | if ( pSourceFormat->wFormatTag == FAUDIO_FORMAT_PCM || |
294 | 294 | pSourceFormat->wFormatTag == FAUDIO_FORMAT_IEEE_FLOAT || |
295 | pSourceFormat->wFormatTag == FAUDIO_FORMAT_XMAUDIO2 || | |
296 | 295 | pSourceFormat->wFormatTag == FAUDIO_FORMAT_WMAUDIO2 ) |
297 | 296 | { |
298 | 297 | FAudioWaveFormatExtensible *fmtex = (FAudioWaveFormatExtensible*) audio->pMalloc( |
316 | 315 | { |
317 | 316 | FAudio_memcpy(&fmtex->SubFormat, &DATAFORMAT_SUBTYPE_IEEE_FLOAT, sizeof(FAudioGUID)); |
318 | 317 | } |
319 | else if (pSourceFormat->wFormatTag == FAUDIO_FORMAT_XMAUDIO2) | |
320 | { | |
321 | FAudio_memcpy(&fmtex->SubFormat, &DATAFORMAT_SUBTYPE_XMAUDIO2, sizeof(FAudioGUID)); | |
322 | } | |
323 | 318 | else if (pSourceFormat->wFormatTag == FAUDIO_FORMAT_WMAUDIO2) |
324 | 319 | { |
325 | 320 | FAudio_memcpy(&fmtex->SubFormat, &DATAFORMAT_SUBTYPE_WMAUDIO2, sizeof(FAudioGUID)); |
352 | 347 | fmtex->wSamplesPerBlock = (( |
353 | 348 | fmtex->wfx.nBlockAlign / fmtex->wfx.nChannels |
354 | 349 | ) - 6) * 2; |
350 | (*ppSourceVoice)->src.format = &fmtex->wfx; | |
351 | } | |
352 | else if (pSourceFormat->wFormatTag == FAUDIO_FORMAT_XMAUDIO2) | |
353 | { | |
354 | FAudioXMA2WaveFormat *fmtex = (FAudioXMA2WaveFormat*) audio->pMalloc( | |
355 | sizeof(FAudioXMA2WaveFormat) | |
356 | ); | |
357 | ||
358 | /* Copy what we can, ideally the sizes match! */ | |
359 | size_t cbSize = sizeof(FAudioWaveFormatEx) + pSourceFormat->cbSize; | |
360 | FAudio_memcpy( | |
361 | fmtex, | |
362 | pSourceFormat, | |
363 | FAudio_min(cbSize, sizeof(FAudioXMA2WaveFormat)) | |
364 | ); | |
365 | if (cbSize < sizeof(FAudioXMA2WaveFormat)) | |
366 | { | |
367 | FAudio_zero( | |
368 | ((uint8_t*) fmtex) + cbSize, | |
369 | sizeof(FAudioADPCMWaveFormat) - cbSize | |
370 | ); | |
371 | } | |
372 | ||
373 | /* Does XAudio2 validate this input?! */ | |
374 | fmtex->wfx.cbSize = sizeof(FAudioXMA2WaveFormat) - sizeof(FAudioWaveFormatEx); | |
355 | 375 | (*ppSourceVoice)->src.format = &fmtex->wfx; |
356 | 376 | } |
357 | 377 | else |
428 | 448 | } |
429 | 449 | else if ( COMPARE_GUID(WMAUDIO2) || |
430 | 450 | COMPARE_GUID(WMAUDIO3) || |
431 | COMPARE_GUID(WMAUDIO_LOSSLESS) || | |
432 | COMPARE_GUID(XMAUDIO2) ) | |
433 | { | |
434 | #ifdef HAVE_GSTREAMER | |
435 | if (FAudio_GSTREAMER_init(*ppSourceVoice, fmtex->SubFormat.Data1) != 0) | |
451 | COMPARE_GUID(WMAUDIO_LOSSLESS) ) | |
452 | { | |
453 | #ifdef HAVE_WMADEC | |
454 | if (FAudio_WMADEC_init(*ppSourceVoice, fmtex->SubFormat.Data1) != 0) | |
436 | 455 | { |
437 | 456 | (*ppSourceVoice)->src.decode = FAudio_INTERNAL_DecodeWMAERROR; |
438 | 457 | } |
439 | 458 | #else |
440 | 459 | FAudio_assert(0 && "xWMA is not supported!"); |
441 | 460 | (*ppSourceVoice)->src.decode = FAudio_INTERNAL_DecodeWMAERROR; |
442 | #endif /* HAVE_GSTREAMER */ | |
461 | #endif /* HAVE_WMADEC */ | |
443 | 462 | } |
444 | 463 | else |
445 | 464 | { |
446 | 465 | FAudio_assert(0 && "Unsupported WAVEFORMATEXTENSIBLE subtype!"); |
447 | 466 | } |
448 | 467 | #undef COMPARE_GUID |
468 | } | |
469 | else if ((*ppSourceVoice)->src.format->wFormatTag == FAUDIO_FORMAT_XMAUDIO2) | |
470 | { | |
471 | #ifdef HAVE_WMADEC | |
472 | if (FAudio_WMADEC_init(*ppSourceVoice, FAUDIO_FORMAT_XMAUDIO2) != 0) | |
473 | { | |
474 | (*ppSourceVoice)->src.decode = FAudio_INTERNAL_DecodeWMAERROR; | |
475 | } | |
476 | #else | |
477 | FAudio_assert(0 && "XMA2 is not supported!"); | |
478 | (*ppSourceVoice)->src.decode = FAudio_INTERNAL_DecodeWMAERROR; | |
479 | #endif /* HAVE_WMADEC */ | |
449 | 480 | } |
450 | 481 | else if ((*ppSourceVoice)->src.format->wFormatTag == FAUDIO_FORMAT_MSADPCM) |
451 | 482 | { |
2207 | 2238 | voice->audio->pFree(voice->src.format); |
2208 | 2239 | LOG_MUTEX_DESTROY(voice->audio, voice->src.bufferLock) |
2209 | 2240 | FAudio_PlatformDestroyMutex(voice->src.bufferLock); |
2210 | #ifdef HAVE_GSTREAMER | |
2211 | if (voice->src.gstreamer) | |
2212 | { | |
2213 | FAudio_GSTREAMER_free(voice); | |
2214 | } | |
2215 | #endif /* HAVE_GSTREAMER */ | |
2241 | #ifdef HAVE_WMADEC | |
2242 | if (voice->src.wmadec) | |
2243 | { | |
2244 | FAudio_WMADEC_free(voice); | |
2245 | } | |
2246 | #endif /* HAVE_WMADEC */ | |
2216 | 2247 | } |
2217 | 2248 | else if (voice->type == FAUDIO_VOICE_SUBMIX) |
2218 | 2249 | { |
2420 | 2451 | ) |
2421 | 2452 | |
2422 | 2453 | FAudio_assert(voice->type == FAUDIO_VOICE_SOURCE); |
2423 | #ifdef HAVE_GSTREAMER | |
2424 | FAudio_assert( (voice->src.gstreamer != NULL && pBufferWMA != NULL) || | |
2425 | (voice->src.gstreamer == NULL && pBufferWMA == NULL) ); | |
2426 | #endif /* HAVE_GSTREAMER */ | |
2454 | #ifdef HAVE_WMADEC | |
2455 | FAudio_assert( (voice->src.wmadec != NULL && (pBufferWMA != NULL || voice->src.format->wFormatTag == FAUDIO_FORMAT_XMAUDIO2)) || | |
2456 | (voice->src.wmadec == NULL && (pBufferWMA == NULL && voice->src.format->wFormatTag != FAUDIO_FORMAT_XMAUDIO2)) ); | |
2457 | #endif /* HAVE_WMADEC */ | |
2427 | 2458 | |
2428 | 2459 | /* Start off with whatever they just sent us... */ |
2429 | 2460 | playBegin = pBuffer->PlayBegin; |
2450 | 2481 | fmtex->wSamplesPerBlock |
2451 | 2482 | ) - playBegin; |
2452 | 2483 | } |
2484 | else if (voice->src.format->wFormatTag == FAUDIO_FORMAT_XMAUDIO2) | |
2485 | { | |
2486 | FAudioXMA2WaveFormat *fmtex = (FAudioXMA2WaveFormat*) voice->src.format; | |
2487 | playLength = fmtex->dwSamplesEncoded - playBegin; | |
2488 | } | |
2453 | 2489 | else if (pBufferWMA != NULL) |
2454 | 2490 | { |
2455 | 2491 | playLength = ( |
2466 | 2502 | } |
2467 | 2503 | } |
2468 | 2504 | |
2469 | if (pBuffer->LoopCount > 0 && pBufferWMA == NULL) | |
2505 | if (pBuffer->LoopCount > 0 && pBufferWMA == NULL && voice->src.format->wFormatTag != FAUDIO_FORMAT_XMAUDIO2) | |
2470 | 2506 | { |
2471 | 2507 | /* "The value of LoopBegin must be less than PlayBegin + PlayLength" */ |
2472 | 2508 | if (loopBegin >= (playBegin + playLength)) |
2508 | 2544 | pBuffer->AudioBytes / voice->src.format->nBlockAlign |
2509 | 2545 | ) * voice->src.format->nBlockAlign; |
2510 | 2546 | } |
2511 | else if (pBufferWMA != NULL) | |
2547 | else if (pBufferWMA != NULL || voice->src.format->wFormatTag == FAUDIO_FORMAT_XMAUDIO2) | |
2512 | 2548 | { |
2513 | 2549 | /* WMA only supports looping the whole buffer */ |
2514 | 2550 | loopBegin = 0; |
32 | 32 | #include <gst/audio/audio.h> |
33 | 33 | #include <gst/app/gstappsink.h> |
34 | 34 | |
35 | typedef struct FAudioGSTREAMER | |
35 | typedef struct FAudioWMADEC | |
36 | 36 | { |
37 | 37 | GstPad *srcpad; |
38 | 38 | GstElement *pipeline; |
43 | 43 | size_t convertCacheLen, prevConvertCacheLen; |
44 | 44 | uint32_t curBlock, prevBlock; |
45 | 45 | size_t *blockSizes; |
46 | } FAudioGSTREAMER; | |
46 | uint32_t blockAlign; | |
47 | uint32_t blockCount; | |
48 | size_t maxBytes; | |
49 | } FAudioWMADEC; | |
47 | 50 | |
48 | 51 | #define SIZE_FROM_DST(sample) \ |
49 | 52 | ((sample) * voice->src.format->nChannels * sizeof(float)) |
60 | 63 | #define SIZE_DST_TO_SRC(len) \ |
61 | 64 | ((len) / (sizeof(float) / (voice->src.format->wBitsPerSample / 8))) |
62 | 65 | |
63 | static int FAudio_GSTREAMER_RestartDecoder(FAudioGSTREAMER *gstreamer) | |
66 | static int FAudio_GSTREAMER_RestartDecoder(FAudioWMADEC *wmadec) | |
64 | 67 | { |
65 | 68 | GstEvent *event; |
66 | 69 | |
67 | 70 | event = gst_event_new_flush_start(); |
68 | gst_pad_push_event(gstreamer->srcpad, event); | |
71 | gst_pad_push_event(wmadec->srcpad, event); | |
69 | 72 | |
70 | 73 | event = gst_event_new_flush_stop(TRUE); |
71 | gst_pad_push_event(gstreamer->srcpad, event); | |
72 | ||
73 | event = gst_event_new_segment(&gstreamer->segment); | |
74 | gst_pad_push_event(gstreamer->srcpad, event); | |
75 | ||
76 | gstreamer->curBlock = ~0u; | |
77 | gstreamer->prevBlock = ~0u; | |
78 | ||
79 | if (gst_element_set_state(gstreamer->pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) | |
74 | gst_pad_push_event(wmadec->srcpad, event); | |
75 | ||
76 | event = gst_event_new_segment(&wmadec->segment); | |
77 | gst_pad_push_event(wmadec->srcpad, event); | |
78 | ||
79 | wmadec->curBlock = ~0u; | |
80 | wmadec->prevBlock = ~0u; | |
81 | ||
82 | if (gst_element_set_state(wmadec->pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) | |
80 | 83 | { |
81 | 84 | return 0; |
82 | 85 | } |
92 | 95 | gchar *debug_info = NULL; |
93 | 96 | int ret = 0; |
94 | 97 | |
95 | bus = gst_pipeline_get_bus(GST_PIPELINE(voice->src.gstreamer->pipeline)); | |
98 | bus = gst_pipeline_get_bus(GST_PIPELINE(voice->src.wmadec->pipeline)); | |
96 | 99 | |
97 | 100 | while((msg = gst_bus_pop_filtered(bus, GST_MESSAGE_ERROR))) |
98 | 101 | { |
126 | 129 | static size_t FAudio_GSTREAMER_FillConvertCache( |
127 | 130 | FAudioVoice *voice, |
128 | 131 | FAudioBuffer *buffer, |
129 | size_t maxBytes | |
132 | size_t availableBytes | |
130 | 133 | ) { |
131 | 134 | GstBuffer *src, *dst; |
132 | 135 | GstSample *sample; |
138 | 141 | LOG_FUNC_ENTER(voice->audio) |
139 | 142 | |
140 | 143 | /* Write current block to input buffer, push to pipeline */ |
144 | ||
145 | clipStartBytes = ( | |
146 | (size_t) voice->src.wmadec->blockAlign * | |
147 | voice->src.wmadec->curBlock | |
148 | ); | |
149 | toCopyBytes = voice->src.wmadec->blockAlign; | |
150 | clipEndBytes = clipStartBytes + toCopyBytes; | |
151 | if (buffer->AudioBytes < clipEndBytes) | |
152 | { | |
153 | /* XMA2 assets can ship with undersized ends. | |
154 | * If your first instinct is to "pad with 0xFF, that's what game data trails with," | |
155 | * sorry to disappoint but it inserts extra silence in loops. | |
156 | * Instead, push an undersized buffer and pray that the decoder handles it properly. | |
157 | * At the time of writing, it's FFmpeg, and it's handling it well. | |
158 | * For everything else, uh, let's assume the same. If you're reading this: sorry. | |
159 | * -ade | |
160 | */ | |
161 | toCopyBytes = buffer->AudioBytes - clipStartBytes; | |
162 | } | |
163 | clipStartBytes += (size_t) buffer->pAudioData; | |
164 | ||
141 | 165 | src = gst_buffer_new_allocate( |
142 | 166 | NULL, |
143 | voice->src.format->nBlockAlign, | |
167 | toCopyBytes, | |
144 | 168 | NULL |
145 | 169 | ); |
146 | 170 | |
147 | 171 | if ( gst_buffer_fill( |
148 | 172 | src, |
149 | 173 | 0, |
150 | buffer->pAudioData + ( | |
151 | voice->src.format->nBlockAlign * | |
152 | voice->src.gstreamer->curBlock | |
153 | ), | |
154 | voice->src.format->nBlockAlign | |
155 | ) != voice->src.format->nBlockAlign ) | |
174 | (gconstpointer*) clipStartBytes, | |
175 | toCopyBytes | |
176 | ) != toCopyBytes ) | |
156 | 177 | { |
157 | 178 | LOG_ERROR( |
158 | 179 | voice->audio, |
163 | 184 | return (size_t) -1; |
164 | 185 | } |
165 | 186 | |
166 | push_ret = gst_pad_push(voice->src.gstreamer->srcpad, src); | |
187 | push_ret = gst_pad_push(voice->src.wmadec->srcpad, src); | |
167 | 188 | if( push_ret != GST_FLOW_OK && |
168 | 189 | push_ret != GST_FLOW_EOS ) |
169 | 190 | { |
181 | 202 | { |
182 | 203 | /* Pull frames one into cache */ |
183 | 204 | sample = gst_app_sink_try_pull_sample( |
184 | GST_APP_SINK(voice->src.gstreamer->dst), | |
205 | GST_APP_SINK(voice->src.wmadec->dst), | |
185 | 206 | 0 |
186 | 207 | ); |
187 | 208 | |
206 | 227 | clipEndBytes = 0; |
207 | 228 | } |
208 | 229 | |
209 | toCopyBytes = FAudio_min(info.size - (clipStartBytes + clipEndBytes), maxBytes - pulled); | |
210 | ||
211 | if (voice->src.gstreamer->convertCacheLen < pulled + toCopyBytes) | |
212 | { | |
213 | voice->src.gstreamer->convertCacheLen = pulled + toCopyBytes; | |
214 | voice->src.gstreamer->convertCache = (uint8_t*) voice->audio->pRealloc( | |
215 | voice->src.gstreamer->convertCache, | |
230 | toCopyBytes = FAudio_min(info.size - (clipStartBytes + clipEndBytes), availableBytes - pulled); | |
231 | ||
232 | if (voice->src.wmadec->convertCacheLen < pulled + toCopyBytes) | |
233 | { | |
234 | voice->src.wmadec->convertCacheLen = pulled + toCopyBytes; | |
235 | voice->src.wmadec->convertCache = (uint8_t*) voice->audio->pRealloc( | |
236 | voice->src.wmadec->convertCache, | |
216 | 237 | pulled + toCopyBytes |
217 | 238 | ); |
218 | 239 | } |
219 | 240 | |
220 | 241 | |
221 | FAudio_memcpy(voice->src.gstreamer->convertCache + pulled, | |
242 | FAudio_memcpy(voice->src.wmadec->convertCache + pulled, | |
222 | 243 | info.data + clipStartBytes, |
223 | 244 | toCopyBytes |
224 | 245 | ); |
234 | 255 | return pulled; |
235 | 256 | } |
236 | 257 | |
237 | static int FAudio_GSTREAMER_DecodeBlock(FAudioVoice *voice, FAudioBuffer *buffer, uint32_t block, size_t maxBytes) | |
258 | static int FAudio_WMADEC_DecodeBlock(FAudioVoice *voice, FAudioBuffer *buffer, uint32_t block, size_t availableBytes) | |
238 | 259 | { |
239 | FAudioGSTREAMER *gstreamer = voice->src.gstreamer; | |
260 | FAudioWMADEC *wmadec = voice->src.wmadec; | |
240 | 261 | uint8_t *tmpBuf; |
241 | 262 | size_t tmpLen; |
242 | 263 | |
243 | if (gstreamer->curBlock != ~0u && block != gstreamer->curBlock + 1) | |
264 | if (wmadec->curBlock != ~0u && block != wmadec->curBlock + 1) | |
244 | 265 | { |
245 | 266 | /* XAudio2 allows looping back to start of XMA buffers, but nothing else */ |
246 | 267 | if (block != 0) |
250 | 271 | "for voice %p, out of sequence block: %u (cur: %d)\n", |
251 | 272 | voice, |
252 | 273 | block, |
253 | gstreamer->curBlock | |
274 | wmadec->curBlock | |
254 | 275 | ); |
255 | 276 | } |
256 | 277 | FAudio_assert(block == 0); |
257 | if (!FAudio_GSTREAMER_RestartDecoder(gstreamer)) | |
278 | if (!FAudio_GSTREAMER_RestartDecoder(wmadec)) | |
258 | 279 | { |
259 | 280 | LOG_WARNING( |
260 | 281 | voice->audio, |
265 | 286 | } |
266 | 287 | |
267 | 288 | /* store previous block to allow for minor rewinds (FAudio quirk) */ |
268 | tmpBuf = gstreamer->prevConvertCache; | |
269 | tmpLen = gstreamer->prevConvertCacheLen; | |
270 | gstreamer->prevConvertCache = gstreamer->convertCache; | |
271 | gstreamer->prevConvertCacheLen = gstreamer->convertCacheLen; | |
272 | gstreamer->convertCache = tmpBuf; | |
273 | gstreamer->convertCacheLen = tmpLen; | |
274 | ||
275 | gstreamer->prevBlock = gstreamer->curBlock; | |
276 | gstreamer->curBlock = block; | |
277 | ||
278 | gstreamer->blockSizes[block] = FAudio_GSTREAMER_FillConvertCache( | |
289 | tmpBuf = wmadec->prevConvertCache; | |
290 | tmpLen = wmadec->prevConvertCacheLen; | |
291 | wmadec->prevConvertCache = wmadec->convertCache; | |
292 | wmadec->prevConvertCacheLen = wmadec->convertCacheLen; | |
293 | wmadec->convertCache = tmpBuf; | |
294 | wmadec->convertCacheLen = tmpLen; | |
295 | ||
296 | wmadec->prevBlock = wmadec->curBlock; | |
297 | wmadec->curBlock = block; | |
298 | ||
299 | wmadec->blockSizes[block] = FAudio_GSTREAMER_FillConvertCache( | |
279 | 300 | voice, |
280 | 301 | buffer, |
281 | maxBytes | |
302 | availableBytes | |
282 | 303 | ); |
283 | 304 | |
284 | return gstreamer->blockSizes[block] != (size_t) -1; | |
305 | return wmadec->blockSizes[block] != (size_t) -1; | |
285 | 306 | } |
286 | 307 | |
287 | 308 | static void FAudio_INTERNAL_DecodeGSTREAMER( |
290 | 311 | float *decodeCache, |
291 | 312 | uint32_t samples |
292 | 313 | ) { |
293 | size_t byteOffset, siz, maxBytes; | |
314 | size_t byteOffset, siz, availableBytes, blockCount, maxBytes; | |
294 | 315 | uint32_t curBlock, curBufferOffset; |
295 | 316 | uint8_t *convertCache; |
296 | 317 | int error = 0; |
297 | FAudioGSTREAMER *gstreamer = voice->src.gstreamer; | |
318 | FAudioWMADEC *wmadec = voice->src.wmadec; | |
319 | ||
320 | if (wmadec->blockCount != 0) | |
321 | { | |
322 | blockCount = wmadec->blockCount; | |
323 | maxBytes = wmadec->maxBytes; | |
324 | } | |
325 | else | |
326 | { | |
327 | blockCount = voice->src.bufferList->bufferWMA.PacketCount; | |
328 | maxBytes = voice->src.bufferList->bufferWMA.pDecodedPacketCumulativeBytes[blockCount - 1]; | |
329 | } | |
298 | 330 | |
299 | 331 | LOG_FUNC_ENTER(voice->audio) |
300 | 332 | |
301 | if (!gstreamer->blockSizes) | |
302 | { | |
303 | size_t sz = voice->src.bufferList->bufferWMA.PacketCount * sizeof(*gstreamer->blockSizes); | |
304 | gstreamer->blockSizes = (size_t *) voice->audio->pMalloc(sz); | |
305 | memset(gstreamer->blockSizes, 0xff, sz); | |
333 | if (!wmadec->blockSizes) | |
334 | { | |
335 | size_t sz = blockCount * sizeof(*wmadec->blockSizes); | |
336 | wmadec->blockSizes = (size_t *) voice->audio->pMalloc(sz); | |
337 | memset(wmadec->blockSizes, 0xff, sz); | |
306 | 338 | } |
307 | 339 | |
308 | 340 | curBufferOffset = voice->src.curBufferOffset; |
310 | 342 | byteOffset = SIZE_FROM_DST(curBufferOffset); |
311 | 343 | |
312 | 344 | /* the last block size can truncate the length of the buffer */ |
313 | maxBytes = SIZE_SRC_TO_DST(voice->src.bufferList->bufferWMA.pDecodedPacketCumulativeBytes[voice->src.bufferList->bufferWMA.PacketCount - 1]); | |
314 | for (curBlock = 0; curBlock < voice->src.bufferList->bufferWMA.PacketCount; curBlock += 1) | |
345 | availableBytes = SIZE_SRC_TO_DST(maxBytes); | |
346 | for (curBlock = 0; curBlock < blockCount; curBlock += 1) | |
315 | 347 | { |
316 | 348 | /* decode to get real size */ |
317 | if (gstreamer->blockSizes[curBlock] == (size_t)-1) | |
318 | { | |
319 | if (!FAudio_GSTREAMER_DecodeBlock(voice, buffer, curBlock, maxBytes)) | |
349 | if (wmadec->blockSizes[curBlock] == (size_t)-1) | |
350 | { | |
351 | if (!FAudio_WMADEC_DecodeBlock(voice, buffer, curBlock, availableBytes)) | |
320 | 352 | { |
321 | 353 | error = 1; |
322 | 354 | goto done; |
323 | 355 | } |
324 | 356 | } |
325 | 357 | |
326 | if (gstreamer->blockSizes[curBlock] > byteOffset) | |
358 | if (wmadec->blockSizes[curBlock] > byteOffset) | |
327 | 359 | { |
328 | 360 | /* ensure curBlock is decoded in either slot */ |
329 | if (gstreamer->curBlock != curBlock && gstreamer->prevBlock != curBlock) | |
361 | if (wmadec->curBlock != curBlock && wmadec->prevBlock != curBlock) | |
330 | 362 | { |
331 | if (!FAudio_GSTREAMER_DecodeBlock(voice, buffer, curBlock, maxBytes)) | |
363 | if (!FAudio_WMADEC_DecodeBlock(voice, buffer, curBlock, availableBytes)) | |
332 | 364 | { |
333 | 365 | error = 1; |
334 | 366 | goto done; |
337 | 369 | break; |
338 | 370 | } |
339 | 371 | |
340 | byteOffset -= gstreamer->blockSizes[curBlock]; | |
341 | maxBytes -= gstreamer->blockSizes[curBlock]; | |
342 | if(maxBytes == 0) | |
372 | byteOffset -= wmadec->blockSizes[curBlock]; | |
373 | availableBytes -= wmadec->blockSizes[curBlock]; | |
374 | if (availableBytes == 0) | |
343 | 375 | break; |
344 | 376 | } |
345 | 377 | |
346 | if (curBlock >= voice->src.bufferList->bufferWMA.PacketCount || maxBytes == 0) | |
378 | if (curBlock >= blockCount || availableBytes == 0) | |
347 | 379 | { |
348 | 380 | goto done; |
349 | 381 | } |
350 | 382 | |
351 | 383 | /* If we're in a different block from last time, decode! */ |
352 | if (curBlock == gstreamer->curBlock) | |
353 | { | |
354 | convertCache = gstreamer->convertCache; | |
355 | } | |
356 | else if (curBlock == gstreamer->prevBlock) | |
357 | { | |
358 | convertCache = gstreamer->prevConvertCache; | |
384 | if (curBlock == wmadec->curBlock) | |
385 | { | |
386 | convertCache = wmadec->convertCache; | |
387 | } | |
388 | else if (curBlock == wmadec->prevBlock) | |
389 | { | |
390 | convertCache = wmadec->prevConvertCache; | |
359 | 391 | } |
360 | 392 | else |
361 | 393 | { |
364 | 396 | } |
365 | 397 | |
366 | 398 | /* Copy to decodeCache, finally. */ |
367 | siz = FAudio_min(SIZE_FROM_DST(samples), gstreamer->blockSizes[curBlock] - byteOffset); | |
399 | siz = FAudio_min(SIZE_FROM_DST(samples), wmadec->blockSizes[curBlock] - byteOffset); | |
368 | 400 | if (convertCache) |
369 | 401 | { |
370 | 402 | FAudio_memcpy( |
402 | 434 | /* If the cache isn't filled yet, keep decoding! */ |
403 | 435 | if (samples > 0) |
404 | 436 | { |
405 | if ( !error && | |
406 | curBlock < voice->src.bufferList->bufferWMA.PacketCount - 1 ) | |
437 | if (!error && curBlock < blockCount - 1) | |
407 | 438 | { |
408 | 439 | goto decode; |
409 | 440 | } |
415 | 446 | LOG_FUNC_EXIT(voice->audio) |
416 | 447 | } |
417 | 448 | |
418 | void FAudio_GSTREAMER_end_buffer(FAudioSourceVoice *pSourceVoice) | |
449 | void FAudio_WMADEC_end_buffer(FAudioSourceVoice *pSourceVoice) | |
419 | 450 | { |
420 | FAudioGSTREAMER *gstreamer = pSourceVoice->src.gstreamer; | |
451 | FAudioWMADEC *wmadec = pSourceVoice->src.wmadec; | |
421 | 452 | |
422 | 453 | LOG_FUNC_ENTER(pSourceVoice->audio) |
423 | 454 | |
424 | pSourceVoice->audio->pFree(gstreamer->blockSizes); | |
425 | gstreamer->blockSizes = NULL; | |
426 | ||
427 | gstreamer->curBlock = ~0u; | |
428 | gstreamer->prevBlock = ~0u; | |
455 | pSourceVoice->audio->pFree(wmadec->blockSizes); | |
456 | wmadec->blockSizes = NULL; | |
457 | ||
458 | wmadec->curBlock = ~0u; | |
459 | wmadec->prevBlock = ~0u; | |
429 | 460 | |
430 | 461 | LOG_FUNC_EXIT(pSourceVoice->audio) |
431 | 462 | } |
432 | 463 | |
433 | static void FAudio_GSTREAMER_NewDecodebinPad(GstElement *decodebin, | |
464 | static void FAudio_WMADEC_NewDecodebinPad(GstElement *decodebin, | |
434 | 465 | GstPad *pad, gpointer user) |
435 | 466 | { |
436 | FAudioGSTREAMER *gstreamer = user; | |
467 | FAudioWMADEC *wmadec = user; | |
437 | 468 | GstPad *ac_sink; |
438 | 469 | |
439 | ac_sink = gst_element_get_static_pad(gstreamer->resampler, "sink"); | |
470 | ac_sink = gst_element_get_static_pad(wmadec->resampler, "sink"); | |
440 | 471 | if (GST_PAD_IS_LINKED(ac_sink)) |
441 | 472 | { |
442 | 473 | gst_object_unref(ac_sink); |
448 | 479 | gst_object_unref(ac_sink); |
449 | 480 | } |
450 | 481 | |
451 | uint32_t FAudio_GSTREAMER_init(FAudioSourceVoice *pSourceVoice, uint32_t type) | |
482 | /* XMA2 doesn't have a recognized mimetype and so far only libav's avdec_xma2 exists for XMA2. | |
483 | * Things can change and as of writing this patch, things are actively changing. | |
484 | * As for this being a possible leak: using gstreamer is a static endeavour elsewhere already~ | |
485 | * -ade | |
486 | */ | |
487 | /* #define FAUDIO_GST_LIBAV_EXPOSES_XMA2_CAPS_IN_CURRENT_YEAR */ | |
488 | #ifndef FAUDIO_GST_LIBAV_EXPOSES_XMA2_CAPS_IN_CURRENT_YEAR | |
489 | static const char *FAudio_WMADEC_XMA2_Mimetype = NULL; | |
490 | static GstElementFactory *FAudio_WMADEC_XMA2_DecoderFactory = NULL; | |
491 | static GValueArray *FAudio_WMADEC_XMA2_DecodebinAutoplugFactories( | |
492 | GstElement *decodebin, | |
493 | GstPad *pad, | |
494 | GstCaps *caps, | |
495 | gpointer user | |
496 | ) { | |
497 | FAudio *audio = user; | |
498 | GValueArray *result; | |
499 | gchar *capsText; | |
500 | ||
501 | if (!gst_structure_has_name(gst_caps_get_structure(caps, 0), FAudio_WMADEC_XMA2_Mimetype)) | |
502 | { | |
503 | capsText = gst_caps_to_string(caps); | |
504 | LOG_ERROR( | |
505 | audio, | |
506 | "Expected %s (XMA2) caps not %s", | |
507 | FAudio_WMADEC_XMA2_Mimetype, | |
508 | capsText | |
509 | ) | |
510 | g_free(capsText); | |
511 | /* | |
512 | * From the docs: | |
513 | * | |
514 | * If this function returns NULL, @pad will be exposed as a final caps. | |
515 | * | |
516 | * If this function returns an empty array, the pad will be considered as | |
517 | * having an unhandled type media type. | |
518 | */ | |
519 | return g_value_array_new(0); | |
520 | } | |
521 | ||
522 | result = g_value_array_new(1); | |
523 | GValue val = G_VALUE_INIT; | |
524 | g_value_init(&val, G_TYPE_OBJECT); | |
525 | g_value_set_object(&val, FAudio_WMADEC_XMA2_DecoderFactory); | |
526 | g_value_array_append(result, &val); | |
527 | g_value_unset(&val); | |
528 | return result; | |
529 | } | |
530 | #endif | |
531 | ||
532 | ||
533 | uint32_t FAudio_WMADEC_init(FAudioSourceVoice *pSourceVoice, uint32_t type) | |
452 | 534 | { |
453 | FAudioGSTREAMER *result; | |
535 | FAudioWMADEC *result; | |
454 | 536 | GstElement *decoder = NULL, *converter = NULL; |
537 | const GList *tmpls; | |
538 | GstStaticPadTemplate *tmpl; | |
455 | 539 | GstCaps *caps; |
540 | const char *mimetype; | |
541 | const char *versiontype; | |
456 | 542 | int version; |
457 | 543 | GstBuffer *codec_data; |
458 | 544 | size_t codec_data_size; |
472 | 558 | gst_init(NULL, NULL); |
473 | 559 | } |
474 | 560 | |
561 | #ifndef FAUDIO_GST_LIBAV_EXPOSES_XMA2_CAPS_IN_CURRENT_YEAR | |
562 | if (type == FAUDIO_FORMAT_XMAUDIO2 && !FAudio_WMADEC_XMA2_Mimetype) | |
563 | { | |
564 | /* Old versions ship with unknown/unknown as the sink caps mimetype. | |
565 | * A patch has been submitted (and merged!) to expose avdec_xma2 as audio/x-xma with xmaversion 2: | |
566 | * https://gitlab.freedesktop.org/gstreamer/gst-libav/-/merge_requests/124 | |
567 | * For now, try to find avdec_xma2 if it's found, match the mimetype on the fly. | |
568 | * This should also take future alternative XMA2 decoders into account if avdec_xma2 is missing. | |
569 | * -ade | |
570 | */ | |
571 | FAudio_WMADEC_XMA2_Mimetype = "audio/x-xma"; | |
572 | FAudio_WMADEC_XMA2_DecoderFactory = gst_element_factory_find("avdec_xma2"); | |
573 | if (FAudio_WMADEC_XMA2_DecoderFactory) | |
574 | { | |
575 | for (tmpls = gst_element_factory_get_static_pad_templates(FAudio_WMADEC_XMA2_DecoderFactory); tmpls; tmpls = tmpls->next) | |
576 | { | |
577 | tmpl = (GstStaticPadTemplate*) tmpls->data; | |
578 | if (tmpl->direction == GST_PAD_SINK) | |
579 | { | |
580 | caps = gst_static_pad_template_get_caps(tmpl); | |
581 | FAudio_WMADEC_XMA2_Mimetype = gst_structure_get_name(gst_caps_get_structure(caps, 0)); | |
582 | gst_caps_unref(caps); | |
583 | break; | |
584 | } | |
585 | } | |
586 | } | |
587 | } | |
588 | #endif | |
589 | ||
475 | 590 | /* Match the format with the codec */ |
476 | 591 | switch (type) |
477 | 592 | { |
478 | #define GSTTYPE(fmt, ver) \ | |
479 | case FAUDIO_FORMAT_##fmt: version = ver; break; | |
480 | GSTTYPE(WMAUDIO2, 2) | |
481 | GSTTYPE(WMAUDIO3, 3) | |
482 | GSTTYPE(WMAUDIO_LOSSLESS, 4) | |
483 | /* FIXME: XMA2 */ | |
593 | #define GSTTYPE(fmt, mt, vt, ver) \ | |
594 | case FAUDIO_FORMAT_##fmt: mimetype = mt; versiontype = vt; version = ver; break; | |
595 | GSTTYPE(WMAUDIO2, "audio/x-wma", "wmaversion", 2) | |
596 | GSTTYPE(WMAUDIO3, "audio/x-wma", "wmaversion", 3) | |
597 | GSTTYPE(WMAUDIO_LOSSLESS, "audio/x-wma", "wmaversion", 4) | |
598 | #ifndef FAUDIO_GST_LIBAV_EXPOSES_XMA2_CAPS_IN_CURRENT_YEAR | |
599 | GSTTYPE(XMAUDIO2, FAudio_WMADEC_XMA2_Mimetype, "xmaversion", 2) | |
600 | #else | |
601 | GSTTYPE(XMAUDIO2, "audio/x-xma", "xmaversion", 2) | |
602 | #endif | |
484 | 603 | #undef GSTTYPE |
485 | 604 | default: |
486 | 605 | LOG_ERROR( |
496 | 615 | * Note that we're not assigning names, since many pipelines will exist |
497 | 616 | * at the same time (one per source voice). |
498 | 617 | */ |
499 | result = (FAudioGSTREAMER*) pSourceVoice->audio->pMalloc(sizeof(FAudioGSTREAMER)); | |
500 | FAudio_zero(result, sizeof(FAudioGSTREAMER)); | |
618 | result = (FAudioWMADEC*) pSourceVoice->audio->pMalloc(sizeof(FAudioWMADEC)); | |
619 | FAudio_zero(result, sizeof(FAudioWMADEC)); | |
501 | 620 | |
502 | 621 | result->pipeline = gst_pipeline_new(NULL); |
503 | 622 | |
512 | 631 | goto free_without_bin; |
513 | 632 | } |
514 | 633 | |
515 | g_signal_connect(decoder, "pad-added", G_CALLBACK(FAudio_GSTREAMER_NewDecodebinPad), result); | |
634 | #ifndef FAUDIO_GST_LIBAV_EXPOSES_XMA2_CAPS_IN_CURRENT_YEAR | |
635 | if (type == FAUDIO_FORMAT_XMAUDIO2 && FAudio_WMADEC_XMA2_DecoderFactory) | |
636 | { | |
637 | g_signal_connect(decoder, "autoplug-factories", G_CALLBACK(FAudio_WMADEC_XMA2_DecodebinAutoplugFactories), pSourceVoice->audio); | |
638 | } | |
639 | #endif | |
640 | g_signal_connect(decoder, "pad-added", G_CALLBACK(FAudio_WMADEC_NewDecodebinPad), result); | |
516 | 641 | |
517 | 642 | result->srcpad = gst_pad_new(NULL, GST_PAD_SRC); |
518 | 643 | |
606 | 731 | gst_pad_push_event(result->srcpad, event); |
607 | 732 | |
608 | 733 | /* Prepare the input format */ |
734 | result->blockAlign = (uint32_t) pSourceVoice->src.format->nBlockAlign; | |
609 | 735 | if (type == FAUDIO_FORMAT_WMAUDIO3) |
610 | 736 | { |
611 | 737 | const FAudioWaveFormatExtensible *wfx = |
612 | 738 | (FAudioWaveFormatExtensible*) pSourceVoice->src.format; |
613 | 739 | extradata = (uint8_t*) &wfx->Samples; |
614 | 740 | codec_data_size = pSourceVoice->src.format->cbSize; |
741 | /* bufferList (and thus bufferWMA) can't be accessed yet. */ | |
615 | 742 | } |
616 | 743 | else if (type == FAUDIO_FORMAT_WMAUDIO2) |
617 | 744 | { |
620 | 747 | |
621 | 748 | extradata = fakeextradata; |
622 | 749 | codec_data_size = sizeof(fakeextradata); |
750 | /* bufferList (and thus bufferWMA) can't be accessed yet. */ | |
751 | } | |
752 | else if (type == FAUDIO_FORMAT_XMAUDIO2) | |
753 | { | |
754 | const FAudioXMA2WaveFormat *wfx = | |
755 | (FAudioXMA2WaveFormat*) pSourceVoice->src.format; | |
756 | extradata = (uint8_t*) &wfx->wNumStreams; | |
757 | codec_data_size = pSourceVoice->src.format->cbSize; | |
758 | /* XMA2 block size >= 16-bit limit and it doesn't come with bufferWMA. */ | |
759 | result->blockAlign = wfx->dwBytesPerBlock; | |
760 | result->blockCount = wfx->wBlockCount; | |
761 | result->maxBytes = ( | |
762 | (size_t) wfx->dwSamplesEncoded * | |
763 | pSourceVoice->src.format->nChannels * | |
764 | (pSourceVoice->src.format->wBitsPerSample / 8) | |
765 | ); | |
623 | 766 | } |
624 | 767 | else |
625 | 768 | { |
626 | 769 | extradata = NULL; |
770 | codec_data_size = 0; | |
627 | 771 | FAudio_assert(0 && "Unrecognized WMA format!"); |
628 | 772 | } |
629 | 773 | codec_data = gst_buffer_new_and_alloc(codec_data_size); |
630 | 774 | gst_buffer_fill(codec_data, 0, extradata, codec_data_size); |
631 | 775 | caps = gst_caps_new_simple( |
632 | "audio/x-wma", | |
633 | "wmaversion", G_TYPE_INT, version, | |
776 | mimetype, | |
777 | versiontype, G_TYPE_INT, version, | |
634 | 778 | "bitrate", G_TYPE_INT, pSourceVoice->src.format->nAvgBytesPerSec * 8, |
635 | 779 | "channels", G_TYPE_INT, pSourceVoice->src.format->nChannels, |
636 | 780 | "rate", G_TYPE_INT, pSourceVoice->src.format->nSamplesPerSec, |
637 | "block_align", G_TYPE_INT, pSourceVoice->src.format->nBlockAlign, | |
781 | "block_align", G_TYPE_INT, result->blockAlign, | |
638 | 782 | "depth", G_TYPE_INT, pSourceVoice->src.format->wBitsPerSample, |
639 | 783 | "codec_data", GST_TYPE_BUFFER, codec_data, |
640 | 784 | NULL |
669 | 813 | goto free_with_bin; |
670 | 814 | } |
671 | 815 | |
672 | pSourceVoice->src.gstreamer = result; | |
816 | pSourceVoice->src.wmadec = result; | |
673 | 817 | pSourceVoice->src.decode = FAudio_INTERNAL_DecodeGSTREAMER; |
674 | 818 | |
675 | 819 | if (FAudio_GSTREAMER_CheckForBusErrors(pSourceVoice)) |
680 | 824 | "Got a bus error after creation!" |
681 | 825 | ) |
682 | 826 | |
683 | pSourceVoice->src.gstreamer = NULL; | |
827 | pSourceVoice->src.wmadec = NULL; | |
684 | 828 | pSourceVoice->src.decode = NULL; |
685 | 829 | |
686 | 830 | goto free_with_bin; |
726 | 870 | return FAUDIO_E_UNSUPPORTED_FORMAT; |
727 | 871 | } |
728 | 872 | |
729 | void FAudio_GSTREAMER_free(FAudioSourceVoice *voice) | |
873 | void FAudio_WMADEC_free(FAudioSourceVoice *voice) | |
730 | 874 | { |
731 | 875 | LOG_FUNC_ENTER(voice->audio) |
732 | gst_element_set_state(voice->src.gstreamer->pipeline, GST_STATE_NULL); | |
733 | gst_object_unref(voice->src.gstreamer->pipeline); | |
734 | gst_object_unref(voice->src.gstreamer->srcpad); | |
735 | voice->audio->pFree(voice->src.gstreamer->convertCache); | |
736 | voice->audio->pFree(voice->src.gstreamer->prevConvertCache); | |
737 | voice->audio->pFree(voice->src.gstreamer->blockSizes); | |
738 | voice->audio->pFree(voice->src.gstreamer); | |
739 | voice->src.gstreamer = NULL; | |
876 | gst_element_set_state(voice->src.wmadec->pipeline, GST_STATE_NULL); | |
877 | gst_object_unref(voice->src.wmadec->pipeline); | |
878 | gst_object_unref(voice->src.wmadec->srcpad); | |
879 | voice->audio->pFree(voice->src.wmadec->convertCache); | |
880 | voice->audio->pFree(voice->src.wmadec->prevConvertCache); | |
881 | voice->audio->pFree(voice->src.wmadec->blockSizes); | |
882 | voice->audio->pFree(voice->src.wmadec); | |
883 | voice->src.wmadec = NULL; | |
740 | 884 | LOG_FUNC_EXIT(voice->audio) |
741 | 885 | } |
742 | 886 |
304 | 304 | |
305 | 305 | LOG_FUNC_ENTER(voice->audio) |
306 | 306 | |
307 | #ifdef HAVE_GSTREAMER | |
308 | if (voice->src.gstreamer != NULL) | |
307 | #ifdef HAVE_WMADEC | |
308 | if (voice->src.wmadec != NULL) | |
309 | 309 | { |
310 | 310 | /* Always 0, per the spec */ |
311 | 311 | LOG_FUNC_EXIT(voice->audio) |
312 | 312 | return 0; |
313 | 313 | } |
314 | #endif /* HAVE_GSTREAMER */ | |
314 | #endif /* HAVE_WMADEC */ | |
315 | 315 | while (list != NULL && decoding > 0) |
316 | 316 | { |
317 | 317 | buffer = &list->buffer; |
454 | 454 | } |
455 | 455 | else |
456 | 456 | { |
457 | #ifdef HAVE_GSTREAMER | |
458 | if (voice->src.gstreamer != NULL) | |
457 | #ifdef HAVE_WMADEC | |
458 | if (voice->src.wmadec != NULL) | |
459 | 459 | { |
460 | FAudio_GSTREAMER_end_buffer(voice); | |
460 | FAudio_WMADEC_end_buffer(voice); | |
461 | 461 | } |
462 | #endif /* HAVE_GSTREAMER */ | |
462 | #endif /* HAVE_WMADEC */ | |
463 | 463 | /* For EOS we can stop storing fraction offsets */ |
464 | 464 | if (buffer->Flags & FAUDIO_END_OF_STREAM) |
465 | 465 | { |
998 | 998 | |
999 | 999 | sendwork: |
1000 | 1000 | |
1001 | /* Nowhere to send it? Just skip the rest...*/ | |
1002 | if (voice->sends.SendCount == 0) | |
1003 | { | |
1004 | FAudio_PlatformUnlockMutex(voice->sendLock); | |
1005 | LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock) | |
1006 | LOG_FUNC_EXIT(voice->audio) | |
1007 | return; | |
1008 | } | |
1009 | ||
1010 | 1001 | /* Filters */ |
1011 | 1002 | if (voice->flags & FAUDIO_VOICE_USEFILTER) |
1012 | 1003 | { |
1049 | 1040 | FAudio_PlatformUnlockMutex(voice->effectLock); |
1050 | 1041 | LOG_MUTEX_UNLOCK(voice->audio, voice->effectLock) |
1051 | 1042 | |
1043 | /* Nowhere to send it? Just skip the rest...*/ | |
1044 | if (voice->sends.SendCount == 0) | |
1045 | { | |
1046 | FAudio_PlatformUnlockMutex(voice->sendLock); | |
1047 | LOG_MUTEX_UNLOCK(voice->audio, voice->sendLock) | |
1048 | LOG_FUNC_EXIT(voice->audio) | |
1049 | return; | |
1050 | } | |
1051 | ||
1052 | 1052 | /* Send float cache to sends */ |
1053 | 1053 | FAudio_PlatformLockMutex(voice->volumeLock); |
1054 | 1054 | LOG_MUTEX_LOCK(voice->audio, voice->volumeLock) |
1109 | 1109 | FAudio_PlatformLockMutex(voice->sendLock); |
1110 | 1110 | LOG_MUTEX_LOCK(voice->audio, voice->sendLock) |
1111 | 1111 | |
1112 | /* Nothing to do? */ | |
1113 | if (voice->sends.SendCount == 0) | |
1114 | { | |
1115 | goto end; | |
1116 | } | |
1117 | ||
1118 | 1112 | /* Resample */ |
1119 | 1113 | if (voice->mix.resampleStep == FIXED_ONE) |
1120 | 1114 | { |
1180 | 1174 | } |
1181 | 1175 | FAudio_PlatformUnlockMutex(voice->effectLock); |
1182 | 1176 | LOG_MUTEX_UNLOCK(voice->audio, voice->effectLock) |
1177 | ||
1178 | /* Nothing more to do? */ | |
1179 | if (voice->sends.SendCount == 0) | |
1180 | { | |
1181 | goto end; | |
1182 | } | |
1183 | 1183 | |
1184 | 1184 | /* Send float cache to sends */ |
1185 | 1185 | FAudio_PlatformLockMutex(voice->volumeLock); |
27 | 27 | #include "FAPOBase.h" |
28 | 28 | #include <stdarg.h> |
29 | 29 | |
30 | #ifdef FAUDIO_UNKNOWN_PLATFORM | |
30 | #ifdef FAUDIO_WIN32_PLATFORM | |
31 | 31 | #include <stdio.h> |
32 | 32 | #include <stdlib.h> |
33 | 33 | #include <string.h> |
35 | 35 | #include <assert.h> |
36 | 36 | #include <inttypes.h> |
37 | 37 | |
38 | #include <windef.h> | |
39 | #include <winbase.h> | |
40 | ||
38 | 41 | #define FAudio_malloc malloc |
39 | 42 | #define FAudio_realloc realloc |
40 | 43 | #define FAudio_free free |
41 | #define FAudio_alloca(x) alloca(uint8_t, x) | |
42 | #define FAudio_dealloca(x) dealloca(x) | |
44 | #define FAudio_alloca(x) alloca(x) | |
45 | #define FAudio_dealloca(x) (void)(x) | |
43 | 46 | #define FAudio_zero(ptr, size) memset(ptr, '\0', size) |
44 | 47 | #define FAudio_memset(ptr, val, size) memset(ptr, val, size) |
45 | 48 | #define FAudio_memcpy(dst, src, size) memcpy(dst, src, size) |
48 | 51 | |
49 | 52 | #define FAudio_strlen(ptr) strlen(ptr) |
50 | 53 | #define FAudio_strcmp(str1, str2) strcmp(str1, str2) |
51 | #define FAudio_strlcpy(ptr1, ptr2, size) strlcpy(ptr1, ptr2, size) | |
54 | #define FAudio_strlcpy(ptr1, ptr2, size) lstrcpynA(ptr1, ptr2, size) | |
52 | 55 | |
53 | 56 | #define FAudio_pow(x, y) pow(x, y) |
54 | 57 | #define FAudio_log(x) log(x) |
75 | 78 | #define FAudio_assert assert |
76 | 79 | #define FAudio_snprintf snprintf |
77 | 80 | #define FAudio_vsnprintf vsnprintf |
78 | #define FAudio_Log(msg) fprintf(stderr, "%s\n", msg) | |
79 | 81 | #define FAudio_getenv getenv |
80 | 82 | #define FAudio_PRIu64 PRIu64 |
81 | 83 | #define FAudio_PRIx64 PRIx64 |
84 | ||
85 | extern void FAudio_Log(char const *msg); | |
82 | 86 | |
83 | 87 | /* FIXME: Assuming little-endian! */ |
84 | 88 | #define FAudio_swap16LE(x) (x) |
463 | 467 | uint64_t curBufferOffsetDec; |
464 | 468 | uint32_t curBufferOffset; |
465 | 469 | |
466 | /* GStreamer */ | |
467 | #ifdef HAVE_GSTREAMER | |
468 | struct FAudioGSTREAMER *gstreamer; | |
469 | #endif /* HAVE_GSTREAMER*/ | |
470 | /* WMA decoding */ | |
471 | #ifdef HAVE_WMADEC | |
472 | struct FAudioWMADEC *wmadec; | |
473 | #endif /* HAVE_WMADEC*/ | |
470 | 474 | |
471 | 475 | /* Read-only */ |
472 | 476 | float maxFreqRatio; |
731 | 735 | DECODE_FUNC(WMAERROR) |
732 | 736 | #undef DECODE_FUNC |
733 | 737 | |
734 | /* GStreamer */ | |
735 | ||
736 | #ifdef HAVE_GSTREAMER | |
737 | uint32_t FAudio_GSTREAMER_init(FAudioSourceVoice *pSourceVoice, uint32_t type); | |
738 | void FAudio_GSTREAMER_free(FAudioSourceVoice *voice); | |
739 | void FAudio_GSTREAMER_end_buffer(FAudioSourceVoice *voice); | |
740 | #endif /* HAVE_GSTREAMER */ | |
738 | /* WMA decoding */ | |
739 | ||
740 | #ifdef HAVE_WMADEC | |
741 | uint32_t FAudio_WMADEC_init(FAudioSourceVoice *pSourceVoice, uint32_t type); | |
742 | void FAudio_WMADEC_free(FAudioSourceVoice *voice); | |
743 | void FAudio_WMADEC_end_buffer(FAudioSourceVoice *voice); | |
744 | #endif /* HAVE_WMADEC */ | |
741 | 745 | |
742 | 746 | /* Platform Functions */ |
743 | 747 |
23 | 23 | * |
24 | 24 | */ |
25 | 25 | |
26 | #ifndef FAUDIO_WIN32_PLATFORM | |
27 | ||
26 | 28 | #include "FAudio_internal.h" |
27 | 29 | |
28 | 30 | #include <SDL.h> |
220 | 222 | ) { |
221 | 223 | const char *name, *envvar; |
222 | 224 | int channels, rate; |
225 | SDL_AudioSpec spec; | |
226 | uint32_t devcount, i; | |
223 | 227 | |
224 | 228 | FAudio_zero(details, sizeof(FAudioDeviceDetails)); |
225 | if (index >= FAudio_PlatformGetDeviceCount()) | |
229 | ||
230 | devcount = FAudio_PlatformGetDeviceCount(); | |
231 | if (index >= devcount) | |
226 | 232 | { |
227 | 233 | return FAUDIO_E_INVALID_CALL; |
228 | 234 | } |
257 | 263 | sizeof(details->DisplayName) |
258 | 264 | ); |
259 | 265 | |
260 | /* TODO: SDL_GetAudioDeviceSpec! */ | |
266 | /* Environment variables take precedence over all possible values */ | |
261 | 267 | envvar = SDL_getenv("SDL_AUDIO_FREQUENCY"); |
262 | if (!envvar || ((rate = SDL_atoi(envvar)) == 0)) | |
268 | if (envvar != NULL) | |
269 | { | |
270 | rate = SDL_atoi(envvar); | |
271 | } | |
272 | else | |
273 | { | |
274 | rate = 0; | |
275 | } | |
276 | envvar = SDL_getenv("SDL_AUDIO_CHANNELS"); | |
277 | if (envvar != NULL) | |
278 | { | |
279 | channels = SDL_atoi(envvar); | |
280 | } | |
281 | else | |
282 | { | |
283 | channels = 0; | |
284 | } | |
285 | ||
286 | #if SDL_VERSION_ATLEAST(2, 0, 15) | |
287 | if (index == 0) | |
288 | { | |
289 | /* Okay, so go grab something from the liquor cabinet and get | |
290 | * ready, because this loop is a bit of a trip: | |
291 | * | |
292 | * We can't get the spec for the default device, because in | |
293 | * audio land a "default device" is a completely foreign idea, | |
294 | * some APIs support it but in reality you just have to pass | |
295 | * NULL as a driver string and the sound server figures out the | |
296 | * rest. In some psychotic universe the device can even be a | |
297 | * network address. No, seriously. | |
298 | * | |
299 | * So what do we do? Well, at least in my experience shipping | |
300 | * for the PC, the easiest thing to do is assume that the | |
301 | * highest spec in the list is what you should target, even if | |
302 | * it turns out that's not the default at the time you create | |
303 | * your device. | |
304 | * | |
305 | * Consider a laptop that has built-in stereo speakers, but is | |
306 | * connected to a home theater system with 5.1 audio. It may be | |
307 | * the case that the stereo audio is active, but the user may | |
308 | * at some point move audio to 5.1, at which point the server | |
309 | * will simply move the endpoint from underneath us and move our | |
310 | * output stream to the new device. At that point, you _really_ | |
311 | * want to already be pushing out 5.1, because if not the user | |
312 | * will be stuck recreating the whole program, which on many | |
313 | * platforms is an instant cert failure. The tradeoff is that | |
314 | * you're potentially downmixing a 5.1 stream to stereo, which | |
315 | * is a bit wasteful, but presumably the hardware can handle it | |
316 | * if they were able to use a 5.1 system to begin with. | |
317 | * | |
318 | * So, we just aim for the highest channel count on the system. | |
319 | * We also do this with sample rate to a lesser degree; we try | |
320 | * to use a single device spec at a time, so it may be that | |
321 | * the sample rate you get isn't the highest from the list if | |
322 | * another device had a higher channel count. | |
323 | * | |
324 | * Lastly, if you set SDL_AUDIO_CHANNELS but not | |
325 | * SDL_AUDIO_FREQUENCY, we don't bother checking for a sample | |
326 | * rate, we fall through to the hardcoded value at the bottom of | |
327 | * this function. | |
328 | * | |
329 | * I'm so tired. | |
330 | * | |
331 | * -flibit | |
332 | */ | |
333 | if (channels <= 0) | |
334 | { | |
335 | const uint8_t setRate = (rate <= 0); | |
336 | devcount -= 1; /* Subtracting the default index */ | |
337 | for (i = 0; i < devcount; i += 1) | |
338 | { | |
339 | SDL_GetAudioDeviceSpec(i, 0, &spec); | |
340 | if (spec.channels > channels) | |
341 | { | |
342 | channels = spec.channels; | |
343 | if (setRate) | |
344 | { | |
345 | /* May be 0! That's okay! */ | |
346 | rate = spec.freq; | |
347 | } | |
348 | } | |
349 | } | |
350 | } | |
351 | } | |
352 | else | |
353 | { | |
354 | SDL_GetAudioDeviceSpec(index - 1, 0, &spec); | |
355 | if ((spec.freq > 0) && (rate <= 0)) | |
356 | { | |
357 | rate = spec.freq; | |
358 | } | |
359 | if ((spec.channels > 0) && (channels <= 0)) | |
360 | { | |
361 | channels = spec.channels; | |
362 | } | |
363 | } | |
364 | #endif /* SDL >= 2.0.15 */ | |
365 | ||
366 | /* If we make it all the way here with no format, hardcode a sane one */ | |
367 | if (rate <= 0) | |
263 | 368 | { |
264 | 369 | rate = 48000; |
265 | 370 | } |
266 | envvar = SDL_getenv("SDL_AUDIO_CHANNELS"); | |
267 | if (!envvar || ((channels = SDL_atoi(envvar)) == 0)) | |
371 | if (channels <= 0) | |
268 | 372 | { |
269 | 373 | channels = 2; |
270 | 374 | } |
375 | ||
376 | /* Write the format, finally. */ | |
271 | 377 | WriteWaveFormatExtensible( |
272 | 378 | &details->OutputFormat, |
273 | 379 | channels, |
599 | 705 | } |
600 | 706 | |
601 | 707 | /* vim: set noexpandtab shiftwidth=8 tabstop=8: */ |
708 | ||
709 | #else | |
710 | ||
711 | extern int this_tu_is_empty; | |
712 | ||
713 | #endif /* FAUDIO_WIN32_PLATFORM */ |
0 | /* FAudio - XAudio Reimplementation for FNA | |
1 | * | |
2 | * Copyright (c) 2011-2020 Ethan Lee, Luigi Auriemma, and the MonoGame Team | |
3 | * | |
4 | * This software is provided 'as-is', without any express or implied warranty. | |
5 | * In no event will the authors be held liable for any damages arising from | |
6 | * the use of this software. | |
7 | * | |
8 | * Permission is granted to anyone to use this software for any purpose, | |
9 | * including commercial applications, and to alter it and redistribute it | |
10 | * freely, subject to the following restrictions: | |
11 | * | |
12 | * 1. The origin of this software must not be misrepresented; you must not | |
13 | * claim that you wrote the original software. If you use this software in a | |
14 | * product, an acknowledgment in the product documentation would be | |
15 | * appreciated but is not required. | |
16 | * | |
17 | * 2. Altered source versions must be plainly marked as such, and must not be | |
18 | * misrepresented as being the original software. | |
19 | * | |
20 | * 3. This notice may not be removed or altered from any source distribution. | |
21 | * | |
22 | * Ethan "flibitijibibo" Lee <flibitijibibo@flibitijibibo.com> | |
23 | * | |
24 | */ | |
25 | ||
26 | #ifdef FAUDIO_WIN32_PLATFORM | |
27 | ||
28 | #include "FAudio_internal.h" | |
29 | ||
30 | #include <stddef.h> | |
31 | ||
32 | #define COBJMACROS | |
33 | #include <windows.h> | |
34 | #include <mfidl.h> | |
35 | #include <mfapi.h> | |
36 | #include <mferror.h> | |
37 | #include <mfreadwrite.h> | |
38 | #include <propvarutil.h> | |
39 | ||
40 | #include <initguid.h> | |
41 | #include <audioclient.h> | |
42 | #include <mmdeviceapi.h> | |
43 | ||
44 | DEFINE_GUID(CLSID_CWMADecMediaObject, 0x2eeb4adf, 0x4578, 0x4d10, 0xbc, 0xa7, 0xbb, 0x95, 0x5f, 0x56, 0x32, 0x0a); | |
45 | DEFINE_MEDIATYPE_GUID(MFAudioFormat_XMAudio2, FAUDIO_FORMAT_XMAUDIO2); | |
46 | ||
47 | static CRITICAL_SECTION faudio_cs = { NULL, -1, 0, 0, 0, 0 }; | |
48 | static IMMDeviceEnumerator *device_enumerator; | |
49 | static HRESULT init_hr; | |
50 | ||
51 | struct FAudioWin32PlatformData | |
52 | { | |
53 | IAudioClient *client; | |
54 | HANDLE audioThread; | |
55 | HANDLE stopEvent; | |
56 | }; | |
57 | ||
58 | struct FAudioAudioClientThreadArgs | |
59 | { | |
60 | WAVEFORMATEXTENSIBLE format; | |
61 | IAudioClient *client; | |
62 | HANDLE events[2]; | |
63 | FAudio *audio; | |
64 | UINT updateSize; | |
65 | }; | |
66 | ||
67 | void FAudio_Log(char const *msg) | |
68 | { | |
69 | OutputDebugStringA(msg); | |
70 | } | |
71 | ||
72 | static HRESULT FAudio_FillAudioClientBuffer( | |
73 | struct FAudioAudioClientThreadArgs *args, | |
74 | IAudioRenderClient *client, | |
75 | UINT frames, | |
76 | UINT padding | |
77 | ) { | |
78 | HRESULT hr = S_OK; | |
79 | BYTE *buffer; | |
80 | ||
81 | while (padding + args->updateSize <= frames) | |
82 | { | |
83 | hr = IAudioRenderClient_GetBuffer( | |
84 | client, | |
85 | frames - padding, | |
86 | &buffer | |
87 | ); | |
88 | if (FAILED(hr)) return hr; | |
89 | ||
90 | FAudio_zero( | |
91 | buffer, | |
92 | args->updateSize * args->format.Format.nBlockAlign | |
93 | ); | |
94 | ||
95 | if (args->audio->active) | |
96 | { | |
97 | FAudio_INTERNAL_UpdateEngine( | |
98 | args->audio, | |
99 | (float*) buffer | |
100 | ); | |
101 | } | |
102 | ||
103 | hr = IAudioRenderClient_ReleaseBuffer( | |
104 | client, | |
105 | args->updateSize, | |
106 | 0 | |
107 | ); | |
108 | if (FAILED(hr)) return hr; | |
109 | ||
110 | padding += args->updateSize; | |
111 | } | |
112 | ||
113 | return hr; | |
114 | } | |
115 | ||
116 | static DWORD WINAPI FAudio_AudioClientThread(void *user) | |
117 | { | |
118 | struct FAudioAudioClientThreadArgs *args = user; | |
119 | IAudioRenderClient *render_client; | |
120 | HRESULT hr = S_OK; | |
121 | UINT frames, padding = 0; | |
122 | ||
123 | hr = IAudioClient_GetService( | |
124 | args->client, | |
125 | &IID_IAudioRenderClient, | |
126 | (void **)&render_client | |
127 | ); | |
128 | FAudio_assert(!FAILED(hr) && "Failed to get IAudioRenderClient service!"); | |
129 | ||
130 | hr = IAudioClient_GetBufferSize(args->client, &frames); | |
131 | FAudio_assert(!FAILED(hr) && "Failed to get IAudioClient buffer size!"); | |
132 | ||
133 | hr = FAudio_FillAudioClientBuffer(args, render_client, frames, 0); | |
134 | FAudio_assert(!FAILED(hr) && "Failed to initialize IAudioClient buffer!"); | |
135 | ||
136 | hr = IAudioClient_Start(args->client); | |
137 | FAudio_assert(!FAILED(hr) && "Failed to start IAudioClient!"); | |
138 | ||
139 | while (WaitForMultipleObjects(2, args->events, FALSE, INFINITE) == WAIT_OBJECT_0) | |
140 | { | |
141 | hr = IAudioClient_GetCurrentPadding(args->client, &padding); | |
142 | FAudio_assert(!FAILED(hr) && "Failed to get IAudioClient current padding!"); | |
143 | ||
144 | hr = FAudio_FillAudioClientBuffer(args, render_client, frames, padding); | |
145 | FAudio_assert(!FAILED(hr) && "Failed to fill IAudioClient buffer!"); | |
146 | } | |
147 | ||
148 | hr = IAudioClient_Stop(args->client); | |
149 | FAudio_assert(!FAILED(hr) && "Failed to stop IAudioClient!"); | |
150 | ||
151 | IAudioRenderClient_Release(render_client); | |
152 | FAudio_free(args); | |
153 | return 0; | |
154 | } | |
155 | ||
156 | void FAudio_PlatformInit( | |
157 | FAudio *audio, | |
158 | uint32_t flags, | |
159 | uint32_t deviceIndex, | |
160 | FAudioWaveFormatExtensible *mixFormat, | |
161 | uint32_t *updateSize, | |
162 | void** platformDevice | |
163 | ) { | |
164 | struct FAudioAudioClientThreadArgs *args; | |
165 | struct FAudioWin32PlatformData *data; | |
166 | REFERENCE_TIME duration; | |
167 | WAVEFORMATEX *closest; | |
168 | IMMDevice *device = NULL; | |
169 | HRESULT hr; | |
170 | HANDLE audioEvent = NULL; | |
171 | BOOL has_sse2 = IsProcessorFeaturePresent(PF_XMMI64_INSTRUCTIONS_AVAILABLE); | |
172 | ||
173 | FAudio_INTERNAL_InitSIMDFunctions(has_sse2, FALSE); | |
174 | ||
175 | FAudio_PlatformAddRef(); | |
176 | ||
177 | *platformDevice = NULL; | |
178 | if (deviceIndex > 0) return; | |
179 | ||
180 | args = FAudio_malloc(sizeof(*args)); | |
181 | FAudio_assert(!!args && "Failed to allocate FAudio thread args!"); | |
182 | ||
183 | data = FAudio_malloc(sizeof(*data)); | |
184 | FAudio_assert(!!data && "Failed to allocate FAudio platform data!"); | |
185 | FAudio_zero(data, sizeof(*data)); | |
186 | ||
187 | args->format.Format.wFormatTag = mixFormat->Format.wFormatTag; | |
188 | args->format.Format.nChannels = mixFormat->Format.nChannels; | |
189 | args->format.Format.nSamplesPerSec = mixFormat->Format.nSamplesPerSec; | |
190 | args->format.Format.nAvgBytesPerSec = mixFormat->Format.nAvgBytesPerSec; | |
191 | args->format.Format.nBlockAlign = mixFormat->Format.nBlockAlign; | |
192 | args->format.Format.wBitsPerSample = mixFormat->Format.wBitsPerSample; | |
193 | args->format.Format.cbSize = mixFormat->Format.cbSize; | |
194 | ||
195 | if (args->format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) | |
196 | { | |
197 | args->format.Samples.wValidBitsPerSample = mixFormat->Samples.wValidBitsPerSample; | |
198 | args->format.dwChannelMask = mixFormat->dwChannelMask; | |
199 | FAudio_memcpy( | |
200 | &args->format.SubFormat, | |
201 | &mixFormat->SubFormat, | |
202 | sizeof(GUID) | |
203 | ); | |
204 | } | |
205 | ||
206 | audioEvent = CreateEventW(NULL, FALSE, FALSE, NULL); | |
207 | FAudio_assert(!!audioEvent && "Failed to create FAudio thread buffer event!"); | |
208 | ||
209 | data->stopEvent = CreateEventW(NULL, FALSE, FALSE, NULL); | |
210 | FAudio_assert(!!data->stopEvent && "Failed to create FAudio thread stop event!"); | |
211 | ||
212 | hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint( | |
213 | device_enumerator, | |
214 | eRender, | |
215 | eConsole, | |
216 | &device | |
217 | ); | |
218 | FAudio_assert(!FAILED(hr) && "Failed to get default audio endpoint!"); | |
219 | ||
220 | hr = IMMDevice_Activate( | |
221 | device, | |
222 | &IID_IAudioClient, | |
223 | CLSCTX_ALL, | |
224 | NULL, | |
225 | (void **)&data->client | |
226 | ); | |
227 | FAudio_assert(!FAILED(hr) && "Failed to create audio client!"); | |
228 | IMMDevice_Release(device); | |
229 | ||
230 | if (flags & FAUDIO_1024_QUANTUM) duration = 21330; | |
231 | else duration = 30000; | |
232 | ||
233 | hr = IAudioClient_IsFormatSupported( | |
234 | data->client, | |
235 | AUDCLNT_SHAREMODE_SHARED, | |
236 | &args->format.Format, | |
237 | &closest | |
238 | ); | |
239 | FAudio_assert(!FAILED(hr) && "Failed to find supported audio format!"); | |
240 | ||
241 | if (closest) | |
242 | { | |
243 | if (closest->wFormatTag != WAVE_FORMAT_EXTENSIBLE) args->format.Format = *closest; | |
244 | else args->format = *(WAVEFORMATEXTENSIBLE *)closest; | |
245 | CoTaskMemFree(closest); | |
246 | } | |
247 | ||
248 | hr = IAudioClient_Initialize( | |
249 | data->client, | |
250 | AUDCLNT_SHAREMODE_SHARED, | |
251 | AUDCLNT_STREAMFLAGS_EVENTCALLBACK, | |
252 | duration, | |
253 | 0, | |
254 | &args->format.Format, | |
255 | &GUID_NULL | |
256 | ); | |
257 | FAudio_assert(!FAILED(hr) && "Failed to initialize audio client!"); | |
258 | ||
259 | hr = IAudioClient_SetEventHandle(data->client, audioEvent); | |
260 | FAudio_assert(!FAILED(hr) && "Failed to set audio client event!"); | |
261 | ||
262 | mixFormat->Format.wFormatTag = args->format.Format.wFormatTag; | |
263 | mixFormat->Format.nChannels = args->format.Format.nChannels; | |
264 | mixFormat->Format.nSamplesPerSec = args->format.Format.nSamplesPerSec; | |
265 | mixFormat->Format.nAvgBytesPerSec = args->format.Format.nAvgBytesPerSec; | |
266 | mixFormat->Format.nBlockAlign = args->format.Format.nBlockAlign; | |
267 | mixFormat->Format.wBitsPerSample = args->format.Format.wBitsPerSample; | |
268 | ||
269 | if (args->format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) | |
270 | { | |
271 | mixFormat->Format.cbSize = sizeof(FAudioWaveFormatExtensible) - sizeof(FAudioWaveFormatEx); | |
272 | mixFormat->Samples.wValidBitsPerSample = args->format.Samples.wValidBitsPerSample; | |
273 | mixFormat->dwChannelMask = args->format.dwChannelMask; | |
274 | FAudio_memcpy( | |
275 | &mixFormat->SubFormat, | |
276 | &args->format.SubFormat, | |
277 | sizeof(GUID) | |
278 | ); | |
279 | } | |
280 | else | |
281 | { | |
282 | mixFormat->Format.cbSize = sizeof(FAudioWaveFormatEx); | |
283 | } | |
284 | ||
285 | args->client = data->client; | |
286 | args->events[0] = audioEvent; | |
287 | args->events[1] = data->stopEvent; | |
288 | args->audio = audio; | |
289 | args->updateSize = args->format.Format.nSamplesPerSec / 100; | |
290 | ||
291 | data->audioThread = CreateThread(NULL, 0, &FAudio_AudioClientThread, args, 0, NULL); | |
292 | FAudio_assert(!!data->audioThread && "Failed to create audio client thread!"); | |
293 | ||
294 | *updateSize = args->updateSize; | |
295 | *platformDevice = data; | |
296 | return; | |
297 | } | |
298 | ||
299 | void FAudio_PlatformQuit(void* platformDevice) | |
300 | { | |
301 | struct FAudioWin32PlatformData *data = platformDevice; | |
302 | ||
303 | SetEvent(data->stopEvent); | |
304 | WaitForSingleObject(data->audioThread, INFINITE); | |
305 | if (data->client) IAudioClient_Release(data->client); | |
306 | FAudio_PlatformRelease(); | |
307 | } | |
308 | ||
309 | void FAudio_PlatformAddRef() | |
310 | { | |
311 | HRESULT hr; | |
312 | EnterCriticalSection(&faudio_cs); | |
313 | if (!device_enumerator) | |
314 | { | |
315 | init_hr = CoInitialize(NULL); | |
316 | hr = CoCreateInstance( | |
317 | &CLSID_MMDeviceEnumerator, | |
318 | NULL, | |
319 | CLSCTX_INPROC_SERVER, | |
320 | &IID_IMMDeviceEnumerator, | |
321 | (void**)&device_enumerator | |
322 | ); | |
323 | FAudio_assert(!FAILED(hr) && "CoCreateInstance failed!"); | |
324 | } | |
325 | else IMMDeviceEnumerator_AddRef(device_enumerator); | |
326 | LeaveCriticalSection(&faudio_cs); | |
327 | } | |
328 | ||
329 | void FAudio_PlatformRelease() | |
330 | { | |
331 | EnterCriticalSection(&faudio_cs); | |
332 | if (!IMMDeviceEnumerator_Release(device_enumerator)) | |
333 | { | |
334 | device_enumerator = NULL; | |
335 | if (SUCCEEDED(init_hr)) CoUninitialize(); | |
336 | } | |
337 | LeaveCriticalSection(&faudio_cs); | |
338 | } | |
339 | ||
340 | uint32_t FAudio_PlatformGetDeviceCount(void) | |
341 | { | |
342 | IMMDevice *device; | |
343 | uint32_t count; | |
344 | HRESULT hr; | |
345 | ||
346 | FAudio_PlatformAddRef(); | |
347 | hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint( | |
348 | device_enumerator, | |
349 | eRender, | |
350 | eConsole, | |
351 | &device | |
352 | ); | |
353 | FAudio_assert(!FAILED(hr) && "Failed to get default audio endpoint!"); | |
354 | ||
355 | IMMDevice_Release(device); | |
356 | FAudio_PlatformRelease(); | |
357 | ||
358 | return 1; | |
359 | } | |
360 | ||
361 | uint32_t FAudio_PlatformGetDeviceDetails( | |
362 | uint32_t index, | |
363 | FAudioDeviceDetails *details | |
364 | ) { | |
365 | WAVEFORMATEXTENSIBLE *ext; | |
366 | WAVEFORMATEX *format; | |
367 | IAudioClient *client; | |
368 | IMMDevice *device; | |
369 | uint32_t ret = 0; | |
370 | HRESULT hr; | |
371 | WCHAR *str; | |
372 | ||
373 | FAudio_memset(details, 0, sizeof(FAudioDeviceDetails)); | |
374 | if (index > 0) return FAUDIO_E_INVALID_CALL; | |
375 | ||
376 | FAudio_PlatformAddRef(); | |
377 | ||
378 | hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint( | |
379 | device_enumerator, | |
380 | eRender, | |
381 | eConsole, | |
382 | &device | |
383 | ); | |
384 | FAudio_assert(!FAILED(hr) && "Failed to get default audio endpoint!"); | |
385 | ||
386 | details->Role = FAudioGlobalDefaultDevice; | |
387 | ||
388 | hr = IMMDevice_GetId(device, &str); | |
389 | FAudio_assert(!FAILED(hr) && "Failed to get audio endpoint id!"); | |
390 | ||
391 | lstrcpynW(details->DeviceID, str, ARRAYSIZE(details->DeviceID) - 1); | |
392 | lstrcpynW(details->DisplayName, str, ARRAYSIZE(details->DisplayName) - 1); | |
393 | CoTaskMemFree(str); | |
394 | ||
395 | hr = IMMDevice_Activate( | |
396 | device, | |
397 | &IID_IAudioClient, | |
398 | CLSCTX_ALL, | |
399 | NULL, | |
400 | (void **)&client | |
401 | ); | |
402 | FAudio_assert(!FAILED(hr) && "Failed to activate audio client!"); | |
403 | ||
404 | hr = IAudioClient_GetMixFormat(client, &format); | |
405 | FAudio_assert(!FAILED(hr) && "Failed to get audio client mix format!"); | |
406 | ||
407 | details->OutputFormat.Format.wFormatTag = format->wFormatTag; | |
408 | details->OutputFormat.Format.nChannels = format->nChannels; | |
409 | details->OutputFormat.Format.nSamplesPerSec = format->nSamplesPerSec; | |
410 | details->OutputFormat.Format.nAvgBytesPerSec = format->nAvgBytesPerSec; | |
411 | details->OutputFormat.Format.nBlockAlign = format->nBlockAlign; | |
412 | details->OutputFormat.Format.wBitsPerSample = format->wBitsPerSample; | |
413 | details->OutputFormat.Format.cbSize = format->cbSize; | |
414 | ||
415 | if (format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) | |
416 | { | |
417 | ext = (WAVEFORMATEXTENSIBLE *)format; | |
418 | details->OutputFormat.Samples.wValidBitsPerSample = ext->Samples.wValidBitsPerSample; | |
419 | details->OutputFormat.dwChannelMask = ext->dwChannelMask; | |
420 | FAudio_memcpy( | |
421 | &details->OutputFormat.SubFormat, | |
422 | &ext->SubFormat, | |
423 | sizeof(GUID) | |
424 | ); | |
425 | } | |
426 | ||
427 | IAudioClient_Release(client); | |
428 | ||
429 | IMMDevice_Release(device); | |
430 | ||
431 | FAudio_PlatformRelease(); | |
432 | ||
433 | return ret; | |
434 | } | |
435 | ||
436 | FAudioMutex FAudio_PlatformCreateMutex(void) | |
437 | { | |
438 | CRITICAL_SECTION *cs; | |
439 | ||
440 | cs = FAudio_malloc(sizeof(CRITICAL_SECTION)); | |
441 | if (!cs) return NULL; | |
442 | ||
443 | InitializeCriticalSection(cs); | |
444 | ||
445 | return cs; | |
446 | } | |
447 | ||
448 | void FAudio_PlatformLockMutex(FAudioMutex mutex) | |
449 | { | |
450 | if (mutex) EnterCriticalSection(mutex); | |
451 | } | |
452 | ||
453 | void FAudio_PlatformUnlockMutex(FAudioMutex mutex) | |
454 | { | |
455 | if (mutex) LeaveCriticalSection(mutex); | |
456 | } | |
457 | ||
458 | void FAudio_PlatformDestroyMutex(FAudioMutex mutex) | |
459 | { | |
460 | if (mutex) DeleteCriticalSection(mutex); | |
461 | FAudio_free(mutex); | |
462 | } | |
463 | ||
464 | struct FAudioThreadArgs | |
465 | { | |
466 | FAudioThreadFunc func; | |
467 | const char *name; | |
468 | void* data; | |
469 | }; | |
470 | ||
471 | static DWORD WINAPI FaudioThreadWrapper(void *user) | |
472 | { | |
473 | struct FAudioThreadArgs *args = user; | |
474 | DWORD ret; | |
475 | ||
476 | ret = args->func(args->data); | |
477 | ||
478 | FAudio_free(args); | |
479 | return ret; | |
480 | } | |
481 | ||
482 | FAudioThread FAudio_PlatformCreateThread( | |
483 | FAudioThreadFunc func, | |
484 | const char *name, | |
485 | void* data | |
486 | ) { | |
487 | struct FAudioThreadArgs *args; | |
488 | ||
489 | if (!(args = FAudio_malloc(sizeof(*args)))) return NULL; | |
490 | args->func = func; | |
491 | args->name = name; | |
492 | args->data = data; | |
493 | ||
494 | return CreateThread(NULL, 0, &FaudioThreadWrapper, args, 0, NULL); | |
495 | } | |
496 | ||
497 | void FAudio_PlatformWaitThread(FAudioThread thread, int32_t *retval) | |
498 | { | |
499 | WaitForSingleObject(thread, INFINITE); | |
500 | GetExitCodeThread(thread, (DWORD *)retval); | |
501 | } | |
502 | ||
503 | void FAudio_PlatformThreadPriority(FAudioThreadPriority priority) | |
504 | { | |
505 | /* FIXME */ | |
506 | } | |
507 | ||
508 | uint64_t FAudio_PlatformGetThreadID(void) | |
509 | { | |
510 | return GetCurrentThreadId(); | |
511 | } | |
512 | ||
513 | void FAudio_sleep(uint32_t ms) | |
514 | { | |
515 | Sleep(ms); | |
516 | } | |
517 | ||
518 | uint32_t FAudio_timems() | |
519 | { | |
520 | return GetTickCount(); | |
521 | } | |
522 | ||
523 | /* FAudio I/O */ | |
524 | ||
525 | static size_t FAUDIOCALL FAudio_FILE_read( | |
526 | void *data, | |
527 | void *dst, | |
528 | size_t size, | |
529 | size_t count | |
530 | ) { | |
531 | if (!data) return 0; | |
532 | return fread(dst, size, count, data); | |
533 | } | |
534 | ||
535 | static int64_t FAUDIOCALL FAudio_FILE_seek( | |
536 | void *data, | |
537 | int64_t offset, | |
538 | int whence | |
539 | ) { | |
540 | if (!data) return -1; | |
541 | fseek(data, offset, whence); | |
542 | return ftell(data); | |
543 | } | |
544 | ||
545 | static int FAUDIOCALL FAudio_FILE_close(void *data) | |
546 | { | |
547 | if (!data) return 0; | |
548 | fclose(data); | |
549 | return 0; | |
550 | } | |
551 | ||
552 | FAudioIOStream* FAudio_fopen(const char *path) | |
553 | { | |
554 | FAudioIOStream *io; | |
555 | ||
556 | io = (FAudioIOStream*) FAudio_malloc(sizeof(FAudioIOStream)); | |
557 | if (!io) return NULL; | |
558 | ||
559 | io->data = fopen(path, "rb"); | |
560 | io->read = FAudio_FILE_read; | |
561 | io->seek = FAudio_FILE_seek; | |
562 | io->close = FAudio_FILE_close; | |
563 | io->lock = FAudio_PlatformCreateMutex(); | |
564 | ||
565 | return io; | |
566 | } | |
567 | ||
568 | struct FAudio_mem | |
569 | { | |
570 | char *mem; | |
571 | int64_t len; | |
572 | int64_t pos; | |
573 | }; | |
574 | ||
575 | static size_t FAUDIOCALL FAudio_mem_read( | |
576 | void *data, | |
577 | void *dst, | |
578 | size_t size, | |
579 | size_t count | |
580 | ) { | |
581 | struct FAudio_mem *io = data; | |
582 | size_t len = size * count; | |
583 | ||
584 | if (!data) return 0; | |
585 | ||
586 | while (len && len > (io->len - io->pos)) len -= size; | |
587 | FAudio_memcpy(dst, io->mem + io->pos, len); | |
588 | io->pos += len; | |
589 | ||
590 | return len; | |
591 | } | |
592 | ||
593 | static int64_t FAUDIOCALL FAudio_mem_seek( | |
594 | void *data, | |
595 | int64_t offset, | |
596 | int whence | |
597 | ) { | |
598 | struct FAudio_mem *io = data; | |
599 | if (!data) return -1; | |
600 | ||
601 | if (whence == SEEK_SET) | |
602 | { | |
603 | if (io->len > offset) io->pos = offset; | |
604 | else io->pos = io->len; | |
605 | } | |
606 | if (whence == SEEK_CUR) | |
607 | { | |
608 | if (io->len > io->pos + offset) io->pos += offset; | |
609 | else io->pos = io->len; | |
610 | } | |
611 | if (whence == SEEK_END) | |
612 | { | |
613 | if (io->len > offset) io->pos = io->len - offset; | |
614 | else io->pos = 0; | |
615 | } | |
616 | ||
617 | return io->pos; | |
618 | } | |
619 | ||
620 | static int FAUDIOCALL FAudio_mem_close(void *data) | |
621 | { | |
622 | if (!data) return 0; | |
623 | FAudio_free(data); | |
624 | } | |
625 | ||
626 | FAudioIOStream* FAudio_memopen(void *mem, int len) | |
627 | { | |
628 | struct FAudio_mem *data; | |
629 | FAudioIOStream *io; | |
630 | ||
631 | io = (FAudioIOStream*) FAudio_malloc(sizeof(FAudioIOStream)); | |
632 | if (!io) return NULL; | |
633 | ||
634 | data = FAudio_malloc(sizeof(struct FAudio_mem)); | |
635 | if (!data) | |
636 | { | |
637 | FAudio_free(io); | |
638 | return NULL; | |
639 | } | |
640 | ||
641 | data->mem = mem; | |
642 | data->len = len; | |
643 | data->pos = 0; | |
644 | ||
645 | io->data = data; | |
646 | io->read = FAudio_mem_read; | |
647 | io->seek = FAudio_mem_seek; | |
648 | io->close = FAudio_mem_close; | |
649 | io->lock = FAudio_PlatformCreateMutex(); | |
650 | return io; | |
651 | } | |
652 | ||
653 | uint8_t* FAudio_memptr(FAudioIOStream *io, size_t offset) | |
654 | { | |
655 | struct FAudio_mem *memio = io->data; | |
656 | return memio->mem + offset; | |
657 | } | |
658 | ||
659 | void FAudio_close(FAudioIOStream *io) | |
660 | { | |
661 | io->close(io->data); | |
662 | FAudio_PlatformDestroyMutex((FAudioMutex) io->lock); | |
663 | FAudio_free(io); | |
664 | } | |
665 | ||
666 | /* XNA Song implementation over Win32 MF */ | |
667 | ||
668 | static FAudioWaveFormatEx activeSongFormat; | |
669 | IMFSourceReader *activeSong; | |
670 | static uint8_t *songBuffer; | |
671 | static SIZE_T songBufferSize; | |
672 | ||
673 | static float songVolume = 1.0f; | |
674 | static FAudio *songAudio = NULL; | |
675 | static FAudioMasteringVoice *songMaster = NULL; | |
676 | ||
677 | static FAudioSourceVoice *songVoice = NULL; | |
678 | static FAudioVoiceCallback callbacks; | |
679 | ||
680 | /* Internal Functions */ | |
681 | ||
682 | static void XNA_SongSubmitBuffer(FAudioVoiceCallback *callback, void *pBufferContext) | |
683 | { | |
684 | IMFMediaBuffer *media_buffer; | |
685 | FAudioBuffer buffer; | |
686 | IMFSample *sample; | |
687 | HRESULT hr; | |
688 | DWORD flags, buffer_size = 0; | |
689 | BYTE *buffer_ptr; | |
690 | ||
691 | LOG_FUNC_ENTER(songAudio); | |
692 | ||
693 | FAudio_memset(&buffer, 0, sizeof(buffer)); | |
694 | ||
695 | hr = IMFSourceReader_ReadSample( | |
696 | activeSong, | |
697 | MF_SOURCE_READER_FIRST_AUDIO_STREAM, | |
698 | 0, | |
699 | NULL, | |
700 | &flags, | |
701 | NULL, | |
702 | &sample | |
703 | ); | |
704 | FAudio_assert(!FAILED(hr) && "Failed to read audio sample!"); | |
705 | ||
706 | if (flags & MF_SOURCE_READERF_ENDOFSTREAM) | |
707 | { | |
708 | buffer.Flags = FAUDIO_END_OF_STREAM; | |
709 | } | |
710 | else | |
711 | { | |
712 | hr = IMFSample_ConvertToContiguousBuffer( | |
713 | sample, | |
714 | &media_buffer | |
715 | ); | |
716 | FAudio_assert(!FAILED(hr) && "Failed to get sample buffer!"); | |
717 | ||
718 | hr = IMFMediaBuffer_Lock( | |
719 | media_buffer, | |
720 | &buffer_ptr, | |
721 | NULL, | |
722 | &buffer_size | |
723 | ); | |
724 | FAudio_assert(!FAILED(hr) && "Failed to lock buffer bytes!"); | |
725 | ||
726 | if (songBufferSize < buffer_size) | |
727 | { | |
728 | songBufferSize = buffer_size; | |
729 | songBuffer = FAudio_realloc(songBuffer, songBufferSize); | |
730 | FAudio_assert(songBuffer != NULL && "Failed to allocate song buffer!"); | |
731 | } | |
732 | FAudio_memcpy(songBuffer, buffer_ptr, buffer_size); | |
733 | ||
734 | hr = IMFMediaBuffer_Unlock(media_buffer); | |
735 | FAudio_assert(!FAILED(hr) && "Failed to unlock buffer bytes!"); | |
736 | ||
737 | IMFMediaBuffer_Release(media_buffer); | |
738 | IMFSample_Release(sample); | |
739 | } | |
740 | ||
741 | if (buffer_size > 0) | |
742 | { | |
743 | buffer.AudioBytes = buffer_size; | |
744 | buffer.pAudioData = songBuffer; | |
745 | buffer.PlayBegin = 0; | |
746 | buffer.PlayLength = buffer_size / activeSongFormat.nBlockAlign; | |
747 | buffer.LoopBegin = 0; | |
748 | buffer.LoopLength = 0; | |
749 | buffer.LoopCount = 0; | |
750 | buffer.pContext = NULL; | |
751 | FAudioSourceVoice_SubmitSourceBuffer( | |
752 | songVoice, | |
753 | &buffer, | |
754 | NULL | |
755 | ); | |
756 | } | |
757 | ||
758 | LOG_FUNC_EXIT(songAudio); | |
759 | } | |
760 | ||
761 | static void XNA_SongKill() | |
762 | { | |
763 | if (songVoice != NULL) | |
764 | { | |
765 | FAudioSourceVoice_Stop(songVoice, 0, 0); | |
766 | FAudioVoice_DestroyVoice(songVoice); | |
767 | songVoice = NULL; | |
768 | } | |
769 | if (activeSong) | |
770 | { | |
771 | IMFSourceReader_Release(activeSong); | |
772 | activeSong = NULL; | |
773 | } | |
774 | FAudio_free(songBuffer); | |
775 | songBuffer = NULL; | |
776 | songBufferSize = 0; | |
777 | } | |
778 | ||
779 | /* "Public" API */ | |
780 | ||
781 | FAUDIOAPI void XNA_SongInit() | |
782 | { | |
783 | HRESULT hr; | |
784 | ||
785 | hr = MFStartup(MF_VERSION, MFSTARTUP_FULL); | |
786 | FAudio_assert(!FAILED(hr) && "Failed to initialize Media Foundation!"); | |
787 | ||
788 | FAudioCreate(&songAudio, 0, FAUDIO_DEFAULT_PROCESSOR); | |
789 | FAudio_CreateMasteringVoice( | |
790 | songAudio, | |
791 | &songMaster, | |
792 | FAUDIO_DEFAULT_CHANNELS, | |
793 | FAUDIO_DEFAULT_SAMPLERATE, | |
794 | 0, | |
795 | 0, | |
796 | NULL | |
797 | ); | |
798 | } | |
799 | ||
800 | FAUDIOAPI void XNA_SongQuit() | |
801 | { | |
802 | XNA_SongKill(); | |
803 | FAudioVoice_DestroyVoice(songMaster); | |
804 | FAudio_Release(songAudio); | |
805 | MFShutdown(); | |
806 | } | |
807 | ||
808 | FAUDIOAPI float XNA_PlaySong(const char *name) | |
809 | { | |
810 | IMFAttributes *attributes = NULL; | |
811 | IMFMediaType *media_type = NULL; | |
812 | UINT32 channels, samplerate; | |
813 | UINT64 duration; | |
814 | PROPVARIANT var; | |
815 | HRESULT hr; | |
816 | WCHAR filename_w[MAX_PATH]; | |
817 | ||
818 | LOG_FUNC_ENTER(songAudio); | |
819 | LOG_INFO(songAudio, "name %s\n", name); | |
820 | XNA_SongKill(); | |
821 | ||
822 | MultiByteToWideChar(CP_UTF8, 0, name, -1, filename_w, MAX_PATH); | |
823 | ||
824 | hr = MFCreateAttributes(&attributes, 1); | |
825 | FAudio_assert(!FAILED(hr) && "Failed to create attributes!"); | |
826 | hr = MFCreateSourceReaderFromURL( | |
827 | filename_w, | |
828 | attributes, | |
829 | &activeSong | |
830 | ); | |
831 | FAudio_assert(!FAILED(hr) && "Failed to create source reader!"); | |
832 | IMFAttributes_Release(attributes); | |
833 | ||
834 | hr = MFCreateMediaType(&media_type); | |
835 | FAudio_assert(!FAILED(hr) && "Failed to create media type!"); | |
836 | hr = IMFMediaType_SetGUID( | |
837 | media_type, | |
838 | &MF_MT_MAJOR_TYPE, | |
839 | &MFMediaType_Audio | |
840 | ); | |
841 | FAudio_assert(!FAILED(hr) && "Failed to set major type!"); | |
842 | hr = IMFMediaType_SetGUID( | |
843 | media_type, | |
844 | &MF_MT_SUBTYPE, | |
845 | &MFAudioFormat_Float | |
846 | ); | |
847 | FAudio_assert(!FAILED(hr) && "Failed to set sub type!"); | |
848 | hr = IMFSourceReader_SetCurrentMediaType( | |
849 | activeSong, | |
850 | MF_SOURCE_READER_FIRST_AUDIO_STREAM, | |
851 | NULL, | |
852 | media_type | |
853 | ); | |
854 | FAudio_assert(!FAILED(hr) && "Failed to set source media type!"); | |
855 | hr = IMFSourceReader_SetStreamSelection( | |
856 | activeSong, | |
857 | MF_SOURCE_READER_FIRST_AUDIO_STREAM, | |
858 | TRUE | |
859 | ); | |
860 | FAudio_assert(!FAILED(hr) && "Failed to select source stream!"); | |
861 | IMFMediaType_Release(media_type); | |
862 | ||
863 | hr = IMFSourceReader_GetCurrentMediaType( | |
864 | activeSong, | |
865 | MF_SOURCE_READER_FIRST_AUDIO_STREAM, | |
866 | &media_type | |
867 | ); | |
868 | FAudio_assert(!FAILED(hr) && "Failed to get current media type!"); | |
869 | hr = IMFMediaType_GetUINT32( | |
870 | media_type, | |
871 | &MF_MT_AUDIO_NUM_CHANNELS, | |
872 | &channels | |
873 | ); | |
874 | FAudio_assert(!FAILED(hr) && "Failed to get channel count!"); | |
875 | hr = IMFMediaType_GetUINT32( | |
876 | media_type, | |
877 | &MF_MT_AUDIO_SAMPLES_PER_SECOND, | |
878 | &samplerate | |
879 | ); | |
880 | FAudio_assert(!FAILED(hr) && "Failed to get sample rate!"); | |
881 | IMFMediaType_Release(media_type); | |
882 | ||
883 | hr = IMFSourceReader_GetPresentationAttribute( | |
884 | activeSong, | |
885 | MF_SOURCE_READER_MEDIASOURCE, | |
886 | &MF_PD_DURATION, | |
887 | &var | |
888 | ); | |
889 | FAudio_assert(!FAILED(hr) && "Failed to get song duration!"); | |
890 | hr = PropVariantToInt64(&var, &duration); | |
891 | FAudio_assert(!FAILED(hr) && "Failed to get song duration!"); | |
892 | PropVariantClear(&var); | |
893 | ||
894 | activeSongFormat.wFormatTag = FAUDIO_FORMAT_IEEE_FLOAT; | |
895 | activeSongFormat.nChannels = channels; | |
896 | activeSongFormat.nSamplesPerSec = samplerate; | |
897 | activeSongFormat.wBitsPerSample = sizeof(float) * 8; | |
898 | activeSongFormat.nBlockAlign = activeSongFormat.nChannels * activeSongFormat.wBitsPerSample / 8; | |
899 | activeSongFormat.nAvgBytesPerSec = activeSongFormat.nSamplesPerSec * activeSongFormat.nBlockAlign; | |
900 | activeSongFormat.cbSize = 0; | |
901 | ||
902 | /* Init voice */ | |
903 | FAudio_zero(&callbacks, sizeof(FAudioVoiceCallback)); | |
904 | callbacks.OnBufferEnd = XNA_SongSubmitBuffer; | |
905 | FAudio_CreateSourceVoice( | |
906 | songAudio, | |
907 | &songVoice, | |
908 | &activeSongFormat, | |
909 | 0, | |
910 | 1.0f, /* No pitch shifting here! */ | |
911 | &callbacks, | |
912 | NULL, | |
913 | NULL | |
914 | ); | |
915 | FAudioVoice_SetVolume(songVoice, songVolume, 0); | |
916 | XNA_SongSubmitBuffer(NULL, NULL); | |
917 | ||
918 | /* Finally. */ | |
919 | FAudioSourceVoice_Start(songVoice, 0, 0); | |
920 | LOG_FUNC_EXIT(songAudio); | |
921 | return duration / 10000000.; | |
922 | } | |
923 | ||
924 | FAUDIOAPI void XNA_PauseSong() | |
925 | { | |
926 | if (songVoice == NULL) | |
927 | { | |
928 | return; | |
929 | } | |
930 | FAudioSourceVoice_Stop(songVoice, 0, 0); | |
931 | } | |
932 | ||
933 | FAUDIOAPI void XNA_ResumeSong() | |
934 | { | |
935 | if (songVoice == NULL) | |
936 | { | |
937 | return; | |
938 | } | |
939 | FAudioSourceVoice_Start(songVoice, 0, 0); | |
940 | } | |
941 | ||
942 | FAUDIOAPI void XNA_StopSong() | |
943 | { | |
944 | XNA_SongKill(); | |
945 | } | |
946 | ||
947 | FAUDIOAPI void XNA_SetSongVolume(float volume) | |
948 | { | |
949 | songVolume = volume; | |
950 | if (songVoice != NULL) | |
951 | { | |
952 | FAudioVoice_SetVolume(songVoice, songVolume, 0); | |
953 | } | |
954 | } | |
955 | ||
956 | FAUDIOAPI uint32_t XNA_GetSongEnded() | |
957 | { | |
958 | FAudioVoiceState state; | |
959 | if (songVoice == NULL || activeSong == NULL) | |
960 | { | |
961 | return 1; | |
962 | } | |
963 | FAudioSourceVoice_GetState(songVoice, &state, 0); | |
964 | return state.BuffersQueued == 0; | |
965 | } | |
966 | ||
967 | FAUDIOAPI void XNA_EnableVisualization(uint32_t enable) | |
968 | { | |
969 | /* TODO: Enable/Disable FAPO effect */ | |
970 | } | |
971 | ||
972 | FAUDIOAPI uint32_t XNA_VisualizationEnabled() | |
973 | { | |
974 | /* TODO: Query FAPO effect enabled */ | |
975 | return 0; | |
976 | } | |
977 | ||
978 | FAUDIOAPI void XNA_GetSongVisualizationData( | |
979 | float *frequencies, | |
980 | float *samples, | |
981 | uint32_t count | |
982 | ) { | |
983 | /* TODO: Visualization FAPO that reads in Song samples, FFT analysis */ | |
984 | } | |
985 | ||
986 | /* FAudio WMADEC implementation over Win32 MF */ | |
987 | ||
988 | struct FAudioWMADEC | |
989 | { | |
990 | IMFTransform *decoder; | |
991 | IMFSample *output_sample; | |
992 | ||
993 | char *output_buf; | |
994 | size_t output_pos; | |
995 | size_t output_size; | |
996 | size_t input_pos; | |
997 | size_t input_size; | |
998 | }; | |
999 | ||
1000 | static BOOL FAudio_WMAMF_ProcessInput( | |
1001 | FAudioVoice *voice, | |
1002 | FAudioBuffer *buffer | |
1003 | ) { | |
1004 | struct FAudioWMADEC *impl = voice->src.wmadec; | |
1005 | IMFMediaBuffer *media_buffer; | |
1006 | IMFSample *sample; | |
1007 | DWORD copy_size; | |
1008 | BYTE *copy_buf; | |
1009 | HRESULT hr; | |
1010 | ||
1011 | copy_size = min(buffer->AudioBytes - impl->input_pos, impl->input_size); | |
1012 | if (!copy_size) return FALSE; | |
1013 | LOG_INFO(voice->audio, "pushing %x bytes at %x", copy_size, impl->input_pos); | |
1014 | ||
1015 | hr = MFCreateSample(&sample); | |
1016 | FAudio_assert(!FAILED(hr) && "Failed to create sample!"); | |
1017 | hr = MFCreateMemoryBuffer(copy_size, &media_buffer); | |
1018 | FAudio_assert(!FAILED(hr) && "Failed to create buffer!"); | |
1019 | hr = IMFMediaBuffer_SetCurrentLength(media_buffer, copy_size); | |
1020 | FAudio_assert(!FAILED(hr) && "Failed to set buffer length!"); | |
1021 | hr = IMFMediaBuffer_Lock( | |
1022 | media_buffer, | |
1023 | ©_buf, | |
1024 | NULL, | |
1025 | ©_size | |
1026 | ); | |
1027 | FAudio_assert(!FAILED(hr) && "Failed to lock buffer bytes!"); | |
1028 | FAudio_memcpy(copy_buf, buffer->pAudioData + impl->input_pos, copy_size); | |
1029 | hr = IMFMediaBuffer_Unlock(media_buffer); | |
1030 | FAudio_assert(!FAILED(hr) && "Failed to unlock buffer bytes!"); | |
1031 | ||
1032 | hr = IMFSample_AddBuffer(sample, media_buffer); | |
1033 | FAudio_assert(!FAILED(hr) && "Failed to buffer to sample!"); | |
1034 | IMFMediaBuffer_Release(media_buffer); | |
1035 | ||
1036 | hr = IMFTransform_ProcessInput(impl->decoder, 0, sample, 0); | |
1037 | IMFSample_Release(sample); | |
1038 | if (hr == MF_E_NOTACCEPTING) return TRUE; | |
1039 | if (FAILED(hr)) LOG_ERROR(voice->audio, "IMFTransform_ProcessInput returned %#x", hr); | |
1040 | FAudio_assert(!FAILED(hr) || !"Failed to process input sample!"); | |
1041 | ||
1042 | impl->input_pos += copy_size; | |
1043 | return TRUE; | |
1044 | }; | |
1045 | ||
1046 | static BOOL FAudio_WMAMF_ProcessOutput( | |
1047 | FAudioVoice *voice, | |
1048 | FAudioBuffer *buffer | |
1049 | ) { | |
1050 | struct FAudioWMADEC *impl = voice->src.wmadec; | |
1051 | MFT_OUTPUT_DATA_BUFFER output; | |
1052 | IMFMediaBuffer *media_buffer; | |
1053 | DWORD status, copy_size; | |
1054 | BYTE *copy_buf; | |
1055 | HRESULT hr; | |
1056 | ||
1057 | while (1) | |
1058 | { | |
1059 | FAudio_memset(&output, 0, sizeof(output)); | |
1060 | output.pSample = impl->output_sample; | |
1061 | hr = IMFTransform_ProcessOutput(impl->decoder, 0, 1, &output, &status); | |
1062 | if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) return FALSE; | |
1063 | if (FAILED(hr)) LOG_ERROR(voice->audio, "IMFTransform_ProcessInput returned %#x", hr); | |
1064 | FAudio_assert(!FAILED(hr) && "Failed to process output sample!"); | |
1065 | ||
1066 | if (output.dwStatus & MFT_OUTPUT_DATA_BUFFER_NO_SAMPLE) continue; | |
1067 | ||
1068 | hr = IMFSample_ConvertToContiguousBuffer( | |
1069 | output.pSample, | |
1070 | &media_buffer | |
1071 | ); | |
1072 | FAudio_assert(!FAILED(hr) && "Failed to get sample buffer!"); | |
1073 | hr = IMFMediaBuffer_Lock( | |
1074 | media_buffer, | |
1075 | ©_buf, | |
1076 | NULL, | |
1077 | ©_size | |
1078 | ); | |
1079 | FAudio_assert(!FAILED(hr) && "Failed to lock buffer bytes!"); | |
1080 | if (impl->output_pos + copy_size > impl->output_size) | |
1081 | { | |
1082 | impl->output_size = max( | |
1083 | impl->output_pos + copy_size, | |
1084 | impl->output_size * 3 / 2 | |
1085 | ); | |
1086 | impl->output_buf = voice->audio->pRealloc( | |
1087 | impl->output_buf, | |
1088 | impl->output_size | |
1089 | ); | |
1090 | FAudio_assert(impl->output_buf && "Failed to resize output buffer!"); | |
1091 | } | |
1092 | FAudio_memcpy(impl->output_buf + impl->output_pos, copy_buf, copy_size); | |
1093 | impl->output_pos += copy_size; | |
1094 | LOG_INFO(voice->audio, "pulled %x bytes at %x", copy_size, impl->output_pos); | |
1095 | hr = IMFMediaBuffer_Unlock(media_buffer); | |
1096 | FAudio_assert(!FAILED(hr) && "Failed to unlock buffer bytes!"); | |
1097 | ||
1098 | IMFMediaBuffer_Release(media_buffer); | |
1099 | if (!impl->output_sample) IMFSample_Release(output.pSample); | |
1100 | } | |
1101 | ||
1102 | return TRUE; | |
1103 | }; | |
1104 | ||
1105 | static void FAudio_INTERNAL_DecodeWMAMF( | |
1106 | FAudioVoice *voice, | |
1107 | FAudioBuffer *buffer, | |
1108 | float *decodeCache, | |
1109 | uint32_t samples | |
1110 | ) { | |
1111 | const FAudioWaveFormatExtensible *wfx = (FAudioWaveFormatExtensible *)voice->src.format; | |
1112 | struct FAudioWMADEC *impl = voice->src.wmadec; | |
1113 | size_t samples_pos, samples_size, copy_size; | |
1114 | HRESULT hr; | |
1115 | ||
1116 | LOG_FUNC_ENTER(voice->audio) | |
1117 | ||
1118 | if (!impl->output_pos) | |
1119 | { | |
1120 | if (wfx->Format.wFormatTag == FAUDIO_FORMAT_EXTENSIBLE) | |
1121 | { | |
1122 | const FAudioBufferWMA *wma = &voice->src.bufferList->bufferWMA; | |
1123 | const UINT32 *output_sizes = wma->pDecodedPacketCumulativeBytes; | |
1124 | ||
1125 | impl->input_size = wfx->Format.nBlockAlign; | |
1126 | impl->output_size = max( | |
1127 | impl->output_size, | |
1128 | output_sizes[wma->PacketCount - 1] | |
1129 | ); | |
1130 | } | |
1131 | else | |
1132 | { | |
1133 | const FAudioXMA2WaveFormat *xwf = (const FAudioXMA2WaveFormat *)wfx; | |
1134 | ||
1135 | impl->input_size = xwf->dwBytesPerBlock; | |
1136 | impl->output_size = max( | |
1137 | impl->output_size, | |
1138 | (size_t) xwf->dwSamplesEncoded * | |
1139 | voice->src.format->nChannels * | |
1140 | (voice->src.format->wBitsPerSample / 8) | |
1141 | ); | |
1142 | } | |
1143 | ||
1144 | impl->output_buf = voice->audio->pRealloc( | |
1145 | impl->output_buf, | |
1146 | impl->output_size | |
1147 | ); | |
1148 | FAudio_assert(impl->output_buf && "Failed to allocate output buffer!"); | |
1149 | ||
1150 | LOG_INFO(voice->audio, "sending BOS to %p", impl->decoder); | |
1151 | hr = IMFTransform_ProcessMessage( | |
1152 | impl->decoder, | |
1153 | MFT_MESSAGE_NOTIFY_START_OF_STREAM, | |
1154 | 0 | |
1155 | ); | |
1156 | FAudio_assert(!FAILED(hr) && "Failed to notify decoder stream start!"); | |
1157 | FAudio_WMAMF_ProcessInput(voice, buffer); | |
1158 | } | |
1159 | ||
1160 | samples_pos = voice->src.curBufferOffset * voice->src.format->nChannels * sizeof(float); | |
1161 | samples_size = samples * voice->src.format->nChannels * sizeof(float); | |
1162 | ||
1163 | while (impl->output_pos < samples_pos + samples_size) | |
1164 | { | |
1165 | if (FAudio_WMAMF_ProcessOutput(voice, buffer)) continue; | |
1166 | if (FAudio_WMAMF_ProcessInput(voice, buffer)) continue; | |
1167 | if (!impl->input_size) break; | |
1168 | ||
1169 | LOG_INFO(voice->audio, "sending EOS to %p", impl->decoder); | |
1170 | hr = IMFTransform_ProcessMessage( | |
1171 | impl->decoder, | |
1172 | MFT_MESSAGE_NOTIFY_END_OF_STREAM, | |
1173 | 0 | |
1174 | ); | |
1175 | FAudio_assert(!FAILED(hr) && "Failed to send EOS!"); | |
1176 | impl->input_size = 0; | |
1177 | } | |
1178 | ||
1179 | copy_size = FAudio_clamp(impl->output_pos - samples_pos, 0, samples_size); | |
1180 | FAudio_memcpy(decodeCache, impl->output_buf + samples_pos, copy_size); | |
1181 | LOG_INFO( | |
1182 | voice->audio, | |
1183 | "decoded %x / %x bytes, copied %x / %x bytes", | |
1184 | impl->output_pos, | |
1185 | impl->output_size, | |
1186 | copy_size, | |
1187 | samples_size | |
1188 | ); | |
1189 | ||
1190 | LOG_FUNC_EXIT(voice->audio) | |
1191 | } | |
1192 | ||
1193 | uint32_t FAudio_WMADEC_init(FAudioSourceVoice *voice, uint32_t type) | |
1194 | { | |
1195 | static const uint8_t fake_codec_data[16] = {0, 0, 0, 0, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; | |
1196 | const FAudioWaveFormatExtensible *wfx = (FAudioWaveFormatExtensible *)voice->src.format; | |
1197 | struct FAudioWMADEC *impl; | |
1198 | MFT_OUTPUT_STREAM_INFO info = {0}; | |
1199 | IMFMediaBuffer *media_buffer; | |
1200 | IMFMediaType *media_type; | |
1201 | IMFTransform *decoder; | |
1202 | HRESULT hr; | |
1203 | UINT32 i, value; | |
1204 | GUID guid; | |
1205 | ||
1206 | LOG_FUNC_ENTER(voice->audio) | |
1207 | ||
1208 | if (!(impl = voice->audio->pMalloc(sizeof(*impl)))) return 0; | |
1209 | FAudio_memset(impl, 0, sizeof(*impl)); | |
1210 | ||
1211 | hr = CoCreateInstance( | |
1212 | &CLSID_CWMADecMediaObject, | |
1213 | 0, | |
1214 | CLSCTX_INPROC_SERVER, | |
1215 | &IID_IMFTransform, | |
1216 | (void **)&decoder | |
1217 | ); | |
1218 | FAudio_assert(!FAILED(hr) && "Failed to create decoder!"); | |
1219 | ||
1220 | hr = MFCreateMediaType(&media_type); | |
1221 | FAudio_assert(!FAILED(hr) && "Failed create media type!"); | |
1222 | hr = IMFMediaType_SetGUID( | |
1223 | media_type, | |
1224 | &MF_MT_MAJOR_TYPE, | |
1225 | &MFMediaType_Audio | |
1226 | ); | |
1227 | FAudio_assert(!FAILED(hr) && "Failed set media major type!"); | |
1228 | ||
1229 | switch (type) | |
1230 | { | |
1231 | case FAUDIO_FORMAT_WMAUDIO2: | |
1232 | hr = IMFMediaType_SetBlob( | |
1233 | media_type, | |
1234 | &MF_MT_USER_DATA, | |
1235 | (void *)fake_codec_data, | |
1236 | sizeof(fake_codec_data) | |
1237 | ); | |
1238 | FAudio_assert(!FAILED(hr) && "Failed set codec private data!"); | |
1239 | hr = IMFMediaType_SetGUID( | |
1240 | media_type, | |
1241 | &MF_MT_SUBTYPE, | |
1242 | &MFAudioFormat_WMAudioV8 | |
1243 | ); | |
1244 | FAudio_assert(!FAILED(hr) && "Failed set media sub type!"); | |
1245 | hr = IMFMediaType_SetUINT32( | |
1246 | media_type, | |
1247 | &MF_MT_AUDIO_BLOCK_ALIGNMENT, | |
1248 | wfx->Format.nBlockAlign | |
1249 | ); | |
1250 | FAudio_assert(!FAILED(hr) && "Failed set input block align!"); | |
1251 | break; | |
1252 | case FAUDIO_FORMAT_WMAUDIO3: | |
1253 | hr = IMFMediaType_SetBlob( | |
1254 | media_type, | |
1255 | &MF_MT_USER_DATA, | |
1256 | (void *)&wfx->Samples, | |
1257 | wfx->Format.cbSize | |
1258 | ); | |
1259 | FAudio_assert(!FAILED(hr) && "Failed set codec private data!"); | |
1260 | hr = IMFMediaType_SetGUID( | |
1261 | media_type, | |
1262 | &MF_MT_SUBTYPE, | |
1263 | &MFAudioFormat_WMAudioV9 | |
1264 | ); | |
1265 | FAudio_assert(!FAILED(hr) && "Failed set media sub type!"); | |
1266 | hr = IMFMediaType_SetUINT32( | |
1267 | media_type, | |
1268 | &MF_MT_AUDIO_BLOCK_ALIGNMENT, | |
1269 | wfx->Format.nBlockAlign | |
1270 | ); | |
1271 | FAudio_assert(!FAILED(hr) && "Failed set input block align!"); | |
1272 | break; | |
1273 | case FAUDIO_FORMAT_WMAUDIO_LOSSLESS: | |
1274 | hr = IMFMediaType_SetBlob( | |
1275 | media_type, | |
1276 | &MF_MT_USER_DATA, | |
1277 | (void *)&wfx->Samples, | |
1278 | wfx->Format.cbSize | |
1279 | ); | |
1280 | FAudio_assert(!FAILED(hr) && "Failed set codec private data!"); | |
1281 | hr = IMFMediaType_SetGUID( | |
1282 | media_type, | |
1283 | &MF_MT_SUBTYPE, | |
1284 | &MFAudioFormat_WMAudio_Lossless | |
1285 | ); | |
1286 | FAudio_assert(!FAILED(hr) && "Failed set media sub type!"); | |
1287 | hr = IMFMediaType_SetUINT32( | |
1288 | media_type, | |
1289 | &MF_MT_AUDIO_BLOCK_ALIGNMENT, | |
1290 | wfx->Format.nBlockAlign | |
1291 | ); | |
1292 | FAudio_assert(!FAILED(hr) && "Failed set input block align!"); | |
1293 | break; | |
1294 | case FAUDIO_FORMAT_XMAUDIO2: | |
1295 | { | |
1296 | const FAudioXMA2WaveFormat *xwf = (const FAudioXMA2WaveFormat *)wfx; | |
1297 | hr = IMFMediaType_SetBlob( | |
1298 | media_type, | |
1299 | &MF_MT_USER_DATA, | |
1300 | (void *)&wfx->Samples, | |
1301 | wfx->Format.cbSize | |
1302 | ); | |
1303 | FAudio_assert(!FAILED(hr) && "Failed set codec private data!"); | |
1304 | hr = IMFMediaType_SetGUID( | |
1305 | media_type, | |
1306 | &MF_MT_SUBTYPE, | |
1307 | &MFAudioFormat_XMAudio2 | |
1308 | ); | |
1309 | FAudio_assert(!FAILED(hr) && "Failed set media sub type!"); | |
1310 | hr = IMFMediaType_SetUINT32( | |
1311 | media_type, | |
1312 | &MF_MT_AUDIO_BLOCK_ALIGNMENT, | |
1313 | xwf->dwBytesPerBlock | |
1314 | ); | |
1315 | FAudio_assert(!FAILED(hr) && "Failed set input block align!"); | |
1316 | break; | |
1317 | } | |
1318 | default: | |
1319 | FAudio_assert(0 && "Unsupported type!"); | |
1320 | break; | |
1321 | } | |
1322 | ||
1323 | hr = IMFMediaType_SetUINT32( | |
1324 | media_type, | |
1325 | &MF_MT_AUDIO_BITS_PER_SAMPLE, | |
1326 | wfx->Format.wBitsPerSample | |
1327 | ); | |
1328 | FAudio_assert(!FAILED(hr) && "Failed set input bits per sample!"); | |
1329 | hr = IMFMediaType_SetUINT32( | |
1330 | media_type, | |
1331 | &MF_MT_AUDIO_AVG_BYTES_PER_SECOND, | |
1332 | wfx->Format.nAvgBytesPerSec | |
1333 | ); | |
1334 | FAudio_assert(!FAILED(hr) && "Failed set input bytes per sample!"); | |
1335 | hr = IMFMediaType_SetUINT32( | |
1336 | media_type, | |
1337 | &MF_MT_AUDIO_NUM_CHANNELS, | |
1338 | wfx->Format.nChannels | |
1339 | ); | |
1340 | FAudio_assert(!FAILED(hr) && "Failed set input channel count!"); | |
1341 | hr = IMFMediaType_SetUINT32( | |
1342 | media_type, | |
1343 | &MF_MT_AUDIO_SAMPLES_PER_SECOND, | |
1344 | wfx->Format.nSamplesPerSec | |
1345 | ); | |
1346 | FAudio_assert(!FAILED(hr) && "Failed set input sample rate!"); | |
1347 | ||
1348 | hr = IMFTransform_SetInputType( | |
1349 | decoder, | |
1350 | 0, | |
1351 | media_type, | |
1352 | 0 | |
1353 | ); | |
1354 | FAudio_assert(!FAILED(hr) && "Failed set decoder input type!"); | |
1355 | IMFMediaType_Release(media_type); | |
1356 | ||
1357 | i = 0; | |
1358 | while (SUCCEEDED(hr)) | |
1359 | { | |
1360 | hr = IMFTransform_GetOutputAvailableType( | |
1361 | decoder, | |
1362 | 0, | |
1363 | i++, | |
1364 | &media_type | |
1365 | ); | |
1366 | FAudio_assert(!FAILED(hr) && "Failed get output media type!"); | |
1367 | ||
1368 | hr = IMFMediaType_GetGUID( | |
1369 | media_type, | |
1370 | &MF_MT_MAJOR_TYPE, | |
1371 | &guid | |
1372 | ); | |
1373 | FAudio_assert(!FAILED(hr) && "Failed get media major type!"); | |
1374 | if (!IsEqualGUID(&MFMediaType_Audio, &guid)) goto next; | |
1375 | ||
1376 | hr = IMFMediaType_GetGUID( | |
1377 | media_type, | |
1378 | &MF_MT_SUBTYPE, | |
1379 | &guid | |
1380 | ); | |
1381 | FAudio_assert(!FAILED(hr) && "Failed get media major type!"); | |
1382 | if (!IsEqualGUID(&MFAudioFormat_Float, &guid)) goto next; | |
1383 | ||
1384 | hr = IMFMediaType_GetUINT32( | |
1385 | media_type, | |
1386 | &MF_MT_AUDIO_BITS_PER_SAMPLE, | |
1387 | &value | |
1388 | ); | |
1389 | if (FAILED(hr)) | |
1390 | { | |
1391 | value = 32; | |
1392 | hr = IMFMediaType_SetUINT32( | |
1393 | media_type, | |
1394 | &MF_MT_AUDIO_BITS_PER_SAMPLE, | |
1395 | value | |
1396 | ); | |
1397 | } | |
1398 | FAudio_assert(!FAILED(hr) && "Failed get bits per sample!"); | |
1399 | if (value != 32) goto next; | |
1400 | ||
1401 | hr = IMFMediaType_GetUINT32( | |
1402 | media_type, | |
1403 | &MF_MT_AUDIO_NUM_CHANNELS, | |
1404 | &value | |
1405 | ); | |
1406 | if (FAILED(hr)) | |
1407 | { | |
1408 | value = wfx->Format.nChannels; | |
1409 | hr = IMFMediaType_SetUINT32( | |
1410 | media_type, | |
1411 | &MF_MT_AUDIO_NUM_CHANNELS, | |
1412 | value | |
1413 | ); | |
1414 | } | |
1415 | FAudio_assert(!FAILED(hr) && "Failed get channel count!"); | |
1416 | if (value != wfx->Format.nChannels) goto next; | |
1417 | ||
1418 | hr = IMFMediaType_GetUINT32( | |
1419 | media_type, | |
1420 | &MF_MT_AUDIO_SAMPLES_PER_SECOND, | |
1421 | &value | |
1422 | ); | |
1423 | if (FAILED(hr)) | |
1424 | { | |
1425 | value = wfx->Format.nSamplesPerSec; | |
1426 | hr = IMFMediaType_SetUINT32( | |
1427 | media_type, | |
1428 | &MF_MT_AUDIO_SAMPLES_PER_SECOND, | |
1429 | value | |
1430 | ); | |
1431 | } | |
1432 | FAudio_assert(!FAILED(hr) && "Failed get sample rate!"); | |
1433 | if (value != wfx->Format.nSamplesPerSec) goto next; | |
1434 | ||
1435 | hr = IMFMediaType_GetUINT32( | |
1436 | media_type, | |
1437 | &MF_MT_AUDIO_BLOCK_ALIGNMENT, | |
1438 | &value | |
1439 | ); | |
1440 | if (FAILED(hr)) | |
1441 | { | |
1442 | value = wfx->Format.nChannels * sizeof(float); | |
1443 | hr = IMFMediaType_SetUINT32( | |
1444 | media_type, | |
1445 | &MF_MT_AUDIO_BLOCK_ALIGNMENT, | |
1446 | value | |
1447 | ); | |
1448 | } | |
1449 | FAudio_assert(!FAILED(hr) && "Failed get block align!"); | |
1450 | if (value == wfx->Format.nChannels * sizeof(float)) break; | |
1451 | ||
1452 | next: | |
1453 | IMFMediaType_Release(media_type); | |
1454 | } | |
1455 | FAudio_assert(!FAILED(hr) && "Failed to find output media type!"); | |
1456 | hr = IMFTransform_SetOutputType(decoder, 0, media_type, 0); | |
1457 | FAudio_assert(!FAILED(hr) && "Failed set decoder output type!"); | |
1458 | IMFMediaType_Release(media_type); | |
1459 | ||
1460 | hr = IMFTransform_GetOutputStreamInfo(decoder, 0, &info); | |
1461 | FAudio_assert(!FAILED(hr) && "Failed to get output stream info!"); | |
1462 | ||
1463 | impl->decoder = decoder; | |
1464 | if (!(info.dwFlags & MFT_OUTPUT_STREAM_CAN_PROVIDE_SAMPLES)) | |
1465 | { | |
1466 | hr = MFCreateSample(&impl->output_sample); | |
1467 | FAudio_assert(!FAILED(hr) && "Failed to create sample!"); | |
1468 | hr = MFCreateMemoryBuffer(info.cbSize, &media_buffer); | |
1469 | FAudio_assert(!FAILED(hr) && "Failed to create buffer!"); | |
1470 | hr = IMFSample_AddBuffer(impl->output_sample, media_buffer); | |
1471 | FAudio_assert(!FAILED(hr) && "Failed to buffer to sample!"); | |
1472 | IMFMediaBuffer_Release(media_buffer); | |
1473 | } | |
1474 | ||
1475 | hr = IMFTransform_ProcessMessage( | |
1476 | decoder, | |
1477 | MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, | |
1478 | 0 | |
1479 | ); | |
1480 | FAudio_assert(!FAILED(hr) && "Failed to start decoder stream!"); | |
1481 | ||
1482 | voice->src.wmadec = impl; | |
1483 | voice->src.decode = FAudio_INTERNAL_DecodeWMAMF; | |
1484 | ||
1485 | LOG_FUNC_EXIT(voice->audio); | |
1486 | return 0; | |
1487 | } | |
1488 | ||
1489 | void FAudio_WMADEC_free(FAudioSourceVoice *voice) | |
1490 | { | |
1491 | struct FAudioWMADEC *impl = voice->src.wmadec; | |
1492 | HRESULT hr; | |
1493 | ||
1494 | LOG_FUNC_ENTER(voice->audio) | |
1495 | FAudio_PlatformLockMutex(voice->audio->sourceLock); | |
1496 | LOG_MUTEX_LOCK(voice->audio, voice->audio->sourceLock) | |
1497 | ||
1498 | if (impl->input_size) | |
1499 | { | |
1500 | LOG_INFO(voice->audio, "sending EOS to %p", impl->decoder); | |
1501 | hr = IMFTransform_ProcessMessage( | |
1502 | impl->decoder, | |
1503 | MFT_MESSAGE_NOTIFY_END_OF_STREAM, | |
1504 | 0 | |
1505 | ); | |
1506 | FAudio_assert(!FAILED(hr) && "Failed to send EOS!"); | |
1507 | impl->input_size = 0; | |
1508 | } | |
1509 | if (impl->output_pos) | |
1510 | { | |
1511 | LOG_INFO(voice->audio, "sending DRAIN to %p", impl->decoder); | |
1512 | hr = IMFTransform_ProcessMessage( | |
1513 | impl->decoder, | |
1514 | MFT_MESSAGE_COMMAND_DRAIN, | |
1515 | 0 | |
1516 | ); | |
1517 | FAudio_assert(!FAILED(hr) && "Failed to send DRAIN!"); | |
1518 | impl->output_pos = 0; | |
1519 | } | |
1520 | ||
1521 | if (impl->output_sample) IMFSample_Release(impl->output_sample); | |
1522 | IMFTransform_Release(impl->decoder); | |
1523 | voice->audio->pFree(impl->output_buf); | |
1524 | voice->audio->pFree(voice->src.wmadec); | |
1525 | voice->src.wmadec = NULL; | |
1526 | voice->src.decode = NULL; | |
1527 | ||
1528 | FAudio_PlatformUnlockMutex(voice->audio->sourceLock); | |
1529 | LOG_MUTEX_UNLOCK(voice->audio, voice->audio->sourceLock) | |
1530 | LOG_FUNC_EXIT(voice->audio) | |
1531 | } | |
1532 | ||
1533 | void FAudio_WMADEC_end_buffer(FAudioSourceVoice *voice) | |
1534 | { | |
1535 | struct FAudioWMADEC *impl = voice->src.wmadec; | |
1536 | HRESULT hr; | |
1537 | ||
1538 | LOG_FUNC_ENTER(voice->audio) | |
1539 | ||
1540 | if (impl->input_size) | |
1541 | { | |
1542 | LOG_INFO(voice->audio, "sending EOS to %p", impl->decoder); | |
1543 | hr = IMFTransform_ProcessMessage( | |
1544 | impl->decoder, | |
1545 | MFT_MESSAGE_NOTIFY_END_OF_STREAM, | |
1546 | 0 | |
1547 | ); | |
1548 | FAudio_assert(!FAILED(hr) && "Failed to send EOS!"); | |
1549 | impl->input_size = 0; | |
1550 | } | |
1551 | impl->output_pos = 0; | |
1552 | impl->input_pos = 0; | |
1553 | ||
1554 | LOG_FUNC_EXIT(voice->audio) | |
1555 | } | |
1556 | ||
1557 | #else | |
1558 | ||
1559 | extern int this_tu_is_empty; | |
1560 | ||
1561 | #endif /* FAUDIO_WIN32_PLATFORM */ |
69 | 69 | #define SEEK_END FAUDIO_SEEK_END |
70 | 70 | #define EOF FAUDIO_EOF |
71 | 71 | #define fopen(path, mode) FAudio_fopen(path) |
72 | #define fopen_s(io, path, mode) (!(*io = FAudio_fopen(path))) | |
72 | 73 | #define fclose(io) FAudio_close(io) |
73 | 74 | #define fread(dst, size, count, io) io->read(io->data, dst, size, count) |
74 | 75 | #define fseek(io, offset, whence) io->seek(io->data, offset, whence) |