_ 2.1.5 Stream sockets och ljudöverföring Vi ska nu gå vidare med media och titta på Java Sound API som är mycket användbart i många olika program. Java Sound består av två huvuddelar: - §javax.sound.sampled§ - §javax.sound.midi§ Vi ska fokusera på §javax.sound.sampled§ paketet och börja med att titta på hur man i Internetbaserade program: = Spelar in ett ljud = Sänder ett ljud med TCP = Tar emot ett ljud med TCP = Spelar upp ett ljud __ Uppgift Gör ett program som använder samplat ljud över stream sockets, exempelvis: - En chattklient som sänder och tar emot ljud - En epostklient för ljudmeddelanden - En Internetbaserad telefonsvarare där besökare kan spela in ljud som sedan sparas i en SQL-databas där ägaren sedan via en annan (hemlig) sida kan lista alla ljudmeddelanden och lyssna av dem - En SQL-gästbok för ljud __ Exempel Ett exempel på en icke kontinuerlig chattklient kan köras enligt följande: = Hämta filen: §[Client.jar, 05_ass/ip1/2/2.1.5/Client.jar]§ = Kör programmet med: §java -jar Client.jar§ Detta är en vanlig chattklient som är utbyggd med hjälp av Java Sound API. Chattklienten kopplar sig default till: - Host: §atlas.dsv.su.se§ - Port: §4848§ På denna adress kör en vanlig chattserver som är något omgjord så att den hanterar objekt (via §ObjectInputStream§ och §ObjectOutputStream§) istället för strängar. Chattklienten sänder objekt av typen §[Storage.java, 05_ass/ip1/2/2.1.5/Storage.java]§ så om man vill använda denna chattserver så bör man sända samma typ av objekt. Man kan testa systemet lokalt om man har en långsam uppkoppling: - Hämta filen: §[Server.jar, 05_ass/ip1/2/2.1.5/Server.jar]§ (utan GUI) - Kör chattserver programmet: §java -jar Server.jar§ - Kör chattklient programmet: §java -jar Client.jar localhost§ Notera att man måste ha en mikrofon och högtalare anslutna till datorn för att man ska kunna testa detta. __ Tips Java Sound API ingår numera i JDK så man behöver inga extra filer. Vill man göra ett äpple körbart i en webbläsare så måste man signera äpplet eftersom äpplen default bara får spela upp ljud, inte spela in ljud. Här finns några exempel som man kan titta på för att komma igång: - Spela in ljud och spara till en fil (på au-format): -- Källkod: §[FileRecorder.java, 05_ass/ip1/2/2.1.5/examples/FileRecorder.java]§ -- Användning: §java FileRecorder § - Spela upp ljud från en fil (på au-format): -- Källkod: §[FilePlayer.java, 05_ass/ip1/2/2.1.5/examples/FilePlayer.java]§ -- Användning: §java FilePlayer § - Spela in och upp ljud och spara ljudet internt i ett serialiserbart objekt §Storage§: -- Källkod: --- §[RecordAndPlay.java, 05_ass/ip1/2/2.1.5/examples/RecordAndPlay.java]§ --- §[Storage.java, 05_ass/ip1/2/2.1.5/examples/Storage.java]§ (använd inte detta object till chattservern ovan) -- Användning: §java RecordAndPlay § När man använder ljud över Internet så är det mycket viktigt att man sänder så lite information som möjligt, speciellt om det finns användare som är uppkopplade via mobiler. Hur gör man det? Man använder två olika sätt: - Sampla ljudet med: -- Färre samples per tidsenhet, 8KHz är tillräckligt för tal -- Färre bitar för varje sample -- Färre kanaler, använd mono - Komprimera ljudet innan sändning och dekomprimera det efter mottagning: -- Metoder specialiserade för ljud komprimerar ofta så att information försvinner och det finns många olika metoder för detta (många olika codecs): --- En del specialiserade på talat ljud: ---- ALAW är en standard som används i telefonerna i Europa med mera, denna codec ingår i JDK och halverar mängden information men ljudet blir av ganska dålig kvalitet ---- ULAW är en standard som används i telefonerna i USA och Japan, denna codec ingår i JDK och halverar mängden information men ljudet blir av ganska dålig kvalitet ---- GSM är en standard som används av mobiltelefoner, denna codec ingår inte i JDK och informationen blir ca 10 gånger mindre och ljudet blir av ganska bra kvalitet --- En del specialiserade på musik: MP3, MP4 med flera --- Metoder som är generella, exempelvis ZIP, komprimerar ofta så att information inte försvinner Notera att de flesta ljudkorten inte har direkt stöd för olika codecs utan att man måste omkoda så att man alltid använder PCM längst fram (input och output). Vill man använda GSM, MP3 eller andra codecs som inte ingår i JDK så måste man använda tillbehörsfiler, exempelvis från [Tritonus, http://www.tritonus.org/plugins.html]. Man kan själv testa följande exempel för att se hur mycket man kan komprimera ljudet: - Hämta filen: §[Client.jar, 05_ass/ip1/2/2.1.5/codec_version/Client.jar]§ - Kör programmet med: §java -jar Client.jar§ Default så startar detta program med GSM- och ZIP-koprimering/dekomprimering mot: - Host: §atlas.dsv.su.se§ - Port: §4848§ Man kan köra med följande varianter på komprimeringar: - Kör med: §java -jar Client.jar atlas.dsv.su.se 4848 alaw§ - Kör med: §java -jar Client.jar atlas.dsv.su.se 4848 ulaw§ - Kör med: §java -jar Client.jar atlas.dsv.su.se 4848 gsm§ - Kör med: §java -jar Client.jar atlas.dsv.su.se 4848 alaw no_zip§ - Kör med: §java -jar Client.jar atlas.dsv.su.se 4848 ulaw no_zip§ - Kör med: §java -jar Client.jar atlas.dsv.su.se 4848 gsm no_zip§ I både detta exempel och i det tidigare (utan codec och ZIP) används: § AudioFormat format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 8000.0F, 16, 1, 2, 8000.0F, false); § Det vill säga 16 bitars ljud samplat med 8 KHz i mono. Om man använder codec och ZIP så får man följande siffror i ett exempel där man räknar sakta från ett till tio: - Utan codec och utan ZIP: 176000 Byte. - Med ALAW-codec och utan ZIP: 88024 Byte. - Med ALAW-codec och med ZIP: 45462 Byte. - Med GSM-codec och utan ZIP: 18150 Byte. - Med GSM-codec och med ZIP: 15965 Byte. Man komprimerar alltså som mest 11 ggr, vilket ju är enormt bra! För GSM-codecen så använder programmet två JAR-filer: - §[tritonus_share.jar, 05_ass/ip1/2/2.1.5/codec_version/tritonus_share.jar]§ - §[tritonus_gsm.jar, 05_ass/ip1/2/2.1.5/codec_version/tritonus_gsm.jar]§ Se vidare: - [Tritonus: Open Source Java Sound, https://www.tritonus.org] - [Tritonus: Plug-ins, https://www.tritonus.org/plugins.html] Dessa finns redan inbakade i §Client.jar§ så man behöver dem inte för att testköra. Vill man själv baka in allt i en JAR-fil så gör enligt följande: - Packa upp §tritonus_share.jar§: §jar xf tritonus_share.jar§ - Packa upp §tritonus_gsm.jar§: §jar xf tritonus_gsm.jar§ - Kopiera katalogen §META-INF\services§ till arbetskatalogen - Jara ihop allt: §jar cmfv mainClassClient Client.jar *.class META-INF\services\* org§ Det är ganska svårt att använda olika codecs men i stora drag så gör man så här när man encodar: § byte[] audio = out.toByteArray(); InputStream input = new ByteArrayInputStream(audio); AudioInputStream ais1 = new AudioInputStream(input, format1, audio.length / format.getFrameSize()); AudioInputStream ais2 = AudioSystem.getAudioInputStream(encoding2, ais1); ByteArrayOutputStream out2 = new ByteArrayOutputStream(); int nWrittenFrames = AudioSystem.write(ais2, fileType, out2); § Här ligger alla ljuddata initialt i §out§ (som är ett objekt av typ §ByteArrayOutputStream§) och exempelvis: § AudioFormat.Encoding encoding1 = AudioFormat.Encoding.PCM_SIGNED; AudioFormat format = new AudioFormat(encoding1, 8000.0F, 16, 1, 2, 8000.0F, false); AudioFormat.Encoding encoding2 = Encodings.getEncoding("GSM0610"); AudioFileFormat.Type fileType = AudioFileTypes.getType("GSM", ".gsm"); § Nu finns alla encodade ljuddata i §out2§. När man ska decoda ljuddata så gör man: § byte[] audio = out.toByteArray(); InputStream input = new ByteArrayInputStream(audio); AudioInputStream aisTmp = AudioSystem.getAudioInputStream(input); AudioInputStream ais = AudioSystem.getAudioInputStream(format, aisTmp); § Om man vill använda den server som finns på: - Host: §atlas.dsv.su.se§ - Port: §4848§ så sänd ljudet i objekt av typen §Storage§. Använd exempelvis §ObjectInputStream§ och §ObjectOutputStream§ kopplade till socketen och sänd och ta emot sådana (serialiserade) §Storage§-objekt som innehåller ljudet. Om man försöker använda ovanstående program för att kontinuerligt sända och ta emot "klumpar" av ljud utan att man behöver klicka på några knappar så blir ljudet mycket dåligt och uppdelat (även om man enbart pratar med sig själv :-). En lösning är då att man har en speciell server som mixar ihop alla inkommande ljudströmmar till en enda ljudström (med klassen §Mixer§) och sen sänder ut denna till alla klienter. Man kan även experimentera med ljudfilter och lägga på roliga effekter som exempelvis eko. Ett mycket enkelt exempel: - Hämta filen: §[RecordAndPlay.jar, 05_ass/ip1/2/2.1.5/dsp_version/RecordAndPlay.jar]§ - Kör med: §java -jar RecordAndPlay.jar§ Med det programmet kan man testa fyra stycken enkla ljudprocessorer och alla kombinationer av dessa. Notera att ljudet sparas på två byten när man använder 16-bitars ljud och att det är lättare att använda en short-array än en byte-array om man samplat med 16-bitar och ska transformera ljudet på så här låg nivå. Det finns två metoder §short2byte§ och §byte2short§ och ett par exempel på användning av dem i källkoden (se klassen §DSP.java§): - §[RecordAndPlay.java, 05_ass/ip1/2/2.1.5/dsp_version/RecordAndPlay.java]§ - §[Storage.java, 05_ass/ip1/2/2.1.5/dsp_version/Storage.java]§ - §[DSP.java, 05_ass/ip1/2/2.1.5/dsp_version/DSP.java]§ Vill man lära sig göra avancerade ljudfilter och ljudprocessorer så är boken "Digital Audio with Java" skriven av Lindley utgiven av Prentice Hall bra: - Ljudfilter: -- Low-pass filters -- High-pass filters -- Band-pass filters - Ljudprocessorer: -- Amplitude adjust processor -- Cache processor -- Chorus/Flanger processor -- Compressor/Expander/Limiter/Noise gate processor -- Delay processor -- Distortion processor -- Graphic equalizer processor -- Panner processor -- Parametric equalizer processor -- Phaser processor -- Pith shifter processor -- Reverb processor Notera dock att den är något gammal (1999) och därmed inte utgår från Java Sound men man kan ändå använda det som beskrivs tillsammans med Java Sound. Alltså, fokus är på transformering av ljud, inte teknikerna i Java Sound. Se vidare [Oracle: Java Sound Programmer's Guide, http://docs.oracle.com/javase/1.5.0/docs/guide/sound/programmer_guide/contents.html]. __ Hjälp ~ 05_ass/help/01.txt > [Klicka här för hjälp, helper.dsv@gmail.com, IP Stationär: 2.1.5 Stream sockets och ljudöverföring: http://people.dsv.su.se/~pierre/i/i.cgi?href=05_ass/ip1/2.1.5.txt] ½===system_tutoring_message===½ < ~ 05_ass/help/02.txt