Статьи

Интеграция Dolby Audio API с мармеладом

Конечный продукт
Что вы будете создавать

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

Некоторые мобильные устройства поддерживают Dolby Digital Plus, в частности линейку Kindle Fire, а также растущее число других устройств Android. Dolby Digital Plus может значительно улучшить вывод звука вашего приложения, применяя фильтры, которые могут усиливать определенные части вашего вывода звука, например музыку или голос. Это может показаться сложной задачей, но, к счастью, Dolby предоставила API, который делает использование этой функции невероятно простым.

Из этого руководства вы узнаете, как разработать приложение с использованием Marmalade SDK, которое может использовать преимущества Dolby Digital Plus с использованием расширения Marbylade API Dolby Audio. Если вы заинтересованы только в интеграции Dolby Audio API в ваше приложение Marmalade, тогда переходите к концу этой статьи .

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

Сначала мы создадим новый проект Marmalade, а также папки и исходные файлы, которые будут составлять проект.

Далее я объясню, как реализовать пользовательский интерфейс, загрузив изображение, содержащее необходимые изображения кнопок, и нарисовав различные его части на экране. Я также проиллюстрирую, как использовать Marmalade для реагирования на прикосновения пользователей.

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

Наконец, я интегрирую Dolby Audio API в приложение и буду использовать различные типы аудиофильтров, которые оно предоставляет.

В этом руководстве я буду предполагать, что вы разрабатываете на компьютере под управлением Windows и уже имеете Marmalade SDK и версию Microsoft Visual Studio. Marmalade также можно использовать с Xcode на OS X, и вам все равно будет легко следовать инструкциям этого руководства, если вы используете Mac для разработки.

Создайте папку верхнего уровня с именем DolbyTestApp для хранения файлов проекта. В этой папке создайте другую папку с именем source . Давайте также создадим файлы, которые нам понадобятся в нашем проекте. В исходной папке создайте пять пустых файлов с именами button.cpp , button.h , main.cpp , sound.cpp и sound.h .

Создайте пустой файл с именем 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.

Теперь вы можете использовать файл 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

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

Давайте начнем писать основной цикл программы для нашего приложения. В 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, чтобы выполнить его. Вы должны увидеть нечто похожее на скриншот ниже.

Чтобы отобразить пользовательский интерфейс, приложению понадобятся некоторые изображения для рендеринга, поэтому сейчас я покажу вам, как загрузить изображение 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 .

Для пользовательского интерфейса этого приложения потребуются нажимаемые кнопки, поэтому давайте создадим новый класс с именем 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);
}

Пользовательский интерфейс приложения будет автоматически подстраиваться под разрешение экрана устройства, на котором оно запущено, но для упрощения мы будем поддерживать только альбомную ориентацию. Первый шаг — форсировать ландшафт, введя следующее в файл 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 , вы должны увидеть два ряда кнопок, причем нижний ряд будет темнее, поскольку они находятся в отключенном состоянии.

Давайте теперь сделаем это приложение немного более интерактивным, отслеживая сенсорный ввод от пользователя. Для начала вам нужно добавить новый метод в класс 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 будут менять цвет при нажатии.

Воспроизведение сжатых аудиофайлов, таких как файлы 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).

Воспроизведение несжатых сэмплов также возможно в 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();
}

Если вы хотите начать воспроизведение аудио, скорее всего, вы также захотите остановить его воспроизведение. Приведенный ниже кодовый блок иллюстрирует, как сделать это в Marmalade с помощью кнопки в пользовательском интерфейсе, чтобы остановить все воспроизводимое в данный момент аудио. Добавьте следующий блок в конец блока if...else в самом конце функции Update в main.cpp .

1
2
3
4
5
else if (gButton[BUTTON_AUDIO_OFF]->IsReleased())
{
    s3eAudioStop();
    s3eSoundStopAllChannels();
}

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

Теперь, когда у нас есть приложение, которое может воспроизводить некоторые звуки, пришло время обратить наше внимание на Dolby Audio API. Как вы заметите, добавить поддержку Dolby Audio API невероятно просто и это можно сделать не более чем за пять минут.

Сначала вам необходимо получить расширение Marmalade для Dolby Audio API, посетив веб-сайт Dolby Developer . После создания бесплатной учетной записи разработчика вы можете скачать расширение Marmalade со вкладки Framework .

Извлеките архив расширений Dolby Audio API Marmalade в папку на компьютере разработчика и найдите в извлеченной папке « Библиотеки» папку с именем s3eDolbyAudio . Скопируйте эту папку и ее содержимое в папку DolbyTestApp нашего проекта вместе с папками источника и данных .

Чтобы включить расширение в свой проект, отредактируйте файл DolbyTestApp.mkb и добавьте s3eDolbyAudio в список subprojects . Если вы затем перестроите проект в Visual Studio, файл MKB будет обработан повторно, и в проект будет добавлено расширение 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();
}

Поскольку мобильные телефоны и планшеты используются для выполнения множества задач, ваше приложение нередко приостанавливается либо потому, что пользователь хочет выполнить другую задачу, либо произошло событие, такое как входящий вызов. В этих условиях 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 был инициализирован.

Последний шаг к интеграции 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);
    }
}

Давайте завершим, запустив приложение на устройстве. Первый шаг — убедиться, что окончательный пакет развертывания содержит все необходимые файлы ресурсов. Отредактируйте 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разделе, поэтому можно переключаться между различными наборами активов , если Вам потребуется, например, если вы хотите , чтобы создать полную и облегченную версию приложения.

Чтобы создать установочный пакет для выбранного вами устройства, сначала необходимо скомпилировать исходный код приложения для типа процессора, используемого целевым устройством. В большинстве случаев это будет чип 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, чтобы предоставить своим пользователям наилучшее звучание.