Как произвести точно синхронизированный тон и тишину? - PullRequest
22 голосов
/ 28 апреля 2010

У меня есть проект C #, который воспроизводит азбуку Морзе для RSS-каналов. Я пишу это, используя Managed DirectX, только чтобы обнаружить, что Managed DirectX устарел и устарел. Моя задача состоит в том, чтобы играть чистые синусоидальные всплески, чередующиеся с периодами молчания (код), которые точно рассчитаны по продолжительности. Мне нужно иметь возможность вызывать функцию, которая воспроизводит чистый тон в течение стольких миллисекунд, затем Thread.Sleep (), затем воспроизводит другую и т. Д. В самом быстром случае тоны и пробелы могут быть короткими, как 40 мс.

В Managed DirectX он работает довольно хорошо. Чтобы получить точно синхронизированный тон, я создаю 1 сек. синусоидальной волны во вторичный буфер, затем для воспроизведения тона определенной длительности, который я ищу вперед в пределах x миллисекунд от конца буфера, затем воспроизведите.

Я пробовал System.Media.SoundPlayer. Это проигравший [править - см. Мой ответ ниже] , потому что вам нужно Play (), Sleep (), затем Stop () для произвольной длины тона. В результате получается слишком длинный тон, который зависит от загрузки процессора. Требуется неопределенное количество времени, чтобы фактически остановить тон.

Затем я предпринял длинную попытку использовать NAudio 1.3 . Я закончил с резидентным потоком памяти, предоставляющим данные тона, и снова искал вперед, оставляя желаемую длину тона, оставшегося в потоке, затем проигрывая. Некоторое время это работало нормально с классом DirectSoundOut (см. Ниже), но класс WaveOut быстро умирает с внутренним утверждением, говорящим, что буферы все еще находятся в очереди, несмотря на PlayerStopped = true. Это странно, так как я играю до конца, затем помещаю ожидание той же продолжительности между концом тона и началом следующего. Вы можете подумать, что через 80 мс после начала воспроизведения тона 40 мс у него не будет буферов в очереди.

DirectSoundOut некоторое время работает хорошо, но его проблема в том, что для каждого набора тонов Play () он выходит из отдельного потока. В конце концов (5 минут или около того) он просто перестает работать. Вы можете видеть поток за потоком после выхода из потока в окне «Вывод» во время выполнения проекта в IDE VS2008. Я не создаю новые объекты во время игры, я просто ищу () тональный поток, затем снова и снова вызываю Play (), так что я не думаю, что это проблема с потерянными буферами / какими бы то ни было накоплениями, пока он не захлебнется.

Мне не хватает терпения, поэтому я спрашиваю в надежде, что кто-то здесь столкнулся с аналогичным требованием и может направить меня в направлении возможного решения.

Ответы [ 4 ]

8 голосов
/ 28 апреля 2010

Я не могу в это поверить ... Я вернулся к System.Media.SoundPlayer и заставил его делать то, что я хочу ... нет гигантской библиотеки зависимостей с 95% неиспользуемого кода и / или причуд, ожидающих обнаружения :-). Кроме того, он работает на MacOSX под Mono (2.6) !!! [неправильно - нет звука, задаст отдельный вопрос]

Я использовал MemoryStream и BinaryWriter для написания WAV-файла с заголовком RIFF и чанкингом. Блок фактов не требуется, это 16-битные сэмплы на частоте 44100 Гц. Так что теперь у меня есть MemoryStream с 1000 мс сэмплами в нем, обернутый BinaryReader.

В файле RIFF есть две 4-байтовые / 32-битные длины, «общая» длина, составляющая 4 байта в потоке (сразу после «RIFF» в ASCII), и «длина данных» непосредственно перед байты данных образца. Моя стратегия состояла в том, чтобы искать в потоке и использовать BinaryWriter, чтобы изменить две длины, чтобы обмануть SoundPlayer, заставляя думать, что аудиопоток - это только та длина / продолжительность, которую я хочу, а затем Play (). В следующий раз продолжительность будет другой, поэтому еще раз перезапишите длины в MemoryStream с помощью BinaryWriter, Flush () и снова вызовите Play ().

Когда я попробовал это, я не смог заставить SoundPlayer увидеть изменения в потоке, даже если я установил его свойство Stream. Я был вынужден создать новый SoundPlayer ... каждые 40 миллисекунд ??? Нет.

Хорошо, я хочу вернуться к этому коду сегодня и начал смотреть на участников SoundPlayer. Я увидел «SoundLocation» и прочитал его. Там сказано, что побочным эффектом установки SoundLocation будет нулевое свойство Stream, и наоборот для Stream. Поэтому я добавил строку кода, чтобы присвоить свойству SOundLocation нечто фиктивное «x», а затем установил для свойства Stream значение моего (только что измененного) MemoryStream. Черт, если бы он не уловил это и не играл тоном ровно столько, сколько я просил. Кажется, нет никаких сумасшедших побочных эффектов, таких как мертвое время после или увеличение памяти, или ??? Требуется 1-2 миллисекунды, чтобы выполнить настройку потока WAV, а затем загрузить / запустить плеер, но он очень маленький и цена подходящая!

Я также реализовал свойство Frequency, которое повторно генерирует выборки и использует трюк Seek / BinaryWriter для наложения старых данных в RIFF / WAV MemoryStream с тем же числом выборок, но для другой частоты, и снова сделал то же самое вещь для свойства Amplitude.

Этот проект находится на SourceForge . Вы можете получить код C # для этого взлома в SPTones.CS с этой страницы в браузере SVN . Спасибо всем, кто предоставил информацию об этом, включая @arke, чье мышление было близко к моему. Я ценю это.

3 голосов
/ 28 апреля 2010

Лучше всего просто генерировать синусоиды и тишины вместе в буфер, который вы играете. То есть всегда проигрывайте что-нибудь, но пишите все, что вам нужно дальше, в этот буфер.

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

uint numSamples = timeWantedInSeconds * sampleRate;

Это количество сэмплов, которое вам нужно для генерации синусоиды или тишины, в зависимости от того, что вам нужно. Затем просто заполните буфер по мере необходимости. Таким образом, вы получите максимально точное время.

1 голос
/ 28 апреля 2010

Преобразование из ManagedDX в SlimDX ...

должно быть довольно простым

Редактировать: Что останавливает вас, между прочим, просто предварительно генерируя 'n' семплов синусоидальной волны? (Где n - самое близкое к желаемому количеству миллисекунд). Это действительно не займет много времени для генерации данных. Более того, если у вас есть буфер 22 кГц, и вы хотите получить последние 100 выборок, почему бы вам просто не отправить 'buffer + 21950' и установить длину буфера в 100 выборок?

1 голос
/ 28 апреля 2010

Попробуйте использовать XNA.

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

Так как XNA создан для игр, у него не будет никаких проблем с задержкой в ​​40 мс.

...