我想知道是否有任何快速方法可以对音频样本数组(使用内在函数或 asm)进行 24 位到 16 位的量化。
源格式是有符号的 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
}
}
现在是抖动——如何避免量化效应?
欢迎任何提示。谢谢
最佳答案
你的最终代码看起来很奇怪。为什么先洗牌然后对整个寄存器进行字节移位?相反,设置你的 shuffle 控制掩码以将事情放在正确的位置开始。
另外,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 个融合域 uops,其中 2 个需要 port5。没有循环携带的依赖性,并且足够的 uops 是每个周期 4uops 的加载/存储应该是可能的。)按 2 或 3 展开应该可以解决问题。 Nehalem/Sandybridge/Ivybridge 不会在执行端口上出现瓶颈,因为它们可以在两个端口上混洗。 Core2 为
PSHUFB
需要 4 个 uops ,并且每 2 个周期只能维持 1 个,但它仍然是进行这种数据移动的最快方式。 Penryn(又名 wolfdale)也应该很快,但我没有看过细节。不过,解码器吞吐量将是 Nehalem 之前的一个问题。所以如果一切都在 L1 缓存中,我们可以每 2 个周期生成 16B 的 16b 音频。 (或更少,有一些展开,在前 Haswell 上。)
AMD CPU(例如 Steamroller)也有
pshufb
在与 punpck
相同的端口上,而 bool 值可以在其他 2 个向量端口中的任何一个上运行,因此情况相同。 Shuffle 的延迟比 Intel 高,但吞吐量仍然是每个周期 1。如果您想要适当的舍入而不是截断,请在截断之前向样本添加 2^7 之类的内容。 (可能需要一些符号调整。)如果你想要抖动,你需要更复杂的东西,应该用谷歌搜索,或者寻找一个库实现。 Audacity 是开源的,所以你可以看看他们是怎么做的。
关于audio - 使用 SSE/simd 指令将 24 位音频转换为 16 位音频,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30007801/