Sådan bygger du en lydstreamingsserver i Go

Se på den lille musikalske Gopher!

Lyd i browseren er ikke altid velkommen med åbne arme. Med de fleste videoer, der deles på sociale medier, nu forudse folk, der ser dem med lyden slukket, hjemmesider med baggrundsmusik, der automatisk afspilles (RIP Geocities) længe glemt, og knapper på nettet, der får en hørbar 'klik' -lyd til en ting, der aldrig startede , Internettet er temmelig stille.

Streaming af lyd, hverken efter behov eller live, har dog aldrig været mere populær; tjenester som Spotify, Twitch og videoopkaldssoftware som Skype eller Discord transformerer alle lydene, som vi mennesker laver til 1'er og 0'er, og sender dem til lyttere over hele verden via internettet. Overraskende nok er der ikke en hel masse information om, hvordan man gør dette online, i det mindste ikke rettet mod begyndere.

Se og se: "Sådan bygger du en lydstreamingsserver", der sigter mod at få folk som mig, der har noget kendskab til lyd og / eller musik og en grundlæggende forståelse af, hvordan internettet fungerer begejstrede for streaming og digital lyd generelt.

Her vil vi fokusere på at opbygge en meget enkel server, der får lyd fra det ene sted til det andet, via magien fra http. Denne server får lydinput fra enhver tilgængelig lydindgang på serveren, ligesom din Macbooks mikrofon, hvis du kører serveren på en Macbook, konverterer den til binær og sender et chunked svar til klienten. Chunking af svaret i http er en måde at sende delvise data på eller "chunks" af data, hvilket er især praktisk, hvis du har et datasæt, der ikke er komplet endnu. Tænk over det: når du optager, har du ikke en komplet optagelse endnu, fordi du stadig optager. Hver gang du stopper optagelsen, er det når du har en komplet optagelse i form af en .wav- eller .mp3-fil. Vi er interesseret i at sende dataene, før vi er færdig med optagelsen, dvs. før vi har en afsluttet fil 'recording.wav'.

Vi vil bruge Go til dette lille projekt, da jeg synes, det passer perfekt til en lille, enkel http-server, der ikke har brug for en hel masse opsætning for at fungere. Vi bruger også et bibliotek kaldet Portaudio, der håndterer al lyd I / O. Dette er praktisk, fordi det håndterer lyd uanset serverens OS (OSX / Windows / Linux). Portaudio arbejder med en hovedlydsløjfe, hvor du vil gøre noget med lydindgangen og -udgangen. I dette tilfælde ønsker vi kun input - output er serverens højttalere (eller hvad hovedudgang der er konfigureret på serveren), som vi ikke har brug for. Vi er interesseret i at gemme lydindgangen i en buffer, som vi derefter kan skubbe ud via http. Vores hovedsløjfe (og Portaudio-opsætning) ser sådan ud:

pakke vigtigste
import (
 "Kodning / binært"
 "Github.com/gordonklaus/portaudio"
 "Netto / http"
)
const sampleRate = 44100
const sekunder = 1
func main () {
 portaudio.Initialize ()
 udsæt portaudio. Bestem ()
 buffer: = make ([] float32, sampleRate * seconds)
 stream, err: = portaudio.OpenDefaultStream (1, 0, sampleRate, len (buffer), func (i [] float32) {
  for i: = rækkevidde buffer {
   buffer [i] = i [i]
  }
 })
 hvis fejlagtigt! = nul {
   panik (err)
 }
 stream.Start ()
 udsæt strøm. Luk ()
}

Vi initialiserer først () portaudio og udsætter Termin (). Vi opretter derefter en buffersnit, og indstiller den til den samme længde, som vores samplinghastighed vil være. Prøvehastigheden bestemmer, hvor mange eksempler et sekund lyd vil indeholde, så indstilling af bufferen til den samme samplingshastighed betyder, at den vil indeholde et sekund lyd. Vi opretter derefter en standardstrøm (hvilket betyder, at den vil bruge standardindgang og -udgang, eller i dette tilfælde kun input) med OpenDefaultStream (), med en input, nul udgange, en samplingshastighed på 44100 og vi specificerer vores rammer pr. buffer skal være længden på vores tidligere oprettede buffer. Derefter videregiver vi en funktion, der har vores input som et argument, som vil være hovedlydsløjfen. Her kortlægger vi simpelthen værdierne indeholdt i in array til vores buffer array. Endelig starter vi strømmen og udsætter strømmen. Luk (), når du er færdig.

Derefter kan vi oprette en simpel rute / lyd, der sender bufferen ud som et chunket svar. Vi opretter en http.Flusher, der bruges af Go til at udsende bunker med data, og vi indstiller overførsels-kodningens overskrift til "chunked". Vi opretter en uendelig sløjfe, hvor vi koder bufferen til binær og skriver den ud til strømmen.

http.HandleFunc ("/ lyd", func (w http.ResponseWriter, r * http.Request) {
  flusher, ok: = w. (http.Flusher)
  hvis! ok {
   panik ("forventes at http.ResponseWriter skal være en http.Flusher")
  }
  w.Header (). Indstil ("Forbindelse", "Keep-Alive")
  w.Header (). Sæt ("Transfer-Encoding", "chunked")
  for sandt {
   binær.Skriv (w, binær.BigEndian & buffer)
   flusher.Flush () // Trigger "chunked" -kodning
   Vend tilbage
  }
 })

Nu har vi et meget grundlæggende lydstreamingsprogram, der indfanger lyd fra standardindgangen på din computer og gør det tilgængeligt til download via chunked kodning. Lad os dykke ned i håndteringen af ​​lyden på klientsiden næste!

Vi vil igen bruge Go og Portaudio til at oprette et lille program, der GET har de rå binære data fra vores / lydendepunkt, afkoder dem og afspiller dem ved hjælp af Portaudio. Det fungerer stort set det samme som vores tidligere program, bortset fra at det skriver lyd til en standardoutput i stedet for at læse fra en standardindgang. Det ser sådan ud:

import (
 "bytes"
 "Kodning / binært"
 "FMT"
 "Github.com/gordonklaus/portaudio"
 "Io / ioutil"
 "Netto / http"
 "tid"
)
const sampleRate = 44100
const sekunder = 1
func main () {
 portaudio.Initialize ()
 udsæt portaudio. Bestem ()
 buffer: = make ([] float32, sampleRate * seconds)
stream, err: = portaudio.OpenDefaultStream (0, 1, sampleRate, len (buffer), func (out [] float32) {
  resp, err: = http.Get ("http: // localhost: 8080 / lyd")
  chk (err)
  krop, _: = ioutil.ReadAll (resp.Body)
  responseReader: = bytes.NewReader (body)
  binær.læs (responslæsning, binær.bigindisk & buffer)
  for i: = rækkevidde {
   ud [i] = buffer [i]
  }
 })
 chk (err)
 chk (stream.Start ())
 time.Sleep (time.Second * 40)
 chk (stream.Stop ())
 udsæt strøm. Luk ()
}
func chk (ærlig fejl) {
 hvis fejlagtigt! = nul {
  panik (err)
 }
}

vi bruger ReadAll-metoden fra ioutil-pakken til at læse hoveddelen af ​​det http-svar, vi får fra vores server. For at læse kroppen har vi også brug for en svarlæser, som vi kan få fra bytes-pakken via NewReader (). Vi bruger derefter Read () -metoden fra den binære pakke, der udgør modsatsen til Writ (metoden), vi brugte på vores server. Endelig kortlægger vi indholdet af vores læsebuffer til output, som Portaudio vil afspille som en smuk, uberørt lyd.

Det er klart, at dette eksempel er lidt mangelfuldt - det vil kun få det næste svar fra / lyd-endepunktet, når loopen er færdig, eller når bufferen er færdig med at afspille. Et godt næste trin ville være at få svardataene asynkront, måske ved hjælp af en Go-rutine eller -kanal. Måske skal bufferne opbevares i en række buffere, så vi kan afspille dem den ene efter den anden! Hvis du har tanker om dette, skal du ikke være bange for at give en kommentar.

Du kan finde den fulde eksempelkode her!

Redigering: Jeg ville først gøre dette til en flerdelsserie, men besluttede i sidste ende imod den, hovedsageligt på grund af tidsbegrænsninger. Tak for at have læst!