如何将 Alexa 语音服务集成到自定义 Android 应用程序

How to Integrate Alexa Voice Service to Custom Android App

我计划开发一个 Android 集成了 Alexa 语音服务的应用程序,以开发像 Reverb 这样的应用程序。以下是我尝试过的...

  1. 检查了 AVS 设备 SDK,可以在 Android 中获得正确的实施指南。

  2. 已检查 https://github.com/willblaschko/AlexaAndroid,无法正常工作。

  3. 打算自己实现,下面是自己做的。

    一个。集成登录框架,能够成功登录并获取token

    b。创建了一个录音机,可以在本地录音和播放。

    c。创建了将音频发送到 https://avs-alexa-eu.amazon.com/v20160207/events

  4. 的请求

[更新]

将 shortArray 更改为字节数组后,我得到了响应,但现在的问题是 MediaPlayer 无法播放响应 mp3,在准备时出错

package com.example.anoopmohanan.alexaandroid

import android.content.Context
import android.media.*
import android.media.AudioFormat.ENCODING_PCM_16BIT
import android.media.AudioFormat.CHANNEL_CONFIGURATION_MONO
import android.os.Environment
import android.os.Environment.getExternalStorageDirectory
import java.io.*
import com.example.anoopmohanan.alexaandroid.ResponseParser.getBoundary
import okhttp3.*
import org.jetbrains.anko.doAsync
import org.json.JSONObject
import java.nio.file.Files.exists
import okhttp3.OkHttpClient
import java.net.HttpURLConnection
import android.os.Looper
import android.os.PowerManager
import android.util.Log
import okio.BufferedSink
import okhttp3.RequestBody
import org.apache.commons.io.FileUtils
import org.jetbrains.anko.Android
import org.jetbrains.anko.runOnUiThread
import org.jetbrains.anko.toast
import java.util.*
import okhttp3.ResponseBody
import okio.Buffer
import com.example.anoopmohanan.alexaandroid.SoundRecorder.LoggingInterceptor
import android.media.MediaDataSource






class SoundRecorder(context: Context) {

    private var appcontext: Context? = null
    private var recording = false
    val MEDIA_JSON = MediaType.parse("application/json; charset=utf-8")
    val MEDIA_TYPE_AUDIO = MediaType.parse("application/octet-stream")
    var accessToken = ""
    private var mediaPlayer: MediaPlayer? = null
    private var streamToSend:ByteArray? = null
    private val client = OkHttpClient.Builder()
            .addInterceptor(LoggingInterceptor())
            .build()

    init {
        this.appcontext = context

    }
    fun startRecording(accessToken: String){
        this.accessToken = accessToken
        doAsync {
            startRecord()
        }

    }

    fun stopRecording(){

        doAsync {
            stopRecord()
        }

    }

    fun playRecording(){

        doAsync {
            playRecord()
        }

    }
    private fun stopRecord(){

        recording = false
        //val file = File(Environment.getExternalStorageDirectory(), "test.pcm")
        //sendAuio(file)
    }

    fun playRecord() {

        val file = File(Environment.getExternalStorageDirectory(), "speech2.mp3")

            val mplayer = MediaPlayer()
            mplayer.setDataSource(file.path)
            mplayer.prepare()
            mplayer.start()
//        val file = File(Environment.getExternalStorageDirectory(), "test.pcm")
//
//        val shortSizeInBytes = java.lang.Short.SIZE / java.lang.Byte.SIZE
//
//        val bufferSizeInBytes = (file.length() / shortSizeInBytes).toInt()
//        val audioData = ByteArray(bufferSizeInBytes)
//
//        try {
//            val inputStream = FileInputStream(file)
//            val bufferedInputStream = BufferedInputStream(inputStream)
//            val dataInputStream = DataInputStream(bufferedInputStream)
//
//            var i = 0
//            while (dataInputStream.available() > 0) {
//                audioData[i] = dataInputStream.readByte()
//                i++
//            }
//
//            dataInputStream.close()
//
//            val audioTrack = AudioTrack.Builder()
//                    .setAudioAttributes(AudioAttributes.Builder()
//                            .setUsage(AudioAttributes.USAGE_MEDIA)
//                            .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
//                            .build())
//                    .setAudioFormat(AudioFormat.Builder()
//                            .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
//                            .setSampleRate(16000)
//                            .setChannelMask(AudioFormat.CHANNEL_OUT_MONO).build())
//                    .setBufferSizeInBytes(bufferSizeInBytes)
//                    .setTransferMode(AudioTrack.MODE_STREAM)
//                    .build()
//
//            audioTrack.play()
//            audioTrack.write(audioData, 0, bufferSizeInBytes)
//
//
//        } catch (e: FileNotFoundException) {
//            e.printStackTrace()
//        } catch (e: IOException) {
//            e.printStackTrace()
//        }

    }



    private fun startRecord() {

        val file = File(Environment.getExternalStorageDirectory(), "test.pcm")

        try {
            file.createNewFile()

            val outputStream = FileOutputStream(file)
            val bufferedOutputStream = BufferedOutputStream(outputStream)
            val dataOutputStream = DataOutputStream(bufferedOutputStream)

            val minBufferSize = AudioRecord.getMinBufferSize(16000,
                    AudioFormat.CHANNEL_IN_MONO,
                    AudioFormat.ENCODING_PCM_16BIT)


            val audioData = ByteArray(minBufferSize)

            val audioRecord = AudioRecord(MediaRecorder.AudioSource.MIC,
                    16000,
                    AudioFormat.CHANNEL_IN_MONO,
                    AudioFormat.ENCODING_PCM_16BIT,
                    800)
            if (audioRecord.recordingState != AudioRecord.RECORDSTATE_STOPPED){

                this.appcontext?.runOnUiThread {
                    toast("No recording source available")
                }

                return
            }
            recording = true
            audioRecord.startRecording()
            if (audioRecord.recordingState != AudioRecord.RECORDSTATE_RECORDING){

                this.appcontext?.runOnUiThread {
                    toast("Someone is still recording")
                }
                recording = false
                audioRecord.stop()
                audioRecord.release()
                return
            }

            this.appcontext?.runOnUiThread {
                toast("Recording started hurray")
            }
            while (recording) {
                audioRecord.read(audioData, 0, minBufferSize)
                dataOutputStream.write(audioData,0,minBufferSize)
//                for (i in 0 until numberOfShort) {
//                    dataOutputStream.writeShort(audioData[i].toInt())
//                }
            }

            audioRecord.stop()
            audioRecord.release()
            dataOutputStream.close()
            sendAuio(file)

        } catch (e: IOException) {

            e.printStackTrace()
        }

    }

    fun sendAuio(audio: File){

        streamToSend = audio.readBytes()
        val requestBody = MultipartBody.Builder()
                .setType(MultipartBody.FORM)
                .addFormDataPart("metadata","metadata",RequestBody.create(MEDIA_JSON, generateSpeechMetadata()))
                .addFormDataPart("audio", "test.pcm",
                        RequestBody.create(MEDIA_TYPE_AUDIO, streamToSend))
                .build()

        val request = Request.Builder()
                .url("https://avs-alexa-eu.amazon.com/v20160207/events")
                .addHeader("Authorization","Bearer $accessToken")
                .post(requestBody)
                .build()
        print (request.body().toString())
        client.newCall(request).execute().use({ response ->
            if (!response.isSuccessful()) throw IOException("Unexpected code $response")

            val items = if (response.code() == HttpURLConnection.HTTP_NO_CONTENT)
                AvsResponse()
            else
                ResponseParser.parseResponse(response.body()!!.byteStream(), getBoundary(response))
            if (items.size > 0){

                handle(items)
            }
            System.out.println("[TRACE]"+response.body()!!.string())
        })
    }

    @Throws(IOException::class)
    fun toByteArray(`in`: InputStream): ByteArray {
        val out = ByteArrayOutputStream()
        var read = 0
        val buffer = ByteArray(1024)
        while (read != -1) {
            read = `in`.read(buffer)
            if (read != -1)
                out.write(buffer, 0, read)
        }
        out.close()
        return out.toByteArray()
    }
    private fun generateSpeechMetadata(): String {
        val messageId = UUID.randomUUID().toString();
        val dialogId = UUID.randomUUID().toString();

        return  "{\"event\": {\"header\": {\"namespace\": \"SpeechRecognizer\",\"name\": \"Recognize\",\"messageId\": \"$messageId\",\"dialogRequestId\": \"$dialogId\"},\"payload\": {\"profile\": \"CLOSE_TALK\", \"format\": \"AUDIO_L16_RATE_16000_CHANNELS_1\"}},\"context\": [{\"header\": {\"namespace\": \"AudioPlayer\",\"name\": \"PlaybackState\"},\"payload\": {\"token\": \"\",\"offsetInMilliseconds\": 0,\"playerActivity\": \"FINISHED\"}}, {\"header\": {\"namespace\": \"SpeechSynthesizer\",\"name\": \"SpeechState\"},\"payload\": {\"token\": \"\",\"offsetInMilliseconds\": 0,\"playerActivity\": \"FINISHED\"}}, { \"header\" : { \"namespace\" : \"Alerts\", \"name\" : \"AlertsState\" }, \"payload\" : { \"allAlerts\" : [ ], \"activeAlerts\" : [ ] } }, {\"header\": {\"namespace\": \"Speaker\",\"name\": \"VolumeState\"},\"payload\": {\"volume\": 25,\"muted\": false}}]}"
//        return "{\n" +
//                "\"messageHeader\": {\n" +
//                "\"deviceContext\": [\n" +
//                "{\n" +
//                "\"name\": \"playbackState\",\n" +
//                "\"namespace\": \"AudioPlayer\",\n" +
//                "\"payload\": {\n" +
//                "\"streamId\": \"\",\n" +
//                "\"offsetInMilliseconds\": \"\",\n" +
//                "\"playerActivity\": \"IDLE\"\n" +
//                "}\n" +
//                "}\n" +
//                "]\n" +
//                "},\n" +
//                "\"messageBody\": {\n" +
//                "\"profile\": \"doppler-scone\",\n" +
//                "\"locale\": \"en-us\",\n" +
//                "\"format\": \"audio/L16; rate=16000; channels=1\"\n" +
//                "}\n" +
//                "}"
    }


    private val audioRequestBody = object : RequestBody() {
        override fun contentType(): MediaType? {
            return MediaType.parse("application/octet-stream")
        }

        @Throws(IOException::class)
        override fun writeTo(sink: BufferedSink?) {
            //while our recorder is not null and it is still recording, keep writing to POST data
            sink!!.write(streamToSend)
        }
    }

    inner class LoggingInterceptor : Interceptor {
        @Throws(IOException::class)
        override fun intercept(chain: Interceptor.Chain): Response {
            val request = chain.request()

            val t1 = System.nanoTime()
            Log.d("OkHttp", String.format("--> Sending request %s on %s%n%s", request.url(), chain.connection(), request.headers()))

            val requestBuffer = Buffer()
            request.body()?.writeTo(requestBuffer)
            Log.d("OkHttp", requestBuffer.readUtf8())

            val response = chain.proceed(request)

            val t2 = System.nanoTime()
            Log.d("OkHttp", String.format("<-- Received response for %s in %.1fms%n%s", response.request().url(), (t2 - t1) / 1e6, response.headers()))

            val contentType = response.body()?.contentType()
            val content = response.body()?.string()
            Log.d("OkHttp", content)

            val wrappedBody = ResponseBody.create(contentType, content)
            return response.newBuilder().body(wrappedBody).build()
        }
    }


    private fun getMediaPlayer(): MediaPlayer? {
        if (mediaPlayer == null) {
            mediaPlayer = MediaPlayer()
        }
        return mediaPlayer
    }

    fun handle(items:AvsResponse){

        for (item in items){
            handle(item)
        }
    }
    fun handle(item: AvsItem){


        if (item is AvsSpeakItem){

        }else{
            return
        }
        //cast our item for easy access
        //write out our raw audio data to a file
        val path = File(Environment.getExternalStorageDirectory(), "speech.mp3")

        //path.deleteOnExit()
        //val path = File(appcontext!!.getCacheDir(), System.currentTimeMillis().toString() + ".mp3")
        //var fos: FileOutputStream? = null
        try {
//            fos = FileOutputStream(path)
//            fos!!.write(item.audio)
//            fos.close()

            path.createNewFile()
            path.writeBytes(item.audio)
//            var ds = ByteArrayMediaDataSource(item.audio)
//            val fis = FileInputStream(path)
//            //play our newly-written file
//            val mplayer = MediaPlayer()
//            mplayer.setDataSource(path.path)
//            mplayer.prepare()
//            mplayer.start()
//            getMediaPlayer()?.setDataSource(fis.fd)
//            getMediaPlayer()?.prepare()
//            getMediaPlayer()?.start()
        } catch (e: IOException) {
            e.printStackTrace()

        } catch (e: IllegalStateException) {
            e.printStackTrace()
        }

    }


    inner class ByteArrayMediaDataSource(private val data: ByteArray?) : MediaDataSource() {

        init {
            assert(data != null)
        }

        @Throws(IOException::class)
        override fun readAt(position: Long, buffer: ByteArray, offset: Int, size: Int): Int {
            System.arraycopy(data, position.toInt(), buffer, offset, size)
            return size
        }

        @Throws(IOException::class)
        override fun getSize(): Long {
            return data?.size?.toLong()!!
        }

        @Throws(IOException::class)
        override fun close() {
            // Nothing to do here
        }
    }
}

来自 ALEXA 的回应

--------abcde123
Content-Type: application/json; charset=UTF-8

{"directive":{"header":{"namespace":"SpeechSynthesizer","name":"Speak","messageId":"d58c83fe-377f-4d1d-851b-a68cf5686280","dialogRequestId":"d83b8496-e6a5-4fc5-b07b-32f70acd1f15"},"payload":{"url":"cid:2aed5305-081d-4624-b2ba-ef51eba6aa32_1353236331","format":"AUDIO_MPEG","token":"amzn1.as-ct.v1.Domain:Application:Knowledge#ACRI#2aed5305-081d-4624-b2ba-ef51eba6aa32"}}}
--------abcde123
Content-ID: <2aed5305-081d-4624-b2ba-ef51eba6aa32_1353236331>
Content-Type: application/octet-stream

ID3#TSSELavf57.71.100dUC\[QwwD뻝q    \[D*An. .`>X>>@|L?&|>4=A?w'<<Ɨd"_h{[M̢8j8ǙbA

@=$T2bX2LM~NBPp˧Eh$`hoV

ԁh3}ɧ2/_74g挺`;*
W`dq՗HY)@mN<fG?l74(
    ˚z
BD)jy7uӹ\ۭc|>)qcsZQN} QppҜ{_]>iWw$Yd!.VEi]
<ƹYK.l6$̄)uLPGⰞRTFK>cr{\UK.|-EWGWȍ{3UN6]1tX@jr`5ka5/d V
WzLٺt?Aɬg%Я;la N%
A`r:ۙJSD3\´P y%eWOG5As
۪<*qfom
d !JjadR"=2vg?ZzrKZrV*Y2ش!XFOI EP99
Fdmy;ꡐ^JJ5С/\   _-LuafVMFZT-TUadRwO(A.2>sʳ!Y&)h`x<RR<AC|pTi"`k9#ɱX*%AP0CR7u+<VmFq ,ł)EhH굫.<hd#%[6h$kRO'IZ.VMX>!fqi+`:'j-Z6X *C
0S'9)yd&ĽX+)d Ӳ@Xz3 M xNgV9Vc׿?:ot\w}&dZk)b.`C$w1*\y?O՗qld\&R=bcQt]
r*U{ztUT-| b%BN'<^潦P2Dtc1dھ]KN!*gxxv[dp0 ЈaR'id \@G_=f:fř6~pdcg.k/_E0lY
(XvoR.w*0U>/9_`ra1ANo^8&Ո{2d5Y{қ|Xo6`J|   !mcx".~_Da_,êJgt7,xkdO,Ӭ4e 葅*J
wd0FsKb[g#1TN*ydJBhJ    .p}䁤`%JHj~"kJl`P_Q#&6_F!Ίrw|Ȇ%vs 0$F    P8n*c#eWO@  dEM/dZY4N[j\Y]t~b3ݚKJo4Qff.|RSxLVo%$ߟ;I"XjRRC׾YK#PĹ?&w3B D@\Jη!dk>Do
C5 0*6G7`Y4םke.Sv(J SIH$&pf|fP!rې3  %o@%gN`cM,E/}PV*(d}aڠNц3!qɿhinvhֹ`,+?_hEAi*Uu)LB)̈j{Rs$yTYQ*)_
]M4Pa6dĒLJyw*)3x÷/@s.Æ
(J%{iwhNi^hAyW+į8SɜJg )%8Wa6uaZ gZT   vWUdğד]:C
'52CON.kYp,c9HO#JZ3T|{8+Q䫚YYkytơʫ6tݼt$V@z9O*7`BqdįʸT?Tc=YP&c:PLB~Ȧ&3r*kg9CY$pJMN鱆cz,eM0V!I7uD8_c9odĿKɖy3|Z+r`w-{c
ww*nej5h8DLHO-
}g['cT
U :WTGVNo+ٚ@v7j-!G[ZOdondê099E     ڀn҂c@R4&@fEkSvGxժI+7Jѐ>L)u8:
Fv*$ܷ{s4ğ7! ddҤ6
\a`dvFSeT#"uVVhbԧRarEr\]C(rMfarw\j7n[Xon*1x i
J?mL0Kh.Rsd&CtVWxxny:fN
ǃ>T
.)":`pUÂȆ^#z@1·fRu:;KN
sN< |zXqs<nWP&O3qēXx-FTd,јϐxE$LUf{%dHĤK4RX[9g^fY7"הv6
#t*u-#PhySi犂>v@KDan
F[0pxԐÍL)1YlG
ÄdĨ#1i.%u7_shnA A,G)KJL8HEgc_X}Sr6E;#_%9@!
aSi@5dF E#'Z=&XrPMH4<f7/T"vdĞ!`JFL0ʊS2TKαYiFgƞ`C'?(Lt   bW$"ᾇ0V5)ǗqWX}(-H*=[bi3(ܷdĚ!SJɎtoZd`$c("2ݩEUSJjzBׂLߍA%a@d'dZ(ʺ?j}\C;8 gjV
oz3/`dĠN`dė^L)Xc 8, 7>xzMTT| ZRBe#M7X7"(8n2f    Nc@AJ3" 0A
@   3qxf;CAA7db8#@eps
EdĬ52_E S0/i 
[M94<@١n2("6k97L+ƪHSjW]Z6Uoi??3967ī?_K1NDN9dX3<6`>Vn#"jM(^BN  GEc:29.XnOB(3qNyjH$~s˧~8ԯ&ˬ<\(.:s%5(p3:yt
e^;DGY'd~T1GP1kkDs#bj /R!r0PU
fRϪ\)p٢4l*%:H'zmᠯ<`NjNe%l\y2k(7Rm%fDd⺲T0G%z;*&e_2֏SLZvv$_զ76!#r?mc2Up]gU;zgǝCTY jēnJ    BQ,$@ŶZxr$Q,.d+9VI\^)[pPXh*@z8vYáY"$Ǹ*
üK;㡯5*ƈ#A`sJ*]_"M=L\ǖ֗d'×^y2uR[DdA!`
aLQ$HQJOe,ڰP*x=]J%. "!hI&NX2T>rp4[=12=]P%*D(wT%~nZLAMdW~@I\E3.99.5LAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUd|HUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUd|HUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUd|HUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUd|HUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUd|HUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUd|HUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUd|HUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUd|HUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUd|HUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUd|HUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUd|HUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUd|HUUUUUUULAME3.99.5UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUd|HUUUUUUU0aAۇL>!*d2XqD ` 1 @l|9P $+t0\F f0N_6b18a`1HT:a`d|H@wp@0
0:pLOwfĨ.fV\O`,1qMLKBȰ@-    LJ91G{`/#UT1FSj-m3C}bSd99AU
3#^d0]sP3ƀDVd1G8Na&a?(vfPc|\hQMdqD0iA   yQH 6DRH\f*0BO
ܾhl\ERdĚ5k_*]eJ,e@0h(:Ko&ɲi&QU>
Zn#ET&I]"r[SScv#yQG#ڭ% XiOiȵ4tjyѵdf{dG_8Ϧ˪Os1Ӫ~b}=@
uEH͋(|a}%ڮ[lr\ʢobx
Ç~>
[2MwۙrjdW>N<8#ZT})1sB
zȀs뫳AAsK#\R:Zh"BOR¯bw..\jk!,G6Ʉ%Bbh+|ۚfsN`\ЖL cCdh.t9xڽww1LeJ-OgBݑCH2(D`3S c...DaD4>-5i;t8cmr7`o-А    aI~_*d1${ڴE0jpA?D1ypr|H|JAQ˲8-2;[J22Ȁݝu|;DdE}^$C)   87ԬfS!nA{o1BJEId" 6E!ܻǜ2VwnqQy90I(Цa3]]]t0 _NgWI
eV3khuv[OcGyEMYP1중ƻր)~= &]d!>k
ߟ$Ѻ>
YaH
o>G]-կW+?fԬok!DXY,I(SrFH@$Ʌ2:2\Ŵ&(
<K2d4>*6l+_~5M
AAy'X84wSYƍ brTrʈ
eMw7N@ƭV]o!{ǹL"b]Bρh16dDPRJS_\n6aOPi,zL
:;%P5(ۍ9/   6qoD֏X01}p\u@TIKc`,R+L$YLOdS2^xľw8ɯiKR/QDsf}zo{MA%  gbU\}
Ⴠʨ-':@qt>$(QM{Hɱ"]<~O_^dc>zGHX].S
ۘ%VFr(@Q,p@mSfO{u9 *}u}ִOt#ws>qd$Ttޢ,(ld{TɊ*kJ3r_RU˶6~*^ba..-:C٦]}?*6O*8WBFlHU1.st/dČ\
Ix  )v1S3m{P.:6\>Q2t̰kQ6ȿ[8tˎ!ڑ*]O.''#vZZZ,In>5u*P*pNc0Wbi(
c4  iCdĕ

ž8X6O5!HH%TGZDoYJ_r
-7{
.D2YaWoU]JG+d;*5A.{9S`@VX
8cLML?dģ!#]zYyAHLH:VؠT<jN!YSX]E`Y־WzP>̺>ӿr'V+`;pJ)A6mA@RJ&Qdġ!W[@"Cn
X&X4"To;2gr3m#0iȖ
Ϙ*ǚִn|?^$y<1^w-R3 ,7ASFqKCdWOWUrdĝ5Mx)O>>7
ŷ3}_5ky+1yC7Y^qD\<<9l@5K("YʕItj"M51ĐfbtL8nxAdH,t:hOG_AJ$.Bj^j#-yL)#!,
rD>VGVvL-k&/Z̒OJMK&f|ţ֟P$G"g   }Q
Idƾ~\C1Xөbfd8&u>nXAGۨO`CDrTPE)B>_OE-ݤ ?jd&^oW$Q;Wmad C~OBOazRp0TF0}޿Eҳ'ݷduĎQs9ḨawWH]dRXԬ~ZXH%MKg)DRd2ܲ)~o:ԷwFld"sl$DLSR?ӷ
YdI8    U8%sY %~p̎]kYSkz-1@pMܟډ9$䳳To4d@bdm[0dϺjjy(תj^_6ݷؤĮ
 "_F(]$P0P@U/fFȠv68%atvaː^@=80pse$]L%@ 1dM/:`lTږI\
hNhv
L`-4/oִWTʂ"ȧ~p($':|0403['4yP5*=zi֕@Wf\ysacd!#6+@Y\qoU,\|pa9aQ<M ѱkXCmuk$EqAP8$#ozo׋Zo׋&8yOohHiVq9dz~(s%WisOPjWIg9/5T,jD-XeQ@H҆!6>LJ%i^zPY|jݢlew<b6@V-K0rՠօLd#ڥtEɟʲ&<ܻN$U-Gӎ?H -/sÀ֑@MjWBi9x%<'Bqb [LP   d2r^HPNB"Q`PPqN.A"R!`AqqsvOSQՃTRMB3M(|&e^L4<6bإA=Qt#1gBP_dC^
OS;OYȏ4yϿaIJG   g橣vs۩[44&?IGJ%"Lη/;_߶e!Þ_j! YSyQ5VIk @q`dO4;^n  'ÃzL$v"4_I.yKvQɕ㥕Vy'ͱtvb1얰Qp@iң;j}){|n[V%FCf-}r:]G3eG&0PQ
Ddm+ZY_fwkljbFzGP9Rjr(T :
V2
2/nܳPZ\oj>gԲiʆ*~,*)c1RT:Z%qoZvnޞg{e VIdH֐>,B뤋i^v>+vTL#
C᤬бaXeDM,gGR'_n<|5bH:ࣃ\Un
Umm^C۩Cj@4e39
ق2dėɒZy~=LՈ
ՊC7WQ{+ۤH0\
e6kΜcGe}K]u^;Tk 6tmbD8sM    Ʒb?
Wmi 8dĦ%YZȟ``9#(I   xP9,(Lܲy@M2z 
08bh`C,[ckZv7OU'6:Brm){ׁ,#_dēI>XXO^1JrmLe3I}syL͂%NgFZ{Lؕ}T|xE$]K.U{ !#  3m?]lGjV{0Ɏ|\w-W!3ݠ]qoXdĨq6Dc)B_ݕfK<U&2nYݵ<",XQ*b8w%&:&X5IesLBsh{IAC0ݲ⯩^    ̨0:/rpcdĽ>348D;r؍ӯ*\s0}B%   PyT6n*y(MUuj?=\)Ît^q,gg1l)d"KC 8%s(E2 ʁF t2!<:
:0<k¤5\!JSv ΅JDI[=12=]ŤSIB!7_'"k"XHAyԪU4Qh^<=Od"{>{;ُO>Da Ly7\F
z`A!2Is ޿s6 ՌHq;"r>QH-Fz)Q|B1t߬*_)Ld%c:L    J^  ۧ+65d
a|:Ppr]is2=V;nfDkŮdh\%h%@!0`vcJͲ?cj_d{ݞyB^o2q\:G
)E0)иzH_Ff(=Z1<Â/òPm[@8!(NCViE@6+kXmΙ*SL7
ƵUi_S?+D5sd!ɅLd
a3m6    
hDdj$؉տ}/g#Z
C測AC"
(-13~71&8I*Hh.ͬY4td\
Z@$$"]MMv,7{zbToShI&&)%uvmk
n"35j_KO֖Cfwf;C) dR&d=k6rKu3s2%Cȝ/ɻ>X%·CJ6<
IXkYj=dN
CA`hyVX%
x8Wo MvV͊SD^)W*0V0RXR+dĆ9jOX,tj˚àQ̆iQãAJnV6tΑc
(Äj~֥_ۛ'9ĬgPL,֯q)e7\PG
>Ċ@۾6'w}MEdĔJpSL(A=@s`e0pd09<R
fny5bhO.
)\!:]"XZge;!/4sD"LSH%ԍQsuNOR]p\M$\bdl67dħ5._ݐZihN uN'fFP
erE0Im=s
 =rD3SgY,VqVE5u+mc:n
0<CH Gq-T0XNfbۤMV_QmdS!f؀htYkcd_M*4IP11v8/ՄFA*JNknhc9)J _G
96ఀܡٌIB`'$AJikӈ5rAjR4dO":f^Bʢ堠xHY?zTюp.>L((h

F@
08.|@t +sik
MY&'j`*X'%  (dIE**bDX9i[}Q?Msru^<];X7!ʔfR(os,2S is
ƋUY4"~]t=~YMoyU`GhdYR^˪p"ㄠl2;X8&zwBjJyAwjB^&-Dgߘpbj["oQٹKjV[t)Bc,mO?սJke
ciT~ndj>DҶ+`-a[V?+uBrGjKjаw%R?% odxD6[;g+p&/`Tf\S7CULOoҖ    nku+3""J* @s-dyj:~yF%@ADBB"@:o
_MޔBDsܓ$pc  #;g|F:܊b#Ë;$maZE$jdĊc^C?z?׹BNthH\G#A:@CQ
H   &gJ
    fmֽUSddsI5k2PUffg-KoRfga{ffݙ_j$dė:h$;L4'ݸpT'<+ܨ\IG-tvǶFq;[JQ}KRZx5h <0`2:|P8[RV#dĥ:2v;pM!9IP&P
ډsCCSzDIg_hO=9 B co|meb"ϐe)fQuJ7fr]ꚶjQdĵG{%PRA'VJy^;PYD\*
    RߘkF`/(  EJfHX\uMNH" xjVϫ9*0:5V2Q\Md2D\Õu,Y)T6;cp4]8\[gE\FF!;uJݼ
T3%OeńIhտb  +MEDCÿ2_x{^BeدOd9.
fMHS?]+˼ϙ`,y
!{OC5*(i:V.f{9M\}@?I7#Nݸzx<X`襳9;Fa(*d"HHD%(,(F8*H廑S/L!
GS܋ 8h69[>!r!Т$H(V<S.Z%'    I
;<87)֊
dI6F(P{')?nQ<( P+b
huiUDǕCG@b  }p1ކ/C?iuc/}NuY6TףsH1ů'ʓpB+f&Q7tReZVd!Vw׫:!LfH,q-*i"bA
GEەI:IK.Qңs_n׹[yzWv5B>V˗+ašd V,|;ݙJ)XFCX4;-PZCƁ%
E@+4G<vExlj מ%?M8poz9[GVr%S7_?d +DؘBc;YXC^ei$`hYR<eX7
d*dm5g>uPzlROtZ ]r  Җ}BIF̼SR4OԽd ╖ʸ6#3)}KF?BΨa1+UGejLxuPH Lem#mUWKsN{7BexhxuxULq})^iNO"oD,[=6nY_d ]J\'ZߔtX|ONYGaHNa*#-HwAVŷזAysR^ISׯOon1V(cyF6"ڬ5~C]dz_)jϋ#Kd .6X_Qɬn7u3NF_gIۻK$g=4;0t
J9gˎa:!X`D*J&w.S*5EZHdsw"`W9d-:ɞ
Zw}$PqbYįb<U9T(!Llȴͧ]{4sP:vԅ!~1YRa]+Ri@T>]0mHI]dĴ'|V
jRq%
vpbAr}4&ىg'X:40KbIh瑏XA"}F9b?̀*8T,@Ta$,Ya"h_dę$#
~EHkv1]֥ӱ1yZxXqy-4֏K:Ճ:TNDP{ScR,QF"'F`.R *RATsq܀d±ݬdċ!+^lZ;:T,1j
f3V˹ݻ?YuW>5(Jrf3tABG$Ot"Ψo̴3d-0#t{oJ *7mq37<0Ddĉ>M#OҬ4I6aNTyTsWi[!̥d,oYJL]A\; Ti_5$Ua!'L]@r57,yi4"hoMuӵ(_?
@P9gdĝzD@5^X=v;jD0k-XΏF&gR|3[_׼MYnRVន%Es?e#܅BiSXWo(qlܖQmukݙBU^  "+10dĪXC{BUf'*']REJ,]WX~[-hH2
C@\ꄶ&@i{0a1nC98G.>dĹX]X*#:P|O<lԴW|

Pe%r!A<r|f      1,B`H, 8FLAMu9udC_[d<bUdҊ豋d5:_*)ZFInqՁFM$L-ɀ7
LՑ}>+<?H/dӠrJdDj/jZ~s2^͉u>.[5qWM zvXu*sE&&B0VͯoZ'KABr#dwن0/7"qǚ0K`9kft));9V侗'7
[d*2H3MS{;&hx蟑=
2%'^٢,c.!Y1bj6[dĆD*6O}:hT#XX>KzĹV
&?N e8 %
QtcR-s__+"!ùۅJ^N]Q=M2Ѥu-ZpbdČ#D6R+`4ڷ ʛ+,P|GO+
dEL1\~+F}*C#L
q3
:
&*(?cPƘ&s,b=gQ5O$ny% }kh1dĂ!^|Ka5WC-n-HK5g55&
Rnǝ'I,!̡Ӈg]rەzKB?#@fwǀaXg15)/dNDVcbW0jח4c_Ϭ{G)x+[gHʝ
HB
@^7"@wx{bkSt1X#.?@`a[{/ 9#T;)mS!Ⱥj$TTXdē6xzs۹&0Eg4VvTkн=c@lj9ڢ2OwH3X.c]IFKk것³΋-[B.*P;dĤĢ)ZEs]IS_5hC_][
?1){܊E4:9F<MvsU˹X#/+R()Šq:F_9Ns2eCD\IRdj|$DĐQOdĴN

:c)>Tm}ۦWov{zU)Y|twQmxߨj>GeRyl1u"</wbr2--*o*hs(Scь
[:<_}[dľ#y9oVv4V
fKM@P82VuX}Vh8FaUD$v0ceAT>WNՂyg3=ncu<T*
D@֠ds6
D,
N2VY*yen5Xt"(1HAar%*LMzDfW99FCnRAqzp`hÊxA1C ѫViAqd޷H̼Gp^_N+NUxvX;ҫJALb92ܵhgo>fYNj5={Z3ӉbYw&PEI(o5D2d IaIvgrQs~by!")|Di:RdžlE%s#|E@d3H3zfPhlsL&et3d)D3q"Z*GVHܖf(P98@z A`b|(0| H;-}!!df>܅rNV1jV7dN,;*d!xöޕy{ }QCS"21jP:FvVSEnjyۘ/%h/MpuzϚ0}n`WʁlVv@\qoO͏5+9߉e`4$WPՅd لm&óf-(}n
;CRȶu| 1xٚx-T^dBQ=k
#,:*]~L"8\OzeLBN{
H^EM]bV FoʸAnNSܡrdi),:[ {*Jd{$V
Êܒn*BlFFTd]r)
lYυs~NѦ_?<j7eH#@}4iNv!DidJ6ѣ}d!֔'xzu:
H!0`8CC`    ƀLAD0/?&Sy!U^a<YR+#E0,2XF͡jJS(KZw1i1V8ҋN^Qd.aKuDˈNY J&@
C(kM8V8`e
($V܌9##ɟb*өcIiU%hÒYWP(bI!d#Rbhʼ.<`cy
`-w|/ӄ_n098'.0ؠ|SJP{K "z߸u!6TQҥ|M2'bw꾔>'evסsd!:bd-, o]~jZՂ.1)ħ@c pphXp-`Q
"DXI{ێˑsD!P:T@YGhFC CU`uL2w߬V/מz-gvdz`(ļ8tnKOC{&\(Cle4[I%xPli   !mxlc3/rV諐.JO=r<*gޛ
̢zUN;
P{N3k
P!Lo&Cn'~DRd"dWQs{ohtN.Tk#[r/K_3"q꽍plSU4fba&p*sNyq՞ Yצ)׾TM*d;L6]xZrz9E:ae,ZÆ1:;k;<[JS4צ/Ǧ?=_WS~"=FPɩ*>ĩ\k\LBe/Zdă4d:xs+"VIԨ
ƃx*;\&,2wAd7j9%}G4~5YOhDMζ!.ڧbfʱ3R4!ipL8^AsG2-Q!d4)sZyP<8,:VeDA̝EpY8SBOِCռ]\]ClWcgQf.BPA~ga9wٻD2^c:xfXdPP˵ƈildr@Khn+īo=g    0^h)dob@cLDD!<麱_K1"cS0YHAB{( 
!o=g(^E$%ERYdHA)'iL
pʧ8X&/T|;SȎTsj3203N_4l?|3_'TwHÞ;|>JxVlj+1-@K}$iLw]iDad%~a^IiXGܼwB2ʼn!Ͱȧ2YhY;?" *EǂN7FXpC$,~`K=xwmqDTDh.ufW+K:}DbH5~gj@Ȣ>Bd4Kj0JU]9[}#'I[=]>R~ߩDžG#CTF&jNwge]kqR䋌,,?K&*eH   
Hp3tUDsWje(Z=01dEH;e4+JJݷk[e`S$X[ewxݵ(Li!6EM
26`'q9Ăn֫SoeOGQqdU2
FԓgJt   nnLRkF"Veߜ
}WpcrNQ{K͛{lK$/p<w!#I#)$Kޱb;b'R%dgF]O0xwS<j2*!L5hT9I'%ʮO
J;ZT4PCd2m{22Rj
R2611r;%PmDY2ENe"Nwdz3T1cȢL@@ކ
$B<@ȩ:ELHDJzɥU1.
,A0""]!eQ&9U>2
(.YVfUST)KJgKGoRRd-{1b1jRJR)t1ig1))JR1gR/C~`E(
LAME3.99.5dBHLAME3.99.5d|HLAME3.99.5d|HLAME3.99.5d|HLAME3.99.5d|HLAME3.99.5d|HLAME3.99.5d|HLAME3.99.5d|HLAME3.99.5d|HLAME3.99.5d|HLAME3.99.5d|H
--------abcde123--

我想通了,我正在为 OKHttp 使用 LoggingInterceptor,它用于将字节转换为字符串并记录它,由于某种原因,它无法识别某些字符,并为这些字符提供了一些垃圾值字符,并且由于某种原因,这些值也保留在字节数组中......这曾经给出格式错误的 mp3 文件。删除拦截器后...它开始工作...所以第 1 步已完成...更多内容。