当前位置: 首页 > news >正文

客户评价 网站建设杭州有做网站

客户评价 网站建设,杭州有做网站,做新浪微博网站需要,海珠哪家网站建设好仓库:https://gitee.com/mrxiao_com/2d_game_3 资产#xff1a;game_hero_test_assets_003.zip 发布 我们的目标是展示游戏运行时的完整过程#xff0c;从像素渲染到不使用GPU的方式#xff0c;我们自己编写了渲染器并完成了所有的工作。今天我们开始了一些新的内容#…仓库:https://gitee.com/mrxiao_com/2d_game_3 资产game_hero_test_assets_003.zip 发布 我们的目标是展示游戏运行时的完整过程从像素渲染到不使用GPU的方式我们自己编写了渲染器并完成了所有的工作。今天我们开始了一些新的内容觉得现在是一个合适的时机整理一下之前的工作开始着手声音部分的开发。声音是一个像图形一样的领域许多人只会用现成的库而不深入理解其内部原理。然而我们的目标是教育帮助大家了解其中的每个环节理解这些东西的工作原理声音处理就是其中之一。通过理解声音你能做很多事情而如果只使用库而不清楚其原理可能就无法做到这些。 为了实现这一目标game_test_asset\data\test3这个文件夹中包含了我自己制作的WAV文件我确保这些文件没有版权问题可以自由使用并复制。我们今天的目标是加载这些WAV文件以便我们能够进行播放从而完善我们的资源加载系统。目前我们的资源加载系统只能处理位图还不能处理WAV文件因此需要扩展它来支持WAV文件的加载。 为了实现这一点我们需要理解WAV文件的结构因此我们需要实现WAV文件的加载代码。WAV文件虽然比位图稍微复杂一些但它们还是相对容易处理的文件格式。作为第一步我们需要能够加载WAV数据并进行处理这样后续的音频混音等功能才能继续进行。 解析 WAV 文件中的数据块Chunks WAV 文件采用 块Chunk结构即文件内部是由多个独立的子块组成的每个子块都有自己的 Chunk ID 和 Chunk Size 我们遍历整个 WAV 文件按 Chunk ID 解析各个部分例如 fmt 格式块定义音频格式PCM、采样率、通道数等。WAVE数据块存储真正的音频数据。其他扩展块可忽略。 遍历数据块 定义数据块结构typedef struct {uint32_t ID; // 数据块类型fmt 、data 等uint32_t Size; // 数据块大小 } WaveChunk;遍历所有数据块uint8_t* cursor fileData sizeof(WaveHeader); uint8_t* fileEnd fileData header-Size 8;while (cursor fileEnd) {WaveChunk* chunk (WaveChunk*)cursor;// 处理 fmt 数据块if (chunk-ID RIFF_CODE(f, m, t, )) {// 解析格式信息}// 处理 data 数据块else if (chunk-ID RIFF_CODE(d, a, t, a)) {// 解析音频数据}// 移动到下一个数据块cursor sizeof(WaveChunk) chunk-Size; }总结 检查 WAV 头部 ID 必须是 RIFFFormat 必须是 WAVE否则 assert 失败。 遍历数据块 读取 ID 和 Size根据不同 ID 解析不同数据块。关键数据块包括 fmt 格式信息和 data音频数据。 使用 assert 进行调试 该代码仅用于调试模式最终的资产加载器将进行更完整的错误处理。 这样我们就完成了 WAV 文件的基本解析框架后续可以继续实现对 fmt 和 data 数据块的具体解析。 game_asset.cpp修正 WAVE_fmt 中的拼写错误 我们现在正在查看资源加载器尤其是加载WAV文件的部分。昨天有观众指出在我编写WAV文件头部时可能有一个拼写错误我记得可能是在格式块format chunk里出错了不过我不记得具体是哪一项。经过检查发现确实是“有效位数”这一项出错了我把它写成了4但应该是2所以感谢观众指出这个问题避免了额外的调试工作。 昨天的工作主要是开始构建WAV文件的基础结构。我们并没有做太多只是初步地复制了WAV文件的结构。接下来我打算把这些内容放到和位图定义相同的位置以确保它们在同一个pragma pack指令内。我们希望这些结构体能够使用pragma pack 1这样编译器就不会插入额外的空间。例如int16类型的数据可能会被填充到32位但是如果我们使用pragma pack指令就能够确保数据按需对齐不会出现额外的填充。 总之我要确保这些数据在内存中是紧凑排列的不会被编译器处理成其他格式接下来会继续优化这些部分保证WAV文件的加载能够顺利进行。 game.cpp在 GameUpdateAndRender 中调用 DEBUGLoadWAV 为了测试这些功能首先决定在代码中进行一些调整以便更高效地进行测试而无需每次都运行整个游戏。具体的做法是在每次更新和渲染的调用开始时插入一段代码来加载一个 WAV 文件。这样一来可以在代码的某个特定位置设置断点这样测试就可以从该位置直接开始避免了繁琐的启动游戏过程。这种方式能够提高测试效率特别是当需要调试和测试一些功能时。 为了实现这一点决定在代码中添加一个“加载声音”的步骤示例文件为 bloop000.wav这样就可以确保每次进入测试时程序会加载指定的音频文件方便检查和验证音频加载的相关功能。 调试器进入 DEBUGLoadWAV 多写了一个data/test 编译并运行代码确保文件能够完整加载。查看 WAV 文件的头部信息检查文件的尺寸参数是否符合预期并确认 RIFF ID 是否正确。检查时发现 RIFF ID 确实按照小端字节序little-endian排列并且文件头中的字符代码为 52 49 46 46与预期一致。此外文件中的 WAV ID 也符合预期的字符码 57 41 56 45。 通过这些检查确认 WAV 文件头部的加载没有问题断言检查也通过了。接下来需要处理 WAV 文件的解析部分继续进行文件解析的实现。 game_asset.cpp引入 riff_iterator 用于 DEBUGLoadWAV 在实现 WAV 文件解析时首先需要处理文件中的数据块chunk。为了方便处理这些块设计了一个类似迭代器的工具来帮助管理读取文件的过程。这个工具将包含一个指向当前文件位置的指针以及当前处理的 RIFF 块的信息。 在实现中首先解析文件头然后使用迭代器开始读取文件中的第一个数据块。每个块有一个大小参数迭代器将帮助逐步遍历每个块并在每次读取后移动到下一个块。为了使这个过程更加清晰和易于管理使用了一个循环结构来处理每个块。 在循环中首先判断当前块是否有效然后使用 NextChunk 函数来跳转到下一个块。每当读取一个块时检查该块的类型根据类型执行不同的操作。比如如果读取的是格式块format chunk则根据块的内容进行相应的处理。 总的来说这个实现的目的是为了在读取 WAV 文件时能够高效地管理每个数据块并根据不同块的类型执行不同的操作简化了文件解析的过程。 game_asset.cpp向 RIFF_CODE 添加 WAVE_ChunkID_data 在分析 WAV 文件格式时发现其中有几个数据块chunk但并不是所有的数据块都需要关心。对于当前的需求压缩格式的文件并不需要处理因此可以忽略 fact 块。主要关注两个数据块格式块format chunk和数据块data chunk。 格式块包含了 WAV 文件的基本信息例如音频的格式和参数数据块则包含了实际的音频样本数据。因此在实现解析时主要需要处理这两个块格式块用于解析音频的格式数据块则用于读取实际的声音数据。 在代码中首先要读取格式块这将帮助理解后续数据块的内容。然后读取数据块获取实际的音频数据。除了这两个块其他的块可以暂时忽略因为它们对当前目标没有实际意义。 简而言之整个解析过程主要集中在格式块和数据块的读取确保能够正确解析音频的格式并获取样本数据。 https://www.mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html game_asset.cpp继续编写 DEBUGLoadWAV 的使用代码 接下来要做的事情是从 WAV 文件中获取实际的音频数据。为了实现这一点可以使用 GetChunkData 函数来提取格式块format chunk和数据块data chunk。对于格式块WAVE_fmt可以通过迭代器获取该块的数据并存储为 WAVE_fmt 结构体。对于数据块data chunk虽然它本身只包含音频的字节数据不包含其他信息但依然可以使用 GetChunkData 来提取该数据并将其保存为 SampleData。 然后提取到的样本数据会被保存这样就能够获得实际的音频数据准备进行后续处理。整体流程是首先提取格式块数据来了解音频的基本信息然后提取数据块以获取实际的音频样本数据。这样做可以帮助完成 WAV 文件的解析进而能够进行后续的音频处理和播放。 game_asset.cpp引入 ParseChunkAt 首先需要创建一个 riff_iterator它将帮助在 WAV 文件的各个块之间进行迭代。这个迭代器将假定当前指向一个有效的块例如 WAVE_chunk这是迭代器的初始状态。迭代器的主要职责就是在文件中遍历这些块提取所需的数据。接下来还需要确保迭代器知道文件的大小这样它才能知道何时超出文件的有效数据范围避免访问无效区域。为了做到这一点迭代器还需要存储当前块的大小并且在读取数据时要对文件的边界进行检查。 此外还需要实现一个 next 函数该函数将帮助迭代器跳转到下一个有效的块以便进行后续的读取和处理。在此过程中要特别注意文件的边界和数据的有效性以确保迭代过程的正确性。 game_asset.cpp引入 NextChunk 首先创建一个 riff_iterator NextChunk(riff_iterator Iter) 函数用来推进到下一个块。在这个函数中需要通过当前块的大小来决定要前进多少位置。根据 WAV 文件格式块的大小通常是指块头以外的数据大小。因此在计算前进的距离时可能需要排除块头的部分。假设一个块的大小为 16 字节如果块头的大小为 8 字节那么有效数据的大小为 8 字节前进时就需要跳过这部分数据。 为了处理这一情况假设块大小不包括块头代码会在当前指针位置上增加相应的有效数据长度。接着为了确保正确解析设置一个停止条件参数来控制迭代器在合适的位置停止。通过这种方法可以有效地解析文件中的每个块并确保在迭代时不会超出文件数据的有效范围。 game_asset.cpp引入 IsValid 在实现 IsValid 方法时核心目标是判断迭代器是否有效。具体来说通过检查当前的迭代器位置Iter.At是否小于预设的停止位置Iter.Stop来确定迭代器是否仍然有效。如果当前迭代器位置小于停止点则返回有效表示可以继续迭代。如果不满足条件则表示迭代器无效无法继续迭代。 此外遇到代码缩进问题时需要确保所有的代码块按照正确的缩进规则进行排列以避免编译器产生不必要的警告或错误。 game_asset.cpp继续编写 DEBUGLoadWAV 在实现 ParseChunkAt 时需要考虑如何处理数据的读取位置。具体而言在解析时会根据头部信息确定从哪里开始读取数据并按照头部指定的大小读取后续数据。然而是否应包括头部的大小仍不确定。这意味着要判断头部的大小是否包含在内可能需要通过调试或其他方法确认具体的处理方式会在后续的测试中明确。 此外尽管最初考虑为 GetChunkData 增加类型安全检查但发现实际使用中并不需要因为当前的实现并不会在多个不同场景下调用该方法。所以最终决定不做额外的类型安全处理。 game_asset.cpp引入 GetChunkData 在实现 GetChunkData 时目标是返回块的数据内容。该方法通过从当前迭代器的位置返回数据具体来说就是在头部信息之后的实际数据部分。为了简化这些方法实际上都是简单的工具函数因此最好将它们内联。 GetChunkData 方法会返回当前迭代器位置的指针并且跳过头部部分返回实际数据。GetType 方法则返回一个 32 位的数值表示当前块的类型。对于 wave chunk它包含一个 ID 和大小方法应返回这个 ID以便我们能够识别出块的类型。 调试器进入 DEBUGLoadWAV 在进行第一次实现时首先我们读取了文件并开始解析。当我们创建了一个迭代器后查看迭代器的内容时发现其包含了一个块ID。为了确定块ID的具体值可以通过打印输出这个ID来查看。第一次解析时遇到的是格式块format chunk并且我们确认其符合预期。 在查看这个格式块时发现它包含了音频文件的一些重要信息比如采样率为48kHz立体声两个通道等这符合预期。格式标签format tag也表明文件是PCM格式这是我们计划读取的无压缩音频格式。 其他字段比如块对齐block align和每个样本的位数bits per sample也看起来正常16位的样本深度符合预期。不过其中有一些字段如CV size可能对我们当前的读取操作没有太大影响。 接着解析到下一个块时发现又出现了一个格式块这显然不符合预期。此时意识到可能在代码中出现了某些错误导致格式块被错误地重复读取。 game_asset.cpp移除 riff_iterator 和 ParseChunkAt 中的 Chunk改为在 NextChunk 和 GetType 中使用 在发现出现重复的块后决定修复这个问题。问题出在一个冗余的指针上且没有在移动时更新这个指针。因此考虑到这个指针已经没有太大作用决定将其移除改为直接通过at块头来进行操作。这样可以避免之前的错误并使代码更加简洁避免出现不必要的重复读取。 通过这种方式代码能够更加清晰和高效同时也解决了之前的问题。 调试器继续逐步调试 DEBUGLoadWAV 在查看下一个块类型时发现它是数据块data chunk这是存储音频样本的地方。除了这个数据块还有其他块但它们不是我们能够理解的类型因此跳过了这些块。为了进一步了解情况决定查看一下这些未读取的块的类型。结果发现数据块的类型是零这让人感到有些奇怪。此时怀疑可能是在数据块的末尾四个字节附近可能存在一些问题。 继续分析后猜测可能存在一些数据对齐的问题或其他原因导致读取到的数据块位置出现了偏差。 网络尝试确定 RIFF 文件是否应以零结尾 在检查文件结尾时发现没有明确的规范说明文件应该如何结束。因此想确保没有出现错误。如果文件结尾确实应该是零字节那就没有问题但还是想确保没有 bug。查阅相关文档时发现文档中提到文件的字节排列方式。特别是当文件大小是奇数时会在数据块后面添加一个零值的填充字节pad byte。这意味着如果块大小是奇数文件会在数据部分后附加一个零字节以确保文件对齐。 目前实现没有遵循这个规定这可能导致文件末尾出现不符合规范的情况。因此需要更新代码以遵循这一规则确保文件按照规范处理。 然而文档中没有完全解释为什么会出现读取到零值的现象因此可能需要进一步调查。 https://www.mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/riffmci.pdf game_asset.cpp如果 Chunk-Size 为奇数则填充 在处理块大小时如果块大小是奇数那么需要根据规范进行填充。可以通过调整块大小确保其为偶数。例如可以通过将块大小加一来进行向上舍入这样就符合规范确保正确对齐。然后按照新的大小进行处理。 尽管如此这并没有完全解决问题因为仍然不清楚为何会遇到零字节的情况。虽然文档中提到当块大小为奇数时需要进行填充但这并没有完全解释为什么在特定情况下会出现零字节。因此还需要进一步分析和确认这一现象的原因。 这行代码的作用是确保 Size 是一个偶数即使 Chunk-Size 是奇数。 详细解释 uint32 Size (Chunk-Size 1) ~1;Chunk-Size 1: 这部分代码将 Chunk-Size 增加 1。如果 Chunk-Size 是奇数增加 1 后它会变成偶数如果已经是偶数则增加后变成下一个奇数。 ~1: ~1 是按位取反操作1 在二进制中是 00000001因此 ~1 就是 11111110。按位与操作会将 Chunk-Size 1 的最低有效位最右边的那个二进制位清零。这样任何数的最低有效位都被强制设置为 0从而使得最终的 Size 成为偶数。 例子 如果 Chunk-Size 是 5奇数 Chunk-Size 1 变成 6偶数然后 6 ~1 还是 6因为 6 已经是偶数。 如果 Chunk-Size 是 4偶数 Chunk-Size 1 变成 5奇数然后 5 ~1 变成 4通过按位与操作清除最低有效位。 总结 这行代码确保无论 Chunk-Size 是奇数还是偶数最终的 Size 总是一个偶数。 game_asset.cpp从 Header-Size 中减去 4 在这种情况下可能是因为我们没有正确解释头部大小所包含的内容。所以可能发生的情况是RIFF 文件中的块大小chunk size是 4 n。也许他们的意思是块头部的大小不包括在内而我们错误地将其包括了进去。如果这是正确的理解那么就可以推测出问题所在。 为了正确处理这个问题应该将实际大小的计算从块头部的大小中减去 4因为我们需要排除掉已经跳过的那个wave部分wave part。这种方式可能是更合适的解决办法。所以可以尝试这样调整看看是否能解决问题。 调试器逐步调试 DEBUGLoadWAV发现正确地遍历了所有块 现在可以看看这样是否会让事情变得更清晰了。进入之后发现有格式块format chunk和数据块data chunk除此之外没有其他块。因此实际上我们不需要处理或跳过其他块。事实证明这些文件是从一个叫做 Gold Wave 的程序中保存下来的结果是文件中没有其他块只有我们具体想要的块。这样当我们加载时处理就变得简单了。 game_asset.cpp断言某些数据是有效的 现在想要做的是记住实际需要的显著数据也就是我们真正想要获取的数据。所以我会尝试具体查找我们需要的数据。接下来我会进行一些断言确保我们期望的数据格式是正确的。首先我们希望 wFormatTag 的标签是 PCM 格式。然后我们希望通道数为 1 或 2采样率始终是我们首选的采样率。接着我们不太关心是否是块对齐block aligned但我们希望每个样本的位数总是 16 位。 网络查找 nBlockAlign 的含义 不太清楚“channel mask”到底意味着什么不过看起来跟位置信息有关但不太关心这一部分。至于“block aligned data block size in bytes”是什么意思也不清楚。查找了一些文档发现格式块的第一个部分提到PCM 数据经过增强之后格式块和头部声明了每个样本的位数。原始文档规定样本的位数应向上取整为 8 位的倍数这个取整后的值被用作容器大小这个信息在容器中是冗余的。每个样本的大小可以通过块大小除以通道数来确定。这个冗余信息被用来定义新格式例如 Cool Edit 使用 4 来声明样本大小为 24 位其他格式也根据块大小和通道数来确定。看来“block aligned”指的是通过块大小除以通道数来确定每个样本的字节数而不是位数。 game_asset.cpp断言 nBlockAlign 为 2 * nChannels 基本上“block aligned”必须是 2 或 4。我们希望它等于通道数乘以 2因为我们规定每个通道的位数必须是 16 位。所以最终的目标是确保每个通道都为 16 位因为这就是我们唯一支持的格式。因此所有这些条件都必须满足才能保证格式是正确的。 game_asset.cpp设置 ChannelCount 和 SampleData 需要的信息是通道数因此在代码中设定了一个 ChannelCount初始值为 0。接着通过读取相关数据来更新这个通道数。其他的参数目前并不需要关注只需要这个通道数因为在此实现中只支持这种格式。 在处理WAVE_ChunkID_data时会提取出实际的WAVE数据。对于 PCM 格式数据本身仅包含大小和原始数据。PCM 格式不包含其他复杂的数据结构因此提取的就是需要的样本数据。 game_asset.cpp断言 ChannelCount 和 SampleData 为有效 在完成数据的提取后接下来需要确认通道数和样本数据的有效性因此会使用 assert 来验证这两个条件确保文件有效。之后目标是让 load sound 功能正常工作。要实现这一点需要知道样本的数量和内存位置其中内存位置就是之前提取的样本数据。 在处理内存数据时可能还需要做一些额外的操作建议对这些数据进行进一步处理或调整以确保在加载声音时数据的正确性和效率。 game_asset.h向 loaded_sound 中添加 ChannelCount 考虑到需要支持不同类型的声音格式计划对 loaded_sound 进行改进使其支持单声道mono和立体声stereo两种声音。为此打算引入 ChannelCount通道计数和 SampleCount样本计数的概念以便能够处理单声道和立体声的音频文件。这样一来未来也可以轻松扩展到支持更多通道的格式例如杜比音效Dolby。 这种做法的好处在于它不仅能支持当前的需求还能为将来可能的音频格式扩展做好准备避免在面对更多通道的音频时没有合适的解决方案。 网络尝试确定如何计算样本计数减去任何舍入部分 为了正确设置音频数据首先需要确定样本计数。样本计数是指音频数据中实际包含的样本数量。需要考虑是否存在任何因为文件格式的要求而进行的四舍五入或填充操作。根据音频文件的格式可能会有一些计算规则来确定样本数例如通过块大小block size来推算每个样本的大小。 在查看文件格式的描述时看到了一些信息比如 bits per sample 和 block size这些信息对于计算样本计数很有帮助。具体来说可以通过块大小block size除以每个样本的字节数来推算样本数。如果每个样本为16位即2字节那么可以根据块大小除以2来获得样本数。 所以关键是在文件格式的不同字段中找到相关的信息如块大小block size并根据每个样本的大小来进行计算确保样本计数是准确的。 game_asset.cpp计算 SampleCount 为了正确计算样本数量首先需要知道通道数和每个样本的大小。可以通过以下方式计算样本数量 通道数 × 每个样本的大小。由于每个样本的大小是16位即2字节可以通过这个公式来确定样本总数。 在计算时还需要考虑样本数据的大小。因此样本数量的计算不仅依赖于通道数还需要用到样本数据的大小。可以通过将样本数据的总大小除以每个样本的大小来计算样本数量。因此样本数据的大小需要作为一个重要的变量来考虑在处理数据时也需要保持对这个值的跟踪。 在实现时应确保正确地获取并使用数据块的大小这样可以确保样本数量的计算是准确的。 game_asset.cpp引入 GetChunkDataSize 为了计算样本数量首先需要获取数据块的大小。这里的 GetChunkDataSize 方法返回的大小不包括头部数据因此是准确的。然后通过获取样本数据大小 (sample data size)可以进行计算确定样本的数量。 具体来说通过 GetChunkDataSize 方法我们可以得到样本数据的总大小这个大小不包含文件头部。通过此数据结合每个样本的大小假设每个样本大小为16位即2字节可以计算出总的样本数量。这个计算过程包括将样本数据的总大小除以每个样本的大小。 通过这样的方式能够准确计算出样本的数量并继续进行后续的数据处理和操作。 game_asset.cpp为 SampleData 创建数组 首先如果通道数量为 1则处理起来非常简单。我们只需要确保结果中的通道数等于当前的通道数即 1然后样本数据直接指向正确的内存位置。在这种情况下所有样本都已按顺序存储可以直接使用。 但如果通道数量为 2立体声则样本数据是交替存储的左声道和右声道交替排列例如 左右左右。这种存储方式对于处理每个通道的数据来说并不方便因为需要分别访问每个通道的数据。因此为了方便处理我决定不直接使用交错的数据格式而是将其拆开使得每个通道的数据分别存储。 这样处理后我们可以更加方便地访问和操作每个通道的数据避免交错带来的复杂性。 黑板就位进行音频通道去交错处理 在处理音频数据时目标是将音频样本从交错存储格式即左右声道交替存储转换为每个声道单独存储的格式。这意味着原始的样本数据格式是像这样存储的L0, R0, L1, R1, L2, R2依此类推。我们希望将其转换为类似于L0, L1, L2, L3, L4 和 R0, R1, R2, R3, R4 的格式使得每个通道的样本数据能够独立存取。 为了实现这一点关键的问题是如何在内存中对交错的音频数据进行“去交错”操作。首先可以通过交换样本来实现这一目标然而这个过程可能并不像想象中那么简单。理想情况下如果我们交换每一对 L 和 R 样本那么会发现最终的结果可能并不会符合预期因为只是简单交换并没有完全解决问题。 比如如果将 L0 和 R0 交换再将 L1 和 R1 交换接着继续按此规则进行交换最终可能得到一个看似无序的排列。而要解决这一问题需要通过一种方式“旋转”样本顺序让它们变得整齐。具体来说需要在交换后按一定规则将顺序调整正确例如通过交换某些特定位置的元素来保证每个通道的数据正确对齐。 这种去交错的操作实际上可能并不简单尤其是在要求数据能够就地in-place操作的情况下。虽然初步的交换操作看起来能起到一定作用但对最终的样本进行排序时如何有效地调整顺序并确保每个通道的数据正确对齐仍然是一个需要考虑的问题。 可以通过尝试实现这些操作或者用一组更长的样本数据来验证这一策略看看是否能找到更有效的解决方案。 game_asset.cpp编写左声道的 swizzling 算法 在处理音频数据时关键步骤之一是将交错的左右声道数据L0, R0, L1, R1等转换为每个声道单独存储的格式例如L0, L1, L2, L3和R0, R1, R2, R3等。为了实现这一目标需要在内存中操作这些数据。 首先确定样本数据类型为16位整数16-bit samples因为这是所需的格式。然后基于每个样本数据计算出正确的位置。由于数据是交错存储的因此每次访问某个样本时都需要确保读取的是正确的左或右声道样本。例如在处理时如果数据按照“L0, R0, L1, R1”的顺序存储我们需要通过指针偏移来访问正确的样本并将左声道L数据正确地放入新的缓冲区。 在实现过程中需要通过循环遍历样本数据并为每个样本在适当的位置存储其值。具体来说首先获取当前样本的地址通过计算偏移量来定位到正确的位置然后将其放入目标位置确保左声道和右声道的数据分开存储。 此外需要注意的是循环过程的执行顺序非常重要。在每次迭代时左声道和右声道样本都需要从交错的数据中提取出来存储到新的位置。通过这种方式可以确保数据的去交错操作按预期进行。 总体来说核心操作是通过在缓冲区中进行位置交换swap来实现数据的重新排列。通过这种方式最终可以将交错存储的音频数据转换为每个声道单独存储的格式。这些操作可以逐步进行确保每个样本的数据正确存储。 这段代码的目的是对交错存储的立体声stereo音频数据进行去交错uninterleave操作也就是将存储在内存中的左右声道数据分别提取出来存放到两个独立的缓冲区中。下面是对这段代码的解释并通过一个简单的例子来说明它是如何工作的。 举例说明 假设有以下交错存储的音频数据只考虑简化的2个样本假设每个样本为16位即2字节 L0, R0, L1, R1其中 L0 是左声道的第一个样本R0 是右声道的第一个样本L1 是左声道的第二个样本R1 是右声道的第二个样本。 我们要做的事情是将交错存储的样本数据拆分为两个独立的缓冲区分别存储左声道和右声道的数据。 步骤 初始化 Result.Samples[0] SampleData; 左声道的数据 L0, L1 存储在 Result.Samples[0] 中。Result.Samples[1] SampleData (SampleDataSize / 2); 右声道的数据 R0, R1 存储在 Result.Samples[1] 中。 交换操作 假设 SampleData 中的内容为 SampleData [L0, R0, L1, R1]对于 SampleIndex 0即第一个样本 Source SampleData[0]即 L0。将 SampleData[0] 设置为 SampleData[0]即没有变化因为本来就是 L0。将 SampleData[0] 设置为 Source即 L0。 经过交换后SampleData 还是 [L0, R0, L1, R1]对于 SampleIndex 1即第二个样本 Source SampleData[2]即 L1。将 SampleData[2] 设置为 SampleData[1]即右声道 R0的值。将 SampleData[1] 设置为 Source即将左声道 L1 放到右声道的位置。 经过交换后SampleData 变为 [L0, L1, R0, R1]最终效果 这样经过去交错操作后音频数据就被拆分成了两个独立的声道 左声道 L0, L1右声道 R0, R1 这种操作是为了将交错存储的样本数据转换为独立存储的左声道和右声道数据使得每个声道的数据更容易单独处理。 段错误 game_asset.cpp插入一些测试数据 当加载任何类型的WAV文件时遇到的问题是如果没有正确处理左声道会正常加载而右声道会变得完全错误。因此想要检查右声道的输出结果以确定问题所在。在调试时可能暂时将数据类型从void改为uint16这样更容易查看结果。 为了便于调试决定在加载数据之前手动填充一些特定的值。具体来说将样本数据的索引与其对应的值一一对应。举例来说数据序列将从0开始按顺序填充0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6这样可以方便地在分析过程中进行对比。 一个通道16字节双通道32 差一个括号 加载之后先查看第一个32个样本确认左声道的数据是否符合预期结果显示左声道的数据是正确的这与预期一致。接下来问题出现在第二个声道右声道。在查看右声道数据时结果显示为零这显然是出错了。怀疑是代码中某些地方的计算出了问题应该仔细检查如何处理数据。 调试器检查右声道的值 在进行Swizzle操作后观察到左右声道的数据分布情况相当有趣。左声道的数据按顺序递增例如97、98、99而右声道的数据同样也按顺序递增但在某些部分出现了不同的模式。这些数据的分布方式看起来很奇怪特别是在某些区域每隔一个值就会出现不同的变化模式有时是当前数值的一半。 观察这种数据分布感觉要在原地进行正确的Swizzle操作会相当复杂。仔细分析后发现数据排列方式相当奇特不太容易找到一个简单的方法在原地完成数据重排。因此推测可能存在一种更智能的方式来完成这个Swizzle操作但目前还不确定具体的实现方法。 考虑到当前的情况可以采取以下几种应对策略 使用额外的内存可以申请额外的缓冲区将数据正确地拷贝并整理而不是尝试在原地重排。仅加载左声道暂时只加载左声道的数据忽略右声道。这样可以保证至少有一部分数据是正确的而不必立即解决右声道的处理问题。添加待办任务在代码中标记一个待办事项未来再考虑如何正确加载右声道的数据。 为了暂时绕开这个问题决定仅加载左声道并在代码中标注TODO提醒以后再处理右声道数据。因此暂时将声道数设为1即 Result.ChannelCount 1;这样即使当前无法正确完成Swizzle操作也至少能够保证左声道数据的正确性并避免因为右声道的数据错乱而导致更大的问题。 game.cpp 和 game.h重新启用 tSine 有部分好像之前给删掉了现在重新修改一下 目前不太确定声音播放的具体状态但GameOutputSound函数仍然被调用并且确实在输出某些内容。目前的测试是一个正弦波sine wave但代码中似乎并没有实际定义它。不过可以手动创建一个tSine变量用于测试。 检查代码后发现GameState.tSine是否被递增查看WavePeriod的值并确认GameState.tSine 1032。因此当前的做法是重新插入测试代码以验证声音输出是否仍然正常。在不确定如何继续的情况下暂时恢复正弦波输出进行调试。 接下来执行代码并运行确认声音是否仍然可以播放。运行后听到声音证明基本的音频输出仍然有效。这说明 声音播放机制仍然正常工作。现阶段可以继续进行进一步的音频处理例如加载和播放WAV文件而不用担心底层音频系统是否正常。 接下来可以考虑如何加载WAV文件并确保其数据被正确处理后输出而不是简单地使用正弦波作为测试信号。 补充正弦波声音 https://www.geogebra.org/m/vputraas 正弦波Sine Wave是一种最基本的波形在声音和信号处理领域中有着重要的地位。以下是对正弦波声音的相关介绍 1. 什么是正弦波 正弦波是一种单一频率的波形其数学表达式为 y ( t ) A sin ⁡ ( 2 π f t ϕ ) y(t) A \sin(2\pi ft \phi) y(t)Asin(2πftϕ)其中 A A A 是振幅Amplitude决定声音的响度 f f f 是频率Frequency决定音调的高低单位为赫兹 Hz t t t 是时间 ϕ \phi ϕ 是相位Phase影响波形的起始位置。 正弦波的特点是它的波形平滑、周期性强没有谐波Harmonics因此听起来非常“纯净”。 2. 正弦波的声音特性 音调正弦波的声音是单一频率的听起来像一个纯音。例如440 Hz 的正弦波对应于音乐中的“A4”音国际标准音高。音色由于没有谐波正弦波的音色非常简单缺乏复杂乐器或人声那样的丰富性。人类耳朵可能会觉得它有些“单调”或“电子感”。响度由振幅决定振幅越大声音越响。 3. 正弦波在现实中的应用 声音合成正弦波是合成声音的基础波形。许多电子音乐或合成器如Moog、Roland通过叠加正弦波来创造复杂的音色。听力测试正弦波常用于听力检测设备如纯音测听因为它能精确测试人对特定频率的感知能力。信号处理在通信和音频工程中正弦波用于分析和调试系统。 4. 正弦波的听觉体验 如果你通过扬声器或耳机播放一个正弦波比如用在线工具或音频软件生成会听到一种持续的、没有起伏的“嗡嗡”声。例如 低频正弦波20-100 Hz听起来像深沉的低鸣可能伴随振动感。中频正弦波300-1000 Hz类似电子设备的提示音。高频正弦波2000 Hz 以上像尖锐的哨声可能会让人感到不适。 5. 生成正弦波声音 如果你想自己体验可以使用免费软件如Audacity生成正弦波 打开Audacity选择“生成” “音调”设置频率如440 Hz、振幅和持续时间播放即可听到。 总结 正弦波是声音世界中的“原子”简单而纯粹。虽然它本身听起来不复杂但它是理解声波、音调和音频技术的基础。如果你有具体问题或想深入某个方面比如生成正弦波的代码可以告诉我 game.h引入 loaded_sound TestSound 我们现在想要播放一个真实的声音波形。如果要在游戏中输出其他声音我们可以使用一个已经加载的声音比如 TestSound。 首先我们需要找到加载声音的地方比如 DebugLoadWAV并将加载的声音赋值给 GameState 中的 TestSound确保它可以在后续被使用。之前的代码中每一帧都会重新加载声音这虽然运行正常但看起来有些奇怪。为了改进我们可以调整逻辑使得 TestSound 只在需要时加载一次而不是每一帧都重新加载。 接下来我们可以利用 GAME_GET_SOUND_SAMPLES 这个接口使它执行更有意义的操作比如实际播放 TestSound 的数据而不仅仅是填充一个正弦波。尽管当前代码中 GameOutputSound 是一个单独的函数但它的作用可能可以整合到 GAME_GET_SOUND_SAMPLES 里从而更方便地管理游戏的音频输出逻辑。 game.cpp注释掉 GameOutputSound改用 TestSound 我们决定不再调用之前的代码而是直接在当前逻辑中处理音频输出。具体来说我们的目标是将 TestSound已经加载的调试音频的数据填充到声音缓冲区中。 首先我们需要访问 game_state 中的 TestSound 采样数据并将其写入声音缓冲区。我们假设 TestSound 具有 samples 数组并从 samples[0] 开始读取数据。然后我们根据全局采样索引 TestSampleIndex 计算当前播放的位置并将相应的音频数据存入缓冲区。 为了让声音能够连续播放我们需要维护 TestSampleIndex表示当前播放的位置。这个索引在每一帧音频数据输出后需要递增以确保下次写入缓冲区时不会重复相同的采样值而是按照正确的顺序播放整个音频数据。 在递增 TestSampleIndex 时需要确保不会超出 TestSound 采样数据的范围。因此我们使用取模运算 TestSampleIndex % TestSound.SampleCount使得索引始终处于有效范围内防止读取超出 TestSound 的数据区域。 最后我们确保正确写入 SampleOut并在缓冲区中填充对应的音频数据。如果 SampleOut 位置不对或者 SampleValue 没有正确获取 TestSound 的采样数据则需要检查代码确保所有的索引计算和缓冲区写入逻辑正确无误。 运行游戏发现声音不太好 当前的声音效果并不好因此需要进行更详细的调试和分析。 首先我们检查 SampleIndex 和 TestSampleIndex确保它们正确地指向 TestSound 采样数据中的位置。这两个索引决定了我们当前读取的音频数据是否是正确的因此需要验证它们的计算是否符合预期。 接下来我们检查 SampleIndex确保它正确地从 TestSound 采样数据中获取了相应的值。如果 SampleIndex 计算有误可能会导致声音出现失真、跳跃或其他异常现象。因此我们需要仔细验证 SampleIndex 是否与 TestSound.samples[SampleIndex] 一致并确保数据没有越界访问或被错误修改。 然后我们暂停代码执行并逐步进入关键代码部分以确认 SampleIndex 和 SampleIndex 计算的正确性。这有助于找出可能的错误比如索引计算错误、数据读取错误或缓冲区写入错误。 最后我们保存当前进度并继续分析代码逻辑以确保所有的索引计算、采样数据读取和缓冲区填充都符合预期。如果仍然存在音质问题我们需要进一步检查音频数据格式、缓冲区大小、数据采样率以及是否正确处理了循环播放逻辑。 发现忘记递增TestSampleIndex 调试器逐步调试 SampleIndex 循环 我们现在检查 TestSoundSampleIndex发现它的初始值是 0SampleValue 也是 0。然后我们让代码继续执行并观察 TestSampleIndex 是否按预期递增同时检查 SampleValue 是否正确地从 TestSound 采样数据中获取。 在调试过程中我们发现 SampleValue 没有正确输出导致音频数据可能不符合预期。经过检查发现 SampleValue 被误解码为无符号 uint16而实际上音频数据是 有符号 int16。这是一个关键问题因为如果使用无符号类型负数采样值会被错误地解释为很大的正数从而导致声音失真或完全错误。更改为 int16 之后这应该能解决一部分问题。 除此之外我们还检查 SampleCount发现它的值是 1066表示当前 TestSound 采样数据的总数。在代码运行过程中TestSampleIndex 按预期递增说明索引计算大体上是正确的。 下一步我们继续验证所有索引计算、数据读取和缓冲区填充逻辑确保 TestSound.samples[TestSampleIndex] 读取的值是正确的 有符号 16 位整数并且 TestSampleIndex 在 SampleCount 之内正确循环。修复 int16 误用问题后应该能改善音频播放效果但仍需进一步测试以确保声音播放正常。 game.cpp将 Sample 数据设置为 int16 经过检查代码整体上看起来是相对正确的。但是之前错误地使用了 uint16无符号 16 位整数来存储音频数据而实际上音频数据是以 int16有符号 16 位整数编码的。这是一个重要的错误因为音频数据需要包含负值以正确表示波形。如果使用 uint16负数部分会被错误地解释为很大的正数导致声音播放异常。 这个错误主要是因为长时间没有编写音频处理代码导致在数据类型选择上出现疏忽。在代码的多个地方都应该将 uint16 改为 int16确保音频数据能够正确存储和解析。 修正数据类型后当前实现应该更符合预期能够正确解码和输出声音。接下来需要进行进一步的测试以确保所有 int16 相关的计算、索引操作和缓冲区填充都是正确的避免因数据类型问题导致的音频失真或其他异常现象。 运行游戏依然听到点击声 目前声音仍然不正确听起来像是“点击”声而不是预期的音频效果。因此我们需要进一步分析问题的原因。 首先检查当前加载的音频文件是否正确。回顾之前的逻辑我们加载了 bloop_00 这个音频文件因此需要确认它的内容是否符合预期。可以通过播放 bloop_00 的原始文件验证它的实际声音是否正确。如果原始文件听起来正常那么问题可能出在加载或播放的代码上。 接下来我们需要检查音频数据在加载时是否被正确解析。例如 采样格式是否匹配确认 bloop_00 的音频格式是否与代码中的解析方式一致例如 16-bit PCM、采样率、单声道/立体声 等。数据是否完整检查 TestSound.samples 是否正确填充了音频数据确保数据没有丢失或截断。缓冲区写入是否正确检查 SampleIndex 计算是否准确是否正确遍历 TestSound.samples并且写入到缓冲区时数据没有错位或重复。 最后可以尝试逐步调试音频输出流程比如 直接在代码中打印 TestSound.samples 的前几个值确认数据是否合理。将 TestSound.samples 写入一个文件再用外部工具如 Audacity查看波形是否正常。调整播放逻辑尝试不同的缓冲区大小、不同的采样率观察声音是否有所变化。 Audacity 是这个东西 通过这些方法可以逐步缩小问题范围找出导致“点击”声的真正原因并最终修正音频播放的逻辑。 听取声音 我们遇到了一些音频问题特别是在播放时出现了点击声这似乎是 VLC 播放器的一个 bug尽管如此至少可以听到其中的 “bloop” 音效部分。为了避免这个问题我们打算换一个播放器进行尝试希望能找到一个不会出现这么大问题的播放器。 接下来我们需要找出出错的原因分析并进行修复。我们可以稍微进入一些质量保证的步骤这样可以确保在继续开发之前当前的状态已经达到一个合理的水平这样明天我们就可以从音频部分开始讨论并确保能够顺利加载所需的资源。通过这样做我们可以更有条理地推进工作避免出现无法解决的问题。 总的来说我们的目标是尽快理清现有的问题并且将其解决以便后续的进展能够顺利进行。在这个过程中我们还需要处理和测试音频样本确保在不同的播放器和设置下都能顺畅播放。 game.cpp检查 SampleIndex 循环 当前我们处理的是立体声输出写入了左右声道并且这是按预期进行的。我们只是将相同的内容写入左右声道这应该是没有问题的。我们从测试音频中获取了第一个样本通道的数据这是我们预期的操作而且我们是按顺序获取数据的同时还对音频缓冲区进行了循环处理。所以理论上音频应该会反复播放形成一个连续的循环。 目前的情况看起来还算正常没有明显的错误。我现在最想做的是再次检查加载代码因为我对加载部分的实现有一些担心。相比之下我对播放代码的关注度要稍微低一些。虽然这只是一个猜测但这是我目前的直觉。 因此我们需要深入查看加载音频的部分确保一切按预期进行避免在这一部分出现问题从而影响后续的音频播放处理。 调试器进入并检查 SampleData 我们想要查看音频采样数据以确认它看起来是否符合预期。首先查看这些采样数据的值发现它们看起来像是合理的声音数据至少在某种程度上可以认为是可信的。这些数据波动看起来也符合常见的声音数据模式尽管波动比较快速但从整体上看是可以接受的。 进一步观察时发现这些数据是紧密排列的并且是立体声样本即每两个连续的样本分别对应左声道和右声道。通过查看这些值像是 -21、-11、-24、42、64、-60 等感觉这些数值有些异常可能并不像预期的那样平滑或正常。这引发了怀疑觉得它们可能有些不对劲。 因此决定返回去仔细检查音频文件的格式看看是否在解析过程中遗漏了什么关键步骤或错误。可能在解码时出现了问题导致数据异常需要进一步确认格式是否正确处理或者是否存在其他解析错误。 网络查看 PCM 数据 我们需要查看PCM数据的实际内容首先确定我们正在处理的是PCM数据而不是其他格式。具体来说我们关心的是是否正确处理了通道交织数据每个样本的字节数以及每个样本的位数应该是八倍样本数。所以目前看起来我们在解释数据时应该是正确的。 不过我们还需要考虑是否存在数据的排列顺序Swizzle问题。我们需要确认是否所有的数据都是双通道的这样我们才能判断问题是否出在数据排列的处理上。为了排查这个问题可以测试一下是否只是我们的排列处理代码有问题。 但在进行这些操作之前首先确认一下在我们进行排列处理之前数据本身是否已经符合预期。之前我们曾通过零、一个、二、三、四这样的方式检查过数据所以我们可以再检查一遍确保排列操作Swizzle按我们预期的方式工作。 game_asset.cpp移除测试代码 问题终于找到了原来是测试代码没有移除。哈哈这真是太搞笑了确实如果在测试代码中写了很多无意义的数据结果就不应该感到惊讶因为它不会发出正常的声音。这真是个笑话但也是个很好的教训。 现在我们明白了问题的根源只是忘了清理测试时使用的那些垃圾数据。这样一来输出的音频自然就不会像预期的声音一样了。 运行游戏听到更正确的声音 现在情况有所改善音频加载和播放已经正确进行但仍然存在一些问题。虽然音频能够正确加载和播放但似乎没有达到预期的帧率。这意味着虽然音频在播放时没有明显的错误但可能因为帧率的问题导致播放效果不够流畅需要进一步检查并解决帧率方面的瓶颈。 目前的计划是切换到 -O2 优化级别进行构建。我怀疑如果我们继续构建现在可能会对帧率有所影响不过如果这样做应该会变得更好。然而我们还需要确保将帧率同步到正确的值而不是固定在 60Hz。所以首先需要回到最初的设置并进行重置。 接下来我们要检查当前的设置并确保在构建之后可以正确调整帧率以确保程序能够按预期运行不会因为帧率问题导致性能不稳定。 win32_game.cpp将 GameUpdateHz 设置为 MonitorRefreshHz / 2.0f 目前的显示器刷新率是 60Hz所以我们打算暂时将设置调整为 60Hz。这样做是为了确保与显示器的刷新率保持一致避免出现因帧率不同步而导致的问题。因此我们决定先按这个设置进行确保程序能够正常运行。 这个已经写死 运行游戏声音播放正确 现在应该会好很多。接下来可以进入质量保证阶段不过为了避免让大家对当前的声音感到烦躁我们可能会将音频切换成钢琴音乐。我记得我在这里添加了来自 ad-lib 库的音频文件具体是哪个文件呢应该是 ../../../../game_test_asset/data/test3/music_test.wav这个文件可以用来替代现在的音效避免长时间播放同一声音造成不适。 game.cpp播放 music_test.wav 好的现在我们换成了一个更加平和的音频播放一些不会引起不适的声音这样就没问题了。这个新的音频更为温和不会像之前的那样让人感到烦躁应该会更合适。 你可以尝试像这样去交错通道http://imgur.com/ZcDu4Tb 有一个建议是尝试将通道交织操作直接放在当前的位置进行处理。有人提供了一个链接我查看了一下链接内容似乎是关于交换通道的操作具体来说是从开始和结束的位置进行交换类似于内外交换的操作。我在想这种方法是否有效是否能够解决问题 不过有一个不确定的地方是这种操作是否对较长的音频序列有效。对于较长的序列可能会出现一些不同的表现这一点还需要进一步确认。所以现在的问题是是否能够适用于长序列还是只有在短序列中才有效这个仍然是一个需要验证的疑问。 去交错的第二容易方法是多次传递。交换 R0/L1, R2/L3, L4/L5 等。然后在两个样本块、四个样本块中做同样的事。最简单的方法是直接在混合/输出缓冲区中去交错。 有一个建议是通过多次交换multi-pass swap来进行通道交织这种方法相对简单。具体操作是先交换 R0 和 R1然后交换 L1, R2, L3, L4, L5接着继续对两个样本块进行相同的交换。这样的方法可以直接将交织后的数据放入混音输出缓冲区。然而考虑到效率我们并不希望采取这种方法。我们更倾向于在构造时就完成去交织D-interleave的操作这样在生成的艺术包art pack中数据已经是去交织好的。 这样做的原因是提前处理交织操作可以避免在后续处理时的额外复杂性从而提高效率。我们不需要在每次运行时都进行去交织的操作直接在构建时完成能更简便且高效。 为什么要去交错声音播放时你还得读取左右声道的值 在播放声音时即使已经去交织uninterleave仍然需要读取左右声道的值。这是因为左右声道的处理方式不同我们需要分别处理这些值。为了达到更广泛的兼容性和灵活性我们希望能够更精细地控制左右声道的数据处理确保每个声道的数据都能按照预期的方式处理。因此去交织操作后左右声道的数据仍然需要被分别读取和处理。 有人在问 extern C 的好处是什么它解决了什么问题 extern C 主要解决了几个问题首先它防止了函数名被修改。通常在链接时C 会对函数名进行修饰以确保操作符重载等特性在链接器不支持的情况下能正常工作。而 extern C 则能防止这种修饰使得函数名保持原样这对于动态链接库DLL尤其重要。举个例子当将游戏的入口点作为 DLL 导出时如果没有使用 extern CDLL 导入表中的函数名会被修改影响正确导入。 其次extern C 还指定了调用约定。在 Windows 的 64 位系统上通常只有一个调用约定即标准调用standard call因此通常没有问题。但在 32 位系统上存在多种调用约定比如 Pascal 调用约定、C 风格调用约定等使用 extern C 可以明确指定使用 C 风格的调用约定尤其是在涉及可变参数时例如 varargs会更加重要。 总的来说当需要进行跨语言互操作性时extern C 就非常有用它确保了链接和调用的方式正确无误。如果只是编译自己的代码不涉及外部链接通常不需要使用 extern C因为在这种情况下函数名修改和调用约定问题并不重要。 你能解释一下 (Chunk-Size 1) -1 吗 在这个解释中主要是讲解了如何通过对“块大小”进行加一和与负一按位与操作来确保数据对齐。 首先解释了如何判断一个数字是奇数还是偶数在二进制中最低位最低有效位决定了一个数字是否为奇数如果该位为1数字就是奇数。如果最低位为0数字就是偶数。而在块大小的处理过程中规范要求如果块大小是奇数则需要对齐到偶数大小。因此如果块大小是奇数例如15需要在它后面插入一个空字节使其成为偶数大小例如16。 为了实现这个对齐首先可以将块大小加1这样可以确保无论当前的块大小是奇数还是偶数都能正确地进行向上取整。对于奇数大小比如15加1后得到16清除最低位后就得到了一个偶数大小。如果块大小本来就是偶数例如16加1后得到17再通过按位与负一操作清除最低位从而确保最终的块大小依然是偶数并且满足对齐要求。 这其实是一种常见的对齐模式类似于对齐到 16 字节边界的方式例如使用 15 和 ~15 的方式。通过这种方式能确保块大小始终向上取整到最接近的偶数或其他的 2 的幂次方的倍数。 总结起来就是通过“块大小加1”再与“-1”进行按位与操作确保数据对齐避免因为奇数块大小而导致的不对齐问题这种方法适用于任何 2 的幂次方对齐问题。 为什么样本是交错的而不是平的 关于为什么音频数据是交织interleaved而不是平铺flat的原因可能是因为早期的声卡通常是以这种方式处理数据的。过去音频数据经常是交织发送的这样的做法是当时硬件和处理方式的需求。 然而现代的音频处理已经不再需要使用这种交织方式因为现在的音频处理通常是在可变数量的声道上进行的。例如可能是2声道、5声道或者更多声道。交织数据会增加额外的处理负担这对于灵活的音频输出如支持不同的声道输出来说并不是高效的做法。因此虽然早期声卡使用交织方式但如今这种方式已经不再适用因为它增加了不必要的复杂性和工作量。 总的来说虽然历史上使用交织数据格式有其原因但随着音频处理的需求变得更加灵活和高效现代的音频系统通常会避免使用交织方式转而采用更简单、直接的数据格式。 如果把交错的数据看作是 Nx2 矩阵转置就可以看作是交错。这里有一种就地做的方法https://goo.gl/fgPmrg https://en.wikipedia.org/wiki/In-place_matrix_transposition#Non-square_matrices:_Following_the_cycles 在这个解释中讨论了如何实现或者将数据保持为一个 N 行 2 列的矩阵并将转置视为交织操作。提到了一种就地in-place实现的方法。具体的过程是 初始化首先对于大于 1 的长度遍历每个排列并选择一个起始地址SC。数据存储设定 d 等于起始地址 S 处的数据。保存数据将起始地址 S 处的数据保存在一个变量 X 中。循环操作当 X 不等于 S 时进行以下操作 从 X 中移动数据到 S 的下一个位置。更新 X 为 S 的前驱位置。将数据从 D 移动到测试位置。 这个方法的核心思想是通过循环和数据移动的方式实现就地的数据转置或者交织操作而不需要额外的内存分配。 需要注意的是实际实现时需要仔细处理数据的位置和顺序确保不会丢失任何数据并且操作的效率较高。 这段说明看起来像是一个需要仔细阅读和理解的算法尤其是在处理数据搬移和循环中的细节时。 在使用 arena内存分配器时创建了一个子 arena 用于游戏资产但在操作过程中未正确地抓取一些数组数据。提出了是否应该将这些数据也放入 arena 的讨论尽管最终认为这可能并不一定是一个 bug而是最初设计的目的。 在考虑了这个问题后表达了对该设计的重新理解认为这种分配方式是合理的因为它分别处理了结构性信息和数据性信息因此没有必要将所有内容都放入 arena 中。最终得出了这个问题并非 bug而是按预期设计进行的结论。 为什么要就地更改 PCM 数据而不是将其压缩成你需要的信息并分配额外的空间 在这段话中首先提到了关于是否需要直接在运行时修改 PCM 数据的问题。这个问题更多是出于好奇而非实际需求。实际上在运行时并不需要进行这样的修改因为资产处理器Asset Processor会在加载过程中将数据整理成正确的频道顺序。因此不需要在程序中手动改变 PCM 数据也不需要额外分配空间来精简数据因为资产处理器会在预处理阶段完成这一工作。 接下来提到原本尝试在运行时对数据进行交错处理interleave时发现虽然可以做到但需要更加巧妙的方法而不是简单地进行处理。最终得出了一个结论虽然可以做但并不值得在运行时进行因为处理器会在前期做好这部分工作。
http://www.dnsts.com.cn/news/105546.html

相关文章:

  • 网站域名建设怎么填写热烈祝贺公司网站上线
  • 网站设计用什么字体好网站怎么添加横幅
  • 精通网站建设网页设计免费模板代码
  • 清远 网站建设如何做楼盘网站
  • 新闻静态网站模板下载自己做网站不推广
  • easyui做的网站购物网站模板代码
  • 物流网站的分类毕业设计做购物网站的要求
  • 2015做网站前景电子商务网站的定义
  • 柳市那些做网站的公司网络公司 给 客户网站备案
  • 做网站的那些个人工作室网站建设功能覆盖范围
  • 虚拟专用网络服务器网站如何做seo优化
  • 福田网站建设推广修改wordpress用户名
  • 转塘有做网站的吗网络营销软件哪个好用
  • 富阳网站网站做中英文英文太长怎么办
  • 网站空间就是服务器吗济南网站建设抖音平台
  • 安徽省芜湖建设定额网站东莞网站建设关键词
  • h5网站开发软件免费申请激活码
  • 网站虚拟空间过期文档流程做网站
  • 网站建设栏目流程管局备案网站
  • 网站的建站流程做网站开发需要什么技能
  • 中山快速做网站费用网站开发就业外部威胁
  • 电子商务毕业设计网站建设业务致设计网站
  • 怎么添加网站 多少钱数字营销经典案例
  • 756ka网站建设做cpa用什么类型的网站好
  • 网站模板的组成门户网站ui设计
  • 网站建设的分工企业网站源码利于优化
  • 黄冈网站建设哪家好做网站傻瓜软件
  • 如何搭建一个网站自己注册公司
  • 介绍网站建设深圳网站关键词推广
  • 2019怎么做网站赚钱建设网站的协议范本