ano*_*eal 4 android alexa kotlin
我计划开发一个带有 Alexa 语音服务集成的 Android 应用程序来开发一个像 Reverb 的应用程序。以下是我尝试过的...
检查 AVS 设备 SDK,可以获得在 Android 中实现它的正确指南。
计划自己实现,下面是我所做的。
一种。集成登录框架,能够成功登录并获取令牌。
湾 创建了一个录音机,能够在本地录制和播放。
C。创建了将音频发送到https://avs-alexa-eu.amazon.com/v20160207/events 的请求
[更新]
将 shortArray 更改为 Byte 数组后,我得到了响应,但现在的问题是 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
}
}
}
Run Code Online (Sandbox Code Playgroud)
来自 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?ql\6d\&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\4 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 :$5WTGVNo+?@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\0P%*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
C?~>
[2Mw?rjdW>N<8#ZT})1sB
z?s?AAsK#\R:Zh"BOR¯bw..\jk!,G6?%Bbh+|?fsN\8`\?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
我想通了,我正在为 OKHttp 使用 LoggingInterceptor,它用于将字节转换为字符串并记录它,由于某种原因,它无法识别某些字符,并且它为这些字符提供了一些垃圾值,并且由于某些原因原因,这些值也保留在字节数组中......它曾经提供格式错误的 mp3 文件。移除拦截器后……它开始工作了……所以第 1 步就完成了……还有更多工作要做。
| 归档时间: |
|
| 查看次数: |
1047 次 |
| 最近记录: |