Feb 23
iPhone Audio Programmierung mit Audio Toolbox
In einem älteren Artikel habe ich darüber geschrieben, wie man mit Celestial Audio auf dem iPhone wiedergeben kann. Mit dieser Methode kann man auf sehr einfache Weise fertige Audio Dateien abspielen. Möchte man jedoch selbst erzeugten Sound oder mehrere Audio-Streams gleichzeitig abspielen, wie es z.B. für Spiele notwendig ist, dann kommt man mit Celestial nicht weiter. Für diesen Fall hat Apple das AudioToolbox Framework erschaffen. Dieses Framework hat eine eigene Audio Queue, welche erschaffen wurde um reine PCM Audio Daten zu verarbeiten. Um die Funktionsweise einer Audio Queue verstehen zu können, sollte man sich im Vorfeld schon mit digitaler Audioverarbeitung auseinander gesetzt haben. Man findet zu dem Thema im Internet genug Infos, daher möchte ich nicht näher darauf eingehen. Zu empfehlen wäre noch das Buch von Bruce und Marty Fries mit dem Titel Digital Audio Essentials.
Eine Audio Queue kann man sich am besten wie eine Menschenschlange vorstellen, die Wasser von Ort A nach Ort B mit Eimern befördern möchte, zB. aus einem Fluss um einen Brand zu löschen. Ein Mensch steht am Fluss und füllt die Eimer, welche dann Stück für Stück durch die Schlange gereicht werden, bis sie schließlich am Feuer angekommen sind. Dort werden die Eimer dann in die Flammen geleert und wandern wieder zurück zum Fluss um erneut gefüllt zu werden. Das Wasser im Eimer wäre in unserem Fall ein Teil des Audio Streams, die Eimer repräsentieren die Audio Buffer und das Feuer wäre die Audio-Hardware, welche den Stream abspielt. Die Schlange selbst ist die Audio Queue. Wie groß die Eimer sind, wie viele Eimer in einer Schlange benötigt werden und welche Art der Eimer benutzt werden, das bestimmen wir als Programmierer. Ebenso müssen wir eine Funktion schreiben, welche die Eimer mit den richtigen Daten füllt.
Im folgenden Beispiel werden wir eine Sinus Welle erzeugen und mit Hilfe der Audio Queue wiedergeben.
Bevor wir mit der Erzeugung der Sinuswelle beginnen, müssen wir uns zuerst überlegen mit welcher Samplingrate und Auflösung die Welle wiedergegeben werden soll. Ein seit Jahren bewährter Standard ist 44.1 kHz Samplingrate mit 16 Bit Auflösung und das Ganze auf 2 Kanälen (Stereo). Das bedeutet dass wir 44100 Werte benötigen um eine Sekunde Audio zu beschreiben. Ein Wert hat die Genauigkeit von 16 Bit; es werden also 2 Bytes pro Wert und Kanal benötigt. Das entspricht einem Wert von -32767 bis +32767. Für eine Sekunde Stereo Audio benötigt man 44100 * 2 * 2 = 176400 Bytes Speicherplatz (= 172,26 kByte). Unsere Sinuswelle soll eine Frequenz von 440 Hz haben, was bedeutet sie wiederholt sich 440 mal pro Sekunde. Um eine solche Welle zu erzeugen bedienen wir uns folgender mathematischer Formel:
y = sin(x)

Diese Funktion liefert allerdings als Ergebnis Werte von -1 bis +1 und sie durchläuft den Nullpunkt das erste Mal bei π (3.14..), das nächste Mal bei 2π, dann 3π und so weiter. Eine komplette Schwingung wird daher bei 2π erreicht. 2π entspricht also einer Schwingung. Um das Ganze in Herz (Schwingungen pro Sekunde) auszudrücken, müssen wir daher den x Wert noch mit 2π multiplizieren:
y = sin(x * 2 * π)
Da wir aber nicht jede Sekunde unsere Welle abtasten, sondern 44100 mal in der Sekunde, muss das Produkt noch durch 44100 geteilt werden:
y = sin((x * 2 * π) / 44100)
Damit die Welle mit 440 Hz (Kammerton A) schwingt, lassen wir den Faktor 440 mit einfließen:
y = sin((x * 2 * π * 440) / 44100)
Das Ergebnis bewegt sich allerdings immer noch zwischen -1 und +1. Wenn wir diese Werte der Audio Queue übergeben, werden wir nichts hören, da die Audio Queue Wete von -32767 bis + 32767 (16 Bit) erwartet. Daher müssen wir das Ergebnis noch mit 32767 multiplizieren:
y = sin((x * 2 * π * 440) / 44100) * 32767;
In C sieht das dann so aus:
#include <stdio.h>
#include <math.h>
int main (int argc, const char * argv[]) {
short sampleValue; // -32767 bis +32767
float floatVal;
int t;
for (t=0; t<=44100; t++) {
floatVal = sin(((float)t * 2.0 * M_PI * 440.0) / 44100.0);
sampleValue = (int)(floatVal * 32767.0);
printf("Sample für %d = %d\n", t, sampleValue);
}
return 0;
}
Nachdem wir nun wissen wie man eine Sinuswelle erzeugt, wenden wir uns wieder der Audio Queue zu, da wir unsere erzeugte Welle ja auch abspielen möchten. Das Audio Toolbox Framework wurde in reinem C geschrieben. Aus diesem Grunde werde ich der Verständlichkeit halber auch die Beispiele in diesem Artikel in C aufführen. Eine Mischung mit Objective C würde das Ganze nur verkomplizieren. Um die Sinuswelle abzuspielen sind folgende Schritte notwendig:
- Erzeugen einer AQCallbackStruct Struktur
- Initialisieren der Struktur mit Eigenschaften, wie Samplingrate, Format, etc …
- Erzeugen der Audio Queue und Übergabe der Struktur.
- Erzeugen der Sound Buffer (Eimer), welche der Queue hinzugefügt werden.
- Bereitstellen einer Funktion, welche die leeren Sound Buffer füllt (Mann am Fluss der die Eimer füllt)
1. Struktur erzeugen
Damit alle Daten zusammen bleiben kapseln wir diese in einer Struktur die wie folgt aussieht:
typedef struct AQCallbackStruct {
AudioQueueRef queue;
UInt32 frameCount;
AudioQueueBufferRef mBuffers[NUM_BUFFERS];
AudioStreamBasicDescription mDataFormat;
} AQCallbackStruct;
Die einzelnen Variablen der Struktur haben folgende Bedeutung:
- AudioQueueRef queue
Ein Zeiger auf das Audio Queue Objekt, welches vom Programm erzeugt wurde - UInt32 frameCount
Anzahl Samples pro Buffer (Eimer) - AudioQueueBufferRef mBuffers[NUM_BUFFERS]
Array um alle benutzen Sound Buffer zu speichern. Die Größe des Arrays (Anzahl der Audio Buffer) muss beim iPhone mindestens 3 sein. - AudioStreamBasicDescription mDataFormat
Informationen über das Audioformat.
2. Struktur initialisieren
AQCallbackStruct in; in.mDataFormat.mSampleRate = 44100.0; in.mDataFormat.mFormatID = kAudioFormatLinearPCM; in.mDataFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; in.mDataFormat.mBytesPerPacket = 4; in.mDataFormat.mFramesPerPacket = 1; in.mDataFormat.mBytesPerFrame = 4; in.mDataFormat.mChannelsPerFrame = 2; in.mDataFormat.mBitsPerChannel = 16; in.frameCount = 1024;
3. Erzeugen der Queue
UInt32 err = AudioQueueNewOutput(&in.mDataFormat, AQBufferCallback, &in, CFRunLoopGetCurrent(), kCFRunLoopCommonModes, 0, &in.queue); if(err) fprintf(stderr, "AudioQueueNewOutput err %d\n", err);
4. Erzeugen der Buffer und Starten der Queue
// Ermittlen der Buffergröße
UInt32 bufferBytes = in.frameCount * in.mDataFormat.mBytesPerFrame;
// alloc 3 buffers.
for (i=0; i< NUM_BUFFERS; i++) {
err = AudioQueueAllocateBuffer(in.queue, bufferBytes, &in.mBuffers[i]);
if(err) fprintf(stderr, "AudioQueueAllocateBuffer [%d] err %d\n",i, err);
// Erster initialer Aufruf der Callback Funktion
AQBufferCallback (&in, in.queue, in.mBuffers[i]);
}
// Einstellung der Lautstärke
err = AudioQueueSetParameter(in.queue, kAudioQueueParam_Volume, 1.0);
if(err) fprintf(stderr, "AudioQueueSetParameter err %d\n", err);
// Starten der Queue
err = AudioQueueStart(in.queue, NULL);
if(err) fprintf(stderr, "AudioQueueStart err %d\n", err);
// Nichts tun und das im Loop ;-)
while(1) CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.25, false);
// Speicher der Queue wieder freigeben.
err = AudioQueueDispose(in.queue, true);
5. Erzeugen der Callback Funktion zum Füllen der Buffer
static void AQBufferCallback(void *in, AudioQueueRef inQ, AudioQueueBufferRef outQB)
{
int i;
UInt32 err;
// Zuweisung der Struktur und des Zeigers für den Output uffer
AQCallbackStruct *inData = (AQCallbackStruct *)in;
short *coreAudioBuffer = (short*) outQB->mAudioData;
// so lange frameCount > 0 ist ist, haben wir auch Daten zum verarbeiten
if (inData->frameCount > 0) {
// Zuweisen der Größe des Buffers
outQB->mAudioDataByteSize = 4*inData->frameCount; // zwei shorts pro sample * 2 Kanäle
// Für jedes Sample pro Kanal
for(i=0; i<inData->frameCount*2; i=i+2) {
// Berechnen der Sinuskurve
floatVal = sin(((float)sampleNr * 2.0 * M_PI * 440.0) / 44100.0); // Kammerton A
sampleValue = (int)(floatVal * 32767.0);
coreAudioBuffer[i] = sampleValue; // Linker Kanal
coreAudioBuffer[i+1] = sampleValue; // Rechter Kanal
sampleNr++;
}
// Buffer entleeren (Wasser des Eimers ins Feuer kippen)
AudioQueueEnqueueBuffer(inQ, outQB, 0, NULL);
} else {
err = AudioQueueStop(inData->queue, false);
}
}
Das komplette Beispiel sieht dann so aus:
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/stat.h>
#include <AudioToolbox/AudioQueue.h>
#define NUM_BUFFERS 3
typedef struct AQCallbackStruct {
AudioQueueRef queue;
UInt32 frameCount;
AudioQueueBufferRef mBuffers[NUM_BUFFERS];
AudioStreamBasicDescription mDataFormat;
} AQCallbackStruct;
int sampleNr = 0; // Nummer des Samples, welches gerade im Buffer verarbeitet wird
static void AQBufferCallback(void *in, AudioQueueRef inQ, AudioQueueBufferRef outQB) {
int i;
UInt32 err;
float floatVal;
short sampleValue; // -32767 bis +32767
// Zuweisung der Struktur und des Zeigers für den Output uffer
AQCallbackStruct *inData = (AQCallbackStruct *)in;
short *coreAudioBuffer = (short*) outQB->mAudioData;
// so lange frameCount > 0 ist ist, haben wir auch Daten zum verarbeiten
if (inData->frameCount > 0) {
// Zuweisen der Größe des Buffers
outQB->mAudioDataByteSize = 4*inData->frameCount; // zwei shorts pro sample * 2 Kanäle
// Für jedes Sample pro Kanal
for(i=0; i<inData->frameCount*2; i=i+2) {
// Berechnen der Sinuskurve
floatVal = sin(((float)sampleNr * 2.0 * M_PI * 440.0) / 44100.0); // Kammerton A
sampleValue = (int)(floatVal * 32767.0);
coreAudioBuffer[i] = sampleValue; // Linker Kanal
coreAudioBuffer[i+1] = sampleValue; // Rechter Kanal
sampleNr++;
}
// Buffer entleeren (Wasser des Eimers ins Feuer kippen)
AudioQueueEnqueueBuffer(inQ, outQB, 0, NULL);
} else {
err = AudioQueueStop(inData->queue, false);
}
}
int main (int argc, const char * argv[]) {
int i;
AQCallbackStruct in;
in.mDataFormat.mSampleRate = 44100.0;
in.mDataFormat.mFormatID = kAudioFormatLinearPCM;
in.mDataFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
in.mDataFormat.mBytesPerPacket = 4;
in.mDataFormat.mFramesPerPacket = 1;
in.mDataFormat.mBytesPerFrame = 4;
in.mDataFormat.mChannelsPerFrame = 2;
in.mDataFormat.mBitsPerChannel = 16;
in.frameCount = 1024;
UInt32 err = AudioQueueNewOutput(&in.mDataFormat, AQBufferCallback, &in, CFRunLoopGetCurrent(), kCFRunLoopCommonModes, 0, &in.queue);
if(err) fprintf(stderr, "AudioQueueNewOutput err %d\n", err);
// Ermittlen der Buffergröße
UInt32 bufferBytes = in.frameCount * in.mDataFormat.mBytesPerFrame;
// alloc 3 buffers.
for (i=0; i< NUM_BUFFERS; i++) {
err = AudioQueueAllocateBuffer(in.queue, bufferBytes, &in.mBuffers[i]);
if(err) fprintf(stderr, "AudioQueueAllocateBuffer [%d] err %d\n",i, err);
// Erster initialer Aufruf der Callback Funktion
AQBufferCallback (&in, in.queue, in.mBuffers[i]);
}
// Einstellung der Lautstärke
err = AudioQueueSetParameter(in.queue, kAudioQueueParam_Volume, 1.0);
if(err) fprintf(stderr, "AudioQueueSetParameter err %d\n", err);
// Starten der Queue
err = AudioQueueStart(in.queue, NULL);
if(err) fprintf(stderr, "AudioQueueStart err %d\n", err);
// Nichts tun und das im Loop ;-)
while(1) CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.25, false);
// Speicher der Queue wieder freigeben.
err = AudioQueueDispose(in.queue, true);
return 0;
}
Das Beispiel sichert man am Besten in einer Datei Namens sinus.c und kompiliert die Datei dann wie folgt:
arm-apple-darwin-gcc -o sinus sinus.c -framework AudioToolbox -framework CoreAudio -framework CoreFoundation
Das compilierte Programm kopiert man dann aufs iPhone und startet es per ssh. Da in Mac OS X 10.5 auch das Audio Toolbox Framework existiert kann man das auch auf dem normalen Desktop mit dem normalen gcc kompilieren:
gcc -o sinus sinus.c -framework AudioToolbox -framework CoreAudio -framework CoreFoundation
Auf diese Art und Weise kann man auch mehrere Sounds gleichzeitig abspielen. Man muss dazu nur die einzelnen Samples der abzuspielenden Sounds zusammen zählen und durch die Anzahl der Sounds Teilen. Wenn man die Callback Funktion wie folgt ändert, wird noch eine 2. Sinuswelle erzeugt:
.... // Berechnen der Sinuskurve floatVal = sin(((float)sampleNr * 2.0 * M_PI * 440.0) / 44100.0); // Kammerton A floatVal2 = sin(((float)sampleNr * 2.0 * M_PI * 587.330) / 44100.0) // D sampleValue = (int)(((floatVal+floatVal2)/2.0) * 32767.0); ....








25.02.2008 um 13:04 Uhr
Hi cooles Bsp.
Wie würde ich ein PCM File, dass lokal vorhanden ist abspielen?
25.02.2008 um 13:16 Uhr
Du legst Dir einen Speicherberich an, der so groß ist wie das PCM File (zB mit malloc() ) In diesen Speicherberich lädst Du dann Dein PCM File. Dann erweiterst Du die AQCallbackStruct um einen Wert mit dem Typ void* (Zeiger auf beliebigen Speicher) Anstelle den Sample-Wert zu berechnen setzt Du die Variable sampleNr einfach ein 2 Bytes im Speicherbereich weiter und liest den Wert als short aus. Diesen Wert weist Du dann coreAudioBuffer zu. Wenn sampleNr die Buffergröße/2 erreicht hat, dann führst Du AudioQueueDispose aus.
25.02.2008 um 13:50 Uhr
Du meinst soetwas in der Art?
in Funktion main:
…
FILE *pFile;
long lSize;
short *buffer;
pFile=fopen(”/var/root/Files/sine440.pcm”,”r”);
if(pFile==NULL)
exit (1);
fseek (pFile,0,SEEK_END);
lSize=ftell(pFile);
buffer=(short*)malloc(lSize);
if(buffer==NULL)
exit (2);
fread(buffer,1,lSize,pFile);
in.pDatatoPlay=buffer;
und in AQBufferCallback:
…
for(i=0; iframeCount*2; i=i+2) {
// Berechnen der Sinuskurve
//floatVal = sin(((float)sampleNr * 2.0 * M_PI * 440.0) / 44100.0); // Kammerton A
sampleValue = (short*)inData->pDatatoPlay;
coreAudioBuffer[i] = sampleValue; // Linker Kanal
coreAudioBuffer[i+1] = sampleValue; // Rechter Kanal
sampleNr=sampleNr+2;
}
und wo soll die abfrage nach der buffergröße/2 hin?
25.02.2008 um 15:28 Uhr
in AQBufferCallback:
if (sampleNr >= bufferLen/2) {
AudioQueueDispose(inData->queue, true);
}
25.02.2008 um 16:03 Uhr
ok thx, funktioniert leider nur trotzdem nicht.
Ich höre nur ein periodisches Knacksen! ;/
was muss ich in der AQBufferCallback alles ändern?
Was stimmt an dem Code nicht?
AQCallbackStruct *inData=(AQCallbackStruct *)in;
short *coreAudioBuffer=(short*) outQB->mAudioData;
if(sampleNr<=(lSize/2)){//lSize = Globale Var. entspricht PCM-File-Größe
for(i=0;iframCount*2;i=i+2){
coreAudioBuffer[i]=inData->DatatoPlay+sampleNr;
coreAudioBuffer[i+1]=inData->DatatoPlay+sampleNr+1;
sampleNr=sampleNr+2; //2 Byte sprung???
}
AudioQueueEnqueueBuffer(inQ,OutQB,0,NULL);
}else{
err=AudioQueueDispose(inData->queue, true);
}
}
//DatatoPlay= mit malloc allozierter speicher der das PCM sample beinhaltet, in AQCallbackStruct ein short Member
Muss ich in main noch was ändern, oder woram liegt es, dass ich nur hin und wieder ein knacksen höre?
Danke für deine Hilfe
25.02.2008 um 16:15 Uhr
habe eine zeile vergessen:
zw. if und for fehlt folgende Zeile:
outQB->mAudioDataByteSize=4*inData->frameCount;
25.02.2008 um 18:20 Uhr
Da sind mehrere Fehler:
1. for(i=0;iframCount*2;i=i+2){ => Wahrscheinlich Tippfehler
2. inData->DatatoPlay+sampleNr; => Hier greifst Du auf auf eine Adresse zu und nicht den Inhalt. *(inData->DatatoPlay+sampleNr) wäre besser ;-)
3. sampleNr=sampleNr+2 => Nur wenn das PCM ein Stereo Signal ist ansonsten nur + 1
Hab mal ohne zu testen (hab gerade nicht die Möglichkeit dazu) die Callback Funktion so geschrieben wie es funktionieren sollte:
void AQBufferCallback(void *in, AudioQueueRef inQ, AudioQueueBufferRef outQB) { int i; UInt32 err; // Zuweisung der Struktur und des Zeigers für den Output buffer AQCallbackStruct *inData = (AQCallbackStruct *)in; short *coreAudioBuffer = (short*) outQB->mAudioData; short sample; int sampleLen = (lSize/2); if (sampleNr >= sampleLen) { AudioQueueDispose(inData->queue, true); return; } if (inData->frameCount > 0) { outQB->mAudioDataByteSize = 4 * inData->frameCount; for(i=0; i<inData->frameCount*2; i+=2) { if (sampleNr > sampleLen || sampleNr < 0) { sample = 0; } else { sample = inData->DatatoPlay[sampleNr]; } coreAudioBuffer[i] = sample; coreAudioBuffer[i+1] = sample; sampleNr++; } AudioQueueEnqueueBuffer(inQ, outQB, 0, NULL); } }Um das ganze rund zu machen würde ich sampleNr und lSize auch als Variable der Struktur zufügen.
25.02.2008 um 18:25 Uhr
Wichtig ist auch dass das raw pcm File im Windows Format (little-endian) und Mono abgespeichert wird! Für Stereo müsste man obigen Code modifizieren.
27.02.2008 um 09:25 Uhr
Hi Holeg!
Thx für deine Hilfe, hab deinen Code heute getestet, aber er funktioniert leider nicht. Meine PCM Demo ist eine Stereodatei, aber das sollte ja an der Grundfunktionalität deines Codes nichts verändern, oder? auf jeden Fall passiert folgendes wenn ich das Programm starte: es läuft ca. 20 sek. ohne einen ton abzuspielen und schließt sich wieder.
Meine nächste Frage ist diese: In der main() Funktion, muss ich sonst nichts ändern? nachdem ich das File eingelesen habe, geht es mit deinem Programmcode weiter? Ich lese zwischen der Definition des Fileformats und dem UInt32 err = AudioQueueNewOutput(…); -Aufruf, das PCM file ein.
Brauche ich dann eigentlich noch 3 Buffer? Wenn ich doch das gesamte File in einen buffer einlese ist doch folgende for Schleife unnötig?!
for (i=0; i< NUM_BUFFERS; i++) {
err = AudioQueueAllocateBuffer(in.queue, bufferBytes, &in.mBuffers[i]);
if(err) fprintf(stderr, “AudioQueueAllocateBuffer [%d] err %ld\n”,i, err);
// Erster initialer Aufruf der Callback Funktion
AQBufferCallback (&in, in.queue, in.mBuffers[i]);
}
Könnte ich die for Schleife auch weglassen und nur die Aufrufe:
err = AudioQueueAllocateBuffer(in.queue, bufferBytes, &in.mBuffers[i]);
if(err) fprintf(stderr, “AudioQueueAllocateBuffer [%d] err %ld\n”,i, err);
// Erster initialer Aufruf der Callback Funktion
AQBufferCallback (&in, in.queue, in.mBuffers[i]);
machen? klar würde ich auch das #define von NUM_BUFFERS auf “1″ setzen…
Leider denke ich, dass dies mir jetzt nicht weiterhilft, dass das File ausgegeben wird…:(
LG und danke nochmal
27.02.2008 um 10:19 Uhr
Die 3 Buffer brauchst Du unbedingt! Das ist die minimale Anzahl von Buffern die das iPhone benötigt. Unter Leopard auf dem Mac reicht ein Buffer. Wenn ich ein wenig mehr Zeit habe, werde ich Dir ein Beispiel posten.
27.02.2008 um 13:48 Uhr
alles klar, ich habe jetzt folgendes problem: ich habe auf meinem MAC ein Beispiel zum laufen gebracht, dass mir mein PCm File abspielt. Lasse ich jetzt den fast identen Code am IPod laufen, so bekommen ich in der Console folgenden Fehler:
udioQueueAllocateBuffer [1] err -50
AQ_Play(490) malloc: *** error for object 0×103430: double free
AQ_Play(490) malloc: *** set a breakpoint in malloc_error_break to debug
AQ_Play(490) malloc: *** error for object 0×1033e0: double free
AQ_Play(490) malloc: *** set a breakpoint in malloc_error_break to debug
AudioQueueAllocateBuffer [2] err -50
Er hat offensichtlich Probleme mit dem malloc.
Ich habe auch schon das Beispiel mit einem statischen Speicher ausprobiert. Wieder: am MAC läuft es, am IPod nicht!
;/
Hier bekomme ich dann in der Console den folgenden Fehler: zsh: segmentation fault /App…
Irgendwelche Vorschläge?
28.02.2008 um 10:53 Uhr
Ich habe die Fehler beseitigen können, aber nun spielt er nachdem er alle 3 Buffer befüllt hat, nur ca. 500ms das PCM file ab und dannach passiert nichts mehr…
Also ich höre nachher nichts mehr.
Liegt das an der while 1 schleife oder besser gesagt mit diesem CFRunLoopInMode (kCFRunLoopDefalutMode,…)?
Muss ich nicht hier irgendwie vermitteln, dass er die Buffer weiterbefüllen muss?
28.02.2008 um 11:26 Uhr
Also die while Schleife ist dazu da, dass das Programm nicht gleich beendet wird. Bei einem UIKit Programm auf dem iPhone brauchst Du das natürlich nicht, da UIKit Programme ihren eigenen Run-Loop haben. Wenn die Audio Queue durch AudioQueueStart() gestartet wurde, dann läuft sie in einem eigenen Thread, welcher die Callbackfunktion automatisch aufruft, sobald ein Buffer geleert wurde.
28.02.2008 um 11:39 Uhr
Jetzt stürtzt das Programm nur noch ab, nachdem ich die While 1 Schleife rausgenommen habe und das UIKit hinzugefügt habe…
<<Bei einem UIKit Programm auf dem iPhone brauchst Du das natürlich nicht, da UIKit Programme ihren eigenen Run-Loop haben….
Heißt das, dass ich mein Programm komplett umschreiben (auf OBjC) muss und der normale C Code, der ja fast ident mit deinem ist, nicht auf dem IPod funktionieren wird?
28.02.2008 um 12:53 Uhr
Der C-Code funktioniert super auf dem iPhone. Siehe mein neues Programm KARAJAN. Da habe ich einen Objective-C Wrapper drumrum gebaut. Inspiriert wurde ich von Pocket-Guitar.
Hast Du mal das UIKit Programm von der Konsole gestartet und Dir ein paar Debug Ausgaben anzeigen lassen, damit Du sehen kannst in welcher Zeile es abstürzt? Hast Du auch das UIKit Framework in Deinem Makefile hinzugefügt?
28.02.2008 um 13:06 Uhr
Hast Du mal das UIKit Programm von der Konsole gestartet und Dir ein paar Debug Ausgaben anzeigen lassen, damit Du sehen kannst in welcher Zeile es abstürzt? Hast Du auch das UIKit Framework in Deinem Makefile hinzugefügt?
Ja habe ich. Nachddem er die Queue gestartet hat, schließt er sie mit AudioQueueDispose wieder. –> dh dieses Threadhandling funktioniert nicht, da er sofort nach dem starten im Code weitermacht und es beendet. Auch wenn ich nur die while 1 schleife lasse, ohne dem CFRunLoopRunInMode(), passiert nichts, er bleibt halt in der Endlosschleife…
mein Problem an der Sache ist, dass ich nicht verstehe, wie ich den Code ändern soll, dass er ein Threadhandling macht. Ich verstehe auch nicht, warum dein oben geposteter Code, wo man sich selbst einen Sinus generiert funktioniert? Da ist doch nichts anders? oder?
28.02.2008 um 13:19 Uhr
lasse ich den code so wie du es in deinem BSP hast, so sehe ich auf der Konsole dass er schon die samples richtig auf den corAudioBuffer schreibt. Aber wie gesagt, ich höre nur einmal kurz etwas, nachdem er die ersten 3 Buffer befüllt hat, danach, rennt das PRG normal weiter und er macht auch das was er machen soll–> die buffer werde immer wieder aufs neue befüllt, bis zum ende des Files, aber hören u ich nichts mehr…
28.02.2008 um 13:30 Uhr
Das AudioQueueDispose brauchst Du bei einem UIKit Programm eigentlich nicht. Im Prinzip erzeugt man bei einem UIKit Programm gleich zu Anfang die AudioQueue und gibt in der Callback-Funktion lauter Nullen als Wert aus (so sieht man es zumindest bei den ganzen Apps die mit AudioQueue arbeiten, zB MobileBeat, PocketGuitar …). Wenn man dann Sound abspielen möchte, erst dann füllt man in der Callback-Funktion die Queue mit richtigen Werten. D.h. die Queue läuft die ganze Zeit während Deine App auch läuft. Wenn Du die App beendest werden eh alle Threads Deiner App gekillt, so dass AudioQueueDispose daher nicht gebraucht wird.
28.02.2008 um 13:42 Uhr
ok, das hab ich gecheckt, aber gibt es echt keine möglichkeit, dass ich vorerst ohne dem UIKit das ganze zum Laufen bringe? Einfach nur App starten und er soll stur das File abspielen.
Das Speicher freigeben passiert sowieso “per Hand” im Code.
Ich meine es wird doch nicht vom UIKit abhängen, ob er nach den ersten 3 Buffern einmal kurz einen Ton abspielt und anschließend nur noch die Werte an den coreAudioBuffer ohne Effekt schreibt…
28.02.2008 um 13:55 Uhr
Natürlich gibt es eine Möglichkeit. Wie Du schon sagst ist bei dem Sinusbeispiel nichts anders, ausser dass die Werte nicht berechnet werden, sondern von einem Speicherbereich kommen. Schätze mal da liegt irgendwo der Fehler. Am Besten Du postest hier mal den kompletten Code, dann schau ich mal drüber. Damit der Code hier aber korrekt angezeigt wird, musst Du HTML Sonderzeichen wie <, >, & und " encodieren und am besten ein <code> Tag aussenrum schreiben! Oder Du zippst das Ganze, legst es auf irgend einen Server und postest hier die URL.
28.02.2008 um 14:01 Uhr
Hallo! Also das File ist dieser Link:
http://www.zippyshare.com/v/23735608/file.html
und das Projekt ist dieses hier:
http://www.zippyshare.com/v/8113809/file.html
Vielen Dank fürs drüberschaun…
LG
28.02.2008 um 14:05 Uhr
Nochetwas:
Das PCM File ist ein Stereo File mit den angegebenen Aprametern wie im Code!
28.02.2008 um 14:59 Uhr
Du wirst lachen, wenn ich in der Callback Funktion folgende fprints rausnehme geht alles:
printf("sample_left: %d\n",sample_l);
printf("sample_right: %d\n",sample_r);
Das iPhone schafft es nämlich nicht 44100 fprints in einer Sekunde auszuführen ;-)
28.02.2008 um 15:18 Uhr
omg!
das gibt es ja nicht!
lol, da kann ich echt nur lachen…
^^
thx und respekt!
gg
ich hoffe, dass ich nun zügiger vorankomme!
:)
Schönes Wochenende, und Danke nochmal!
28.02.2008 um 15:38 Uhr
Ein kleiner Tipp noch: Probier mal das STK Toolkit. Für Audio Processing eine echte Hilfe. Den vom STK generierten Stream kannst Du direkt in die AudioQueue einspeissen.
Viel Spass beim experimentieren.
16.03.2008 um 19:44 Uhr
Ich habe noch ein paar Probleme. Vielleicht kann mir noch jemand helfen. Wenn ich die Anweisungen
// Nichts tun und das im Loop ;-)
while(1) CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.25, false);
// Speicher der Queue wieder freigeben.
err = AudioQueueDispose(in.queue, true);
herausnehme und sämtliche lokalen Variablen als Attribute einer Klasse definiere, wird nur ca. 500 Millisekunden Audio abgespielt. Noch zur Info. Ich habe eine ganz einfache iPhone-Applikation mit dem SDK erstellt und den Code eingetragen.
Danke!
13.08.2008 um 17:51 Uhr
wenn ich in oben angeführtem programm die frequenz während der laufzeit auch nur geringfügig ändere kommt es zu einem hässlichen knacksen.
hat jemand eine idee warum?
16.01.2009 um 02:05 Uhr
Hallo!
Ich bin, was Mac- und iPhone-Programmierung angeht ein Beginner und habe nun mit einigen Mühen eine iPhone-Applikation erstellt, die bei einem touchesBegan-Event auf einer UIView einen Ton abspielt. Das funktioniert auch dank deiner Anleitung. Nur, daß der Sinus-Ton klingt, als ob der Lautsprecher kaputt wäre (also eher wie ein Sägezahn), die Tonhöhe stimmt aber. Das ist aber das kleinere Problem.
Das größere Problem ist für mich:
Wie stelle ich es an, daß der Ton bei einem touchesEnded-Event wieder aufhört?
Nur, wenn ich aus das while (1) weglasse, wird ein touchesEnded-Event getriggert, vorausgesetzt, ich drücke so lange, wie ein Durchlauf von 1024 Frames benötigt. Ansonsten stürzt das Programm ab.
Wahrscheinlich kann touchesEnded erst aufgerufen werden, wenn touchesBegan vollständig abgearbeitet ist (was bei while(true) eine Weile dauern kann…), weil das im selben Thread abläuft. Aber wie kann ich das ändern und erreichen, daß das touchesEnded-Event die Callback-Prozedur abwürgt?
Für hilfreiche Hinweise wäre ich sehr dankbar!
05.08.2009 um 10:56 Uhr
Hey Holeg!
Super, genau, was ich gesucht habe - wusste gar nicht, dass Du schreibst!
Gruß
mü
19.05.2010 um 20:40 Uhr
Der Samplecode Zusammen mit FFMpeg kann mp3/ogg abspielen,ruckelt nur ein bissen am mac unter leopard.