СРВ    

  Практика ~ 
  Тема 01 ~ 
  Тема 02 ~ 
  Тема 03 ~ 
  Тема 04 ~ 
  Тема 05 ~ 
  Тема 06 ~ 
  Тема 07 ~ 
  Тема 08 ~ 
  Тема 09 ~ 
  Тема 10 ~ 
  Тема 11 ~ 
/Студентам/СРВ/Практика

Лабораторный практикум по СРВ

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

План практикума (4 занятия)

Занятие 1.
Изучение нижеприведенных методических указаний. Знакомство с QNX.

Занятие 2.
Изучение приемов работы в среде QNX (работа с файлами, набор текстов, компиляция программ, работа в Internet, справочная система). Изучение QNX-специфичных функций, необходимых для выполнения практического задания (ThreadCtl(), in8(), out8(), InterruptAttach(), InterruptDetach()).

Занятие 3.
Разработка и отладка программы, воспроизводящей звук на системном динамике.

Занятие 4.
Разработка и отладка программы, воспроизводящей звук на системном динамике. Демонстрация результатов.

Использование ШИМ-модуляции для генерации звука

Системный динамик подключен к выходу цифровой схемы (конкретно, к выходу 2-ого канала таймера). Следовательно, нельзя произвольно долгое время держать на входе динамика произвольную (в заданных пределах, разумеется) разность потенциалов. Можно подать только "логический нуль" (0 вольт, к примеру) и "логическую единицу" (3.5 вольта, к примеру). Для того, чтобы воспроизвести на подобного рода динамике оцифрованный звук, отсчеты которого могут принимать значения в диапазоне скажем, 0-255, можно использовать т. наз. широтно-импульсную модуляцию (ШИМ). Такой способ использовался при разработке компьютерных программ в те времена, когда звуковые карты не имели повсеместного распространения.

Метод основан на использовании механической инерционности подвижных частей динамической головки (катушки и диффузора). Пусть в начальный момент времени диффузор находится в "нижнем" положении. Подадим на катушку разность потенциалов. Понятно, что диффузор перейдет в "верхнее" положение не мгновенно; этот процесс требует малого, но все-таки конечного промежутка времени. Если мы подадим очень короткий (много меньший постоянной времени динамической головки) импульс, то катушка и диффузор не успеют отреагировать, в результате диффузор не дойдет до "верхнего" положения. Изменяя ширину импульса, можно добиться того, что диффузор будет некоторое время находиться в различных положениях, промежуточных между "верхним" и "нижним". Если подать короткий импульс, то диффузор лишь слегка отойдет от "нижнего" положения. С увеличением ширины импульса он будет отклоняться все больше и больше, и, начиная с какой-то ширины, начнет доходить до "верхнего" положения. Такова, в общих чертах, идея, на основе которой можно реализовать воспроизведение оцифрованного звука на системном динамике.

Схема включения динамика в ПК на i386+

В персональных компьютерах, начиная с процессоров i8086 и по сей день для управления встроенным динамиком используется один из каналов (именно, 2-ой) системного таймера (i8253/4). Динамик включен следующим образом:



port 0x61[0] |       |  port 0x61[1]  __     PC speaker
*------------| GATE2 |  *------------|   \       _ /|
             |       |               | &  |_____| | |
1.193.180 Hz |       |- OUT2 --------|    |   __|_| |
*------------| CLK   |               |__ /   |     \|
             |_______|                      _|_ 

Подробнее см. здесь

Программирование таймера

См. там же

Алгоритм программы

Для реализации программы второй канал таймера следует использовать в режиме 0 (прерывание по конечному отсчету). В этом режиме таймер работает следующим образом.

После записи управляющего слова OUT = 0. Запись счетчика не изменяет состояние OUT. При GATE = 1 счет разрешен, в противном случае - запрещен. После разрешения счета по первому CLK константа счета копируется в CE. Сам счет начинается по второму CLK. Через (N+1)*T_CLK, когда CE = 0, OUT становится равным 1 и остается таковым до записи новой константы счета или нового управляющего слова.

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

Для такой программы существенно выдерживать более или менее точно интервалы между записями констант счета. Если программа не будет успевать произвести запись очередной константы счета в нужный момент, должного результата не получится. В однозадачной ОС типа DOS это не представляет никакой проблемы. В многозадачных же ОС различные процессы конкурируют за процессорное время, поэтому процесс имеет управление не тогда, когда он "хочет", а тогда, когда "сочтет нужным" планировщик процессов. Ситуация усугубляется следующим обстоятельством. Квант времени, отводимый процессу составляет, к примеру, в OS Linux 100 миллисекунд. Если в системе у нас имеется 3 процесса, интенсивно использующих процессор, то нашему процессу достанется только примерно 30% процессорного времени, то есть он гарантированно не будет успевать выводить отсчеты. На самом деле ситуация еще "хуже". Типичная частота дикретизации для звуковых файлов с не очень хорошим качеством - 11 кГц. Соответствующий период - примерно 0.1 миллисекунды, в то время как период прерываний от системного таймера (опять же, в Linux на i386+) составляет 10 миллисекунд (в последнее время - 1 миллисекунда). Таким образом, прикладная программа, работающая под управлением OS типа Linux, просто не сможет выдерживать требуемые промежутки времени с необходимой точностью, даже если она все время монопольно владеет процессором. В OS семейства Window$ ситуация аналогична. Реализовать подобный алгоритм в таких ОС можно только на уровне ядра.

В отличие от ОС общего назначения типа Unix(Linux) или Window$, ОС реального времени QNX, изучению которой посвящен данный лабораторный практикум, позволяет реализовать идею воспроизведения звука на системном динамике, причем сделать это не на уровне ядра, а на уровне прикладной программы. Это возможно, в основном, благодаря следующим двум свойствам этой ОС:

* в QNX прикладная программа может получать управление по прерываниям (в частности, от системного таймера)
* QNX позволяет изменять частоту системного таймера, точнее, его канала 0, выход которого подключен к линии IRQ0 контроллера прерываний.

Далее, нужно учесть, что мы не сможем, естественно, выдать импульс произвольной ширины за период дискретизации, с которым был оцифрован звук. Максимальная ширина импульса не может превышать период дискретизации. Максимальная ширина (в единицах периода тактовых импульсов таймера) равна отношению частоты импульсов, тактирующих таймер и частоты дискретизации звука. Например, для не очень большой частоты дискретизации 11025 Герц получим

1193180/11025 = 108

Эта же величина определяет максимальное количество РАЗЛИЧНЫХ ШИРИН импульсов, которые мы можем сформировать при заданной частоте дискретизации звука. Чем больше частота дискретизации, тем меньше "градаций" (средних положений динамика) мы сможем воспроизвести. Так, для CD-качества имеем

1193180/44100 = 27

Однако, даже для 8-ми битных файлов число градаций составляет 256. Это нужно учитывать. Именно, перед воспроизведением аудио-данные нужно, в случае необходимости, отмасштабировать таким образом, чтобы максимальный отсчет не превышал обозначенного отношения. Тогда в качестве констант счета можно будет использовать эти отмасштабированные значения. В противном случае и так очень плохое качество звучания будет еще ухудшено из-за того, что импульсы для констант счета, превышающих это отношение, просто не будут сформированы.

Наконец, следует помнить о следующем. Как правило, прямое обращение к портам ввода-вывода (а нам это понадобится для программирования таймера) требует привилегий суперпользователя. Однако, даже в этом случае программа должна явно разрешить себе обращение к портам (то есть исполнение привилегированных инструкций in и out).

С учетом вышесказанного, алгоритм программы можно представить в следующем виде:

I. Загрузить данные, то есть:

I.1 Считать заголовок файла (см. ниже) и проанализировать его на предмет возможности воспроизведения (например, ваша программа может не поддерживать стерео-файлы, файлы со сжатыми аудио-данными, 16-ти битные файлы)

I.2 Выделить память под аудио-данные и загрузить их в оперативную память.

I.3 Отмасштабировать аудио-данные в соответствии с приведенными выше рекомендациями.

II. Разрешить использование привилегированных инструкций in и out.

III. Запрограммировать второй канал таймера, то есть:

III.1 Установить режим работы 0, запись обеих байт константы счета, двоичный счет.

III.2 Включить динамик и разрешить работу 2-му каналу таймера. Для этого нужно прочитать нужный порт (0x61, смотри схему выше) в какую-либо переменную, установить в ней нужные биты и затем записать обратно в порт.

IV. Установить частоту канала 0 таймера равной частоте дискретизации звука. При этом необходимо сделать это не путем прямого обращения к портам, а используя соответствущее обращение у ядру QNX. Если это сделать "вручную", то ядро не будет "знать" о том, что частота прерываний от таймера изменилась и может повести себя некорректно.

V. Установить обработчик прерывания на IRQ0. После 2-х последних действий обработчик прерывания будет получать управление по каждому прерыванию от таймера. В обработчике прерывания необходимо записывать во второй канал таймера очередную константу счета (в старший байт записывать 0) и продвигать индекс массива, в котором хранятся аудио-данные. Для надежности можно еще перед записью константы счета повторить процедуру программирования таймера, пункт III. Обратите внимание, что обработчик прерывания должен вернуть указатель на некую структуру (можно вернуть константу NULL). Если этого не сделать (то есть не написать явно return NULL), то QNX просто "повиснет".

VI. Нужно предусмотреть какой-то механизм останова процесса воспроизведения. Для этого можно в основной программе либо засыпАть на время, немного превышающее время проигрывания, либо отслеживать состояние флага, устнавливаемого обработчиком прерывания по окончании воспроизведения, либо использовать еще какой-то самостоятельно придуманный способ.

VII. По окончании проигрывания необходимо:

VII.1 Снять обработчик прерывания.

VII.2 Восстановить исходную частоту прерываний от таймера

VII.3 Выключить динамик и запретить работу 2-му каналу таймера. Для этого следует прочитать нужный порт (0x61, смотри схему выше) в какую-либо переменную, сбросить в ней нужные биты и затем записать обратно в порт.

VI.4 Освободить память, занятую аудио-данными

Vi.5 Завершить программу.

Выравнивание гистограммы *


for (;i < SamplesToPlay; i++) {
	index = SoundBuffer[i];
	sample_dist[index]++;
};

for (;i < 256; i++) {
	sum += sample_dist[i];
	LookUpTable[i]=( (float)volume*(float)sum )/(float)SamplesToPlay;
	LookUpTable[i]=volume - LookUpTable[i];
}

Некоторые QNX-специфичные системные вызовы, нужные для выполнения некоторых из перечисленных действий, описаны в следующем разделе. Остальные действия (работа с файлами, выделение-освобождение памяти - "стандартные").

Функции QNX, необходимые для реализации алгоритма

Подробности по всем функциям см. в справочной системе QNX.

Разрешение исполнения привилегированных инструкций in и out. Для этого используется функция ThreadCtl()

Запись в порт - out8(), чтение из порта - in8().

Установка частоты канала 0 системного таймера - ClockPeriod().

Установка обработчика прерывания - InterruptAttach(). Снятие обработчика прерываний - InterruptDetach().

Формат WAV-файла

Ниже приведено описание формата WAVe файлов в его простейшем варианте. Файл состоит из фрагментов (англ. "chunks"), каждый фрагмент предваряется 4-х байтным идентификатором фрагмента и 4-х байтным полем, содержащим длину фрагмента в байтах. "Смещение" и "размер" указаны в байтах. RIFF - resource interchange file format, формат файлов для обмена ресурсами. См. также здесь

Смещение Размер Содержимое
0        4      'RIFF'

4        4      размер RIFF-данных 
         // должно быть на 8 меньше размера файла

8        4      'WAVE' 
         // какие именно данные, WAVE означает звук

12       4      'fmt '
         // начало фрагмента описания формата

16       4      размер области описания формата в байтах

20       2      категория формата 
	 // для Microsoft PCM - 0x0001

22       2      число каналов
         // 1 - моно, 2 - стерео

24       4      частота дискретизации (sampling rate), Гц
28       4      средняя скорость потока (байт/сек)
32       2      размер блока данных 

Следующее поле только для категории Microsoft PCM ...
34       2      число бит на отсчет
         // например, 8 или 16

36       4      'data'
         // начало фрагмета с аудио-данными

40       4      размер аудио-данных в байтах

44       ...  аудио-данные 
	 // для Microsoft PCM отсчеты
	 // рассматриваются как беззнаковые целые числа
	 // в случае, если число бит на отсчет - 8 (0..255),
	 // и как знаковые целые числа, в случае,
	 // если число бит на отсчет - 16 (-32768...32767)

Замечание 0:
Компиляторы могут выравнивать поля в структуре на какую-то границу (как правило, на машинное слово). Чтобы поля в структуре были размещены без промежутков, надо указать компилятору соответствующую опцию. Например, для компилятора gcc это -fpack-struct.

Замечание 1:
Обратите внимание, что сами отсчеты в случае, если они имеют размер 1 байт, следует рассматривать как беззнаковые целые, то есть использовать буфер из элементов типа unsigned char, а в случае, если они имеют размер 2 байта, то как знаковые целые, то есть использовать буфер типа signed short int.

Замечание 2:
Для форматов, предусматривающих сжатие данных, используется фрагмент 'fact', в котором указано, какой размер имеют данные в декомпрессированном виде. Однако, некоторые программы создают файлы в формате, не предусматривающем сжатие данных (Microsoft PCM, например), но при этом все равно вставляют такой фрагмент. Обычно он находится перед фрагментом 'data'. Занимает в совокупности 12 байт:

Смещение Размер Содержимое
xx	   4	'fact'
xx+4	   4	размер области, в которой указан 
                фактический размер в байтах (=4)
xx+8	   4    собственно само количество байт

Если 'fact' используется с несжатыми форматами, то значение последнего поля в нем, как правило, совпадает с размером аудио-данных в 'data'.

Замечание 3:
Могут встретиться и другие теги фрагментов. "Умная" программа должна считывать их не все сразу, а друг за другом; если встретился неизвестный тег, его можно просто пропустить (если это не скажется на возможности воспроизведения звука) или же завершить программу с выдачей соответствующего уведомления.

Полное описание RIFF можно посмотреть в документе с названием "Multimedia Programming Interface and Data Specification v1.0", опубликованном в 1991 году компаниями IBM и Microsoft. Отрывок из него, касающийся WAV-файлов, можно посмотреть, например, здесь

Пример простого менеджера ресурсов

РАЗРАБОТКА ДРАЙВЕРОВ
В ОПЕРАЦИОННОЙ СИСТЕМЕ РЕАЛЬНОГО ВРЕМЕНИ QNX
(дипломная работа Трифоновой Н.А.)

Дата последней модификации: 2011-11-06


/Студентам/СРВ/Практика

Содержимое данного сайта может быть использовано кем угодно, когда угодно, как угодно и для каких угодно целей. Автор сайта не несёт абсолютно никакой ответственности за землетрясения, наводнения, финансовые кризисы, глобальные потепления/похолодания, разбитые тарелки, зуд/онемение в левой/правой пятке читателя, эпидемии/пандемии свинячьего/птичьего/тараканьего и иных гриппов, а также за прочие негативные, равно как и позитивные, последствия, вызванные прямым или косвенным использованием материалов данного сайта кем бы то ни было, включая самого автора. При копировании/цитировании материалов данного сайта любым технически возможным в настоящее время способом, а также способом, могущим стать возможным в будущем, указание (либо неуказание) ссылки на первоисточник лежит, блин, тяжким грузом на совести копирующего/цитирующего.

Valid HTML 4.0 Strict Valid CSS!