#c #multithreading #oboe
#c #многопоточность #oboe
Вопрос:
Я пытаюсь написать буфер для аудиосистемы с помощью Oboe, что делать и чего не делать
Что делать и чего не делать при обратном вызове Вы никогда не должны выполнять операцию, которая может блокировать внутри
onAudioReady
. Примеры операций блокировки включают:выделите память, используя, например,
malloc()
или new;операции с файлами, такие как открытие, закрытие, чтение или запись;
сетевые операции, такие как потоковая передача;
используйте мьютексы или другие примитивы синхронизации sleep
остановить или закрыть поток
Вызов
read()
илиwrite()
в потоке, который его вызвал
Аудиопоток считывает данные из моего буфера, а поток декодера записывает в него, и, как вы можете себе представить, все хорошо, пока не возникнут проблемы с потоками. Моя главная проблема в том, что я могу просто использовать мьютекс для решения этой проблемы, но если я это сделаю, я заблокирую один из потоков, и если аудиопоток заблокирован, звук практически не воспроизводится, что приводит к звуку «попкорна». (Звук, который так неприятно слушать)
Я воспроизводю звук через обратный вызов, куда я передаю ему данные.
DataCallbackResult
OboeStreamCallback::onAudioReady(AudioStream *audioStream, void *audioData, int32_t numFrames) {
// fill data here
return DataCallbackResult::Continue;
}
Итак, моя главная проблема заключается в том, как я могу решить, что чтение данных из аудиопотока также не блокирует аудиопоток, чтобы он все еще мог воспроизводить аудио?
Для меня это звучит невозможно. Как можно обеспечить потокобезопасность без мьютекса? Как oboe ожидает, что вы не будете использовать мьютекс для динамического декодирования звука?
Комментарии:
1. github.com/google/oboe/tree/master/samples/…
2. @AsteroidsWithWings Я посмотрел на это, но проблема в том, что если я использую
LockFreeQueue
;pop()
вызов использует две атомарные переменные, учтите следующее: за одно чтение Oboe просит меня заполнить 350 образцов. Это означает 350pop()
вызовов. Он будет использовать атомарные переменные 350 раз. Это замедлило бы работу, поскольку атомарные переменные работают медленно, я не думаю, что смогу использовать этот класс в качестве буфера. В примере они также использовали его с размером всего 4.3.
LockFreeQueue
содержит атомари для счетчиков чтения и записи. Это гарантирует, что потоки чтения и записи могут безопасно получать доступ к этим значениям. Atomics представляют собой наиболее эффективный способ достижения потокобезопасности в этом случае. Почему вы хотите их удалить — у вас проблемы с производительностью?4. Настоящий урок здесь заключается в том, что говорить о производительности, не измеряя ее, просто противоречит самому себе. Наш повседневный опыт мало что может сообщить нам о производительности современных, очень сложных процессорных ядер с обширными и запатентованными механизмами, которые направлены на извлечение максимальной производительности из обычного кода. Я постоянно работал над повышением производительности в проекте с открытым исходным кодом, и единственный реальный способ убедиться, что я не несу чушь, — это запустить сборку и убедиться, что она быстрее в обычных шаблонах использования.
5. Для вашего варианта использования использование
std::atomic
вряд ли будет узким местом в производительности. Гораздо более вероятно, что это будет FFmpeg или какой-либо другой компонент в конвейере декодирования. Как сказал @UnslanderMonica, вы почти наверняка преждевременно оптимизируете.
Ответ №1:
Вы можете использовать потокобезопасную очередь без блокировки для чтения / записи данных. Это позволит избежать необходимости в мьютексах, будет намного быстрее и должно исправить ваши проблемы с «попкорном».
Пример реализации можно найти здесь: https://github.com/google/oboe/blob/master/samples/RhythmGame/src/main/cpp/utils/LockFreeQueue.h