Audio 使用SSE/simd指令将24位音频转换为16位音频

Audio 使用SSE/simd指令将24位音频转换为16位音频,audio,simd,sse2,quantization,sse3,Audio,Simd,Sse2,Quantization,Sse3,我想知道是否有任何快速的方法可以对音频样本阵列进行24位到16位的量化(使用intrinsic或asm) 源格式为24 le签名 更新: 按照如下所述完成转换: static void __cdecl Convert24bitToStereo16_SSE2(uint8_t* src, uint8_t* dst, int len) { __m128i shuffleMask = _mm_setr_epi8(-1,0,1,2,-1,3,4,5,-1,6,7,8,-1,9,10,11);

我想知道是否有任何快速的方法可以对音频样本阵列进行24位到16位的量化(使用intrinsic或asm)

源格式为24 le签名

更新: 按照如下所述完成转换:

static void __cdecl Convert24bitToStereo16_SSE2(uint8_t* src, uint8_t* dst, int len)
{
    __m128i shuffleMask = _mm_setr_epi8(-1,0,1,2,-1,3,4,5,-1,6,7,8,-1,9,10,11);             

    __asm 
  {    
        mov        eax, [src]   // src          
        mov        edi, [dst]   // dst
        mov        ecx, [len]   // len

        movdqu     xmm0,xmmword ptr [shuffleMask]           

      convertloop:
        movdqu     xmm1, [eax]              // read 4 samples           
        lea        eax,  [eax + 12]         // inc pointer                      
        pshufb     xmm1,xmm0                // shuffle using mask
        psrldq     xmm1, 2                  // shift right

        movdqu     xmm2, [eax]              // read next 4 samples          
        lea        eax,  [eax + 12]         // inc pointer                      
        pshufb     xmm2, xmm0               // shuffle
        psrldq     xmm2, 2                  // shift right
        packusdw   xmm1, xmm2               // pack upper and lower samples

        movdqu     [edi], xmm1              // write 8 samples
        lea        edi, [edi + 16]
        sub        ecx, 24
        jg         convertloop
  }
}
现在讨论抖动-如何避免量化效应


欢迎任何提示。Thx

您的最终代码看起来很奇怪。为什么要洗牌,然后对整个寄存器进行字节移位?取而代之的是,设置你的洗牌控制面具,把东西放在正确的地方开始

此外,
packusdw
不会将全量程32位转换为全量程16位。它使大于2^16-1的任何32位元素饱和(至0xffff)。所以你必须自己右移数据,从24位满量程到16位满量程。(在音频中,从16位到24位的转换是通过添加8个零位作为最低有效位,而不是最高有效位来完成的。)

无论如何,这意味着我们要把每24位输入的高16b背靠背地打包。我们可以通过洗牌来实现这一点

//__m128i shuffleMask = _mm_setr_epi8(-1,0,1,2,-1,3,4,5,-1,6,7,8,-1,9,10,11);
// setr takes its args in reverse order, so right-shift by 2 bytes -> move the first 2 args
//__m128i shiftedMask = _mm_setr_epi8(1,2,-1,3,4,5,-1,6,7,8,-1,9,10,11,-1,-1);

// could get 10B, but packing that into the output would be slower
__m128i mask_lo = _mm_setr_epi8( 1,2,  4,5,   7,8,   10,11,
                                -1,-1, -1,-1, -1,-1, -1,-1);
//    __m128i mask_hi = _mm_setr_epi8(-1,-1, -1,-1, -1,-1, -1,-1,
//                                     1,2,  4,5,   7,8,   10,11);
//  generate this from mask_lo instead of using more storage space  

  ... pointer setup
  movdqu     xmm3, xmmword ptr [mask_lo]
  pshufd     xmm4, xmm3, 0x4E  // swap high/low halves

  convertloop:
    movdqu     xmm0, [eax]              // read 4 samples
    pshufb     xmm0, xmm3               // low 8B = 24->16 of first 12B, high8 = 0
    movdqu     xmm1, [eax + 12]         // read next 4 samples
    pshufb     xmm1, xmm4               // high 8B = 2nd chunk of audio, low8 = 0
    por        xmm1, xmm0               // merge the two halves

    movdqu     [edi], xmm1              // write 8 samples
    add        eax, 24
    lea        edi, [edi + 16]
    sub        ecx, 24
    jg         convertloop
另外,要小心读取超过数组末尾的数据。每个
movdqu
读取16B,但只使用前12个

我本可以使用同一个掩码两次,然后使用
PUNPCKLQDQ
将高8B放入持有低8B的reg的上半部分。但是,
punpck
指令与
pshufb
争夺同一端口。(Nehalem/Sandybridge/IvyBridge上的端口1、5,仅Haswell上的端口5。)
por可以在任何端口0、1、5上运行,即使在Haswell上也是如此,因此它不会造成端口5瓶颈问题

即使在Haswell上,如果不展开,循环开销也太高,无法使port5饱和,但很接近。(9个融合域UOP,其中2个需要端口5。没有循环携带的依赖关系,并且足够多的UOP是加载/存储,每个循环可以有4个UOP。)按2或3展开就可以了。Nehalem/Sandybridge/Ivybridge不会在执行端口上出现瓶颈,因为它们可以在两个端口上混洗。Core2在PSHUFB中需要4个UOP,并且每2个周期只能维持1个UOP,但它仍然是进行数据移动的最快方式。Penryn(又名wolfdale)在这方面也应该很快,但我还没有看到细节。不过,解码器吞吐量将是Nehalem之前的一个问题

所以,如果一切都在一级缓存中,我们可以每2个周期生成16B的16B音频。(或更少,在预Haswell上展开一些。)

AMD CPU(例如Steamroller)也在与punpck相同的端口上具有
pshufb
,而Boolean可以在其他两个向量端口中的任何一个上运行,因此情况相同。洗牌的延迟比Intel上的要高,但吞吐量仍然是每个周期1次


如果您希望进行适当的舍入而不是截断,请在截断之前向样本中添加类似于2^7的内容。(可能需要一些符号调整。)如果你想要抖动,你需要更复杂的东西,应该在谷歌上搜索,或者寻找一个库实现。Audacity是开源的,所以你可以看看他们是如何做到的。

24到16位非常简单-你加载三个128位的值,然后你洗牌(
\u mm\u shuffle\u epi8
)字节以删除每三个字节,最后存储两个128位的值作为结果。如果需要精确的四舍五入,则会稍微复杂一些。@RomanR。我认为这不会解决抖动问题。你想应用哪种抖动?