Эффективно конвертировать аудио байты - byte [] в short [] - PullRequest
2 голосов
/ 20 апреля 2011

Я пытаюсь использовать микрофон XNA для захвата звука и передачи его в имеющийся у меня API, который анализирует данные для целей отображения.Однако API требует аудиоданных в массиве 16-битных целых чисел.Так что мой вопрос довольно прост;Каков наиболее эффективный способ преобразования байтового массива в короткий?кажется, работает, но можно ли это сделать быстрее?

    private short[] ConvertBytesToShorts(byte[] bytesBuffer)
    {
        //Shorts array should be half the size of the bytes buffer, as each short represents 2 bytes (16bits)
        short[] shorts = new short[bytesBuffer.Length / 2];

        int currentStartIndex = 0;

        for (int i = 0; i < shorts.Length - 1; i++)
        {
            //Convert the 2 bytes at the currentStartIndex to a short
            shorts[i] = BitConverter.ToInt16(bytesBuffer, currentStartIndex);

            //increment by 2, ready to combine the next 2 bytes in the buffer
            currentStartIndex += 2;
        }

        return shorts;

    }

Ответы [ 3 ]

5 голосов
/ 20 апреля 2011

После прочтения вашего обновления я вижу, что вам нужно скопировать байтовый массив непосредственно в буфер кратких данных, объединяя байты.Вот соответствующий раздел из документации :

Формат буфера byte [], используемый в качестве параметра для конструктора SoundEffect, метода Microphone.GetData и метода DynamicSoundEffectInstance.SubmitBuffer, представляет собой PCMволновые данные.Кроме того, формат PCM чередуется и имеет порядок байтов.

Теперь, если по какой-то странной причине ваша система имеет BitConverter.IsLittleEndian == false, то вам нужно будет перебирать свой буфер, меняя байты по мереиди, чтобы преобразовать из порядкового номера в обратный порядок.Я оставлю код в качестве упражнения - я вполне уверен, что все системы XNA имеют прямой порядок байтов.

Для ваших целей вы можете просто скопировать буфер напрямую, используя Marshal.Copy или Buffer.BlockCopy.И то, и другое даст вам производительность операции копирования памяти в платформе, которая будет очень быстрой:

// Create this buffer once and reuse it! Don't recreate it each time!
short[] shorts = new short[_buffer.Length/2];

// Option one:
unsafe
{
    fixed(short* pShorts = shorts)
        Marshal.Copy(_buffer, 0, (IntPtr)pShorts, _buffer.Length);
}

// Option two:
Buffer.BlockCopy(_buffer, 0, shorts, 0, _buffer.Length);
1 голос
/ 20 апреля 2011

Это вопрос производительности, поэтому: измерьте его!

Стоит отметить, что для измерения производительности в .NET вы хотите сделать сборку релиза и запустить без отладчик подключен (это позволяет оптимизировать JIT).

Стоит прокомментировать ответ Джодрелла: использование AsParallel интересно, но стоит проверить, стоит ли его раскручиватьЭто.(Предположение - измерьте его, чтобы подтвердить: преобразование байта в короткое должно быть чрезвычайно быстрым, поэтому, если данные буфера поступают из общей памяти, а не из кеша ядра, большая часть ваших затрат, вероятно, будет заключаться в передаче данных, а не в их обработке.)1010 *

Также я не уверен, что ToArray подходит.Во-первых, он может не иметь возможности создавать массив правильного размера напрямую, поскольку из-за изменения размера массива он будет очень медленным.Кроме того, он всегда выделяет массив - который сам по себе не медленный, но добавляет стоимость GC, которая вам почти наверняка не нужна.

Редактировать: На основеВаш обновленный вопрос, код в оставшейся части этого ответа не может быть использован напрямую, так как формат данных отличается.И сама техника (петля, безопасная или небезопасная) не такая быстрая, как та, которую вы можете использовать.Подробности смотрите в моем другом ответе.

Итак, вы хотите предварительно выделить свой массив.Где-то в вашем коде вам нужен такой буфер:

short[] shorts = new short[_buffer.Length];

А затем просто скопируйте из одного буфера в другой:

for(int i = 0; i < _buffer.Length; ++i)
    result[i] = ((short)buffer[i]);

Это должно быть очень быстро, и JITдолжен быть достаточно умным, чтобы пропустить одну, если не обе проверки границ массива.

И вот как вы можете сделать это с небезопасным кодом: (Я не проверял этот код, но он должен быть примерно правильным)

unsafe
{
    int length = _buffer.Length;
    fixed(byte* pSrc = _buffer) fixed(short* pDst = shorts)
    {
        byte* ps = pSrc;
        short* pd = pDst;

        while(pd < pd + length)
            *(pd++) = (short)(*(ps++));
    }
}

Теперь небезопасная версия имеет недостаток, заключающийся в требовании /unsafe, а также она может на самом деле быть медленнее , поскольку она не позволяет JIT выполнять различные оптимизации.Еще раз: измерим .

(Также вы, вероятно, сможете увеличить производительность, если попробуете некоторые перестановки на приведенных выше примерах. Измерьте .)

Наконец: Вы уверены, что хотите, чтобы преобразование было (short)sample?Разве это не должно быть что-то вроде ((short)sample-128)*256, чтобы перевести его из неподписанного в подписанное и расширить его до правильной ширины в битах? Обновление: кажется, я ошибся в формате здесь, см. Мой другой ответ

0 голосов
/ 20 апреля 2011

Вредитель PLINQ, который я мог придумать, находится здесь.

private short[] ConvertBytesToShorts(byte[] bytesBuffer)
{         
    //Shorts array should be half the size of the bytes buffer, as each short represents 2 bytes (16bits)
    var odd = buffer.AsParallel().Where((b, i) => i % 2 != 0);
    var even = buffer.AsParallell().Where((b, i) => i % 2 == 0);

    return odd.Zip(even, (o, e) => {
        return (short)((o << 8) | e);
    }.ToArray();
}

Я не уверен в производительности, но у меня достаточно данных и процессоров, кто знает.

Если операция преобразования неверна ((short)((o << 8) | e)), пожалуйста, измените ее на подходящий.

...