Преобразование 24 - 16-битного аудио с использованием инструкций SSE/simd

Интересно, есть ли какой-нибудь быстрый способ сделать квантование от 24 до 16 бит на массиве аудиосэмплов (с использованием встроенных или asm).

Исходный формат подписан 24 ле.

Обновление: удалось выполнить преобразование, как описано:

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
  }
}

Теперь о сглаживании - как избежать эффектов квантования?

Любая подсказка приветствуется. Спасибо

1 ответ

Ваш окончательный код выглядит странно. Зачем тасовать, а затем делать побайтное смещение всего регистра? Вместо этого настройте маску управления перемешиванием, чтобы начать все в нужном месте.

Также, packusdw не конвертирует 32-битный полный диапазон в 16-битный полный диапазон. Он насыщает (до 0xffff) любой 32-битный элемент больше, чем 2^16-1. Таким образом, вы должны сами сдвинуть данные вправо, чтобы перейти от 24-битного диапазона к 16-битному. (В аудио преобразование от 16 до 24 битов выполняется путем добавления 8 нулевых битов как младших битов, не самых значимых.)

В любом случае, это означает, что мы хотим упаковать старшие 16 байт из каждых 24 бит входных данных. Мы можем просто сделать это с шаффлом.

//__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. Тем не мение, punpck инструкции конкурируют за тот же порт, что и pshufb, (порты 1, 5 на Nehalem/Sandybridge/IvyBridge, порт 5 только на Haswell.) por может работать на любом из портов 0,1,5, даже на Haswell, поэтому это не создает проблемы узкого места port 5.

Слишком высокая нагрузка на петлю без развертывания, чтобы насыщать порт 5 даже на Haswell, но она близка. (9 мопов в слитых доменах, 2 из них требуют порта 5. Отсутствует зависимость от переноса цикла, и достаточно мопов для загрузки / сохранения, которые должны быть возможны 4 моп за цикл.) Развертывание на 2 или 3 должно сработать. Nehalem/Sandybridge/Ivybridge не будет узким местом на портах исполнения, поскольку они могут перетасовываться на двух портах. Core2 занимает 4 мопа для PSHUFBи может выдержать только 1 на 2 цикла, но это все же самый быстрый способ сделать это перемещение данных. Пенрин (он же волкдейл) тоже должен быть быстрым, но я не рассматривал детали. Пропускная способность декодера будет проблемой на этапе до Nehalem.

Поэтому, если все находится в кеше L1, мы можем сгенерировать 16B из 16b аудио за 2 цикла. (Или меньше, с некоторым развёртыванием, на пре-Haswell.)

Процессоры AMD (например, Steamroller) также имеют pshufb на том же порту, что и punpckв то время как логические значения могут работать на любом из двух других векторных портов, так что это та же самая ситуация. Тасовки имеют большую задержку, чем у Intel, но пропускная способность по-прежнему равна 1 за цикл.

Если вы хотите правильное округление вместо усечения, добавьте что-то вроде 2^7 к образцам перед усечением. (Вероятно, требующий некоторой корректировки знака.) Если вы хотите сглаживать, вам нужно что-то еще более сложное, и вы должны поискать это в Google или поискать реализацию библиотеки. Audacity с открытым исходным кодом, так что вы можете посмотреть, как они это делают.

Другие вопросы по тегам