Com es crea un servidor de transmissió d’àudio a Go

Mireu aquest petit gopher musical!

L’àudio del navegador no sempre es rep amb els braços oberts. Ara, la majoria de vídeos compartits a les xarxes socials esperen que la gent els vegi amb el so desactivat, les pàgines d’inici de les geocitats RIP que fa temps que s’han oblidat i els botons de la web que fan clic audible a Fer coses que mai no han aturat el web és força tranquil.

Tot i això, la transmissió d’àudio a la carta o en directe mai ha estat tan popular. Serveis com Spotify, Twitch i programes de videotrucades com Skype o Discord transformen tots els nostres sons en uns i zeros i els envien per Internet als oients de tot el món. Sorprenentment, no hi ha molta informació disponible en línia, almenys no per a principiants.

Consulteu també, Com puc crear un servidor de transmissió d’àudio? Per aconseguir que persones com jo tinguin habilitats d’àudio i / o música i una comprensió bàsica de com funciona la web per a la transmissió i l’àudio digital en general.

Aquí ens centrem en la creació d’un servidor molt senzill que utilitzarà la màgia de http per emetre àudio d’un lloc a un altre. Aquest servidor rep l'entrada d'àudio de qualsevol entrada d'àudio disponible al servidor, per exemple. Per exemple, el micròfon del Macbook si esteu executant el servidor en un Macbook, convertint-lo a binari i enviant una resposta parcial al client. Chunking la resposta a http és un mètode per enviar trossos de dades o "trossos" de dades. Això és especialment útil quan teniu un conjunt de dades que encara no està completat. Penseu-hi: quan enregistreu, no teniu cap disc complet perquè encara esteu gravant. Sempre que deixeu de gravar, tindreu un enregistrament complet en forma de fitxer .wav o .mp3. Estem interessats en enviar les dades abans d’acabar la gravació, és a dir, H. Abans de finalitzar el fitxer "recording.wav".

Utilitzarem Go per a aquest petit projecte, ja que crec que és perfecte per a un servidor HTTP petit i senzill que no requereix molta configuració. També utilitzarem una biblioteca anomenada Portaudio que gestiona totes les operacions d'E / S d'àudio. Això és útil ja que l'àudio es processa independentment del sistema operatiu del servidor (OSX / Windows / Linux). Portaudio funciona amb un bucle d'àudio principal en què s'inicia alguna cosa amb l'entrada i la sortida d'àudio. En aquest cas, només volem l'entrada: la sortida són els altaveus del servidor (o la sortida d'àudio principal configurada al servidor), que no necessitem. Ens agradaria desar l'entrada d'àudio en un buffer que després podrem emetre mitjançant http. El nostre bucle principal (i la configuració de portaudio) té aquest aspecte:

Paquet principal
import ("codificació / binari" "github.com/gordonklaus/portaudio" "net / http")
const sampleRate = 44100 Segons constants = 1
func main () {portaudio.Initialize () move portaudio.Terminate () buffer: = make ([] float32, sampleRate * seconds) stream, err: = portaudio.OpenDefaultStream (1, 0, sampleRate, len (buffer), func (in [] float32) {for i: = buffer range {buffer [i] = in [i]}})
si err! = zero {panic} stream.Start () move stream.close ()}

Inicialitzem portaudio () i deferTerminate () primer. A continuació, crearem un segment de memòria intermèdia i el configurarem per tenir la mateixa longitud que la nostra freqüència de mostra. La freqüència de mostra determina quantes mostres ha de contenir un segon d'àudio. Si configureu la memòria intermèdia a la mateixa freqüència de mostra, vol dir que mantindrà un segon d'àudio. A continuació, configurem un flux estàndard amb OpenDefaultStream () (és a dir, utilitza entrada i sortida estàndard, o en aquest cas només entrada), amb una entrada, sortides zero, una freqüència de mostra de 44100 i donant els nostres fotogrames per memòria intermèdia que volem Sigui la longitud del nostre buffer creat prèviament. Després passem una funció l’entrada de la qual s’utilitza com a argument i que és el bucle d’àudio principal. Aquí simplement assignem els valors continguts a la matriu in a la nostra matriu de memòria intermèdia. Finalment, iniciem el flux i el movem. Tanca () quan hagis acabat.

A continuació, podem crear una ruta / àudio senzilla que enviï la memòria intermèdia com a resposta de bloc. Creem un http.Flusher, que Go utilitza per enviar blocs de dades i establim la capçalera de codificació de transferència a "tros". Creem un bucle infinit en què codifiquem la memòria intermèdia com a fitxer binari i l’escrivim al flux.

http.HandleFunc ("/ audio", func (w http.ResponseWriter, r * http.Request) {Flusher, ok: = w. (http.Flusher) if ok {panic ("espera que http.ResponseWriter sigui un http .Flusher és ")}
w.Header (). Estableix ("Connexió", "Manté-viu") w.Header (). Set ("Transferència-codificació", "tros")
per a veritable {binary.Write (w, binary.BigEndian & buffer) flusher.Flush () // activador de retorn de codificació "tros"}})

Ara tenim un programa de transmissió d’àudio molt senzill que capturarà l’àudio de l’entrada estàndard de l’ordinador i el farà disponible per descarregar-lo mitjançant la codificació fragmentada. A continuació, vegem l'àudio del costat del client.

Tornarem a utilitzar Go i Portaudio per crear un petit programa que extreu les dades binàries del nostre extrem / àudio, les descodificarà i les reproduirà amb Portaudio. Funciona pràcticament igual que el nostre programa anterior, excepte que l’àudio s’escriu en una sortida estàndard en lloc de llegir-lo des d’una entrada estàndard. Es veu així:

importació ("bytes" "codificació / binari" "fmt" "github.com/gordonklaus/portaudio" "io / ioutil" "net / http" "time")
const sampleRate = 44100 Segons constants = 1
func main () {portaudio.Initialize () move portaudio.Terminate () buffer: = make ([] float32, sampleRate * seconds)
stream, err: = portaudio.OpenDefaultStream (0, 1, sampleRate, len (buffer), func (out [] float32) {resp, err: = http.Get ("http: // localhost: 8080 / audio") chk (err) body, _: = ioutil.ReadAll (or Body) responseReader: = bytes.NewReader (body) binary.Read (responseReader, binary.BigEndian & buffer) for i: = range out {out [i] = buffer [i]}}) chk (err) chk (stream.Start ()) time.Sleep (time.Second * 40) chk (stream.Stop ()) Move stream.Close ()}
func chk (error) {si err! = nul {pànic}}

Utilitzem el mètode ReadAll del paquet ioutil per llegir el text de la resposta http que obtenim del nostre servidor. Per llegir el text, també necessitem un lector de respostes, que obtenim del paquet Bytes a través de NewReader (). A continuació, fem servir el mètode Read () del paquet binari, que és el contrari del mètode Write () que hem utilitzat al nostre servidor. Finalment, assignem el contingut del nostre buffer de lectura a la sortida, que Portaudio reprodueix com un so preciós i impecable.

Viouslybviament, aquest exemple és un xicotet error: només obtindrà la següent resposta des del punt final / audio quan acabi el bucle o quan acabi la memòria intermèdia. Un bon pas següent seria obtenir les dades de resposta de manera asíncrona, possiblement mitjançant una rutina Go o un canal. És possible que calgui emmagatzemar els buffers en una sèrie de buffers perquè puguem reproduir-los un per un. Si hi penses, no tinguis por de deixar un comentari.

Podeu trobar el codi de mostra complet aquí.

Edició: volia convertir-ho en una sèrie de diverses parts al principi, però finalment vaig decidir-me en contra, principalment per restriccions de temps. Gràcies per llegir!