Преобразование 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 с открытым исходным кодом, так что вы можете посмотреть, как они это делают.