Star Defense Уроки iPhone SDK: Cocos2d: Работа с переходами
Июн 10

Мы уже останавливались на воспроизведении коротких звуков в игре/приложении. Теперь пришло время фоновой музыки. iPod допускает воспроизведение одного типа коротких звуков, а вот добавление фоновой музыки его отключает.
Для проигрывания фоновой музыки (т.е. звуков, не ограниченных по длительности 30 с) можно воспользоваться AVAudioPlayer. Нужный код приведен ниже (не забудьте добавить фреймворк “AVFoundation.framework“).

1
2
AVAudioPlayer *myAVsound = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"backgroundMusic" ofType:@"caf"] ] error:NULL];
[myAVsound play];

Задача данного кода сводится к воспроизведению фоновой музыки [backgroundMusic.caf], но только один раз. Проделать это неоднократно поможет “NSTimer“: вызывайте [myAVsound play].

GBMusicTrack

Хочу представить весьма интересный класс “GBMusicTrack“, написанный Джейком Петерсоном (aka AnotherJake). Более подробную информацию по нему можно найти здесь. Обратите внимание, что в данном случае допускается воспроизведение только одного файла с фоновой музыкой. Второй трек, как и любые последующие, будет воспроизводиться только после остановки первого.

GBMusicTrack.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//
// GBMusicTrack.h
// GameBase
//
// Created by Jake Peterson (AnotherJake) on 7/6/08.
// Copyright 2008 Jake Peterson. All rights reserved.
//
#import // В ОРИГИНАЛЬНЫЙ ФАЙЛ
#import
#import
#define NUM_QUEUE_BUFFERS 3
@interface GBMusicTrack : NSObject
{
AudioFileID audioFile;
AudioStreamBasicDescription dataFormat;
AudioQueueRef queue;
UInt64 packetIndex;
UInt32 numPacketsToRead;
AudioStreamPacketDescription *packetDescs;
BOOL repeat;
BOOL trackClosed;
AudioQueueBufferRef buffers[NUM_QUEUE_BUFFERS];
}
- (id)initWithPath:(NSString *)path;
- (void)setGain:(Float32)gain;
- (void)setRepeat:(BOOL)yn;
- (void)play;
- (void)pause;
// "close" вызывается автоматически в относящемся к GBMusicTrack методе
// "dealloc", но лучше вызвать его первым, чтобы тут же освободить ассоциированный объект "Audio Queue",
// не дожидаясь автоматической операции, чреватой потенциальными конфликтами
- (void)close;
extern NSString *GBMusicTrackFinishedPlayingNotification;
@end

GBMusicTrack.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
//
// GBMusicTrack.m
// GameBase
//
// Created by Jake Peterson (AnotherJake) on 7/6/08.
// Copyright 2008 Jake Peterson. All rights reserved.
//
#import "GBMusicTrack.h"
static UInt32 gBufferSizeBytes = 0x10000; // 64k
NSString *GBMusicTrackFinishedPlayingNotification = @"GBMusicTrackFinishedPlayingNotification";
@interface GBMusicTrack (InternalMethods)
static void BufferCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef buffer);
- (void)callbackForBuffer:(AudioQueueBufferRef)buffer;
- (UInt32)readPacketsIntoBuffer:(AudioQueueBufferRef)buffer;
@end
@implementation GBMusicTrack
#pragma mark -
#pragma mark GBMusicTrack
- (void)dealloc
{
[self close];
[super dealloc];
}
- (void)close
{
// если что-то дожидается автоматического освобождения,
// первым, еще до "dealloc", лучше вызывать "close"
if (trackClosed)
return;
trackClosed = YES;
AudioQueueStop(queue, YES);
AudioQueueDispose(queue, YES);
AudioFileClose(audioFile);
}
- (id)initWithPath:(NSString *)path
{
UInt32 size, maxPacketSize;
char *cookie;
int i;
if(!(self = [super init])) return nil;
if (path == nil) return nil;
// постарайтесь открыть файл через указанный путь
if (noErr != AudioFileOpenURL((CFURLRef)[NSURL fileURLWithPath:path], 0x01, kAudioFileCAFType, &audioFile))
{
NSLog(@"GBMusicTrack Error - initWithPath: could not open audio file. Path given was: %@", path);
return nil;
}
// получаем формат данных файла
size = sizeof(dataFormat);
AudioFileGetProperty(audioFile, kAudioFilePropertyDataFormat, &size, &dataFormat);
// создаем новую очередь воспроизведения посредством указанного формата данных и обратного запроса к буферу
AudioQueueNewOutput(&dataFormat, BufferCallback, self, nil, nil, 0, &queue);
// рассчитываем число считываемых пакетов и при необходимости выделяем место для их описаний
if (dataFormat.mBytesPerPacket == 0 || dataFormat.mFramesPerPacket == 0)
{
// поскольку рабочие размеры отсутствуют, это, должно быть, данные VBR Variable BitRate), поэтому
// нужно запросить у Core Audio оценку с запасом для самого большого пакета, который будет
// считываться "kAudioFilePropertyPacketSizeUpperBound"
size = sizeof(maxPacketSize);
AudioFileGetProperty(audioFile, kAudioFilePropertyPacketSizeUpperBound, &size, &maxPacketSize);
if (maxPacketSize > gBufferSizeBytes)
{
// хм... превышать размеры буфера не хотелось бы, поэтому введем ограничения
maxPacketSize = gBufferSizeBytes;
"NSLog(@"GBMusicTrack Warning - initWithPath:" должен ограничить размер пакета, запрошенного для файла с местоположением: %@", path);
}
numPacketsToRead = gBufferSizeBytes / maxPacketSize;
// потребуется описание для каждого пакета, поскольку это VBR данные, поэтому выделяем соответствующий объем памяти
packetDescs = malloc(sizeof(AudioStreamPacketDescription) * numPacketsToRead);
}
else
{
// для CBR данных(Constant BitRate) достаточно заполнить каждый буфер максимальным количеством вмещающихся в него пакетов
numPacketsToRead = gBufferSizeBytes / dataFormat.mBytesPerPacket;
// для CBR данных описания пакетов не нужны
packetDescs = nil;
}
// проверяем файл на наличие magic cookie (мета-данные, с которыми работают некоторые форматы)
AudioFileGetPropertyInfo(audioFile, kAudioFilePropertyMagicCookieData, &size, nil);
if (size > 0)
{
// копируем данные cookie из файла в очередь аудиофайлов
cookie = malloc(sizeof(char) * size);
AudioFileGetProperty(audioFile, kAudioFilePropertyMagicCookieData, &size, cookie);
AudioQueueSetProperty(queue, kAudioQueueProperty_MagicCookie, cookie, size);
free(cookie);
}
// размещаем и наполняем буферы данными
packetIndex = 0;
for (i = 0; i < NUM_QUEUE_BUFFERS; i++)
{
AudioQueueAllocateBuffer(queue, gBufferSizeBytes, &buffers[i]);
if ([self readPacketsIntoBuffer:buffers[i]] == 0)
{
// такое может произойти, если файл настолько короткий, что потребовал меньше буферов, чем планировалось
break;
}
}
repeat = NO;
trackClosed = NO;
return self;
}
- (void)setGain:(Float32)gain
{
AudioQueueSetParameter(queue, kAudioQueueParam_Volume, gain);
}
- (void)setRepeat:(BOOL)yn
{
repeat = yn;
}
- (void)play
{
AudioQueueStart(queue, nil);
}
- (void)pause
{
AudioQueuePause(queue);
}
#pragma mark -
#pragma mark Callback
static void BufferCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef buffer)
{
// направляем для обработки назад в класс, чтобы обеспечить себе прямой доступ к объектным переменным
[(GBMusicTrack *)inUserData callbackForBuffer:buffer];
}
- (void)callbackForBuffer:(AudioQueueBufferRef)buffer
{
if ([self readPacketsIntoBuffer:buffer] == 0)
{
// достигнут конец файла: выполняем перемотку и заново заполняем буфер
packetIndex = 0;
[self readPacketsIntoBuffer:buffer];
// при отсутствии повтора воспроизведение устанавливается на паузу, чтобы при необходимости вновь запустить фоновую музыку
if (!repeat)
{
AudioQueuePause(queue);
// во время этого обратного вызова мы не в основном потоке, поэтому сообщение с уведомлением о том, что все сделано, ставим в очередь основного потока
// иначе оно будет обрабатываться в данном потоке, что осложнит процесс
[self performSelectorOnMainThread:@selector(postTrackFinishedPlayingNotification:) withObject:nil waitUntilDone:NO];
}
}
}
- (void)postTrackFinishedPlayingNotification:(id)object
{
// здесь мы в основном потоке, как и указано в обратном запросе, поэтому можно разместить уведомление об окончании трека,
// наблюдателю (ям) уведомлений не придется заниматься безопасностью потока и автоматически высвобождаемыми пулами
[[NSNotificationCenter defaultCenter] postNotificationName:GBMusicTrackFinishedPlayingNotification object:self];
}
- (UInt32)readPacketsIntoBuffer:(AudioQueueBufferRef)buffer
{
UInt32 numBytes, numPackets;
// считываем в буфер пакеты из файла
numPackets = numPacketsToRead;
AudioFileReadPackets(audioFile, NO, &numBytes, packetDescs, packetIndex, &numPackets, buffer->mAudioData);
if (numPackets > 0)
{
// - конец файла с момента считывания пакетов еще не достигнут, поэтому ставим считанный буфер в очередь аудиофайлов
// для воспроизведения следующим
// - (packetDescs ? numPackets : 0) означает, что при наличии описаний пакетов (использующихся исключительно для данных Variable
// BitRate (VBR)) нам придется отправить по одному для каждого пакета либо указать ноль
buffer->mAudioDataByteSize = numBytes;
AudioQueueEnqueueBuffer(queue, buffer, (packetDescs ? numPackets : 0), packetDescs);
// готовность к следующему считыванию данных из файла
packetIndex += numPackets;
}
return numPackets;
}
@end

Если некоторые строки кода непонятны, ничего страшного. Вот простая инструкция по работе с ним:

1
2
3
4
GBMusicTrack *backgroundMusic = [[GBMusicTrack alloc] initWithPath:[[NSBundle mainBundle] pathForResource:@"backgroundMusic" ofType:@"caf"]];
[backgroundMusic setRepeat:YES]; // или NO, если воспроизведение будет единичным
[backgroundMusic setGain:1.0]; // громкость
[backgroundMusic play];

Не забудьте, что в класс с указанными выше строками кода не только импортируется “GBMusicTrack.h“, но и добавляется “AudiToolbox.framework“. Как видим, работа с классом “GBMusicTrack” не представляет собой ничего сложного: метод “setRepeat:YES/NO” при необходимости задает циклическое воспроизведение, “setGain” регулирует громкость (кстати, ее не стоит устанавливать больше, чем на 1.0), “play” запускает воспроизведение.

Внимание: компилировать для версии 2.2 и выше!

Внимание: многие жалуются, что на симуляторе iPhone этот урок не работает. Не волнуйтесь, на реальном устройстве все функционирует нормально.

Воспроизведение следующего трека с “GBMusicTrack:”

Как уже говорилось выше, данный класс не допускает параллельного воспроизведения нескольких треков. Чтобы перейти к следующему, вызовите метод “close” и еще раз “initialize“.

1
2
[backgroundMusic close];
[backgroundMusic initWithPath:[[NSBundle mainBundle] pathForResource:@"nextTrack" ofType:@"caf"]];

Желающие могут загрузить мой проект — контроллер музыки, написанный с использованием “GBMusicTrack“. В нем есть возможность постепенно делать музыку тише/громче, устанавливать ее на паузу, настраивать громкость.

musiccontrollerscreenshot-200x300

Исходный код скачать можно [здесь]

Текст оригинальной статьи на английском языке [здесь]

Уважаемые читатели, данный материал был переведен и подготовлен к публикации проектом LookApp.ru, при публикации на другом сайте ссылка на LookApp.ru обязательна.

1 звезда2 звезд3 звезд4 звезд5 звезд (1 голосов, средний: 4.00 из 5)
Загрузка ... Загрузка ...


3 Responses to “Уроки iPhone SDK: Воспроизведение фоновой музыки”

  1. 1. ARtt Says:

    огромное спасибо !!! буду разбератся !!

    хм вот что ГОВОРИТ !!

    “_AudioQueuePause”, referenced from:
    -[GBMusicTrack pause] in GBMusicTrack.o
    -[GBMusicTrack callbackForBuffer:] in GBMusicTrack.o
    “_AudioFileGetPropertyInfo”, referenced from:
    -[GBMusicTrack initWithPath:] in GBMusicTrack.o
    “_AudioFileOpenURL”, referenced from:
    -[GBMusicTrack initWithPath:] in GBMusicTrack.o
    “_AudioFileGetProperty”, referenced from:
    -[GBMusicTrack initWithPath:] in GBMusicTrack.o
    -[GBMusicTrack initWithPath:] in GBMusicTrack.o
    -[GBMusicTrack initWithPath:] in GBMusicTrack.o
    “_AudioQueueEnqueueBuffer”, referenced from:
    -[GBMusicTrack readPacketsIntoBuffer:] in GBMusicTrack.o
    “_AudioQueueSetParameter”, referenced from:
    -[GBMusicTrack setGain:] in GBMusicTrack.o
    “_AudioQueueNewOutput”, referenced from:
    -[GBMusicTrack initWithPath:] in GBMusicTrack.o
    “_AudioQueueSetProperty”, referenced from:
    -[GBMusicTrack initWithPath:] in GBMusicTrack.o
    “_AudioFileClose”, referenced from:
    -[GBMusicTrack close] in GBMusicTrack.o
    “_AudioQueueDispose”, referenced from:
    -[GBMusicTrack close] in GBMusicTrack.o
    “_AudioFileReadPackets”, referenced from:
    -[GBMusicTrack readPacketsIntoBuffer:] in GBMusicTrack.o
    “_AudioQueueStart”, referenced from:
    -[GBMusicTrack play] in GBMusicTrack.o
    “_AudioQueueAllocateBuffer”, referenced from:
    -[GBMusicTrack initWithPath:] in GBMusicTrack.o
    “_AudioQueueStop”, referenced from:
    -[GBMusicTrack close] in GBMusicTrack.o
    ld: symbol(s) not found
    collect2: ld returned 1 exit status
    Build failed (14 errors)

  2. 2. Hayate Says:

    ARtt, Вы забыли добавить к проекту AudioToolbox.framework

  3. 3. ARtt Says:

    нет я во-первых открывал Ваш проект !!!! и токо что опять добавил AudioToolbox.framework !!! скажите мне путь где он находтися, а то может я не тот добавил !! спс

Оставьте комментарий