Audio 使用SIMD指令对音频通道进行解交织

Audio 使用SIMD指令对音频通道进行解交织,audio,x86,sse,simd,intrinsics,Audio,X86,Sse,Simd,Intrinsics,我正在实现一个混音器,它在没有SIMD指令的情况下工作得很好,但很难弄清楚如何将声音数据提取到单独的通道中 我的数据是交错格式的:L0R0 L1R1 L2R2 L3R3。。。 我以相同的格式将它们加载到uum128i中,因此寄存器中有4个样本 我希望它们在单独的频道中:l0l1l2l3r0r1r2r3。这是我错过的部分 因此,输入为:8 x i16(4xi32交错) 我希望输出为left=4xf32和right=4xf32,然后进行混合 混音后,我可以交错通道,得到L0R0 L1R1 L2R2…

我正在实现一个混音器,它在没有SIMD指令的情况下工作得很好,但很难弄清楚如何将声音数据提取到单独的通道中

我的数据是交错格式的:L0R0 L1R1 L2R2 L3R3。。。 我以相同的格式将它们加载到uum128i中,因此寄存器中有4个样本

我希望它们在单独的频道中:l0l1l2l3r0r1r2r3。这是我错过的部分

因此,输入为:8 x i16(4xi32交错) 我希望输出为left=4xf32和right=4xf32,然后进行混合

混音后,我可以交错通道,得到L0R0 L1R1 L2R2…:

__m128 *src0 = mixed_channel0;
__m128 *src1 = mixed_channel1;
__m128 *dest = (__m128i *)buffer;

for (u32 sample_index = 0; sample_index < sample_chunk_count; ++sample_index)
{
    __m128 s0 = _mm_load_ps((f32 *)src0++);
    __m128 s1 = _mm_load_ps((f32 *)src1++);

    __m128i l = _mm_cvtps_epi32(s0);
    __m128i r = _mm_cvtps_epi32(s1);

    __m128i lr0 = _mm_unpacklo_epi32(l, r);
    __m128i lr1 = _mm_unpackhi_epi32(l, r);

    *dest++ = _mm_packs_epi32(lr0, lr1);
}
即使我屏蔽了低/高阶i16-s,那么如何将它们转换为f32-s?掩蔽之后,我会得到:

__m128i right = [xx, R0, xx, R1, xx, R2, xx, R3]
__m128i left = [L0, xx, L1, xx, L2, xx, L3, xx]
如果我能把它们转换成4 x i32-s,那么用_mm_cvtepi32_ps把它们转换成f32-s就很容易了,我就完成了


谢谢。

从16位样本对到32位样本的掩码和移位

// clunky calling convention, but should inline ok.
__m128 unpack_leftright_16bit_channels(__m128i input, __m128 &right_retval) {
    // input = [L0R0, L1R1, L2R2, L3R3] packed pairs of 16bit ints
    __m128i sign_extended_left  = _mm_srai_epi32(input, 16);
    __m128i high_right = _mm_slli_epi32(input, 16);
    __m128i sign_extended_right = _mm_srai_epi32(high_right, 16);

    right_retval = _mm_cvtepi32_ps(sign_extended_right);
    //__m128 right = [R0, R1, R2, R3] packed 32bit floats

    __m128 left  = _mm_cvtepi32_ps(sign_extended_left);
    //__m128 left = [L0, L1, L2, L3] packed 32bit floats
    return left;
}
这个,或者叫叮当声3.7

这将成为大多数微体系结构(请参阅和TagWiki中的其他链接)上洗牌吞吐量的瓶颈。可能值得使用SSSE3
pshufb
来执行逻辑左移位,仅使用算术右移位的实际移位指令,该算术右移位需要在每个32位元素的上半部分保留符号位的副本。如果没有AVX,
pshufb
会原地移动,就像
pslld
原地移动一样(谢谢,英特尔:()),因此它不会避免额外的MOV指令来制作输入的第二个副本

在Skylake上,立即向量移位在p0/p1上运行,cvtdq2ps也在p0/p1上运行。
pshufb
用于左移位会将吞吐量增加到每个时钟一个浮点输出向量,因为混洗在端口5上运行

在skylake之前,即时向量移位仅在单个端口上运行,例如Haswell中的p0。至少这与int->float不同:Haswell在p1上运行
cvtdq2ps
。因此,pshufb将吞吐量增加到每个时钟一个ps向量



似乎应该有更好的方法来实现这一点,比如使用AND掩码或其他方法。但似乎2个移位,或一个shuffle+shift,是将每个32位元素的低16位符号扩展为完整32位元素的最佳方法。

我认为转换低/高
i16
s的最佳方法是使用位移位。
high\halves=_mm_srai_epi32(packed,16);
。由于您的值是有符号的,您可能需要对下半部分进行符号扩展,方法是先左移,然后使用算术右移。我想不出比ATM更好的方法,但这似乎有点笨重。一些SIMD指令集(如ARM NEON/ARMv8)有两条以上的输入或两条以上的输出指令,我认为可以用一条指令来解包(可能解压,IIRC)。因此,重要的是要特别说明英特尔SSE,而不仅仅是任何SIMD。你能要求吗?虽然它不是现代PC的“必需”方式,但它非常常见(对于Steam游戏玩家来说是91%).
\u-mm\u-moveldup\u-ps
\u-mm\u-movehdup\u-ps
非常有用。@ChuckWalbourn:用SSE3复制偶数或奇数元素,你在想什么?OP所需的输出不需要将任何数据移到它们开始的32位元素之外。起初,我在想使用
shufps
来组合数据from有两个向量,但我读得更仔细了,中间的代码块是显示输入和所需输出的。@PeterCordes谢谢,
\u mm\u srai\u epi32
给了我左通道值;
\u mm\u slli\u epi32
16然后
\u mm\u srai\u epi32
16给了我右通道值,将它们转换成
f32-s是直截了当的。如果可以的话,我会接受你的评论作为回答,或者我会在家里为代码做测试时发布代码。谢谢!扩展低位单词的可能替代符号:
((x^0x8000)&0xFFFF)-0x8000
(不过需要3uops而不是2个)。或者,实际上,向左移动,转换为浮点,并在后续操作中考虑额外的因数
0x10000
。如果以后可以补偿额外因数,则高位字甚至可以被一个位和一个字节屏蔽。
// clunky calling convention, but should inline ok.
__m128 unpack_leftright_16bit_channels(__m128i input, __m128 &right_retval) {
    // input = [L0R0, L1R1, L2R2, L3R3] packed pairs of 16bit ints
    __m128i sign_extended_left  = _mm_srai_epi32(input, 16);
    __m128i high_right = _mm_slli_epi32(input, 16);
    __m128i sign_extended_right = _mm_srai_epi32(high_right, 16);

    right_retval = _mm_cvtepi32_ps(sign_extended_right);
    //__m128 right = [R0, R1, R2, R3] packed 32bit floats

    __m128 left  = _mm_cvtepi32_ps(sign_extended_left);
    //__m128 left = [L0, L1, L2, L3] packed 32bit floats
    return left;
}