Androidで
スクリーン配信をする技術

 Androidで
スクリーン配信をする技術

DroidKaigi 2020
https://droidkaigi.jp/2020/

Cb4d62dd7c1d325863696c45e9b6484d?s=128

Shumpei Urabe

January 23, 2020
Tweet

Transcript

  1. "OESPJEͰ εΫϦʔϯ഑৴Λ͢Δٕज़  ߹ಉձࣾφΫα ઎෦ॡฏ

  2. ࣗݾ঺հ ͜Μʹͪ͸

  3. .FEJB1SPKFDUJPO"1* .FEJB1SPKFDUJPO.BOBHFS 7JSUVBM%JTQMBZ .FEJB$PEFD ө૾ϑΟϧλʔ ը໘ճస "VEJP1MBZCBDL$BQUVSF ΋͘͡

  4. .FEJB1SPKFDUJPO"1* .FEJB1SPKFDUJPO.BOBHFS 7JSUVBM%JTQMBZ .FEJB$PEFD ө૾ϑΟϧλʔ ը໘ճస "VEJP1MBZCBDL$BQUVSF ΋͘͡ #"4*$ )"3%

  5. .FEJB1SPKFDUJPO"1* 

  6. None
  7. None
  8. None
  9. None
  10. None
  11. .FEJB1SPKFDUJPOΛߏ੒͢Δཁૉ .FEJB1SPKFDUJPO.BOBHFS 7JSUVBM%JTQMBZ 4VSGBDF7JFX .FEJB$PEFD "VEJP1MBZCBDL$BQUVSF 0QFO(-&4

  12. .FEJB1SPKFDUJPOΛߏ੒͢Δཁૉ .FEJB1SPKFDUJPO.BOBHFS 7JSUVBM%JTQMBZ 4VSGBDF7JFX .FEJB$PEFD "VEJP1MBZCBDL$BQUVSF 0QFO(-&4 JUJT%JGGJDVMU

  13. .FEJB1SPKFDUJPOΛߏ੒͢Δཁૉ .FEJB1SPKFDUJPO.BOBHFS 7JSUVBM%JTQMBZ 4VSGBDF7JFX .FEJB$PEFD "VEJP1MBZCBDL$BQUVSF 0QFO(-&4 /P OP :PVDBOTFFUIJTTMJEF

  14. .FEJB1SPKFDUJPOͷεςοϓ .FEJB1SPKFDUJPO .BOBHFS 4VSGBDF7JFX DSFBUF7JSUVBM%JTQMBZ

  15. DSFBUF.FEJB1SPKFDUJPOҎ߱ͷεςοϓ DSFBUF7JSUVBM%JTQMBZ .FEJB$PEFD 35.1FUD *NBHF3FOEFS #JUNBQFUD

  16. 4FDVSJUZ

  17. 4FDVSJUZ 3&26&45@.&%*"@130+&$5*0/

  18. UBSHFU4EL7FSTJPO "OESPJE <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="your.package.name"> <uses-permission android:name="android.permission.FOREGROUND_SERVICE"

    /> <!-- লུ --> </manifest>
  19. UBSHFU4EL7FSTJPO "OESPJE .FEJB1SPKFDUJPO͸'PSFHSPVOEͰผϓϩηεͰಈͨ͘Ίهड़ඞਢ <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="your.package.name"> <uses-permission

    android:name="android.permission.FOREGROUND_SERVICE" /> <!-- লུ --> </manifest>
  20. 6TFDBTFT 4DSFFO4IPU -JWF4USFBNJOH

  21. .FEJB1SPKFDUJPO.BOBHFS 

  22. (FU1FSNJTTJPO

  23. DPNQBOJPOPCKFDU companion object { private const val REQUEST_CAPTURE = 1

    private var projection: (MediaProjection?) -> Unit = {} val run: (context: Context, (MediaProjection?) -> Unit) -> Unit = { context, callback -> projection = callback context.startActivity(Intent(context, MediaProjectionModel::class.java).addFlags(Intent.FLAG_ACTIVITY_NEW_T ASK)) } }
  24. HFU4ZTUFN4FSWJDF class MediaProjectionModel : Activity() { private lateinit var mediaProjectionManager:

    MediaProjectionManager override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) mediaProjectionManager = getSystemService(Service.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager startActivityForResult(mediaProjectionManager.createScreenCaptureIntent (), REQUEST_CAPTURE) }
  25. TUBSU"DUJWJUZ'PS3FTVMU override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {

    if (requestCode == REQUEST_CAPTURE && resultCode == RESULT_OK && data != null) { projection(mediaProjectionManager.getMediaProjection(resultCode , data)) } finish() }
  26. "MMPX.FEJB1SPKFDUJPO4FSWJDF .FEJB1SPKFDUJPO .BOBHFS 4VSGBDF7JFX DSFBUF7JSUVBM%JTQMBZ

  27. 7JSUVBM%JTQMBZ 

  28. "OESPJE7JFX 7JFX 4VSGBDF7JFX 5FYUVSF7JFX

  29. "OESPJE7JFX 7JFX 4VSGBDF7JFX 5FYUVSF7JFX ߴ଎ඳըʹ͸޲͍͍ͯͳ͍

  30. "OESPJE7JFX 7JFX 4VSGBDF7JFX 5FYUVSF7JFX ߴ଎ඳըʹ͸޲͍͍ͯͳ͍ "OESPJEͰඇਪ঑

  31. "OESPJE7JFX 7JFX 4VSGBDF7JFX 5FYUVSF7JFX ߴ଎ඳըʹ͸޲͍͍ͯͳ͍ "OESPJEͰඇਪ঑ )JHI1FSPSNBODF

  32. 4VSGBDF7JFX ௨ৗͷ7JFXΫϥεΛܧঝͨ͠Ϋϥε 6*εϨου͔Βಠཱͯ͠ॲཧͰ͖Δ (-4VSGBDF7JFXͰ0QFO(-Λ࢖ͬͨඳը΋Ͱ͖Δ

  33. 7JSUVBM%JTQMBZ View

  34. 7JSUVBM%JTQMBZ View VirtualDisplay Mirroring

  35. 7JSUVBM%JTQMBZ VirtualDisplay 4VSGBDF7JFX *NBHF3FOEFS #JUNBQग़ྗ༻ͷ4VSGBDFʹίϐʔ͢Δ ผͷ4VSGBDF7JFXʹίϐʔ͢Δ

  36. *NBHF3FOEFS fun setupVirtualDisplay(): ImageReader? { val width = (metrics.widthPixels *

    scale).toInt() val height = (metrics.heightPixels * scale).toInt() val reader = ImageReader.newInstance(width , height, PixelFormat.RGBA_8888, 2).also { it.setOnImageAvailableListener(this, null) } virtualDisplay = mediaProjection?.createVirtualDisplay( "Capturing Display", width, height, metrics.densityDpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, reader!!.surface, null, null ) callback.changeState(StatesType.Running) return reader
  37. *NBHF3FOEFS override fun onImageAvailable(reader: ImageReader) { reader.acquireLatestImage().use { img ->

    runCatching { heepPlane = img?.planes?.get(0) ?: return@use null val width = heepPlane.rowStride / heepPlane.pixelStride val height = (metrics.heightPixels * scale).toInt() heepBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).apply { copyPixelsFromBuffer(heepPlane.buffer) } callback.changeBitmap(heepBitmap) } } }
  38. #JUNBQ3FOEFSJOH DSFBUF7JSUVBM%JTQMBZ .FEJB$PEFD 35.1FUD *NBHF3FOEFS #JUNBQFUD

  39. 1JYFM'PSNBU 1JYFM'PSNBU3(#"@ 1JYFM'PSNBU3(#9@

  40. 7*356"-@%*41-":@'-"( ఆ਺໊ આ໌ 7*356"-@%*41-":@'-"(@"650@.*3303 ίϯςϯπΛϛϥʔϦϯάදࣔ͢Δ 7*356"-@%*41-":@'-"(@08/@$0/5&/5@0/-: ಠࣗͷίϯςϯπΛදࣔɻϛϥʔϦϯά͠ͳ͍ 7*356"-@%*41-":@'-"(@13&4&/5"5*0/ ϓϨθϯςʔγϣϯϞʔυ 7*356"-@%*41-":@'-"(@16#-*$

    )%.*΍8JSFMFTTσΟεϓϨΠ 7*356"-@%*41-":@'-"(@4&$63& ҉߸Խରࡦ͕ࢪ͞ΕͨηΩϡΞͳσΟεϓϨΠ
  41. "MMPX.FEJB1SPKFDUJPO4FSWJDF .FEJB1SPKFDUJPO .BOBHFS 4VSGBDF7JFX DSFBUF7JSUVBM%JTQMBZ

  42. .FEJB$PEFD 

  43. .FEJB$PEFD .FEJB'PSNBUͰࢦఆ͞Εͨ΋ͷΛΤϯίʔυͨ͠Γ σίʔυͨ͠Γग़དྷΔ"1* $BMMCBDLΛ࢖͏͜ͱͰඇಉظͰॲཧ͢Δ͜ͱ͕ग़དྷΔʢਪ঑ʣ

  44. .FEJB'PSNBU val mime = MediaFormat.MIMETYPE_VIDEO_AVC val format = MediaFormat.createVideoFormat(mime, width,

    height) //ϑΥʔϚοτͷϓϩύςΟΛઃఆ //࠷௿ݶͷϓϩύςΟΛઃఆ͠ͳ͍ͱconfigureͰΤϥʔʹͳΔ format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface) format.setInteger(MediaFormat.KEY_FRAME_RATE, 30) // LolipopͰ͸͜͜͸ແࢹ͞ΕΔ format.setInteger(MediaFormat.KEY_BIT_RATE, 1000 * 1024) format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10) format.setLong(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, 1000000L / 30)
  45. .FEJB'PSNBU ϓϩύςΟ આ໌ ,&:@$0-03@'03."5 ΧϥʔϑΥʔϚοτΛࢦఆ͠·͢ɻ4VSGBDFͷ৔߹͸ωΠςΟϒ3"8ϑΥʔ ϚοτͰ͋Δ$0-03@'PSNBU4VSGBDFΛࢦఆ͠·͢ɻ ,&:@'3".&@3"5& ϑϨʔϜϨʔτΛࢦఆ͠·͢ɻGQTͰΤϯίʔυ͢Δ͜ͱ΋ग़དྷ·͕͢ɺ ݹ͍୺຤ͩͱΧΫΧΫʹͳΔͷͰGQT͋ͨΓ͕͓קΊͰ͢ɻ ,&:@#*5@3"5&

    ϏοτϨʔτΛࢦఆ͠·͢ɻ.FEJB$PEFDͷ)Τϯίʔμʔ͸࢒೦ͳ ͕Βѹॖޮ཰͕Α͘ͳ͍ͨΊɺߴΊʹઃఆ͠ͳ͍ͱϒϩοΫ ϊΠζ͕ग़΍͢ ͍Ͱ͢ɻ ,&:@*@'3".&@*/5&37"- ΩʔϑϨʔϜ *ϑϨʔϜ ͷִؒΛઃఆ͠·͢ɻ௨ৗ͸ϑϨʔϜຖͰ͍͍ ͱࢥ͍·͕͢ɺΩʔϑϨʔϜִؒΛ୹͘͢Δͱ஗Ԇ͕গͳ͘ͳΓ·͢ɻ ,&:@3&1&"5@13&7*064@'3".&@"'5&3 4VSGBDFͰϑϨʔϜ͕͜ͳ͍৔߹ʹɺલͷϑϨʔϜΛ܁Γฦִؒ͢Λઃఆ͠ ·͢ɻ
  46. .FEJB$PEFD$BMMCBDL codec = MediaCodec.createEncoderByType(mime) codec.setCallback(object: MediaCodec.Callback() { override fun onInputBufferAvailable(@NonNull

    codec: MediaCodec, index: Int) { Log.d("MediaCodec", "onInputBufferAvailable : " + codec.codecInfo) }
  47. .FEJB$PEFD-JTU val codecName: String? = if (Build.VERSION_CODES.LOLLIPOP <= Build.VERSION.SDK_INT) {

    MediaCodecList(MediaCodecList.REGULAR_CODECS).findEncoderForFormat(mediaFormat) } else null return if (codecName != null) { MediaCodec.createByCodecName(codecName) } else { MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC) }
  48. .FEJB$PEFD$BMMCBDL override fun onOutputBufferAvailable(@NonNull codec: MediaCodec, index: Int, @NonNull info:

    MediaCodec.BufferInfo) { Log.d("MediaCodec", "onOutputBufferAvailable : $info") val buffer: ByteBuffer = codec.getOutputBuffer(index) val array = ByteArray(buffer.limit()) buffer.get(array) //Τϯίʔυ͞ΕͨσʔλΛૹ৴ Send(array) //όοϑΝΛղ์ codec.releaseOutputBuffer(index, false) }
  49. .FEJB$PEFD$BMMCBDL override fun onError(@NonNull codec: MediaCodec, @NonNull e: CodecException) {

    Log.d("MediaCodec", "onError : " + e.message) } override fun onOutputFormatChanged(@NonNull codec: MediaCodec, @NonNull format: MediaFormat) { Log.d("MediaCodec", "onOutputFormatChanged : " + format.getString(MediaFormat.KEY_MIME)) } })
  50. $POGJHVSF //ΤϯίʔμΛઃఆ codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE) //ΤϯίʔμʹϑϨʔϜΛ౉͢ͷʹ࢖͏SurfaceΛऔಘ //configureͱstartͷؒͰݺͿඞཁ͋Γ inputSurface =

    codec.createInputSurface()
  51. 7JSUVBM%JTQMBZ VirtualDisplay 4VSGBDF7JFX *NBHF3FOEFS #JUNBQग़ྗ༻ͷ4VSGBDFʹίϐʔ͢Δ ผͷ4VSGBDF7JFXʹίϐʔ͢Δ

  52. 7JSUVBM%JTQMBZ fun setupVirtualDisplay(inputSurface: Surface) { val width = (metrics.widthPixels *

    scale).toInt() val height = (metrics.heightPixels * scale).toInt() virtualDisplay = mediaProjection?.createVirtualDisplay( "Capturing Display", width, height, metrics.densityDpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, inputSurface, null, null ) callback.changeState(StatesType.Running) }
  53. .FEJB$PEFD&ODPEF DSFBUF7JSUVBM%JTQMBZ .FEJB$PEFD 35.1FUD *NBHF3FOEFS #JUNBQFUD

  54. ө૾ϑΟϧλʔ 

  55. 4VSGBDF7JFX ௨ৗͷ7JFXΫϥεΛܧঝͨ͠Ϋϥε 6*εϨου͔Βಠཱͯ͠ॲཧͰ͖Δ (-4VSGBDF7JFXͰ0QFO(-Λ࢖ͬͨඳը΋Ͱ͖Δ

  56. 7JSUVBM%JTQMBZ View VirtualDisplay Mirroring

  57. 7JSUVBM%JTQMBZ View VirtualDisplay Mirroring Mirroring OpenGL ES (Shader)

  58. (-4VSGBDF7JFX 4VSGBDF7JFXΑΓ΋ߴ଎ʹॲཧͰ͖Δ "OESPJEͰ͸&(-Λ࢖ͬͯ0QFO(-ͱϒϦοδ͍ͯ͠Δ 4IBEFSΛॻ͘͜ͱͰө૾ϑΟϧλʔΛ͚ͭΔ͜ͱ͕Ͱ͖Δ

  59. (SBGJLB

  60. (SBGJLB (PPHMF͕։ൃͨ͠άϥϑΟοΫε"1*ͷαϯϓϧू "QBDIF-JDFOTFͰެ։͍ͯ͠Δ

  61. TFUVQ&(- fun setupEgl(surface: Surface) { eglCore = EglCore() windowSurface =

    WindowSurface(eglCore, surface, true).apply { makeCurrent() } defaultTexture2dProgram = DefaultTexture2dProgram( DefaultTexture2dProgram.TextureTarget.TextureExt ) blurTexture2dProgram = GaussianBlurTexture2dProgram( outputWidth = outputSize.width, outputHeight = outputSize.height ) texId = GlUtil.createTextureObject(GLES11Ext.GL_TEXTURE_EXTERNAL_OES)
  62. TFUVQ&(- texId = GlUtil.createTextureObject(GLES11Ext.GL_TEXTURE_EXTERNAL_OES) GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR.toFloat()) GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR.toFloat())

    sprite2d = Sprite2d(Drawable2d(Drawable2d.Prefab.RECTANGLE)) .apply { setTexture(texId) setPosition(outputSize.width / 2f, outputSize.height / 2f) setScale(outputSize.width.toFloat(), outputSize.height.toFloat()) }
  63. TFUVQ&(- sourceSurfaceTexture = SurfaceTexture(texId).apply { setDefaultBufferSize(renderConfig.width, renderConfig.height) setOnFrameAvailableListener { synchronized(hasRenderFrame)

    { hasRenderFrame = true } try { updateTexImage() } catch (e: Exception) { Timber.e(e, "failed to updateTexImage.") } } } sourceSurface = Surface(sourceSurfaceTexture) }
  64. TFUVQ7JSUVBM%JTQMBZ fun setupVirtualDisplay() { virtualDisplay = mediaProjection.createVirtualDisplay( "screen_capture", renderConfig.width, renderConfig.height,

    renderConfig.dpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, sourceSurface, null, null ) }
  65. ܨ͍͛ͯ͘ setupEgl(surface) setupVirtualDisplay() renderDisposable = Observable.interval( 0L, 1000L / renderConfig.frameRate,

    TimeUnit.MILLISECONDS ).observeOn(AndroidSchedulers.from(Looper.myLooper())) .subscribe({ drawFrame() }, { Timber.e("draw frame error") }) rotationChangeDetector.addListener(onRotationChange) notificationReceiveDetector.addListener(onNotificationReceive)
  66. ϑϨʔϜ͕དྷͨΒESBX͢Δ private fun drawFrame() { synchronized(hasRenderFrame) { if (!hasRenderFrame) return

    hasRenderFrame = false } windowSurface.makeCurrent() GLES20.glViewport(0, 0, outputSize.width, outputSize.height) if (blurStatus.shouldBlur()) { sprite2d.draw(blurTexture2dProgram, tmpMatrix) } else { sprite2d.draw(defaultTexture2dProgram, tmpMatrix) } windowSurface.swapBuffers() }
  67. ը໘ճస 

  68. ը໘ͷճసΛݕ஌͢Δ override fun onReceive(context: Context, intent: Intent) { val windowManager

    = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager val angle = when (windowManager.defaultDisplay.rotation) { Surface.ROTATION_0 -> 0 Surface.ROTATION_90 -> 90 Surface.ROTATION_180 -> 180 Surface.ROTATION_270 -> 270 else -> throw IllegalArgumentException("unknown rotation.") } listeners.forEach { it(angle) } }
  69. 0QFO(-ͰॎԣൺΛ߹ΘͤΔ private fun updateRotation(rotationAngle: Int) { val diffAngle = rotationAngle

    - renderConfig.rotationAngle val (w, h) = if (Math.abs(diffAngle) == 270 || Math.abs(diffAngle) == 90) { val scale = if (outputSize.width > outputSize.height) { outputSize.width.toFloat() / outputSize.height } else { outputSize.height.toFloat() / outputSize.width } Pair(outputSize.width * scale, outputSize.height * scale) } else { Pair(outputSize.width.toFloat(), outputSize.height.toFloat()) } sprite2d.setScale(w, h) sprite2d.setRotationZ(diffAngle.toFloat()) }
  70. "VEJP1MBZCBDL$BQUVSF 

  71. "VEJP1MBZCBDL$BQUVSF "OESPJEΑΓೖͬͨ୺຤ͷԻΛϧʔϓόοΫͰ͖Δػೳ ϞϊϥϧͰ͸ͳ͘εςϨΦͰग़ྗ͢Δ͜ͱ΋Մೳ .FEJB1SPKFDUJPOͷҰ෦ͱͯ͠ఏڙ͞Ε͍ͯΔ

  72. ྺ࢙ "OESPJEͱڞʹొ৔ͨ͠.FEJB1SPKFDUJPO ͦͷͱ͖ʹ"VEJP3FDPSEͰϧʔϓόοΫػೳ΋࣮૷͞Εͨ

  73. .FEJB3FDPSEFS"VEJP4PVSDF 3&.05&@46#.*9 ୺຤ͷԻ੠ΛϦϞʔτʹྲྀ͢ ͨͩ͠"OESPJEΑΓγεςϜݖݶ͕ඞཁʹͳͬͨ ࢖͑ͳ͘ͳͬͯ͠·ͬͨ

  74. ౪ௌΞϓϦͷྲྀߦ 3&.05&@46#.*9͸௨࿩Ի΋ΩϟϓνϟՄೳͰ͋ͬͨ ͦͷͨΊɺ(PPHMF͸"OESPJEͰར༻ෆՄʹͨ͠ ͔͠͠ɺ"OESPJEͰ"VEJP1MBZCBDL$BQUVSFͱͯ͠෮׆

  75. ੍໿ • DPNQJMF4EL7FSTJPOͷΞϓϦ͸σϑΥϧτͰΩϟϓνϟʔ͞ΕΔɻແޮ Խ͍ͨ͠৔߹͸"OESPJE.BOJGFTU ʹBOESPJEBMMPX"VEJP1MBZCBDL$BQUVSFGBMTF͕ඞཁ • DPNQJMF4EL7FSTJPOҎલͷΞϓϦ͸σϑΥϧτͰΩϟϓνϟ͕ϒϩοΫ ͞ΕΔɻΩϟϓνϟ͢Δ৔߹ ͸BOESPJEBMMPX"VEJP1MBZCBDL$BQUVSFUSVFΛ.BOJGFTUʹೖΕΔඞཁ ͕͋Δ

  76. "VEJP1MBZCBDL$BQUVSF$POGJHVSBUJPO public boolean prepareInternalAudio(int bitrate, int sampleRate, boolean isStereo) {

    if (mediaProjection == null) { mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data); } AudioPlaybackCaptureConfiguration config = new AudioPlaybackCaptureConfiguration .Builder(mediaProjection) .addMatchingUsage(AudioAttributes.USAGE_MEDIA) .addMatchingUsage(AudioAttributes.USAGE_GAME) .addMatchingUsage(AudioAttributes.USAGE_UNKNOWN) .build(); microphoneManager.createInternalMicrophone(config, sampleRate, isStereo); prepareAudioRtp(isStereo, sampleRate); return audioEncoder.prepareAudioEncoder(bitrate, sampleRate, isStereo, microphoneManager.getMaxInputSize()); }
  77. "VEJP1MBZCBDL$BQUVSF$POGJHVSBUJPO public void createInternalMicrophone(AudioPlaybackCaptureConfiguration config, int sampleRate, boolean isStereo) {

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { this.sampleRate = sampleRate; if (!isStereo) channel = AudioFormat.CHANNEL_IN_MONO; audioRecord = new AudioRecord.Builder() .setAudioPlaybackCaptureConfig(config) .setAudioFormat(new AudioFormat.Builder() .setEncoding(audioFormat) .setSampleRate(sampleRate) .setChannelMask(channel) .build()) .setBufferSizeInBytes(getPcmBufferSize()) .build();
  78. ·ͱΊ

  79. εΫϦʔϯ഑৴Λ͢Δٕज़ ಈը഑৴͸༷ʑͳٕज़ͷ૊Έ߹Θٕͤ ݱࡏͰ͸ଟ͘ͷϥΠϒϥϦʹΑΓɺ؆୯ʹ࣮૷͕Ͱ͖Δ ੋඇ͋ͱͰνϟϨϯδͯ͠Έ͍ͯͩ͘͞ʂ

  80. ँࣙ

  81. 5IBOLZPV