【问题标题】:Converting 24 to 16 bit audio using SSE/simd instructions使用 SSE/simd 指令将 24 位音频转换为 16 位音频
【发布时间】:2026-01-06 00:55:01
【问题描述】:

我想知道是否有任何快速方法可以对音频样本数组进行 24 位到 16 位量化(使用内在函数或 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
  }
}

现在是抖动 - 如何避免量化效应?

欢迎任何提示。谢谢

【问题讨论】:

  • 24 到 16 位非常简单 - 您加载三个 128 位值,然后随机播放 (_mm_shuffle_epi8) 字节以删除每个第三个字节,并最终存储两个 128 位值作为结果。如果您需要精确舍入,则稍微复杂一些。
  • @RomanR。我认为这不会解决抖动问题。
  • 您要应用哪种抖动?

标签: audio simd sse2 quantization sse3


【解决方案1】:

您的最终代码看起来很奇怪。为什么要洗牌然后对整个寄存器进行字节移位?相反,设置你的随机控制掩码,将事情放在正确的位置开始。

此外,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 上也没有展开以使端口 5 饱和,但它已经接近了。 (9 个融合域 uop,其中 2 个需要端口 5。没有循环携带的依赖项,并且足够的 uops 是加载/存储,每个周期应该有 4 个 uops。)展开 2 或 3 应该可以解决问题。 Nehalem/Sandybridge/Ivybridge 不会在执行端口上出现瓶颈,因为它们可以在两个端口上洗牌。 Core2 对于PSHUFB 需要 4 微欧,并且每 2 个周期只能维持 1 个,但它仍然是执行此数据移动的最快方法。 Penryn(又名 wolfdale)也应该很快,但我没有看过细节。不过,解码器吞吐量在 Nehalem 之前将是一个问题。

因此,如果所有内容都在 L1 缓存中,我们可以每 2 个周期生成 16B 的 16b 音频。 (或者更少,有一些展开,在前 Haswell 上。)

AMD CPU(例如 Steamroller)在与 punpck 相同的端口上也有 pshufb,而布尔值可以在其他 2 个矢量端口中的任何一个上运行,所以情况相同。 Shuffle 的延迟比 Intel 更高,但吞吐量仍然是每个周期 1。

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

【讨论】: