Как правильно играть в декодированный PCM с Oboe? - PullRequest
0 голосов
/ 26 ноября 2018

Я использую oboe для воспроизведения звуков в моей библиотеке ndk, и я использую OpenSL с расширениями Android для декодирования файлов wav в PCM.Декодированные подписанные 16-битные PCM сохраняются в памяти (std::forward_list<int16_t>), а затем они отправляются в поток гобой через обратный вызов.Звук, который я слышу из своего телефона, похож на оригинальный wav-файл с уровнем громкости, однако «качество» такого звука отсутствует - оно лопается и потрескивает.

Я предполагаю, что отправляю PCM ваудио поток в неправильном порядке или формате (частота дискретизации?).Как я могу использовать OpenSL-декодирование с аудио-потоком oboe?


Для декодирования файлов в PCM я использую AndroidSimpleBufferQueue в качестве приемника и AndroidFD с AAssetManager в качестве источника:

// Loading asset
AAsset* asset = AAssetManager_open(manager, path, AASSET_MODE_UNKNOWN);
off_t start, length;
int fd = AAsset_openFileDescriptor(asset, &start, &length);
AAsset_close(asset);

// Creating audio source
SLDataLocator_AndroidFD loc_fd = { SL_DATALOCATOR_ANDROIDFD, fd, start, length };
SLDataFormat_MIME format_mime = { SL_DATAFORMAT_MIME, NULL, SL_CONTAINERTYPE_UNSPECIFIED };
SLDataSource audio_source = { &loc_fd, &format_mime };

// Creating audio sink
SLDataLocator_AndroidSimpleBufferQueue loc_bq = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 1 };
SLDataFormat_PCM pcm = {
    .formatType = SL_DATAFORMAT_PCM,
    .numChannels = 2,
    .samplesPerSec = SL_SAMPLINGRATE_44_1,
    .bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16,
    .containerSize = SL_PCMSAMPLEFORMAT_FIXED_16,
    .channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,
    .endianness = SL_BYTEORDER_LITTLEENDIAN
};
SLDataSink sink = { &loc_bq, &pcm };

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

ПРИМЕЧАНИЕ: wavаудиофайл также 2-канальный со знаком 16 бит 44,1 Гц PCM

Моя конфигурация потока гобоя такая же:

AudioStreamBuilder builder;
builder.setChannelCount(2);
builder.setSampleRate(44100);
builder.setCallback(this);
builder.setFormat(AudioFormat::I16);
builder.setPerformanceMode(PerformanceMode::LowLatency);
builder.setSharingMode(SharingMode::Exclusive);

Рендеринг аудио работает так:

// Oboe stream callback
audio_engine::onAudioReady(AudioStream* self, void* audio_data, int32_t num_frames) {
    auto stream = static_cast<int16_t*>(audio_data);
    sound->render(stream, num_frames);
}

// Sound::render method
sound::render(int16_t* audio_data, int32_t num_frames) {
    auto iter = pcm_data.begin();
    std::advance(iter, cur_frame);

    const int32_t rem_size = std::min(num_frames, size - cur_frame);
    for(int32_t i = 0; i < rem_size; ++i, std::next(iter), ++cur_frame) {
        audio_data[i] += *iter;
    }
}

Ответы [ 2 ]

0 голосов
/ 09 декабря 2018

Короче говоря: по сути, я испытал переполнение из-за использования std::forward_list для хранения PCM.В таком случае (используя итераторы для извлечения PCM), нужно использовать контейнер, чей итератор реализует LegacyRandomAccessIterator (например, std::vector).


Я был уверен, что линейныйСложность методов std::advance и std::next не имеет никакого значения в моем методе sound::render.Однако, когда я пытался использовать необработанные указатели и арифметику указателей (таким образом, постоянную сложность) с методами отладки, которые были предложены в комментариях (извлечение PCM из WAV с Audacity, затем загрузка этого актива с помощью AAssetManager непосредственно впамяти), я понял, что количество «искажения» выходного звука было прямо пропорционально аргументу position в std::advance(iter, position) в методе рендеринга.

Итак, если количество искажения звукабыл прямо пропорционален сложности std::advance (а также std::next), тогда я должен был сделать сложность постоянной - используя std::vector в качестве контейнера.И используя ответ от @philburk, я получил это как рабочий результат:

class sound {
    private:
        const int samples_per_frame = 2; // stereo
        std::vector<int16_t> pcm_data;
        ...
    public:
        render(int16_t* audio_data, int32_t num_frames) {
            auto iter = std::next(pcm_data.begin(), cur_sample);
            const int32_t s = std::min(num_frames * samples_per_frame,
                                       total_samples - cur_sample);

            for(int32_t i = 0; i < s; ++i, std::advance(iter, 1), ++cur_sample) {
                audio_data[i] += *iter;
            }
        }
}
0 голосов
/ 28 ноября 2018

Похоже, ваш метод render () сбивает с толку сэмплы и фреймы.Кадр - это набор одновременных выборок.В стереопотоке каждый кадр имеет ДВА сэмпла.

Я думаю, что ваш итератор работает на основе сэмплов.Другими словами, next (iter) перейдет к следующему примеру, а не к следующему кадру.Попробуйте этот (непроверенный) код.

sound::render(int16_t* audio_data, int32_t num_frames) {
    auto iter = pcm_data.begin();
    const int samples_per_frame = 2; // stereo
    std::advance(iter, cur_sample);

    const int32_t num_samples = std::min(num_frames * samples_per_frame,
              total_samples - cur_sample);
    for(int32_t i = 0; i < num_samples; ++i, std::next(iter), ++cur_sample) {
        audio_data[i] += *iter;
    }
}
...