我想知道是否有任何快速方法可以对音频样本数组进行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
}
}
现在是抖动 - 如何避免量化效应?
欢迎任何提示。 THX
答案 0 :(得分:2)
你的最终代码看起来很奇怪。为什么要进行随机播放,然后对整个寄存器进行逐字节移位?相反,设置你洗牌控制面具,把东西放在正确的位置开始。
此外,packusdw
无法将全范围32位转换为全范围16位。它使任何大于2 ^ 16-1的32位元素饱和(到0xffff)。所以你必须自己右移数据,从24位全范围到16位全范围。 (在音频中,通过将8个零位添加为最不重要的位来完成从16位到24位的转换,而不是最重要的位。)
无论如何,这意味着我们想要背靠背地输入每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。没有循环携带的依赖,并且足够的uop是每个周期4uops的加载/存储应该是可能的。)展开2或3应该做特技。 Nehalem / Sandybridge / Ivybridge在执行端口上不会出现瓶颈,因为它们可以在两个端口上进行洗牌。 Core2对于PSHUFB
需要4 uop,并且每2个周期只能维持1次,但它仍然是执行此数据移动的最快方式。 Penryn(aka wolfdale)也应该很快,但我还没看过细节。然而,解码器吞吐量将成为Nehalem之前的一个问题。
因此,如果所有内容都在L1缓存中,我们可以每2个周期生成16B的16b音频。 (或更少,有一些展开,在Haswell之前。)
AMD CPU(例如Steamroller)在与pshufb
相同的端口上也有punpck
,而布尔值可以在其他2个向量端口上运行,因此情况相同。随机播放的延迟时间高于英特尔,但吞吐量仍为每周期1个。
如果要进行适当的舍入而不是截断,请在截断前向样本中添加2 ^ 7之类的内容。 (可能需要进行一些符号调整。)如果你想要抖动,你需要一些更复杂的东西,并且应该谷歌,或者寻找一个库实现。 Audacity是开源的,所以你可以看看他们是如何做到的。