1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.googlecode.android_scripting.facade.telephony; 17 18 import static com.googlecode.android_scripting.facade.telephony.InCallServiceImpl.HandleVoiceThreadState.TERMINATE; 19 import static com.googlecode.android_scripting.facade.telephony.InCallServiceImpl.HandleVoiceThreadState.RUN; 20 import static android.os.VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY; 21 import static android.media.AudioAttributes.CONTENT_TYPE_MUSIC; 22 import static android.media.AudioAttributes.USAGE_MEDIA; 23 import static android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION; 24 import static android.os.Build.VERSION; 25 import static android.os.Build.VERSION_CODES; 26 27 import com.googlecode.android_scripting.facade.EventFacade; 28 import com.googlecode.android_scripting.Log; 29 import android.media.AudioDeviceInfo; 30 import android.media.AudioAttributes; 31 import android.media.AudioFormat; 32 import android.media.AudioTrack; 33 import android.media.MediaExtractor; 34 import android.media.MediaFormat; 35 import android.telecom.Call; 36 import java.io.FileInputStream; 37 import java.io.IOException; 38 import java.io.InputStream; 39 import java.io.File; 40 41 /** 42 * The class handles playing an audio file on the route of the telephony network during a phone 43 * call. 44 */ 45 public class PlayAudioInCall { 46 private static Thread playAudioThread = null; 47 private AudioDeviceInfo audioDeviceInfo; 48 private File audioFile; 49 private AudioFileInfo audioFileInfo; 50 private Call call; 51 private EventFacade eventFacade; 52 PlayAudioInCall(EventFacade eventFacade, Call call, File audioFile, AudioDeviceInfo audioDeviceInfo)53 PlayAudioInCall(EventFacade eventFacade, 54 Call call, 55 File audioFile, 56 AudioDeviceInfo audioDeviceInfo) { 57 this.eventFacade = eventFacade; 58 this.call = call; 59 this.audioFile = audioFile; 60 this.audioDeviceInfo = audioDeviceInfo; 61 Log.d(String.format("eventFacade=%s, call=%s, audioFile=%s, audioDeviceInfo=%d", 62 this.eventFacade, this.call, this.audioFile, this.audioDeviceInfo.getId())); 63 } 64 playAudioFile()65 boolean playAudioFile() { 66 if (!setupAudioFileInfo()) 67 return false; 68 return playAudio(); 69 } 70 playAudio()71 private boolean playAudio() { 72 AudioFormat audioFormat = getAudioFormat(); 73 Log.d(String.format("Audio format: %s", audioFormat.toString())); 74 AudioTrack audioTrack = getAudioTrack(audioFormat, getAudioTrackBufferSize(audioFormat)); 75 Log.d(String.format("Audio Track: %s",audioTrack.toString())); 76 if (!audioTrack.setPreferredDevice(audioDeviceInfo)) { 77 audioTrack.release(); 78 return false; 79 } 80 Log.d(String.format("Set the preferred audio device to %d successfully", 81 audioDeviceInfo.getId())); 82 while (audioTrack.getState() != AudioTrack.STATE_INITIALIZED) 83 ; 84 Log.d(String.format("Audio track state: %s", audioTrack.getState())); 85 return createPlayAudioThread(audioTrack); 86 } 87 createPlayAudioThread(AudioTrack audioTrack)88 private boolean createPlayAudioThread(AudioTrack audioTrack) { 89 playAudioThread = new Thread(() -> { 90 byte[] audioRaw = new byte[512]; 91 int readBytes; 92 try { 93 InCallServiceImpl.setPlayAudioInCallState(RUN); 94 InCallServiceImpl.muteCall(true); 95 InputStream inputStream = new FileInputStream(audioFile); 96 inputStream.read(audioRaw, 0, 44); 97 audioTrack.play(); 98 while ((readBytes = inputStream.read(audioRaw)) != -1) { 99 audioTrack.write(audioRaw, 0, readBytes); 100 if (stopPlayAudio()) { 101 break; 102 } 103 } 104 Log.d("End Playing audio!"); 105 inputStream.close(); 106 audioTrack.stop(); 107 audioTrack.release(); 108 eventFacade.postEvent(TelephonyConstants.EventCallPlayAudioStateChanged, 109 new InCallServiceImpl.CallEvent<String>(InCallServiceImpl.getCallId(call), 110 TelephonyConstants.TELEPHONY_STATE_PLAY_AUDIO_END)); 111 } 112 catch (IOException e) { 113 audioTrack.release(); 114 Log.d(String.format("Failed to read audio file \"%s\"!", audioFile.getName())); 115 eventFacade.postEvent(TelephonyConstants.EventCallPlayAudioStateChanged, 116 new InCallServiceImpl.CallEvent<String>(InCallServiceImpl.getCallId(call), 117 TelephonyConstants.TELEPHONY_STATE_PLAY_AUDIO_FAIL)); 118 } 119 finally { 120 InCallServiceImpl.muteCall(false); 121 InCallServiceImpl.setPlayAudioInCallState(TERMINATE); 122 } 123 }); 124 playAudioThread.start(); 125 return true; 126 } 127 getAudioTrack(AudioFormat audioFormat, int bufferSize)128 private AudioTrack getAudioTrack(AudioFormat audioFormat, int bufferSize) { 129 return new AudioTrack.Builder().setAudioFormat(audioFormat).setBufferSizeInBytes(bufferSize) 130 .setAudioAttributes(getAudioAttributes()).setTransferMode(AudioTrack.MODE_STREAM).build(); 131 } 132 getAudioTrackBufferSize(AudioFormat audioFormat)133 private int getAudioTrackBufferSize(AudioFormat audioFormat) { 134 return AudioTrack.getMinBufferSize( 135 audioFormat.getSampleRate(), 136 audioFormat.getChannelMask(), 137 audioFormat.getEncoding()); 138 } 139 getAudioAttributes()140 private AudioAttributes getAudioAttributes() { 141 if (VERSION.SDK_INT >= VERSION_CODES.Q) { 142 Log.d("AudioAttributes above Android Q is used."); 143 return new AudioAttributes.Builder() 144 .setUsage(USAGE_VOICE_COMMUNICATION).build(); 145 } else { 146 Log.d("AudioAttributes below Android Q is used."); 147 return new AudioAttributes.Builder() 148 .setContentType(CONTENT_TYPE_MUSIC) 149 .setFlags(FLAG_BYPASS_INTERRUPTION_POLICY) 150 .setUsage(USAGE_MEDIA).build(); 151 } 152 } 153 setupAudioFileInfo()154 private boolean setupAudioFileInfo() { 155 MediaExtractor extractor = new MediaExtractor(); 156 try { 157 extractor.setDataSource(audioFile.getAbsolutePath()); 158 } catch (IOException e) { 159 Log.d(String.format("Failed to set data source in MediaExtrator, %s", e.getMessage())); 160 return false; 161 } 162 extractor.selectTrack(0); 163 MediaFormat format = extractor.getTrackFormat(0); 164 audioFileInfo = AudioFileInfo.create(format.getString(MediaFormat.KEY_MIME), 165 format.getInteger(MediaFormat.KEY_SAMPLE_RATE), 166 format.getInteger(MediaFormat.KEY_CHANNEL_COUNT)); 167 Log.d(String.format("The media format is %s", audioFileInfo)); 168 return true; 169 } 170 getAudioFormat()171 private AudioFormat getAudioFormat() { 172 int channelMask = audioFileInfo.channelCount() == 1 ? AudioFormat.CHANNEL_OUT_MONO 173 : AudioFormat.CHANNEL_OUT_STEREO; 174 return new AudioFormat.Builder().setChannelMask(channelMask) 175 .setSampleRate(audioFileInfo.sampleRate()).setEncoding(AudioFormat.ENCODING_PCM_16BIT) 176 .build(); 177 } 178 stopPlayAudio()179 private boolean stopPlayAudio() { 180 if (InCallServiceImpl.getPlayAudioInCallState().equals(RUN)) { 181 return false; 182 } 183 Log.d("Stop playing audio!"); 184 return true; 185 } 186 } 187