title: How to record audio on Android Q using PlaybackCapture APIs?
body: <p>I am trying to record audio on Android 10(Q) using <a href="https://developer.android.com/guide/topics/media/playback-capture" rel="nofollow noreferrer">Playback Capture</a> API. As <a href="https://developer.android.com/guide/topics/media/playback-capture" rel="nofollow noreferrer">Playback Capture</a> API, only allow to record sound with <strong>USAGE_GAME</strong>, <strong>USAGE_MEDIA</strong> or <strong>USAGE_UNKNOWN</strong>, so, I have downloaded <a href="https://github.com/android/uamp" rel="nofollow noreferrer">Uamp</a> sample that has <strong>USAGE_MEDIA</strong> set when playing songs. I have also added <code>android:allowAudioPlaybackCapture="true"</code> in the <code>AndroidManifest.xml</code>. Then I have launched the <strong>Uamp</strong>, start playing the song and keep it in the background.</p>
  
  <p>I have developed a <strong>CaptureAudio</strong> project with targetSdk 29 and installed it on my OnePlus 7 Pro which has <strong>Android 10</strong> installed. I have two buttons on the UI for start and stop the capture. When the application starts capture then read function fills all 0's in the buffer.</p>
  
  <p>To use <a href="https://developer.android.com/guide/topics/media/playback-capture" rel="nofollow noreferrer">Playback Capture</a> in the project, I have set up it as follows:</p>
  
  <p><strong>1. Manifest:</strong></p>
  
  <pre><code>        &lt;?xml version="1.0" encoding="utf-8"?&gt;
  &lt;manifest xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:tools="http://schemas.android.com/tools"
      package="com.example.captureaudio"&gt;
  
      &lt;uses-permission android:name="android.permission.RECORD_AUDIO" /&gt;
      &lt;uses-permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT" /&gt;
      &lt;uses-permission android:name="android.permission.FOREGROUND_SERVICE" /&gt;
      &lt;uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /&gt;
  
      &lt;application
          android:allowBackup="false"
          android:icon="@mipmap/ic_launcher"
          android:label="@string/app_name"
          android:roundIcon="@mipmap/ic_launcher_round"
          android:supportsRtl="true"
          android:theme="@style/AppTheme"
          tools:ignore="GoogleAppIndexingWarning"&gt;
          &lt;activity android:name=".MainActivity"&gt;
              &lt;intent-filter&gt;
                  &lt;action android:name="android.intent.action.MAIN" /&gt;
  
                  &lt;category android:name="android.intent.category.LAUNCHER" /&gt;
              &lt;/intent-filter&gt;
          &lt;/activity&gt;
  
          &lt;service
              android:name=".services.MediaProjectionService"
              android:enabled="true"
              android:exported="false"
              android:foregroundServiceType="mediaProjection"
              tools:targetApi="q" /&gt;
      &lt;/application&gt;
  
  &lt;/manifest&gt;
  </code></pre>
  
  <p><strong>2. MainActivity:</strong></p>
  
  <pre><code>class MainActivity : AppCompatActivity() {
  
      companion object {
          private const val REQUEST_CODE_CAPTURE_INTENT = 1
          private const val TAG = "CaptureAudio"
          private const val RECORDER_SAMPLE_RATE = 48000
          private const val RECORDER_CHANNELS = AudioFormat.CHANNEL_IN_MONO
          //or AudioFormat.CHANNEL_IN_BACK
          private const val RECORDER_AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT
          //  AudioFormat.ENCODING_PCM_16BIT
      }
  
      private var audioRecord: AudioRecord? = null
      private val mediaProjectionManager by lazy { (this@MainActivity).getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager }
      private val rxPermissions by lazy { RxPermissions(this) }
      private val minBufferSize by lazy {
          AudioRecord.getMinBufferSize(
              RECORDER_SAMPLE_RATE,
              RECORDER_CHANNELS, RECORDER_AUDIO_ENCODING
          )
      }
  
      override fun onCreate(savedInstanceState: Bundle?) {
          super.onCreate(savedInstanceState)
          setContentView(R.layout.activity_main)
          val intent = Intent(this, MediaProjectionService::class.java)
          startForegroundService(intent)
          getPermissions()
      }
  
      private fun getPermissions() {
          rxPermissions
              .request(
                  Manifest.permission.RECORD_AUDIO,
                  Manifest.permission.FOREGROUND_SERVICE,
                  Manifest.permission.WRITE_EXTERNAL_STORAGE
              )
              .subscribe {
                  log("Permission result: $it")
                  if (it) { // Always true pre-M
                      val captureIntent = mediaProjectionManager.createScreenCaptureIntent()
                      startActivityForResult(captureIntent, REQUEST_CODE_CAPTURE_INTENT)
                  } else {
                      getPermissions()
                  }
              }
      }
  
      override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
          super.onActivityResult(requestCode, resultCode, data)
          if (requestCode == REQUEST_CODE_CAPTURE_INTENT &amp;&amp; data != null) {
              val mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data)
              val playbackConfig = AudioPlaybackCaptureConfiguration.Builder(mediaProjection)
                  .addMatchingUsage(AudioAttributes.USAGE_MEDIA)
                  .addMatchingUsage(AudioAttributes.USAGE_UNKNOWN)
                  .addMatchingUsage(AudioAttributes.USAGE_GAME)
                  .build()
              audioRecord = AudioRecord.Builder()
                  .setAudioPlaybackCaptureConfig(playbackConfig)
                  .setBufferSizeInBytes(minBufferSize * 2)
                  .setAudioFormat(
                      AudioFormat.Builder()
                          .setEncoding(RECORDER_AUDIO_ENCODING)
                          .setSampleRate(RECORDER_SAMPLE_RATE)
                          .setChannelMask(RECORDER_CHANNELS)
                          .build()
                  )
                  .build()
          }
      }
  
      fun startCapture(view: View) {
          audioRecord?.apply {
              startRecording()
              log("Is stopped: $state $recordingState")
              startRecordingIntoFile()
          }
          stopRecBtn.visibility = View.VISIBLE
          startRecBtn.visibility = View.INVISIBLE
      }
  
      private fun AudioRecord.startRecordingIntoFile() {
          val file = File(
              getExternalFilesDir(Environment.DIRECTORY_MUSIC),
              "temp.wav"
              //System.currentTimeMillis().toString() + ".wav"
          )
          if (!file.exists())
              file.createNewFile()
  
          GlobalScope.launch {
              val out = file.outputStream()
              audioRecord.apply {
                  while (recordingState == AudioRecord.RECORDSTATE_RECORDING) {
  
                      val buffer = ShortArray(minBufferSize)//ByteBuffer.allocate(MIN_BUFFER_SIZE)
                      val result = read(buffer, 0, minBufferSize)
  
                      // Checking if I am actually getting something in a buffer
                      val b: Short = 0
                      var nonZeroValueCount = 0
                      for (i in 0 until minBufferSize) {
                          if (buffer[i] != b) {
                              nonZeroValueCount += 1
                              log("Value: ${buffer[i]}")
                          }
                      }
                      if (nonZeroValueCount != 0) {
  
                          // Record the non-zero values in the file..
                          log("Result $nonZeroValueCount")
                          when (result) {
                              AudioRecord.ERROR -&gt; showToast("ERROR")
                              AudioRecord.ERROR_INVALID_OPERATION -&gt; showToast("ERROR_INVALID_OPERATION")
                              AudioRecord.ERROR_DEAD_OBJECT -&gt; showToast("ERROR_DEAD_OBJECT")
                              AudioRecord.ERROR_BAD_VALUE -&gt; showToast("ERROR_BAD_VALUE")
                              else -&gt; {
                                  log("Appending $buffer into ${file.absolutePath}")
                                  out.write(shortToByte(buffer))
                              }
                          }
                      }
                  }
              }
              out.close()
          }
      }
  
      private fun shortToByte(shortArray: ShortArray): ByteArray {
          val byteOut = ByteArray(shortArray.size * 2)
          ByteBuffer.wrap(byteOut).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().put(shortArray)
          return byteOut
      }
  
      private fun showToast(msg: String) {
          runOnUiThread {
              log("Toast: $msg")
              Toast.makeText(this@MainActivity, msg, Toast.LENGTH_LONG).show()
          }
      }
  
      fun stopCapture(view: View) {
          audioRecord?.apply {
              stop()
              log("Is stopped: $state $recordingState")
          }
          stopRecBtn.visibility = View.INVISIBLE
          startRecBtn.visibility = View.VISIBLE
      }
  
      private fun log(msg: String) {
          Log.d(TAG, msg)
      }
  
      override fun onDestroy() {
          super.onDestroy()
          audioRecord?.stop()
          audioRecord?.release()
          audioRecord = null
      }
  }
  </code></pre>
  
  <p><strong>3. MediaProjectionService</strong></p>
  
  <pre><code>    class MediaProjectionService : Service() {
  
      companion object {
          private const val CHANNEL_ID = "ForegroundServiceChannel"
      }
  
      override fun onBind(intent: Intent?): IBinder? {
          return null
      }
  
      override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
  
          createNotificationChannel()
          val notificationIntent = Intent(this, MainActivity::class.java)
          val pendingIntent = PendingIntent.getActivity(
              this,
              0, notificationIntent, 0
          )
  
          val notification = NotificationCompat.Builder(this, CHANNEL_ID)
              .setContentTitle("Foreground Service")
              .setContentText("Call Recording Service")
  //            .setSmallIcon(R.drawable.ic_stat_name)
              .setContentIntent(pendingIntent)
              .build()
  
          startForeground(1, notification)
          return START_NOT_STICKY
      }
  
      private fun createNotificationChannel() {
          val serviceChannel = NotificationChannel(
              CHANNEL_ID,
              "Foreground Service Channel",
              NotificationManager.IMPORTANCE_DEFAULT
          )
  
          val manager = getSystemService(NotificationManager::class.java)
          manager!!.createNotificationChannel(serviceChannel)
      }
  }
  </code></pre>
  
  <p>The problem is, </p>
  
  <p><strong>1.</strong> File <code>/storage/emulated/0/Android/data/com.example.captureaudio/files/Music/temp.wav</code> created but it has only 0s inside it. I have also checked it with <code>xxd /storage/emulated/0/Android/data/com.example.captureaudio/files/Music/temp.wav</code> as following:</p>
  
  <pre><code>OnePlus7Pro:/sdcard # xxd /storage/emulated/0/Android/data/com.example.captureaudio/files/Music/temp.wav | head
  00000000: 0000 0000 0000 0000 0000 0000 0000 0000  ................
  00000010: 0000 0000 0000 0000 0000 0000 0000 0000  ................
  00000020: 0000 0000 0000 0000 0000 0000 0000 0000  ................
  00000030: 0000 0000 0000 0000 0000 0000 0000 0000  ................
  00000040: 0000 0000 0000 0000 0000 0000 0000 0000  ................
  00000050: 0000 0000 0000 0000 0000 0000 0000 0000  ................
  00000060: 0000 0000 0000 0000 0000 0000 0000 0000  ................
  00000070: 0000 0000 0000 0000 0000 0000 0000 0000  ................
  00000080: 0000 0000 0000 0000 0000 0000 0000 0000  ................
  00000090: 0000 0000 0000 0000 0000 0000 0000 0000  ................
  </code></pre>
  
  <p><strong>2.</strong> On a play from the device, it gives an error "Couldn't play the track you requested".</p>
  
  <p>Any help or suggestion, what I am missing?</p>