Как воспроизвести непрерывные звуковые данные?
on 27.01.2013 at 18:36Как непрерывно воспроизводить звуковые данные? Как написать программу потокового звукового вещания? С подобными вопросами дело имеют многие (и откуда я это знаю…). Да к чему эти интриги? Я же в первую очередь говорю о себе! Может читателю вообще будет не интересно читать этот монолог:) И вообще, будет замечательно если найдется хоть один, кому будет интересна эта статья. Так вот, в первую очередь именно мне было интересно воспроизвести произвольные непрерывные данные на звуковой карте моего лэптопа. В этом изложении я постараюсь вкратце описать, каким образом можно это осуществить. Немного предыстории. Начав искать в Интернете способы вывода звука в звуковую карту, я перепробовал многие библиотеки. В первую очередь я уделял внимание выбору именно кроссплатформенного решения. Ведь не сильно хочется полностью переписывать программу, если вдруг придется изменить операционную систему, на которой эта программа должна работать. В результате мой выбор пал на библиотеку «Portaudio». Эта замечательная библиотека позволяет воспроизводить звуковые данные под управлением таких операционных систем как Винда, Мак и Юних т.е. является кроссплатформенной. Ко всему «Portaudio» можно использовать и в коммерческих проектах.
В качестве примера работы с «Portaudio» рассмотрим генератор синусоидального сигнала. Для начала нужно где-то достать эту самую библиотеку. Я смог убедится в том, что бинарные файлы этой библиотеки распространять не принято. Но не составит особого труда самому собрать эти файлы из исходных файлов библиотеки. На сегодняшний день (день написания статьи) исходный код можно скачать по адресу http://www.portaudio.com/download.html. Для того, чтобы успешно собрать библиотеку придется попотеть (конечно же, я говорю о сборке библиотеки под “суперудобной” WindowsOS). Для начала придется найти Readme.txt файл по приблизительному пути …\portaudio\build\msvc\. Потом всё это хорошенько почитать и всё получится. Дабы долго не томить читателя излишним изложением бестолковых предложений и словосочетаний, собранные файлы библиотеки я прикрепляю в проект в конце данной статьи. Перейдем к коду. Я решил использовать уже готовые наработки по работе с «Portaudio», которые можно найти в самом проекте библиотеки. Чуть-чуть переделав (упростив) файл примера я получил следующий исходный код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
#include |
Начинающему не сразу станет ясно, что именно здесь происходит. Поэтому я объясню. В первую очередь нужно инициализировать библиотеку, вызвав функцию Pa_Initialize().
Дальше необходимо получить звуковое устройство, вызвав функцию Pa_GetDefaultOutputDevice().
Если устройство получено без ошибки, тогда можно приступить к его настройке. Для этого нужно заполнить структуру outputParameters.
Когда всё это сделано, можно переходить к самому интересному, а именно к открытию (Pa_OpenStream(…)) и запуску (Pa_StartStream(…)) потока воспроизведения.
Наверное, на данном этапе не совсем понятно, куда и где мы будем передавать наши звуковые выборки. Тут всё просто… Открывая поток вызовом функции Pa_OpenStream(…) мы передаем в эту функцию указатель на функцию patestCallback(…), которую будет дёргать драйвер аудиоустройства (пусть это звучит так) когда ему нужны новые данные. Так вот, в этой функции мы и будем генерировать и передавать драйверу наши аудиовыборки. Как генерировать и записывать выборки в левый и правый каналы, можно посмотреть тут:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
///* This routine will be called by the PortAudio engine when audio is needed. //** It may called at interrupt level on some machines so don't do anything //** that could mess up the system like calling malloc() or free(). //*/ static int patestCallback(const void* inputBuffer, void* outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void* userData) { float *out = (float*)outputBuffer; static unsigned int timeTmp = 0; float sampleValTmp = 0; for(unsigned int i=0; iframesPerBuffer; i++) { sampleValTmp = (float)sin(2.0*M_PI*FREQUENCY*(timeTmp++)/SAMPLE_RATE); *out++ = sampleValTmp; /* left channel */ *out++ = sampleValTmp; /* right channel */ } return 0; } |
Здесь мы генерируем выборку функцией синуса из стандартной математической библиотеки. Значение timeTmp постоянно увеличивается до тех пор, пока не перешагнет максимальное для себя значение и опять станет нулём. Каналы в буфере чередуются один за другим для каждого семпла. В данном случае мы имеем только 2 канала, поэтому и сдвигаем указатель out на буфер только 2 раза.
Для завершения же работы с библиотекой мы должны закрыть поток воспроизведения вызвав функцию Pa_CloseStream(…) после чего можно остановить работу и самой библиотеки вызвав Pa_Terminate().
Вот собственно и всё, что необходимо знать при работе с библиотекой «Portaudio» на начальном этапе!
В завершение, я хотел бы сказать, что это всего-лишь пример и некоторые вещи делать так как в этом проекте нежелательно, например выделять или удалять память внутри функции patestCallback(…). Кроме этого делать громоздкие вычисления тут тоже нежелательно. Конкретно в нашем случае, в случае этого примера я знаю, что за время между соседними вызовами функции patestCallback(…) мы однозначно успеваем выполнить расчет значения для новой выборки. Если же нужно делать более громоздкие вычисления, тогда можно воспользоваться логикой многопоточных приложений и, например, буфером FIFO. Но это уже совсем другая история…
Мне остаётся самое главное — выложить исходный код проекта:). Вот он, дорогия друзья:
Загрузка
Скачать исходный код проекта
Я для воспроизведения и записи пользуюсь такой библиотекой http://naudio.codeplex.com/
Приведенной Вами библиотекой мне пользоваться не приходилось. Возможно из-за того, что работать с обработкой и выводом аудио мне было нужно под «чистым» C/C++. «Naudio» же, в свою очередь, написан на «.NET для .NET».
Если в будующем мне придется работать над обработкой звука и мне будет не нужна кроссплатформенность (ведь я почти уверен в том, что использование «Naudio» в Unix-подобных системах терпит крах) то я конечно же воспользуюсь данной библиотекой:)