Вступление
Если вы разрабатываете мобильное приложение, которое имеет какой-либо аудиовыход, будь то игра или любое другое приложение, вы захотите, чтобы ваш звук звучал наилучшим образом на любом устройстве, с которым он совместим.
Некоторые мобильные устройства поддерживают Dolby Digital Plus, в частности линейку Kindle Fire, а также растущее число других устройств Android. Dolby Digital Plus может значительно улучшить вывод звука вашего приложения, применяя фильтры, которые могут усиливать определенные части вашего вывода звука, например музыку или голос. Это может показаться сложной задачей, но, к счастью, Dolby предоставила API, который делает использование этой функции невероятно простым.
Из этого руководства вы узнаете, как разработать приложение с использованием Marmalade SDK, которое может использовать преимущества Dolby Digital Plus с использованием расширения Marbylade API Dolby Audio. Если вы заинтересованы только в интеграции Dolby Audio API в ваше приложение Marmalade, тогда переходите к концу этой статьи .
1. Обзор
Приложение, которое мы создадим в этом уроке, предоставит простой пользовательский интерфейс, содержащий верхний ряд кнопок, которые могут запускать и останавливать различные виды звука, и нижний ряд кнопок, чтобы установить тип фильтрации звука, который необходимо применить. ,
Сначала мы создадим новый проект Marmalade, а также папки и исходные файлы, которые будут составлять проект.
Далее я объясню, как реализовать пользовательский интерфейс, загрузив изображение, содержащее необходимые изображения кнопок, и нарисовав различные его части на экране. Я также проиллюстрирую, как использовать Marmalade для реагирования на прикосновения пользователей.
Как только пользовательский интерфейс будет запущен, я покажу вам, как заставить Marmalade воспроизводить два разных типа звука: сжатый звук, такой как файл MP3, и необработанные данные звукового примера.
Наконец, я интегрирую Dolby Audio API в приложение и буду использовать различные типы аудиофильтров, которые оно предоставляет.
В этом руководстве я буду предполагать, что вы разрабатываете на компьютере под управлением Windows и уже имеете Marmalade SDK и версию Microsoft Visual Studio. Marmalade также можно использовать с Xcode на OS X, и вам все равно будет легко следовать инструкциям этого руководства, если вы используете Mac для разработки.
2. Создание проекта мармелада
Шаг 1: Создание исходных файлов и структуры папок
Создайте папку верхнего уровня с именем DolbyTestApp для хранения файлов проекта. В этой папке создайте другую папку с именем source . Давайте также создадим файлы, которые нам понадобятся в нашем проекте. В исходной папке создайте пять пустых файлов с именами button.cpp , button.h , main.cpp , sound.cpp и sound.h .
Шаг 2: Создание файла MKB
Создайте пустой файл с именем DolbyTestApp.mkb в папке DolbyTestApp и откройте его в редакторе кода. Файл MKB — это файл, используемый Marmalade для настройки проекта. Он объединяет все ваши исходные файлы и файлы данных и позволяет вам настраивать такие вещи, как значки, используемые вашим приложением при установке на разные платформы, поддерживаемые Marmalade. Добавьте следующее в файл DolbyTestApp.mkb .
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
{
[Source]
(source)
button.cpp
button.h
main.cpp
sound.cpp
sound.h
}
subprojects
{
iwgeom
iwgx
}
|
Раздел files
файла MKB используется для перечисления всех исходных файлов, необходимых для вашего проекта. Это также позволяет применять произвольные группировки к этим файлам и ссылочным файлам из нескольких папок. Группы определяются в квадратных скобках, поэтому строка [Source]
создаст группу с именем Source
. Использование групп позволяет нам создавать организационные папки в решении Visual Studio, которое мы сгенерируем на следующем шаге.
Закругленные скобки используются для указания папки, в которой Мармелад должен искать исходные файлы. Строка (source)
указывает Marmalade искать в source
подпапке нашей основной папки проекта. Пять исходных файлов для проекта перечислены после этой строки.
Раздел subprojects
позволяет вам ссылаться на другие модули исходного кода, которые требуются вашему проекту. Для этого проекта потребуются модули iwgeom и iwgx, которые являются стандартными компонентами, предоставляемыми Marmalade SDK.
Шаг 3: Создание проекта Visual Studio
Теперь вы можете использовать файл MKB для создания решения Visual Studio, которое можно использовать для сборки и тестирования приложения. Дважды щелкните файл MKB в проводнике Windows, который должен автоматически запустить Visual Studio и открыть решение.
Если вы посмотрите на папку DolbyTestApp , вы увидите, что было создано несколько новых файлов. Существует папка с именем build_dolbytestapp_vcxx , где часть имени папки xx зависит от используемой версии Visual Studio. Эта папка используется Marmalade для хранения всех файлов, необходимых в процессе сборки, включая файл решения Visual Studio.
Также была создана папка data
, в которую вы должны поместить любые ресурсы, необходимые для приложения, такие как графические и аудиофайлы. Два файла также были автоматически созданы для вас в этой папке:
- app.icf: файл конфигурации, который позволяет вам изменять настройки приложения, такие как максимальный объем ОЗУ, который приложение может использовать
- app.config.txt: используется для определения пользовательских параметров приложения, которые затем можно использовать в файле app.icf
В этом руководстве вы можете спокойно игнорировать эти файлы, так как в них не нужно вносить никаких изменений.
3. Построение пользовательского интерфейса
Шаг 1: Реализация основного цикла программы
Давайте начнем писать основной цикл программы для нашего приложения. В Visual Studio откройте папку « Источник » в обозревателе решений, дважды щелкните файл main.cpp и добавьте следующий фрагмент кода.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
#include «IwGx.h»
#include «s3e.h»
#include «button.h»
#include «sound.h»
void Initialise()
{
}
void Terminate()
{
}
void Update()
{
}
void Render()
{
}
int main()
{
Initialise();
while (!s3eDeviceCheckQuitRequest())
{
Update();
Render();
}
Terminate();
return 0;
}
|
Этот фрагмент кода начинается с включения двух заголовочных файлов Marmalade. Файл IwGx.h объявляет функции и структуры, которые составляют API IwGx от Marmalade, который можно использовать для наиболее эффективной визуализации как 2D, так и 3D графики на целевом устройстве.
Заголовочный файл s3e.h позволяет получить доступ ко всем функциям более низкого уровня, которые обеспечивают прямой доступ к таким вещам, как настройки устройства, ввод с сенсорного экрана, поддержка звука и многое другое.
main
функция не требует пояснений. Он начинается с вызова Initialise
, который выполнит любую настройку, требуемую приложением, и заканчивается вызовом Terminate
, который освободит все ресурсы, используемые приложением.
Основной цикл while начинается после вызова Initialise
. Условие выхода немного больше, чем вызов s3eDeviceCheckQuitRequest
, который является функцией Marmalade, которая проверяет, был ли получен запрос на закрытие приложения, например, когда пользователь закрыл приложение или операционная система запросила его закрыли по какой-то причине.
Основной цикл просто вызывает функции Update
и Render
непрерывно. Функция Update
будет использоваться для таких вещей, как обнаружение пользовательского ввода, в то время как Render
отвечает за создание пользовательского интерфейса.
Функции Initialise
, Update
, Render
и Terminate
в настоящий момент пусты, но приложение может быть построено и выполнено. Если вы хотите попробовать его в Marmalade Windows Simulator , выберите параметр « Отладка x86» в раскрывающемся меню « Конфигурации решения» на панели инструментов Visual Studio, нажмите клавишу F7, чтобы создать приложение, и клавишу F5, чтобы выполнить его. Вы должны увидеть нечто похожее на скриншот ниже.
Шаг 2: Загрузка изображения
Чтобы отобразить пользовательский интерфейс, приложению понадобятся некоторые изображения для рендеринга, поэтому сейчас я покажу вам, как загрузить изображение PNG в память и перевести его в состояние, в котором оно может быть отображено на экране.
Сначала добавьте следующую строку вверху файла main.cpp , ниже операторов включения вверху:
1
|
CIwTexture* gpTexture = NULL;
|
Класс CIwTexture
используется для представления растрового изображения. В приведенном выше фрагменте я объявляю глобальную переменную gpTexture
, которая будет использоваться для хранения указателя на изображение, используемое в пользовательском интерфейсе приложения. Файл изображения, который мы будем использовать, называется ui.png .
Изображение можно загрузить в память и подготовить к использованию, добавив следующие строки в функцию Initialise
.
1
2
3
4
5
6
7
|
// Initialise Marmalade modules
IwGxInit();
// Create a new CIwTexture and use it to load the GUI image file
gpTexture = new CIwTexture;
gpTexture->LoadFromFile(«ui.png»);
gpTexture->Upload();
|
Вызов IwGxInit
выполняет все необходимые шаги инициализации, необходимые для отображения приложения на экране, включая возможность загрузки файлов изображений.
Переменная gpTexture
используется для хранения указателя на новый экземпляр класса CIwTexture
. Вызов метода LoadFromFile
с именем файла изображения загрузит файл PNG в память и преобразует его в подходящий для рендеринга формат. Имя файла изображения указывается относительно папки data
приложения, поэтому вам необходимо убедиться, что файл ui.png скопирован в эту папку.
Вызов метода Upload
загрузит преобразованные данные изображения в видеопамять устройства, готовую для отображения на экране.
Также важно, чтобы мы убирались за собой, поэтому, когда приложение закрывается, оно должно высвобождать любые ресурсы, которые оно может использовать. Добавьте следующее к функции Terminate
.
1
2
3
4
5
|
// Destroy texture instance
delete gpTexture;
// Terminate Marmalade modules
IwGxTerminate();
|
Приведенный выше фрагмент сначала уничтожает экземпляр CIwTexture
, представляющий образ ui.png , который также освобождает все аппаратные ресурсы и память, используемые этим образом. Вызов IwGxTerminate
освобождает любые ресурсы, которые были первоначально выделены вызовом IwGxInit
в Initialise
.
Шаг 3: Создание класса кнопки
Для пользовательского интерфейса этого приложения потребуются нажимаемые кнопки, поэтому давайте создадим новый класс с именем Button
который будет реализовывать это поведение. Откройте файл button.h и введите следующий код.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
#ifndef BUTTON_H
#define BUTTON_H
#include «IwGx.h»
class Button
{
public:
Button(CIwTexture* apTexture, int32 aX, int32 aY,
int32 aWidth, int32 aHeight, int32 aU, int32 aV,
int32 aUWidth, int32 aVWidth, bool aEnabled);
~Button();
void Render();
private:
CIwMaterial* mpMaterial;
CIwSVec2 mTopLeft;
CIwSVec2 mSize;
CIwFVec2 mUV;
CIwFVec2 mUVSize;
bool mEnabled;
};
#endif
|
Конструктор для этого класса используется для позиционирования и размера кнопки на экране, а также для указания того, какая область исходного изображения должна отображаться на кнопке. Это также указывает, должна ли эта кнопка быть включена для пользовательского ввода.
Деструктор высвободит все ресурсы, созданные конструктором, а метод Render
, что неудивительно, нарисует кнопку на экране.
Еще несколько классов Marmalade введены для переменных-членов класса Button
. Класс CIwMaterial
используется Marmalade для объединения изображений с другой информацией рендеринга, такой как данные о цвете, которые могут потребоваться при рендеринге. Класс CIwSVec2
представляет собой двухкомпонентный вектор, где каждый компонент представляет собой 16-разрядное целое число со CIwSVec2
. Класс CIwFVec2
— это еще один двухкомпонентный вектор, причем каждый компонент имеет тип float
.
Откройте button.cpp и добавьте следующий фрагмент кода для реализации класса Button
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
#include «button.h»
#include «s3e.h»
Button::Button(CIwTexture* apTexture, int32 aX, int32 aY,
int32 aWidth, int32 aHeight,
int32 aU, int32 aV,
int32 aUWidth, int32 aVHeight, bool aEnabled)
{
mpMaterial = new CIwMaterial();
mpMaterial->SetTexture(apTexture);
float lTextureWidth = (float) apTexture->GetWidth();
float lTextureHeight = (float) apTexture->GetHeight();
mTopLeft.x = aX;
mTopLeft.y = aY;
mSize.x = aWidth;
mSize.y = aHeight;
mUV.x = (float) aU / lTextureWidth;
mUV.y = (float) aV / lTextureHeight;
mUVSize.x = (float) aUWidth / lTextureWidth;
mUVSize.y = (float) aVHeight / lTextureHeight;
mEnabled = aEnabled;
}
|
Конструктор для класса Button
начинается с создания нового экземпляра CIwMaterial
, который будет использоваться для визуализации изображения кнопки. Каждый экземпляр Button
имеет свой собственный экземпляр CIwMaterial
поскольку он облегчает изменение цвета кнопки. После CIwMaterial
экземпляра CIwMaterial
экземпляр CIwTexture
передаваемый в конструктор, устанавливается в качестве его изображения.
mTopLeft
член mTopLeft
используется для хранения верхнего левого угла Button
на экране, тогда как mSize
сохраняет ширину и высоту. Эти значения указаны в пикселях.
mUV
члены mUV
и mUVSize
хранят верхний левый угол и размеры области изображения, подлежащей визуализации. Они указываются как дробная часть размера исходного изображения, где (0, 0)
— это верхний левый угол изображения, а (1, 1)
— нижний правый угол.
Значения, передаваемые в конструктор, задаются как смещения пикселей в текстуре, поэтому вам необходимо преобразовать их в дробные значения путем деления на ширину или высоту исходного изображения. Размеры изображения можно найти, GetHeight
методы GetWidth
и CIwTexture
класса CIwTexture
.
Следующий фрагмент кода показывает деструктор класса. Как видите, все, что нужно сделать, это удалить экземпляр CIwMaterial
который был размещен в конструкторе.
1
2
3
4
|
Button::~Button()
{
delete mpMaterial;
}
|
Метод Render
нарисует Button
на экране. Он начинается с проверки mEnabled
члена mEnabled
и устанавливает CIwMaterial
цвет CIwMaterial
, чтобы Button
CIwMaterial
на полной яркости при включении и немного темнее при отключении. Вызов IwGxSetMaterial
сообщает Marmalade, с CIwMaterial
экземпляром CIwMaterial
следует рисовать, а IwGxDrawRectScreenSpace
вызывает рендеринг Button
.
01
02
03
04
05
06
07
08
09
10
|
void Button::Render()
{
if (!mEnabled)
mpMaterial->SetColAmbient(96, 96, 96, 255);
else
mpMaterial->SetColAmbient(255, 255, 255, 255);
IwGxSetMaterial(mpMaterial);
IwGxDrawRectScreenSpace(&mTopLeft, &mSize, &mUV, &mUVSize);
}
|
Шаг 4: Выкладываем пользовательский интерфейс
Пользовательский интерфейс приложения будет автоматически подстраиваться под разрешение экрана устройства, на котором оно запущено, но для упрощения мы будем поддерживать только альбомную ориентацию. Первый шаг — форсировать ландшафт, введя следующее в файл app.icf .
1
2
3
4
5
6
7
8
|
[S3E]
DispFixRot=LANDSCAPE
MemSize0=12000000
{OS=WINDOWS}
WinWidth=1280
WinHeight=800
{}
|
Все настройки в файле app.icf имеют связанную с ними группу. Квадратные скобки используются для обозначения группы, поэтому в этом случае строка [S3E]
указывает, что последующие настройки являются частью группы S3E , которая является зарезервированной Marmalade для настроек, связанных с оборудованием.
Параметр DispFixRot
заставит экран всегда находиться в альбомной ориентации. Параметр MemSize0
также был добавлен для увеличения объема оперативной памяти, доступной для приложения. Когда вы добавите поддержку звука позже в этом учебном пособии, потребуется дополнительная память для хранения данных звукового примера.
Параметры WinWidth
и WinHeight
используются для указания размеров окон, используемых при работе в симуляторе. Строка {OS=WINDOWS}
гарантирует, что эти настройки используются только в симуляторе Windows. Строка {}
отключает это ограничение, поэтому любые настройки после него снова становятся глобальными настройками.
Теперь вы можете начать создавать элементы пользовательского интерфейса. Откройте файл main.cpp и начните с добавления следующего фрагмента после объявления глобальной переменной gpTexture
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
enum ButtonIDs
{
BUTTON_AUDIO_LABEL,
BUTTON_AUDIO_OFF,
BUTTON_AUDIO_MUSIC,
BUTTON_AUDIO_SFX,
BUTTON_AUDIO_SPEECH,
BUTTON_FILTER_LABEL,
BUTTON_FILTER_OFF,
BUTTON_FILTER_MOVIE,
BUTTON_FILTER_MUSIC,
BUTTON_FILTER_GAME,
BUTTON_FILTER_VOICE,
BUTTON_COUNT
};
Button* gButton[BUTTON_COUNT];
bool gDolbySupported;
|
Перечисление ButtonIDs
обеспечивает удобный способ именования каждого из элементов пользовательского интерфейса. Массив gButton
будет хранить указатели на каждый из экземпляров Button
в пользовательском интерфейсе, а логический флаг gDolbySupported
будет использоваться для отключения частей интерфейса, если целевое устройство не поддерживает Dolby Audio API.
Чтобы создать необходимые экземпляры Button
, добавьте следующий код в конец функции Initialise
.
01
02
03
04
05
06
07
08
09
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
|
// Check for Dolby Digital Plus support
gDolbySupported = false;
// Create our interface buttons
int32 lSize = IwGxGetScreenWidth() / 5;
int32 lGap = (int32) ((float) lSize * 0.1f);
lSize = (int32) ((float) lSize * 0.9f);
int32 lRowSize = IwGxGetScreenHeight() / 4;
int32 lTopRowX = (IwGxGetScreenWidth() — (4 * lSize) —
(3 * lGap)) / 2;
int32 lTopRowY = lRowSize — (lSize / 2);
int32 lBottomRowX = (IwGxGetScreenWidth() — (5 * lSize) —
(4 * lGap)) / 2;
int32 lBottomRowY = (3 * lRowSize) — (lSize / 2);
int32 lLabelWidth = (240 * lSize) / 160;
int32 lLabelHeight = (42 * lSize) / 160;
int32 lLabelX = (IwGxGetScreenWidth() — lLabelWidth) / 2;
gButton[BUTTON_AUDIO_LABEL] = new Button(gpTexture,
lLabelX, lTopRowY — lLabelHeight — 10,
lLabelWidth, lLabelHeight, 4, 408,
240, 42, false);
gButton[BUTTON_AUDIO_OFF] = new Button(gpTexture,
lTopRowX, lTopRowY, lSize, lSize, 347, 3,
160, 160, true);
gButton[BUTTON_AUDIO_MUSIC] = new Button(gpTexture,
lTopRowX + (lSize + lGap), lTopRowY,
lSize, lSize, 175, 3, 160, 160, true);
gButton[BUTTON_AUDIO_SFX] = new Button(gpTexture,
lTopRowX + (2 * (lSize + lGap)), lTopRowY,
lSize, lSize, 2, 173, 160, 160, true);
gButton[BUTTON_AUDIO_SPEECH] = new Button(gpTexture,
lTopRowX + (3 * (lSize + lGap)), lTopRowY,
lSize, lSize, 174, 173, 160, 160, true);
gButton[BUTTON_FILTER_LABEL] = new Button(gpTexture,
lLabelX, lBottomRowY — lLabelHeight — 10,
lLabelWidth, lLabelHeight, 2, 353,
240, 42, false);
gButton[BUTTON_FILTER_OFF] = new Button(gpTexture,
lBottomRowX, lBottomRowY, lSize, lSize,
347, 3, 160, 160, gDolbySupported);
gButton[BUTTON_FILTER_MOVIE] = new Button(gpTexture,
lBottomRowX + (lSize + lGap), lBottomRowY,
lSize, lSize, 2, 3, 160, 160, gDolbySupported);
gButton[BUTTON_FILTER_MUSIC] = new Button(gpTexture,
lBottomRowX + (2 * (lSize + lGap)),
lBottomRowY, lSize, lSize, 175, 3,
160, 160, gDolbySupported);
gButton[BUTTON_FILTER_GAME] = new Button(gpTexture,
lBottomRowX + (3 * (lSize + lGap)),
lBottomRowY, lSize, lSize, 2, 173,
160, 160, gDolbySupported);
gButton[BUTTON_FILTER_VOICE] = new Button(gpTexture,
lBottomRowX + (4 * (lSize + lGap)), lBottomRowY,
lSize, lSize, 174, 173,
160, 160, gDolbySupported);
|
В этом блоке кода мы начнем с предположения, что Dolby Audio API не поддерживается устройством пользователя, установив для gDolbySupported
значение false
. Затем функции IwGxGetScreenWidth
и IwGxGetScreenHeight
используются для определения размеров экрана, а также рассчитываются подходящие размеры и позиции для элементов пользовательского интерфейса. Наконец, создается несколько экземпляров Button
, определяющих пользовательский интерфейс.
Возможно, вы заметили, что экземпляры Button
для управления текущим типом фильтра используют переменную gDolbySupported
чтобы указать, должны ли они быть включены или нет. Я немного обманул, используя два отключенных экземпляра Button
чтобы нарисовать некоторые метки.
Теперь вы создали пользовательский интерфейс, но вы всегда должны следить за тем, чтобы вы убирались за собой. Добавьте следующий блок кода в начале функции Terminate
чтобы освободить экземпляры Button
при завершении работы приложения.
1
2
3
4
5
|
// Destroy Button instances
for (uint32 i = 0; i < BUTTON_COUNT; i++)
{
delete gButton[i];
}
|
Если вы запустите приложение на этом этапе, пользовательский интерфейс будет создан и уничтожен, но он все равно не будет отображаться. Вам нужно будет добавить следующий фрагмент кода в функцию Render
прежде чем что-либо будет отображаться на экране.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
// Clear the screen to a pale blue
IwGxSetColClear(128, 224, 255, 0);
IwGxClear(IW_GX_COLOUR_BUFFER_F | IW_GX_DEPTH_BUFFER_F);
// Render the UI
for (uint32 i = 0; i < BUTTON_COUNT; i++)
{
gButton[i]->Render();
}
// Finish rendering and display on screen
IwGxFlush();
IwGxSwapBuffers();
|
Приведенный выше фрагмент кода сначала очищает экран до светло-голубого с помощью вызовов IwGxSetColClear
и IwGxClear
. Затем пользовательский интерфейс рисуется путем вызова метода Render
для каждого из экземпляров Button
. Наконец, вызов IwGxFlush
приводит к тому, что все запросы на рендеринг завершаются до того, как IwGxSwapBuffers
заставит пользовательский интерфейс фактически появиться на экране.
Если вы создадите и запустите приложение в Marmalade Windows Simulator , вы должны увидеть два ряда кнопок, причем нижний ряд будет темнее, поскольку они находятся в отключенном состоянии.
Шаг 5: Ответ на ввод пользователя
Давайте теперь сделаем это приложение немного более интерактивным, отслеживая сенсорный ввод от пользователя. Для начала вам нужно добавить новый метод в класс Button
, поэтому откройте button.h и добавьте следующие прототипы методов.
1
2
|
void Update(uint32 aTouchState, int32 aX, int32 aY);
bool IsReleased();
|
Вам также следует добавить следующие дополнительные закрытые переменные-члены в класс Button
.
1
2
3
|
bool mPressed;
bool mDown;
bool mReleased;
|
Затем откройте button.cpp и добавьте следующие строки в конец конструктора класса, чтобы гарантировать, что новые переменные-члены инициализируются в разумные значения.
1
2
3
|
mDown = false;
mPressed = false;
mReleased = false;
|
Следующий блок кода показывает реализации методов Update
и IsReleased
.
01
02
03
04
05
06
07
08
09
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
|
void Button::Update(uint32 aTouchState, int32 aX, int32 aY)
{
if (!mEnabled)
return;
// Check if the touch position is within bounds of
// this button
aX -= mTopLeft.x;
aY -= mTopLeft.y;
bool lInBounds = (aX >= 0) && (aX < mSize.x) &&
(aY >= 0) && (aY < mSize.y);
// Clear the released flag
mReleased = false;
// Check touch screen state
if (aTouchState & S3E_POINTER_STATE_PRESSED)
{
// User has just touched the screen
if (lInBounds)
{
mPressed = true;
mDown = true;
}
}
else if (aTouchState & S3E_POINTER_STATE_DOWN)
{
// If button has been pressed, check if user
// is still touching inside it
if (mPressed)
{
mDown = lInBounds;
}
}
else if (aTouchState & S3E_POINTER_STATE_RELEASED)
{
// If user has released screen over a pressed
// button, we set the release flag to true
if (mPressed && mDown)
{
mReleased = true;
}
// Button is no longer pressed or down!
mDown = false;
mPressed = false;
}
}
bool Button::IsReleased()
{
return mReleased;
}
|
Метод Update
принимает три параметра: текущее состояние касания и координаты касания на экране. Вскоре вы узнаете, как получить эту информацию. Метод сначала проверяет, отключена ли Button
и сразу же завершает работу, если это так. Координаты экрана, переданные методу, затем сравниваются с границами Button
чтобы определить, не происходит ли Button
.
Параметр aTouchState
метода Update
представляет собой битовую маску, состоящую из трех возможных флагов:
-
S3E_POINTER_STATE_PRESSED
устанавливается, когда пользователь только что коснулся экрана -
S3E_POINTER_STATE_DOWN
устанавливается во время касания экрана -
S3E_POINTER_STATE_RELEASED
устанавливается, когда пользовательS3E_POINTER_STATE_RELEASED
палец с экрана
Метод Update
использует текущее значение aTouchState
для соответствующего обновления внутренних переменных-членов класса.
Метод IsReleased
тривиален, он возвращает текущее состояние переменной mReleased
.
Нам нужно сделать одно последнее изменение в классе Button
. В методе Render
мы рисуем Button
немного темнее, пока пользователь нажимает ее. Эта визуальная обратная связь приносит пользу пользовательскому опыту приложения. Измените начало метода Render
на следующее:
1
2
3
4
5
6
|
if (!mEnabled)
mpMaterial->SetColAmbient(96, 96, 96, 255);
else if (mDown)
mpMaterial->SetColAmbient(192, 192, 192, 255);
else
mpMaterial->SetColAmbient(255, 255, 255, 255);
|
Button
класс Button
, вам теперь нужно просто добавить логику для обнаружения сенсорного ввода от пользователя. Снова откройте main.cpp и добавьте следующее к текущей пустой функции Update
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
// Allow device OS time to do its processing
s3eDeviceYield(0);
// Update pointer (actually touch screen!) inputs
s3ePointerUpdate();
// Read current touch screen inputs and use them to update Button states
uint32 lTouchState =
s3ePointerGetState(S3E_POINTER_BUTTON_SELECT);
int32 x = s3ePointerGetX();
int32 y = s3ePointerGetY();
for (uint32 i = 0; i < BUTTON_COUNT; i++)
{
gButton[i]->Update(lTouchState, x, y);
}
|
Вызов s3eDeviceYield
жизненно важен в приложении Marmalade, поскольку он позволяет операционной системе устройства обрабатывать любые события, такие как сенсорный ввод, входящие вызовы и т. s3ePointerUpdate
Функция s3ePointerUpdate
делает снимок текущего состояния сенсорного экрана.
Текущее состояние первого обнаруженного сенсорного ввода затем определяется с помощью вызова s3ePointerGetState
. Возвращает значение, используя битовую маску, которую я описал ранее. Функции s3ePointer
также используются для обнаружения событий мыши в настольных операционных системах. Значение, переданное в s3ePointerGetState
равно S3E_POINTER_BUTTON_SELECT
, которое будет возвращать состояние первого обнаруженного события касания или левой кнопки мыши, в зависимости от возможностей устройства, на котором работает приложение.
s3ePointerGetX
и s3ePointerGetY
возвращают экранные координаты касания. Затем мы перебираем экземпляры кнопок и вызываем Button::Update
для каждой кнопки, передавая текущее состояние сенсорного экрана и координаты касания.
Приложение теперь способно обнаруживать пользовательский ввод, и экземпляры Button
будут менять цвет при нажатии.
4. Добавление аудио воспроизведения
Шаг 1: Воспроизведение сжатых аудио файлов
Воспроизведение сжатых аудиофайлов, таких как файлы MP3, невероятно просто в Marmalade. На самом деле, это займет всего одну строку кода. Добавьте следующий блок кода в конец функции Update
в main.cpp .
1
2
3
4
5
|
// Check for button presses
if (gButton[BUTTON_AUDIO_MUSIC]->IsReleased())
{
s3eAudioPlay(«black-hole.mp3»);
}
|
Всякий раз, когда пользователь нажимает и отпускает кнопку музыкальной заметки в верхнем ряду кнопок, приложение вызывает функцию s3eAudioPlay
, которая пытается воспроизвести файл MP3 с именем black-hole.mp3 . Этот файл должен существовать в папке данных проекта, чтобы он мог находиться во время выполнения.
Файл black-hole.mp3 был получен с http://www.freesfx.co.uk и составлен Крейгом Райли (SOCAN).
Шаг 2: Воспроизведение несжатых звуковых сэмплов
Воспроизведение несжатых сэмплов также возможно в Marmalade. Хотя это не так просто, как воспроизведение сжатого звукового файла, он более гибкий, поскольку позволяет воспроизводить несколько звуков одновременно, тогда как большинство устройств позволяют воспроизводить только одну сжатую звуковую дорожку в любое время.
Marmalade ожидает, что данные звукового примера будут иметь 16-битный подписанный формат PCM, и большинство программ для редактирования аудио позволит вам сохранять файлы в этом формате, используя формат файла WAV . Однако Marmalade напрямую не поддерживает формат файла WAV, поэтому для целей данного урока я взял звуковые файлы, сохраненные в формате WAV, и удалил заголовок из файла, оставив только данные образца. Для ваших собственных приложений вы, вероятно, захотите поддерживать файлы WAV напрямую, но это выходит за рамки данного руководства.
Я добавил два звуковых файла в папку данных с именами female-counting.raw и gun-battle.raw . Исходные файлы формата WAV были получены с http://soundbible.com и выпущены под лицензией Attribution 3.0 Creative Commons.
Чтобы воспроизвести звуковой эффект сэмплирования, необходимо иметь звуковые данные в памяти. Я создал класс Sound
, который позаботится об этом для нас. Чтобы реализовать этот класс, откройте sound.h и добавьте в него следующий блок кода:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
#ifndef SOUND_H
#define SOUND_H
#include «s3e.h»
class Sound
{
public:
Sound(const char* apFileName);
~Sound();
void Play();
private:
int16* mpSoundData;
uint32 mSamples;
};
#endif
|
Затем откройте sound.cpp и вставьте следующий блок кода:
01
02
03
04
05
06
07
08
09
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
|
#include «sound.h»
Sound::Sound(const char* apFileName)
{
// Attempt to open the sound effect file
s3eFile* f = s3eFileOpen(apFileName, «rb»);
if (f)
{
// Seek to end of file to find its length
s3eFileSeek(f, 0, S3E_FILESEEK_END);
// Number of samples is file size divided by the
// size of an int16
mSamples = s3eFileTell(f) / sizeof(int16);
s3eFileSeek(f, 0, S3E_FILESEEK_SET);
// Allocate buffer for sound data
mpSoundData = new int16[mSamples];
// Read in sound data
s3eFileRead(mpSoundData, sizeof(int16), mSamples, f);
// Close the file
s3eFileClose(f);
}
else
{
// File open failed, zero the member variables
mpSoundData = NULL;
mSamples = 0;
}
}
Sound::~Sound()
{
if (mpSoundData)
delete[] mpSoundData;
}
void Sound::Play()
{
if (mpSoundData)
{
int lChannel = s3eSoundGetFreeChannel();
s3eSoundChannelPlay(lChannel, mpSoundData,
mSamples, 0, 0);
}
}
|
Конструктор берет имя файла звукового эффекта и находит длину файла в байтах. Создается массив 16-разрядных целых чисел со знаком, достаточно большой, чтобы вместить весь звук, и файл считывается в этот буфер. Деструктор удаляет этот буфер.
Метод Play
фактически запускает воспроизведение сэмпла. Для этого он сначала запрашивает у Marmalade бесплатный звуковой канал с вызовом s3eSoundGetFreeChannel
. Звук затем запускается на этом канале путем вызова s3eSoundChannelPlay
, передавая номер канала, начало буфера звука и количество семплов звука в звуке. Оставшиеся два параметра указывают, должен ли звуковой сэмпл зацикливаться, когда он достигает конца, и смещение в данные сэмпла, где должны начаться последующие циклы. Если передать оба этих параметра в ноль, весь звуковой эффект будет зацикливаться непрерывно.
С реализованным классом Sound
вернитесь в main.cpp и добавьте некоторый код для загрузки и уничтожения звуковых сэмплов и запуска воспроизведения звука, когда пользователь нажимает кнопку. Начните с добавления двух новых глобальных переменных после объявления массива gpButton
.
1
2
|
Sound* gpGunBattleSound;
Sound* gpFemaleCountingSound;
|
Затем добавьте следующее в конец функции Initialise
. Этот кодовый блок загрузит два звуковых файла в память, а затем установит частоту дискретизации звука по умолчанию на 44100 Гц, что так и происходит с частотой обоих звуков, используемых в приложении.
1
2
3
4
5
6
|
// Load sound effects into memory
gpGunBattleSound = new Sound(«gun-battle.raw»);
gpFemaleCountingSound = new Sound(«female-counting.raw»);
// Configure default sample rate for s3eSound
s3eSoundSetInt(S3E_SOUND_DEFAULT_FREQ, 44100);
|
Нам также нужно освободить звуковые данные при выключении. Мы делаем это, добавляя следующий блок кода в начало функции Terminate
чтобы уничтожить экземпляры Sound
.
1
2
3
|
// Destroy sound effects
delete gpGunBattleSound;
delete gpFemaleCountingSound;
|
Наконец, добавьте следующий фрагмент кода в конец функции Update
, сразу после конца последнего оператора if
. Это запустит звуковые эффекты, воспроизводимые в ответ на нажатие пользователем правильных кнопок.
01
02
03
04
05
06
07
08
09
10
|
else if (gButton[BUTTON_AUDIO_SFX]->IsReleased())
{
if (gpGunBattleSound)
gpGunBattleSound->Play();
}
else if (gButton[BUTTON_AUDIO_SPEECH]->IsReleased())
{
if (gpFemaleCountingSound)
gpFemaleCountingSound->Play();
}
|
Шаг 3: Остановка вывода звука
Если вы хотите начать воспроизведение аудио, скорее всего, вы также захотите остановить его воспроизведение. Приведенный ниже кодовый блок иллюстрирует, как сделать это в Marmalade с помощью кнопки в пользовательском интерфейсе, чтобы остановить все воспроизводимое в данный момент аудио. Добавьте следующий блок в конец блока if...else
в самом конце функции Update
в main.cpp .
1
2
3
4
5
|
else if (gButton[BUTTON_AUDIO_OFF]->IsReleased())
{
s3eAudioStop();
s3eSoundStopAllChannels();
}
|
Вызов s3eAudioStop
остановит воспроизведение любой воспроизводимой сжатой звуковой дорожки, в то время как s3eSoundStopAllChannels
остановит все несжатые сэмплированные звуки. Можно останавливать сэмплированные звуки для каждого канала отдельно, но для целей этого приложения хорошо останавливать все активные каналы.
5. Интеграция Dolby Audio API
Шаг 1: Загрузка расширения Marmalade для API Dolby Audio
Теперь, когда у нас есть приложение, которое может воспроизводить некоторые звуки, пришло время обратить наше внимание на Dolby Audio API. Как вы заметите, добавить поддержку Dolby Audio API невероятно просто и это можно сделать не более чем за пять минут.
Сначала вам необходимо получить расширение Marmalade для Dolby Audio API, посетив веб-сайт Dolby Developer . После создания бесплатной учетной записи разработчика вы можете скачать расширение Marmalade со вкладки Framework .
Шаг 2: Добавление Dolby Audio API в проект
Извлеките архив расширений Dolby Audio API Marmalade в папку на компьютере разработчика и найдите в извлеченной папке « Библиотеки» папку с именем s3eDolbyAudio . Скопируйте эту папку и ее содержимое в папку DolbyTestApp нашего проекта вместе с папками источника и данных .
Чтобы включить расширение в свой проект, отредактируйте файл DolbyTestApp.mkb и добавьте s3eDolbyAudio
в список subprojects
. Если вы затем перестроите проект в Visual Studio, файл MKB будет обработан повторно, и в проект будет добавлено расширение Dolby Audio API.
Шаг 3: Инициализация Dolby Audio API
Прежде чем вы сможете использовать функциональные возможности Dolby Audio API, вы должны сначала проверить, поддерживает ли устройство, на котором работает ваше приложение, Dolby Digital Plus. Чтобы реализовать необходимые проверки, отредактируйте файл main.cpp и добавьте следующий #include
вверху файла.
1
|
#include «s3eDolbyAudio.h»
|
Затем объявите глобальную переменную с именем gDolbyInitialised
после объявления gDolbySupported
.
1
|
bool gDolbyInitialised;
|
Чтобы проверить, поддерживается ли Dolby Digital Plus, вы можете добавить следующий блок кода в функцию Initialise
после оператора gDolbySupported = false;
,
1
2
3
4
5
6
7
8
|
if (s3eDolbyAudioAvailable() == S3E_TRUE)
{
if (s3eDolbyAudioSupported() == S3E_TRUE)
{
gDolbySupported = true;
}
s3eDeviceYield(0);
}
|
Первый вызов s3eDolbyAudioAvailable
проверяет, s3eDolbyAudioAvailable
ли расширение Dolby Audio API на целевой платформе. Если расширение доступно, вызывается s3eDolbyAudioSupported
, который возвращает S3E_TRUE
если целевое устройство поддерживает Dolby Digital Plus. Если поддерживается, флаг gDolbySupported
устанавливается в значение true
.
Вызов s3eDeviceYield
должен дать устройству время для выполнения фоновой обработки после выполнения теста поддержки Dolby Digital Plus. Dolby рекомендует не инициализировать Dolby Digital Plus сразу после проверки его поддержки, поэтому вызов s3eDeviceYield
поможет предотвратить проблемы во время инициализации.
В конце функции Initialise
вы можете инициализировать Dolby Digital Plus, вызвав функцию s3eDolbyAudioInitialize
. Только если эта функция возвращает S3E_TRUE
, флаг gDolbyInitialised
будет установлен в true
. Код, который вам нужно добавить, выглядит следующим образом:
1
2
3
4
5
6
7
8
|
// Initialise Dolby API
if (gDolbySupported)
{
if (s3eDolbyAudioInitialize() == S3E_TRUE)
{
gDolbyInitialised = true;
}
}
|
Вы также должны закрыть Dolby Audio API, когда ваша программа завершает работу, поэтому добавьте следующее в функцию Terminate
перед вызовом IwGxTerminate
.
1
2
3
4
5
|
// Release resources used by Dolby API
if (gDolbySupported)
{
s3eDolbyAudioRelease();
}
|
Шаг 4: Обработка приостановки и возобновления
Поскольку мобильные телефоны и планшеты используются для выполнения множества задач, ваше приложение нередко приостанавливается либо потому, что пользователь хочет выполнить другую задачу, либо произошло событие, такое как входящий вызов. В этих условиях Dolby Audio API должен быть приостановлен, чтобы он не был активным, пока ваше приложение приостановлено или работает в качестве фоновой задачи.
Marmalade позволяет нам настроить некоторые функции обратного вызова, которые будут срабатывать всякий раз, когда приложение теряет или восстанавливает фокус. Чтобы реализовать это, добавьте следующий код в Initialise
функцию сразу после проверки поддержки Dolby Digital Plus.
1
2
3
4
5
|
// Initialise Pause/Resume callbacks s3eDeviceRegister(S3E_DEVICE_PAUSE, AppSuspended, NULL); s3eDeviceRegister(S3E_DEVICE_BACKGROUND, AppSuspended, NULL); s3eDeviceRegister(S3E_DEVICE_UNPAUSE, AppResumed, NULL); s3eDeviceRegister(S3E_DEVICE_FOREGROUND, AppResumed, NULL); |
Вы также должны удалить эти функции обратного вызова при выключении, поэтому добавьте следующие строки в Terminate
функцию сразу после delete gpTexture
строки.
1
2
3
4
5
|
// Disable Pause/Resume callbacks s3eDeviceUnRegister(S3E_DEVICE_PAUSE, AppSuspended); s3eDeviceUnRegister(S3E_DEVICE_BACKGROUND, AppSuspended); s3eDeviceUnRegister(S3E_DEVICE_UNPAUSE, AppResumed); s3eDeviceUnRegister(S3E_DEVICE_FOREGROUND, AppResumed); |
Теперь вам просто нужно реализовать функции AppSuspended
и AppResumed
обратного вызова. Добавьте этот код после объявления глобальных переменных в верхней части main.cpp .
01
02
03
04
05
06
07
08
09
10
11
12
13
|
int32 AppSuspended( void * apSystemData, void * apUserData) {
if (gDolbyInitialised) s3eDolbyAudioSuspendSession(); return 0;
}
int32 AppResumed( void * apSystemData, void * apUserData) {
if (gDolbyInitialised) s3eDolbyAudioRestartSession(); return 0;
}
|
Когда приложение приостанавливается или переходит в фоновую обработку, AppSuspended
вызывается обратный вызов, который вызывает, s3eDolbyAudioSuspendSession
если установлен gDolbyInitialised
флаг true
. Когда приложение восстановит фокус, AppResumed
будет вызван вызов, который вызывается, s3eDolbyAudioRestartSession
если Dolby Audio API был инициализирован.
Шаг 5. Использование фильтров Dolby Audio API
Последний шаг к интеграции Dolby Audio API состоит в том, чтобы фактически использовать различные аудио фильтры, которые он предоставляет. Доступны четыре предопределенных фильтра, которые подходят для различных типов аудиовыхода, фильма , музыки , голоса и игры .
Когда Dolby Audio API активен, перейдите S3E_TRUE
к, s3eDolbyAudioSetEnabled
чтобы убедиться, что включена поддержка фильтрации, после чего следует вызов s3eDolbyAudioSetProfile
. Если вы хотите прекратить фильтрацию, вы можете сделать это с помощью вызова s3eDolbyAudioSetEnabled
, передавая S3E_FALSE
.
Добавьте следующий блок кода в конец Update
функции, чтобы нижний ряд кнопок мог переключаться между различными типами фильтров.
01
02
03
04
05
06
07
08
09
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
|
if (gButton[BUTTON_FILTER_OFF]->IsReleased()) {
if (gDolbyInitialised) {
s3eDolbyAudioSetEnabled(S3E_FALSE); }
}
else if (gButton[BUTTON_FILTER_MUSIC]->IsReleased()) {
if (gDolbyInitialised) {
s3eDolbyAudioSetEnabled(S3E_TRUE); s3eDolbyAudioSetProfile(MUSIC); }
}
else if (gButton[BUTTON_FILTER_MOVIE]->IsReleased()) {
if (gDolbyInitialised) {
s3eDolbyAudioSetEnabled(S3E_TRUE); s3eDolbyAudioSetProfile(MOVIE); }
}
else if (gButton[BUTTON_FILTER_GAME]->IsReleased()) {
if (gDolbyInitialised) {
s3eDolbyAudioSetEnabled(S3E_TRUE); s3eDolbyAudioSetProfile(GAME); }
}
else if (gButton[BUTTON_FILTER_VOICE]->IsReleased()) {
if (gDolbyInitialised) {
s3eDolbyAudioSetEnabled(S3E_TRUE); s3eDolbyAudioSetProfile(VOICE); }
}
|
6. Создание устройства сборки
Шаг 1: Включение файлов ресурсов
Давайте завершим, запустив приложение на устройстве. Первый шаг — убедиться, что окончательный пакет развертывания содержит все необходимые файлы ресурсов. Отредактируйте DolbyTestApp.mkb и добавьте следующий фрагмент кода в конец файла.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
assets
{
[default] (data) black-hole.mp3 female-counting.raw gun-battle.raw ui.png }
deployments {
name="DolbyTestApp" caption="Dolby Test App" assets="default" }
|
assets
Раздел файла MKB используется для вывода списка файлов ресурсов которые должны быть отправлены с исполняемым для того , чтобы приложение для запуска. Формат аналогичен files
разделу с использованием квадратных скобок для группировки файлов и округленных скобок для указания имен папок, в которых можно найти файлы.
deployments
Раздел позволяет настроить окончательный пакет установки. name
Параметр позволяет нам указать имя файла , который будет использоваться для файла пакета и caption
параметр используется , чтобы объявить текст , который будет отображаться под значком приложения , когда он установлен на устройстве. В assets
ссылках параметров одной из групп , определенных в assets
разделе, поэтому можно переключаться между различными наборами активов , если Вам потребуется, например, если вы хотите , чтобы создать полную и облегченную версию приложения.
Шаг 2: Создание установочного пакета
Чтобы создать установочный пакет для выбранного вами устройства, сначала необходимо скомпилировать исходный код приложения для типа процессора, используемого целевым устройством. В большинстве случаев это будет чип ARM, поэтому вы должны выбрать опцию GCC ARM Release в раскрывающемся меню « Конфигурации решений» в верхней части панели инструментов Visual Studio.
Нажмите F7, чтобы создать приложение, затем F5, чтобы запустить Marmalade Deployment Tool .
Инструмент развертывания Мармелад показывает мастер создания инсталляционных пакетов. Например, если вы хотите создать сборку Android, которая также будет работать на устройствах Kindle Fire, то сначала вы должны выбрать тип сборки ARM GCC Release .
После нажатия кнопки « Следующая стадия»> вам будет предложено выбрать конфигурацию проекта, которую вы хотите развернуть. Проверьте конфигурацию по умолчанию , которая является единственной конфигурацией нашего проекта, и еще раз нажмите кнопку « Следующая стадия»> , чтобы увидеть список платформ, поддерживаемых вашей версией Marmalade. Установите флажок рядом с Android и снова нажмите кнопку « Следующая стадия»> .
Теперь вы можете выбрать, какие действия предпринять при создании установочного пакета. Раскрывающееся меню позволяет вам просто создать пакет, а это значит, что вам нужно будет вручную установить его на устройстве, создать и установить его на устройстве, подключенном через USB, или создать, установить и запустить приложение на подключенное устройство. Для работы последних двух вариантов вам понадобится Android SDK, установленный на вашем компьютере для разработки, потому что эти опции используют инструмент ADB , который является частью Android SDK.
Обсуждение того, как настроить устройство Android для использования в разработке, выходит за рамки данного руководства, но вы можете прочитать больше об этой теме на сайте разработчика Android .
Вывод
Как видите, использование Dolby Audio API в приложении Marmalade — очень простой процесс. Большая часть этого руководства была посвящена настройке пользовательского интерфейса или воспроизведению звуков, а не интеграции Dolby Audio API.
Если вы написали игру или любое другое приложение в Marmalade с богатым звуковым выходом, то вам действительно стоит подумать о добавлении поддержки Dolby Digital Plus, чтобы предоставить своим пользователям наилучшее звучание.