ЗвукФорумОбщее

(Потоковое воспроизведение .ogg)вопрос когда переключать буфера?

#0
0:00, 22 ноя 2005

Привет всем.
Я делаю свою звуковую либу, режил сделать проигрывание
.ogg vorbis с помощью DirectSound8.Накопал кучу материалов
и даже сделал свой класс для потокового воспроизведения
звука. Создал я два буфера по 4096*25 фреймов (а может и не фреймов
к сожалению не знаю как называется), дальше алгоритм прост:

текущий буфер присваиваем первому буферу.

дальше цикл

1)декодируем .ogg в текущий буфер (размером 4096*25) и сразу вызываем для него Play();

2)далее смотрим если мы проиграли текущий буфер более чем на 50 %, то в это время
  когда он играет декодируем дальше .ogg файл в не текущий буфер
3)С помощью функции GetPosition можно получить т.н Read Cursor. и теперь смотрим
  если этот read cursor достиг максимального фрейма (т.е 4096*25), то сразу
  же вызываем Play() для не текущего буфера. Текущий буфер становится вторым

goto 1

Возможно не совсем корректно описал алгоритм, но смысл думаю понятен.Он работает,
но я столкнулся с проблеммой.

Размер одного буфера всегда равен 4096*25, а Read Cursor почти никогда до этого
числа не доходит, он может остановится в пределах от 4096*24 до 4096*25.

Основной вопрос, из-за которого я создал данную тему это когда менять местами
буфера?

Сейчас в коде я сделал условие если ReadCursor >= 4096*25-4096 то меняем местами буфера,
но ведь это не правильно, и иногда слышен треск (правда очень трудно уловимый) при
переключении, это не правильно и я хочу сделать нормально.


Выладываю exe'шник и исходники, сразу предупреждаю исходники сильно загаженны, это
еще не финальная версия класса.

Если кто делал потоковое воспроизведение .ogg то подскажите.

П.С
Буду признателен если кините ссылки по юзанию сабжа в отдельном потоке.

#1
0:00, 22 ноя 2005

Извините не могу выложить исходники и дему, качалка переполненна.

Когда освободится место выложу обязательно

#2
3:17, 22 ноя 2005

KAIN
Юзай OpenAL. :-)
Там всё так классно через DirectSound враппится, что делать нечего.
Теперь по сабжу.
>1)декодируем .ogg в текущий буфер (размером 4096*25) и сразу вызываем для него Play();
>
>2)далее смотрим если мы проиграли текущий буфер более чем на 50 %, то в это время
> когда он играет декодируем дальше .ogg файл в не текущий буфер
>3)С помощью функции GetPosition можно получить т.н Read Cursor. и теперь смотрим
> если этот read cursor достиг максимального фрейма (т.е 4096*25), то сразу
> же вызываем Play() для не текущего буфера. Текущий буфер становится вторым
Это всё неправильно. Я когда-то очень подробно с этим разбирался, сейчас уже подзабыл, но кое-что помню:
Лучше никогда не создавать 2 буфера. Нужно штук 10. В OpenAL управлять ими запросто. Сразу заполняешь их целиком. Так надо!
Просто когда будет 2 больших буфера и звук высокого качества, получается что скажем раз в секунду будет грузиться здоровый кусок и фпс со 100 раз в секунду будет падать до 30 скажем. Вообще конечно на крутых процах разницы особо не видно, но... не профессионально это как-то. :-)
И ещё. Никогда не используй GetPosition. Эта функция некорректно работает на абсолютном большинстве звуковых карт. Она возвращает программно-вычисляемую приблизительную (очень приблизительную) позицию при проигрывании звука. Я как-то пытался сделать проигрывание видео и подумал, что проще всего использовать именно GetPosition для синхронизации, т.е. смотрим где звук и отображаем нужный кадр. Ничего не вышло. Получилась чушь полная -- слайдшоу с частотой где-то в четверть секунды. Но на разных звуковых картах эта функция ведёт себя по разному (и сильно). На профессиональных -- позиция считается аппаратно и выдаётся очень точное значение, но где гарантия, что у игрока будет стоять SB Live?
Так вот, в OpenAL как только проигрался текущий буфер, СРАЗУ автоматически начинает проигрываться следующий буфер. Тебе надо всего навсего в главном цикле проверять функцию "alGetSourcei(source,AL_BUFFERS_PROCESSED,&processed);" и если processed установится в >0, то через  "alSourceUnqueueBuffers(source,processed,processed_buffers);" в массив processed_buffers запишутся ID всех буферов, которые уже успели "проиграться" пока мы тут рисовали. Остаётся только обновить нужные буферы свежими данными.

>Буду признателен если кините ссылки по юзанию сабжа в отдельном потоке.
Ну это несложно сделать и самому, вот только а оно надо?
2 секунды несжатого звука макс. качества занимают 350Кб. У тебя разве планируется падение FPS до 0.5 ?

#3
11:23, 22 ноя 2005

KAIN
Фигня какая-то ... Зачем два буфера? Есть такая вещь, как потоковый буфер. Создаешь один буфер с флагом потокового буфера (сейчас под рукой нет документации , а так не помню. Посмотри в СДК). Делишь буфер пополам. К частям буфера привязываешь евенты (тоже , посмотри СДК, DSound может посылать сообщения при достижении определенной позиции в буфере при воспроизведении). Короче, схематично так: Заполняешь первую часть буфера, запускаешь воспроизведение, создаешь отдельный поток, который у тебя будет обрабатывать сообщения буфера и заполнять его. Когда проигрывается первая часть буфера, поток заполняет вторую, и наоборот.

#4
12:54, 22 ноя 2005

tav
>>Юзай OpenAL. :-)
Ненадо меня агитировать юзать OpenAL, ведь если я юзаю dsound то мне это нужно.

>> Я когда-то очень подробно с этим разбирался, сейчас уже подзабыл, но кое-что помню:
>>Лучше никогда не создавать 2 буфера. Нужно штук 10. В OpenAL управлять ими запросто. >>Сразу заполняешь их целиком. Так надо!
А вот это я не понял, зачем 10 штук, обьясни? В теории достаточно и двух, пока один играет, грузится второй, пока второй играет грузится первый и т.д. Что не так?

>>И ещё. Никогда не используй GetPosition. Эта функция некорректно работает на абсолютном >>большинстве звуковых карт.
Ок.Это я понял, теперь буду юзать евенты.

ksardas
>>Фигня какая-то ... Зачем два буфера?
Я же сказал я раеньше сабж никогда не делал, это так сказать мой первый опыт.

Спасибо за ответ. теперь буду все это реализовывать.

#5
5:51, 23 ноя 2005

KAIN
>Ок.Это я понял, теперь буду юзать евенты.
Вообще-то это было давно, и я наверное перепутал IDirectSound3DBuffer8::GetPosition и waveOutGetPosition, так что...
Но события конечно всё-равно лучше.

>А вот это я не понял, зачем 10 штук, обьясни?
Ну вот представь, будет 2 буфера на 2 сек. каждый. В главном цикле будет стоять проверка, не закончился ли проигрываться первый буфер. Целых 2 секунды эта проверка будет возвращать false и FPS в игре будет напр. 100. Затем в один прекрасный кадр проверка вернёт true, а это значит что мы синхронно (т.е. поток остановится до завершения операции) должны декодировать целые 2 секунды звука, 88 тыс. семплов x 2 канала. Как думаешь сколько займёт эта процедура? Ну я в пред. посте грубо сказал, что FPS упадёт до 30 (т.е. это займёт 23 мс). Конечно я утрировал, и реально даже меньше. Да и размер буферов на 2 секунды конечно не нужен. Но FPS всё равно будет "скакать".
А теперь представь, что буферов у нас 10. Тогда каждый буфер будет хранить 0.4 секунды (ведь до этого 2 буфера хранили 2х2=4 секунды, общий объём хранимого звука есс-но должен быть одинаков, отличается только кол-во буферов, которые разбивают весь буферизуемый объём). Тогда на декомпрессию 0.4 секунд понадобиться в 5 раз меньше времени, т.е. в нашем примере FPS упадёт всего до 68.
Это конечно слишком грубые цифры, но думаю идея понятна.

P.S. Очевидно, что делать 2 маленьких буфера (скажем размером 0.05 сек) глупо, т.к. если FPS будет меньше 20, звук будет "тормозить", зато 10 таких же буферов начнут глючить только при FPS < 2.2 при тех же (!!!) вычислительных затратах.

P.P.S. Кстати, насчёт многопоточности. Создавать отдельный поток, пожалуй не стоит, однако грузить звук лучше асинхронно, т.к. даже маленький кусочек может "долго" грузиться из-за медленного позиционирования головки HDD. Поэтому лучше использовать функции асинхронного файлового ввода (напр. ReadFileEx), которые не будут "вешать" поток до завершения операции загрузки и декомпрессии.

ЗвукФорумОбщее

Тема в архиве.