使用SIMD指令解交织音频通道

时间:2016-08-28 22:00:08

标签: audio x86 sse simd intrinsics

我实现了混音器,它在没有SIMD指令的情况下运行良好,但很难确定如何将声音数据提取到单独的通道中。

我的数据采用交错格式:L0R0 L1R1 L2R2 L3R3 ...... 我以相同的格式将它们加载到__m128i中,所以我在寄存器中有4个样本。

我希望它们位于不同的通道中:L0L1L2L3 R0R1R2R3。这是我错过的部分。

所以输入为:8 x i16(4xi32 interleaved) 我希望输出为left = 4 x f32和right = 4 x f32,然后进行混音。

混音后,我可以交错通道,我得到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);
}

基本上我需要做相反的事情:

__m128i input = [L0R0, L1R1, L2R2, L3R3] packed pairs of 16bit ints
// magic happens, then
__m128 left = [L0, L1, L2, L3] packed 32bit floats
__m128 right = [R0, R1, R2, R3] packed 32bit floats

即使我屏蔽了低/高阶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很容易,我就完成了。

感谢。

1 个答案:

答案 0 :(得分:0)

屏蔽并移位成对的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;
}

compiles to what you'd expect with gcc5.3或clang3.7。

这将导致大多数微体系结构的随机吞吐量出现瓶颈(请参阅Agner Fog's insn tables and microarch pdf以及标记wiki中的其他链接)。使用SSSE3 pshufb进行逻辑左移可能是值得的,仅使用实际移位指令进行算术右移,需要在每个32位元素的上半部分留下符号位的副本。没有AVX,pshufb就地移动,就像pslld就地移位一样(谢谢,英特尔:()),所以它不会避免额外的MOV指令来输入第二个副本

在Skylake上,立即向量移位在p0 / p1上运行,cvtdq2ps也是如此。使用pshufb进行左移将使吞吐量增加到每个时钟一个浮点输出向量,因为随机数在端口5上运行。

Pre-skylake,立即向量移位仅在单个端口上运行,例如哈斯威尔的p0。至少那个与int-&gt; float不同的端口:Haswell在p1上运行cvtdq2ps。同样,pshufb会将吞吐量提高到每个时钟一个ps矢量。

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