SDL_OpenAudioDevice: непрерывное воспроизведение из обработанного в реальном времени исходного буфера - PullRequest
0 голосов
/ 29 апреля 2020

Пишу портирование эмулятора на SDL. Существует метод, вызываемый в каждом кадре, который передает буфер с новыми аудиосэмплами для следующего кадра.

Я открыл устройство с помощью SDL_OpenAudioDevice и в каждом кадре метод обратного вызова SDL воспроизводит сэмплы из аудиобуфера.

Работает, но звук не идеален, некоторые ti c, некоторые металлические c шумы и т. Д.

Звук имеет 16-битную подпись.

РЕДАКТИРОВАТЬ: Хорошо, я нашел решение!

С кодом открытия поста я воспроизводил сэмплы для следующего кадра в текущем кадре в режиме реального времени. Это было неправильно!

Итак, я реализовал кольцевой буфер, в который я помещаю выборки для следующего кадра, который базовый код передает мне в каждом (текущем) кадре.

В этом буфере есть 2 указателя один для точки чтения, а другой для точки записи. SDL вызывает функцию обратного вызова, когда в его аудиопотоке больше нет данных для воспроизведения; поэтому, когда вызывается функция обратного вызова, я воспроизводю аудиосэмплы из точки чтения в круговом буфере, затем обновляю указатель чтения.

Когда базовый код дает мне данные аудиосэмплов для следующего кадра, я записываю их в круговой буфер в точке записи , затем обновите указатель записи.

Указатели чтения и записи смещены на количество выборок, которые будут воспроизводиться в каждом кадре.

Код обновлен, требуется некоторая настройка, когда samplesPerFrame не является int, но это работает; -)

Структура циклического буфера:

typedef struct circularBufferStruct
{
    short *buffer;
    int cells;
    short *readPoint;
    short *writePoint;
} circularBuffer;

Этот метод вызывается при инициализации:

int initialize_audio(int stereo)
{
    if (stereo)
        channel = 2;
    else
        channel = 1;

    // Check if sound is disabled
    if (sampleRate != 0)
    {
        // Initialize SDL Audio
        if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0)
        {
            SDL_Log("SDL fails to initialize audio subsystem!\n%s", SDL_GetError());
            return 1;
        }

        // Number of samples per frame
        samplesPerFrame = (double)sampleRate / (double)framesPerSecond * channel;

        audioSamplesSize = samplesPerFrame * bytesPerSample; // Bytes
        audioBufferSize = audioSamplesSize * 10; // Bytes

        // Set and clear circular buffer
        audioBuffer.buffer = malloc(audioBufferSize); // Bytes, must be a multiple of audioSamplesSize
        memset(audioBuffer.buffer, 0, audioBufferSize);

        audioBuffer.cells = (audioBufferSize) / sizeof(short); // Cells, not Bytes!
        audioBuffer.readPoint = audioBuffer.buffer;
        audioBuffer.writePoint = audioBuffer.readPoint + (short)samplesPerFrame;
    }
    else
        samplesPerFrame = 0;

    // First frame
    return samplesPerFrame;
}

Это обратный вызов метода SDL из want.callback :

void audioCallback(void *userdata, uint8_t *stream, int len)
{
    SDL_memset(stream, 0, len);

    if (audioSamplesSize == 0)
        return;

    if (len > audioSamplesSize)
    {
        len = audioSamplesSize;
    }

    SDL_MixAudioFormat(stream, (const Uint8 *)audioBuffer.readPoint, AUDIO_S16SYS, len, SDL_MIX_MAXVOLUME);
    audioBuffer.readPoint += (short)samplesPerFrame;

    if (audioBuffer.readPoint >= audioBuffer.buffer + audioBuffer.cells)
        audioBuffer.readPoint = audioBuffer.readPoint - audioBuffer.cells;
}

Этот метод вызывается в каждом кадре (после первого прохода нам требуется только количество выборок):

int update_audio(short *buffer)
{
    // Check if sound is disabled
    if (sampleRate != 0)
    {
        memcpy(audioBuffer.writePoint, buffer, audioSamplesSize); // Bytes
        audioBuffer.writePoint += (short)samplesPerFrame; // Cells

        if (audioBuffer.writePoint >= audioBuffer.buffer + audioBuffer.cells)
            audioBuffer.writePoint = audioBuffer.writePoint - audioBuffer.cells;

        if (firstTime)
        {
            // Set required audio specs
            want.freq = sampleRate;
            want.format = AUDIO_S16SYS;
            want.channels = channel;
            want.samples = samplesPerFrame / channel; // total samples divided by channel count
            want.padding = 0;
            want.callback = audioCallback;
            want.userdata = NULL;

            device = SDL_OpenAudioDevice(SDL_GetAudioDeviceName(0, 0), 0, &want, &have, 0);
            SDL_PauseAudioDevice(device, 0);

            firstTime = 0;
        }
    }
    else
        samplesPerFrame = 0;

    // Next frame
    return samplesPerFrame;
}

Я ожидаю, что этот вопрос / ответ будет полезен для другие в будущем, потому что я не нашел почти ничего на net для SDL Audio

1 Ответ

0 голосов
/ 30 апреля 2020

Хорошо, я нашел решение!

С кодом вступительного поста я воспроизводил сэмплы для следующего кадра в текущем кадре в режиме реального времени. Это было неправильно!

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

...