【问题标题】:How to implement 16bit stereo mixing on ARMv6+?如何在 ARMv6+ 上实现 16 位立体声混音?
【发布时间】:2014-01-08 20:40:51
【问题描述】:

我需要优化我在 c 中的混合代码以获得更快的响应时间,因此我决定使用内联汇编将两个缓冲区混合到一个新的更大的缓冲区中。基本上我将左右声道分开,我想将它们放在一起放入缓冲区。所以我需要从左通道放 2 个字节,然后从右通道放两个字节,依此类推。 为此,我决定将我的 3 个指针发送到我的汇编代码,在那里我打算将左通道指针指向的内存复制到 R0 寄存器中,并将右通道指针指向的内存复制到 R1 之后我打算将 R0 和 R1 混合到 R3 和 R4 中稍后将这些寄存器保存到内存中。(我打算使用其他空闲寄存器来执行相同的过程并通过流水线减少处理时间)

所以我有两个带有数据的寄存器 R0 和 R1,需要将它们混合到 R3 和 R4 中,我需要最终得到 R3 = R0HI(high-part) + R1HI(high-part) 和 R4 = R0LO (低频) + R1LO(低频)

我可以考虑使用按位移位,但我的问题是,是否有一种更简单的方法来做到这一点,例如 intel x86 架构,您可以将数据传输到 ax 寄存器,然后将我们 ah 作为高部分和 al 作为低部分部分?

我的想法对吗?有更快的方法吗?

我在 ndk 中的实际(不工作)代码

void mux(short *pLeftBuf, short *pRightBuf, short *pOutBuf, int vecsamps_stereo) {
    int iterations = vecsamps_stereo / 4;
    asm volatile(
        "ldr r0, %[outbuf];"
        "ldr r1, %[leftbuf];"
        "ldr r2, %[rightbuf];"
        "ldr r3, %[iter];"

        "ldr r4, [r3];"
        "mov r8, r4;"
        "mov r9, r0;"
        "mov r4, #0;"
        "mov r10, r4;"

        "loop:; "

        "ldr r2, [r1];"
        "ldr r3, [r2];"

        "ldr r7, =0xffff;"

        "mov r4, r2;"
        "and r4, r4, r7;"
        "mov r5, r3;"
        "and r5, r5, r7;"
        "lsl r5, r5, #16;"
        "orr r4, r4, r5;"

        "lsl r7, r7, #16;"

        "mov r5, r2;"
        "and r5, r5, r7;"
        "mov r6, r3;"
        "and r6, r6, r7;"
        "lsr r6, r6, #16;"
        "orr r5, r5, r6;"

        "mov r6, r9;"
        "str r4, [r6];"
        "add r6, r6, #1;"
        "str r5, [r6];"
        "add r6, r6, #1;"
        "mov r9, r6;"

        "mov r4, r10;"
        "add r4, r4, #1;"
        "mov r10, r4;"
        "cmp r4, r8;"
        "blt loop"

        :[outbuf] "=m" (pOutBuf)
        :[leftbuf] "m" (pLeftBuf) ,[rightbuf] "m" (pRightBuf),[iter] "m" (pIter)
        :"r0","r1","r2","r3","memory"
    );
}

【问题讨论】:

  • 在考虑汇编程序之前,您应该检查现有 C 实现的性能 - 很可能通过对其进行分析。在此处发布您的代码会有所帮助 - 就像准确了解您正在使用的 ARM 设备一样吗?有霓虹灯吗?
  • 您能否澄清一下您是指成对的 16 + 16 到 32 位加法还是只是交错?我提到的打包添加指令可能不相关,但其余的都是任何一种方式。
  • @marko 我在单独的通道中进行信号处理,使用 fft eq,在此之前进行压缩和其他一些事情,时间还不错,但我在这部分发现了一个瓶颈。跨度>
  • @Notlike,我的意思是成对的 16 + 16 位加法,我的目标是带和不带霓虹灯的 arm 处理器,下一个优化将利用它的优势为霓虹灯设备编译。

标签: c audio assembly arm inline-assembly


【解决方案1】:

我可能不是 100% 清楚你想要做什么,但看起来你想要:

    R3[31:16] = R0[31:16], R3[15:0] = R1[31:16];
    R4[31:16] = R0[15:0], R4[15:0] = R1[15:0];

而不是实际的总和。

在这种情况下,您应该能够使用 16 位掩码的备用寄存器相对有效地完成此操作。作为大多数算术或逻辑指令的一部分,ARM 汇编提供了第二个操作数的移位。

    MOV R2, 0xffff               ; load 16-bit mask into lower half of R2

    AND R3, R2, R1, LSR #16      ; R3 = R2 & (R1 >> 16), or R3[15:0] = R1[31:16]
    ORR R3, R3, R0, LSR #16      ; R3 = R3 | (R0 >> 16), or R3[31:16] = R0[31:16]

    AND R4, R2, R1               ; R4 = R2 & R1, or R4[15:0] = R1[15:0]
    ORR R4, R4, R0, LSL #16      ; R4 = R4 | (R1 << 16), or R4[31:16] = R0[15:0]

    ; repeat to taste

另一种选择是一次只加载 16 位,但如果您的缓冲区在慢速内存中,这可能会降低性能,并且如果它不支持少于 32 位的访问,它可能根本无法工作。我不确定核心是否会请求 32 位并屏蔽掉不需要的内容,或者它是否依赖内存来处理字节通道。

    ; assume R2 contains CH1 pointer, R3 contains CH2 pointer,
    ; and R1 contains output ptr
    LDRH R0, [R2]                ; load first 16 bits pointed to by CH1 into R0
    STRH R0, [R1]                ; store those 16 bites back into *output
    LDRH R0, [R3]                ; load first 16 bits pointed to by CH2 into R0
    STRH R0, [R1, #2]!           ; store those 16 bites back into *(output+2),
                                 ; write output+2 to R1

    ; after "priming" we can now run the following for
    ; auto increment of pointers.
    LDRH R0, [R2, #2]!           ; R0 = *(CH1+2), CH1 += 2
    STRH R0, [R1, #2]!           ; *(Out+2) = R0, Out += 2
    LDRH R0, [R3, #2]!           ; R0 = *(CH2+2), CH1 += 2
    STRH R0, [R1, #2]!           ; *(Out+2) = R0, Out += 2

    ; Lather, rinse, repeat.

这两个示例利用了 ARM 汇编的一些便利功能。第一个示例利用了大多数指令上可用的内置移位,而第二个示例利用了调整大小的加载/存储指令以及这些指令的回写。这些都应该与 Cortex-M 内核兼容。如果你有更高级的 ARM,@Notlikethat 的答案更合适。

就代码大小而言,当您将加载和存储添加到第一个示例时,您最终会执行两条加载指令、四条逻辑指令和两条存储,总共八条指令用于混合两个样本。第二个示例在混合一个样本时使用两个加载和两个存储,总共有四个指令,或者,混合两个样本时使用八个指令。

您可能会发现第一个示例运行得更快,因为它具有更少的内存访问,并且可以通过使用STM 存储多指令(即STMIA OutputRegister!, {R3, R4})来减少存储的数量。事实上,第一个示例可以通过使用八个寄存器进行一点流水线化。 LDMIA 可用于在一条指令中从一个通道加载四个 16 位样本,执行两组四个混合指令,然后将四个输出寄存器存储在一条 STMIA 指令中。这可能不会在性能方面提供太多好处,因为它可能会以相同的方式与内存交互(STMLDM 只需执行多个 LDRs 和 STRs),但如果您正在优化最少的指令,这将导致混合四个样本的 11 条指令(与 16 条相比)。

【讨论】:

  • +1 不需要掩码,因为 shifts 通常用零填充;特别是你的逻辑版本。如果系统有 cache,那么ldrh 版本可能会更快。但是,如果这是一个声音驱动的音频缓冲区,它可能会涉及到 DMA 而不会被缓存。
  • 好点!我不知道为什么我没有意识到这一点,因为我指望它与左移。将 R1 的下半部分移动到 R4 的下半部分时仍然需要屏蔽,但可以通过使用UBFX 提取这些位来完成此操作而无需屏蔽。 UBFX R4, R1, #0, #16,但这是特定于 Thumb-2 的。虽然,我所拥有的 16 位立即数 MOV 看起来也是 Thumb-2 的东西。
  • @artlessnoise lsr 移入零,而 asr 是符号保留的,因此它移入第 31 位的副本。
  • @RJP 我已经尝试实现第一个代码,但我无法理解它的作用,因为我看到它擦除了 r1 的低部分,因为 lsr #16 in when保存到 r3。至少这就是我的理解,如果我错了,请纠正我。另一方面,我编写了自己的内联汇编代码并尝试在 ndk 中实现它,但没有成功。我将我的代码添加到问题中
  • @alexm,我相当肯定,当移位作为灵活操作数的一部分应用时,它不会写回源寄存器,而仅作为输入应用。例如mov R1, R0, LSR #16会将R0的高16位放入R1而不修改R0中的值。 ARM 缺乏实际的移位指令。我引用的 ARM 指令集快速参考 LSR{S} Rd, Rm, &lt;shift&gt; 的指令与 MOV{S} Rd, Rm, LSR &lt;shift&gt; 相同
【解决方案2】:

ARM 寄存器是严格 32 位的,但是如果您使用的是最新的内核(v6+,但不是仅限 Thumb 的 v7-M),则有许多合适的指令可用于处理半字 (PKHBT, PKHTB),或任意寄存器切片(BFIUBFX),更不用说让我害怕的疯狂并行加/减指令(可用于音频的饱和算法)..

但是,如果您的机器实现了 NEON 指令,它们将是实现最佳实现的途径,因为这正是它们的设计目的。此外,它们应该可以通过编译器内部函数访问,以便您可以直接在 C 代码中使用它们。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多