• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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