{"id":67,"date":"2005-02-14T00:16:48","date_gmt":"2005-02-13T22:16:48","guid":{"rendered":"http:\/\/blogs.bl0rg.net\/netzstaub\/?p=67"},"modified":"2005-02-14T00:16:48","modified_gmt":"2005-02-13T22:16:48","slug":"audio-unter-macosx","status":"publish","type":"post","link":"https:\/\/blogs.bl0rg.net\/netzstaub\/2005\/02\/14\/audio-unter-macosx\/","title":{"rendered":"Audio unter MacOSX"},"content":{"rendered":"<p>\nIch habe mir jetzt mal vorgenommen, alle technische Probleme, denen ich begegne, hier mal zu dokumentieren. Dann brauch ich n&#228;chstes mal nicht stundenlang nachzudenken &#8220;da hatte ich doch mal&#8221;, sondern brauch nur Google kurz zu bem&#252;hen. Und wichtig ist da auch der Hintergedanke &#8220;das bringt mir totale Hackerleetness, Fame und ganz viele Comments&#8221; \ud83d\ude42\n<\/p>\n<p>\nAls erster Beitrag in der Reihe &#8220;Coden f&#252;r Ged&#228;chtnisbesch&#228;digte&#8221;: Audio unter MacOSX. Hintergrund ist folgender: ich will f&#252;r mein <a href=\"http:\/\/blogs.bl0rg.net\/netzstaub\/archives\/000429.html\">FEC Protokoll<\/a> jetzt gleich den MP3 Player integrieren, damit diese l&#228;stige Konvertiererei zwischen MP3 Frames und MP3 ADUs wegf&#228;llt, und damit ich auch die Pausen bei Packet Loss genauer kontrollieren kann. Dazu w&#252;rde ich gerne die <a href=\"http:\/\/www.underbit.com\/products\/mad\/\">Mad Library<\/a> verwenden, weil der Decoder in mpg123 (den man &#252;brigens in den meisten MP3 Playern unter Unix wiederfindet) wirklich <a href=\"http:\/\/packages.debian.org\/changelogs\/pool\/non-free\/m\/mpg123\/mpg123_0.59r-18\/changelog\">unter aller Sau ist<\/a>. Genau mit der Library hatte ich vor kurzem was programmiert, und zwar eine Wrapperlibrary um Lame, um mit einer &#8220;simplistischen API&#8221; MP3s abspielen zu k&#246;nnen. Die Library hatte ich urspr&#252;nglich unter Linux und FreeBSD geschrieben, wo man zum Ausgeben von Kl&#228;ngen meistens nur &#8220;\/dev\/dsp&#8221; &#246;ffnen muss. Im Moment arbeite ich wieder auf meinem Powerbook, und da geht das leider nicht so einfach.\n<\/p>\n<p>\nUnter MacOSX gibt es verschiedene M&#246;glichkeiten, Rumzuquaken: Carbon, oder CoreAudio, oder Quicktime. Quicktime lassen wir mal aussen aus, und Carbon auch, wir sind ja hier totale Darwin-Cocoa-h4x0r. Bei CoreAudio kann man sich die ID von einem AudioDevice schnappen, auf diesem Device dann verschiedene Properties ver&#228;ndern, und dann eine IO Procedure registrieren. Nachdem das AudioDevice dann &#8220;gestartet&#8221; wurde, ruft das CoreAudio Framework die IO Procedure regelm&#228;ssig in einem zus&#228;tzlichen Thread auf, und erwartet, dass diese ihr Samples zum abspielen gibt. Hintergrund von diesem ganzen Aufwand ist, dass somit Buffering und ruckelloses Abspielen von Audiodaten viel einfacher geht, als wenn man &#8220;PUSH&#8221;-m&#228;ssig die Audiodaten auf ein Device schreibt (und man braucht die Soundausgabe nicht selber zu schedulen). Wenn man nur schnell mal was abspielen will, ist sowas aber schon recht &#228;rgerlich. Erstens muss man die Audiosamples aus dem Decoder (hier aus der Mad-Library) irgendwozwischenspeichern, dann noch auf diese Daten zugreifen k&#246;nnen, und zwar Threadsafe (der Audiothread l&#228;uft parallel zum Decoden), und schliesslich will man auch noch ein bisschen zwischenpuffern.\n<\/p>\n<p>\nDa ich so faul bin, habe ich erstmal geguckt ob ich bei mpg123 und bei madplay, die MacOSX-Backends haben, nicht den code schnell mal rausrippen k&#246;nnte, aber so in der Form, wie man dort diesen Ansatz vorfindet, will man das eigentlich nicht. Deswegen, neu schreiben (nach dem ADAM-Prinzip: Alles Deppen Ausser Mir):\n<\/p>\n<ul>\n<li>als erstes brauchen wir eine Datenstruktur, um die dekodierten Samples abzuspeichern, wenn sie aus der Mad-Library kommen. Dazu verwenden wir einen <a href=\"http:\/\/www.atis.org\/tg2k\/_double-ended_queue.html\">Ringpuffer<\/a>.<\/li>\n<li>als zweites: diese Datenstruktur muss thread-safe sein, dazu verwenden wir Pthread <a href=\"http:\/\/www.unet.univie.ac.at\/aix\/aixprggd\/genprogc\/mutexes.htm\">Mutexe<\/a> und <a href=\"http:\/\/www.unet.univie.ac.at\/aix\/aixprggd\/genprogc\/condition_variables.htm\">Condition Variables<\/a>.<\/li>\n<li>als drittes: drumherum noch ein bisschen CoreAudio Kram.<\/li>\n<\/ul>\n<p>\nDie Ringpufferstruktur habe ich so implementiert, dass ich sie sp&#228;ter mal kurz Copy-pasten kann, ein bisschen ver&#228;ndere, und dann wiederverwende, weil sowas braucht man oft schnell mal. Der Code (besonders f&#252;r den Fall, bei dem die eingef&#252;gten Daten &#252;ber die Puffergrenze fallen) ist nicht so ganz prickelnd, aber nun gut. In der Datei <a href=\"http:\/\/bl0rg.net\/~manuel\/rb\/rb.h\">rb.h<\/a> wird die Ringpuffer Datenstruktur definiert, dazu noch ein paar Compile-Time-Konstante und Typen wie rb_elt_t (der Typ der Elemente in dem Ringpuffer), die Gr&#246;sse des Puffers, und ob der Puffer Threadsafe sein soll oder nicht. Die rb_t Struktur selber ist dann relativ straightforward:\n<\/p>\n<pre>typedef struct rb_s {\n  rb_elt_t buf[RB_SIZE];\n  unsigned long start;\n  unsigned long count;\n\n#ifdef USE_PTHREAD\n  pthread_mutex_t mutex;\n  pthread_cond_t  cond;\n#endif\n} rb_t;\n<\/pre>\n<p>\nDie API dementsprechend auch. rb_init resettet die Felder einer rb_t Struktur, rb_destroy macht dann das Umgekehrte. rb_enqueue f&#252;gt Daten in den Ringpuffer ein, und gibt 1 zur&#252;ck wenn es geklappt hat. rb_dequeue liest Daten aus dem Ringpuffer, und gibt 1 zur&#252;ck wenn es geklappt hat. Ein kleiner Trick jedoch, rb_enqueue wartet auf einen freien Pufferplatz, wenn Pthreads eingeschaltet ist (das ist der Spezialfall beim Schreiben von Audiodaten: die d&#252;rfen auf keinem Fall verloren gehen). Dazu ist dann auch gleich die Funktion rb_wait da. Die Implementierung dieser API k&#246;nnt ihr in <a href=\"http:\/\/bl0rg.net\/~manuel\/rb\/rb.c\">rb.c<\/a> finden.\n<\/p>\n<pre>void rb_init(rb_t *rb);\nint  rb_enqueue(rb_t *rb, rb_elt_t *data, unsigned long count);\nint  rb_dequeue(rb_t *rb, rb_elt_t *dest, unsigned long count);\nvoid rb_destroy(rb_t *rb);\n\n#ifdef USE_PTHREAD\nvoid rb_wait(rb_t *rb, unsigned long count);\n#endif\n<\/pre>\n<p>\nNun geht es weiter mit dem eigentlich Audiocode. Von der Mad-Library wird ein Callback von uns aufgerufen, wenn ein Frame fertig decodet worden ist. Ein MP3 Frame besteht aus 1152 Samples (ist im Standard so festgeschrieben). Mad verwendet intern nur Fixkommazahlen, die den Typ mad_fixed_t haben. Unser Callback kriegt als Argument eine Struktur mad_pcm, die genau 1152 dieser Fixkommazahlen beinh&#228;lt (bzw. 1152 * 2 wenn es 2 Kan&#228;le gibt). Diese Fixkommazahlen m&#252;ssen erstmal nach float konvertiert werden, weil CoreAudio float Zahlen haben will. Nachdem diese Zahlen konvertiert worden sind, werden sie in unserem extra an float-Zahlen und MP3 Frames angepassten Ringpuffer eingef&#252;gt. Das Ganze sieht dann so aus:<\/p>\n<pre>\n  int ret;\n  float buf[1152 * pcm-&gt;channels];\n  float *ptr = buf;\n  int i;\n  mad_fixed_t const *left_ch, *right_ch;\n  left_ch  = pcm-&gt;samples[0];\n  right_ch = pcm-&gt;samples[1];\n  for (i = 0; i length; i++) {\n    signed int sample;\n    *ptr++ = mad_scale(*left_ch++) \/ 32768.0;\n    *ptr++ = mad_scale(*right_ch++) \/ 32768.0;\n  }\n  ret = rb_enqueue(&#38;audio.rb, buf, 1152 * pcm-&gt;channels);\n<\/pre>\n<p>Und im Audio-Callback:<\/p>\n<pre>\n  int i;\n  for(i = 0; i mNumberBuffers; i++) {\n    AudioBuffer *buffer = outOutputData-&gt;mBuffers + i;\n\n    if ((buffer-&gt;mDataByteSize \/ sizeof(float)) != (1152 * 2)) {\n      memset(buffer-&gt;mData, 0, 1152 * 2 * sizeof(float));\n      continue;\n    }\n\n    int ret;\n    ret = rb_dequeue(&#38;audio.rb, buffer-&gt;mData, 1152 * 2);\n    if (ret == 0)\n      memset(buffer-&gt;mData, 0, 1152 * 2 * sizeof(float));\n  }\n\n<\/pre>\n<p>Den kompletten Code k&#246;nnt ihr <a href=\"http:\/\/bl0rg.net\/~manuel\/mp3-decode\/\">hier<\/a> nachchecken, aber das ist im Moment so eine Baustelle :).<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Ich habe mir jetzt mal vorgenommen, alle technische Probleme, denen ich begegne, hier mal zu dokumentieren. Dann brauch ich n&#228;chstes mal nicht stundenlang nachzudenken &#8220;da hatte ich doch mal&#8221;, sondern brauch nur Google kurz zu bem&#252;hen. Und wichtig ist da &hellip; <a href=\"https:\/\/blogs.bl0rg.net\/netzstaub\/2005\/02\/14\/audio-unter-macosx\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[10],"tags":[],"class_list":["post-67","post","type-post","status-publish","format-standard","hentry","category-programmstaub"],"_links":{"self":[{"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/posts\/67","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/comments?post=67"}],"version-history":[{"count":0,"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/posts\/67\/revisions"}],"wp:attachment":[{"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/media?parent=67"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/categories?post=67"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.bl0rg.net\/netzstaub\/wp-json\/wp\/v2\/tags?post=67"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}