|
1 /* |
|
2 * Copyright (C) 2009 The Sipdroid Open Source Project |
|
3 * Copyright (C) 2005 Luca Veltri - University of Parma - Italy |
|
4 * |
|
5 * This file is part of Sipdroid (http://www.sipdroid.org) |
|
6 * |
|
7 * Sipdroid is free software; you can redistribute it and/or modify |
|
8 * it under the terms of the GNU General Public License as published by |
|
9 * the Free Software Foundation; either version 3 of the License, or |
|
10 * (at your option) any later version. |
|
11 * |
|
12 * This source code is distributed in the hope that it will be useful, |
|
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
15 * GNU General Public License for more details. |
|
16 * |
|
17 * You should have received a copy of the GNU General Public License |
|
18 * along with this source code; if not, write to the Free Software |
|
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
|
20 */ |
|
21 |
|
22 package org.sipdroid.media; |
|
23 |
|
24 import jlibrtp.AppCallerThread; |
|
25 import jlibrtp.DataFrame; |
|
26 import jlibrtp.Participant; |
|
27 import jlibrtp.RTPAppIntf; |
|
28 import jlibrtp.RTPReceiverThread; |
|
29 import jlibrtp.RTPSession; |
|
30 |
|
31 import org.sipdroid.media.codecs.Codec; |
|
32 import org.sipdroid.net.tools.DataFramePool; |
|
33 import org.sipdroid.net.tools.DatagramPool; |
|
34 |
|
35 import android.content.ContentResolver; |
|
36 import android.content.Context; |
|
37 import android.content.SharedPreferences.Editor; |
|
38 import android.media.AudioFormat; |
|
39 import android.media.AudioManager; |
|
40 import android.media.AudioTrack; |
|
41 import android.media.ToneGenerator; |
|
42 import android.preference.PreferenceManager; |
|
43 import android.provider.Settings; |
|
44 |
|
45 /** |
|
46 * RtpStreamReceiver is a generic stream receiver. It receives packets from RTP |
|
47 * and writes them into an OutputStream. |
|
48 */ |
|
49 public class RtpStreamReceiver extends Thread implements RTPAppIntf{ |
|
50 |
|
51 /** Whether working in debug mode. */ |
|
52 public static boolean DEBUG = true; |
|
53 |
|
54 /** Size of the read buffer */ |
|
55 public static final int BUFFER_SIZE = 1024; |
|
56 |
|
57 /** Maximum blocking time, spent waiting for reading new bytes [milliseconds] */ |
|
58 public static final int SO_TIMEOUT = 200; |
|
59 |
|
60 /** The RtpSocket */ |
|
61 //RtpSocket rtp_socket = null; |
|
62 RTPSession rtpSession = null; |
|
63 byte[] buffer; |
|
64 |
|
65 /** The codec */ |
|
66 private Codec codec; |
|
67 private Context mContext; |
|
68 |
|
69 private int frame_size; |
|
70 private int codec_frame_size; |
|
71 private int sampling_rate; |
|
72 |
|
73 /** Whether it is running */ |
|
74 boolean running; |
|
75 AudioManager am; |
|
76 ContentResolver cr; |
|
77 |
|
78 private int codec_divider; |
|
79 public static int speakermode; |
|
80 |
|
81 short lin[]; |
|
82 short lin2[]; |
|
83 int user, server, lserver, luser, cnt, todo, headroom, len, timeout = 1, seq = 0, cnt2 = 0, m = 1, |
|
84 expseq, getseq, vm = 1, gap, oldvol; |
|
85 boolean islate; |
|
86 |
|
87 Codec.Context codecCtx; |
|
88 |
|
89 AudioTrack track; |
|
90 |
|
91 /** |
|
92 * Constructs a RtpStreamReceiver. |
|
93 * @param ctx |
|
94 * @param remoteAddr |
|
95 * |
|
96 * @param output_stream |
|
97 * the stream sink |
|
98 * @param socket |
|
99 * the local receiver SipdroidSocket |
|
100 */ |
|
101 public RtpStreamReceiver(Codec ci, RTPSession rtpSession, Context ctx) { |
|
102 init(ci, rtpSession, ctx); |
|
103 } |
|
104 |
|
105 /** Inits the RtpStreamReceiver |
|
106 * @param ctx |
|
107 * @param remoteAddr |
|
108 **/ |
|
109 private void init(Codec ci, RTPSession rtpSession, Context ctx) { |
|
110 this.rtpSession = rtpSession; |
|
111 codec = ci; |
|
112 codec_frame_size = codec.getInfo().codecFrameSize; |
|
113 codec_divider = codec.getInfo().rtpSampleDivider; |
|
114 frame_size = 160 * codec_divider; |
|
115 sampling_rate = codec.getInfo().samplingRate; |
|
116 mContext = ctx; |
|
117 } |
|
118 |
|
119 /** Whether is running */ |
|
120 public boolean isRunning() { |
|
121 return running; |
|
122 } |
|
123 |
|
124 /** Stops running */ |
|
125 public void halt() { |
|
126 running = false; |
|
127 } |
|
128 |
|
129 public int speaker(int mode) { |
|
130 int old = speakermode; |
|
131 |
|
132 saveVolume(); |
|
133 speakermode = mode; |
|
134 restoreVolume(); |
|
135 return old; |
|
136 } |
|
137 |
|
138 double smin = 200,s; |
|
139 |
|
140 private int REAL_BUFFER_SIZE; |
|
141 public static int nearend; |
|
142 |
|
143 void calc(short[] lin,int off,int len) { |
|
144 int i,j; |
|
145 double sm = 30000,r; |
|
146 |
|
147 for (i = 0; i < len; i += 5) { |
|
148 j = lin[i+off]; |
|
149 s = 0.03*Math.abs(j) + 0.97*s; |
|
150 if (s < sm) sm = s; |
|
151 if (s > smin) nearend = 3000/5; |
|
152 else if (nearend > 0) nearend--; |
|
153 } |
|
154 for (i = 0; i < len; i++) { |
|
155 j = lin[i+off]; |
|
156 if (j > 6550) |
|
157 lin[i+off] = 6550*5; |
|
158 else if (j < -6550) |
|
159 lin[i+off] = -6550*5; |
|
160 else |
|
161 lin[i+off] = (short)(j*5); |
|
162 } |
|
163 r = (double)len/100000; |
|
164 smin = sm*r + smin*(1-r); |
|
165 } |
|
166 |
|
167 void restoreVolume() { |
|
168 am.setStreamVolume(AudioManager.STREAM_MUSIC, |
|
169 PreferenceManager.getDefaultSharedPreferences(mContext).getInt("volume"+speakermode, |
|
170 am.getStreamMaxVolume(AudioManager.STREAM_MUSIC)* |
|
171 (speakermode == AudioManager.MODE_NORMAL?4:3)/4 |
|
172 ),0); |
|
173 } |
|
174 |
|
175 void saveVolume() { |
|
176 Editor edit = PreferenceManager.getDefaultSharedPreferences(mContext).edit(); |
|
177 edit.putInt("volume"+speakermode,am.getStreamVolume(AudioManager.STREAM_MUSIC)); |
|
178 edit.commit(); |
|
179 } |
|
180 |
|
181 void saveSettings() { |
|
182 if (!PreferenceManager.getDefaultSharedPreferences(mContext).getBoolean("oldvalid",false)) { |
|
183 int oldvibrate = am.getVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER); |
|
184 int oldvibrate2 = am.getVibrateSetting(AudioManager.VIBRATE_TYPE_NOTIFICATION); |
|
185 if (!PreferenceManager.getDefaultSharedPreferences(mContext).contains("oldvibrate2")) |
|
186 oldvibrate2 = AudioManager.VIBRATE_SETTING_ON; |
|
187 int oldpolicy = android.provider.Settings.System.getInt(cr, android.provider.Settings.System.WIFI_SLEEP_POLICY, |
|
188 Settings.System.WIFI_SLEEP_POLICY_DEFAULT); |
|
189 Editor edit = PreferenceManager.getDefaultSharedPreferences(mContext).edit(); |
|
190 edit.putInt("oldvibrate", oldvibrate); |
|
191 edit.putInt("oldvibrate2", oldvibrate2); |
|
192 edit.putInt("oldpolicy", oldpolicy); |
|
193 edit.putInt("oldring",am.getStreamVolume(AudioManager.STREAM_RING)); |
|
194 edit.putBoolean("oldvalid", true); |
|
195 edit.commit(); |
|
196 } |
|
197 } |
|
198 |
|
199 void restoreSettings() { |
|
200 int oldvibrate = PreferenceManager.getDefaultSharedPreferences(mContext).getInt("oldvibrate",0); |
|
201 int oldvibrate2 = PreferenceManager.getDefaultSharedPreferences(mContext).getInt("oldvibrate2",0); |
|
202 int oldpolicy = PreferenceManager.getDefaultSharedPreferences(mContext).getInt("oldpolicy",0); |
|
203 am.setVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER,oldvibrate); |
|
204 am.setVibrateSetting(AudioManager.VIBRATE_TYPE_NOTIFICATION,oldvibrate2); |
|
205 Settings.System.putInt(cr, Settings.System.WIFI_SLEEP_POLICY, oldpolicy); |
|
206 am.setStreamVolume(AudioManager.STREAM_RING, PreferenceManager.getDefaultSharedPreferences(mContext).getInt("oldring",0), 0); |
|
207 Editor edit = PreferenceManager.getDefaultSharedPreferences(mContext).edit(); |
|
208 edit.putBoolean("oldvalid", false); |
|
209 edit.commit(); |
|
210 } |
|
211 |
|
212 public static float good, late, lost, loss; |
|
213 |
|
214 /** Runs it in a new Thread. */ |
|
215 @Override |
|
216 public void run() { |
|
217 REAL_BUFFER_SIZE = BUFFER_SIZE * codec_divider; |
|
218 speakermode = AudioManager.MODE_IN_CALL; |
|
219 android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO); |
|
220 am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); |
|
221 cr = mContext.getContentResolver(); |
|
222 saveSettings(); |
|
223 |
|
224 Settings.System.putInt(cr, Settings.System.WIFI_SLEEP_POLICY,Settings.System.WIFI_SLEEP_POLICY_NEVER); |
|
225 am.setVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER,AudioManager.VIBRATE_SETTING_OFF); |
|
226 am.setVibrateSetting(AudioManager.VIBRATE_TYPE_NOTIFICATION,AudioManager.VIBRATE_SETTING_OFF); |
|
227 oldvol = am.getStreamVolume(AudioManager.STREAM_MUSIC); |
|
228 restoreVolume(); |
|
229 |
|
230 track = new AudioTrack(AudioManager.STREAM_MUSIC, sampling_rate, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, |
|
231 REAL_BUFFER_SIZE*2*2, AudioTrack.MODE_STREAM); |
|
232 track.play(); |
|
233 lin = new short[REAL_BUFFER_SIZE]; |
|
234 lin2 = new short[REAL_BUFFER_SIZE]; |
|
235 user = 0; //number of samples written |
|
236 server = 0; // number of samples played |
|
237 lserver = 0; // last number of samples played |
|
238 luser = -sampling_rate; // last number of samples written |
|
239 cnt = 0; |
|
240 codecCtx = codec.initDecoder(); |
|
241 System.gc(); |
|
242 println("DEBUG: rtpStreamReceiver session launch"); |
|
243 running = true; |
|
244 AppCallerThread appCall = rtpSession.getAppCallerThrd(); |
|
245 RTPReceiverThread recv = rtpSession.getRTPRecvThrd(); |
|
246 DataFrame frame = null; |
|
247 recv.init(); |
|
248 while (running) { |
|
249 recv.readPacketToBuffer(); |
|
250 frame = appCall.getNextDataFrame(); |
|
251 if (frame == null) |
|
252 continue; |
|
253 buffer = (frame.getPkt()[0]).getPayload(); |
|
254 if (timeout != 0) { //on ecrit du blanc sur l'audiotrack |
|
255 user += track.write(lin,0,REAL_BUFFER_SIZE); |
|
256 user += track.write(lin,0,REAL_BUFFER_SIZE); |
|
257 } |
|
258 timeout = 0; |
|
259 if (running) { |
|
260 |
|
261 //println("seq " + seq + " frame seq " + (frame.getPkt()[0]).getSeqNumber()); |
|
262 if (seq == (frame.getPkt()[0]).getSeqNumber()) { |
|
263 m++; |
|
264 continue; |
|
265 } |
|
266 |
|
267 codec.decode(codecCtx, buffer, 12, codec_frame_size, lin, 0); |
|
268 len = frame_size; |
|
269 |
|
270 if (speakermode == AudioManager.MODE_NORMAL) |
|
271 calc(lin,0,len); |
|
272 |
|
273 server = track.getPlaybackHeadPosition(); // on récupère la position actuel de la tete de lecture |
|
274 headroom = user-server; // on recalcule la différence entre la position de la tete de lecture et ce qu'on a écrit sur la piste |
|
275 //println("headroom " + headroom + " user " + user + " server " + server); |
|
276 if (headroom < 250 * codec_divider) { // si le headroom est trop petit, il faut rattraper le retard en écrivant du blanc/répétant ce qu'il y a à ecrire |
|
277 todo = 625 * codec_divider - headroom; |
|
278 //println("insert "+todo); |
|
279 android.util.Log.d("RECV", "insert"); |
|
280 islate = true; |
|
281 if (todo < len) |
|
282 user += track.write(lin,0,todo); // on écrit le packet reçu tel quel |
|
283 else |
|
284 user += track.write(lin2,0,todo); // ecriture de blanc de taille 625 - headroom, avant l'écriture du packet |
|
285 } else |
|
286 islate = false; |
|
287 |
|
288 if (headroom > 1000 * codec_divider) // si le headroom est trop grand, on calcule l'écart. |
|
289 cnt += len; // on additione le nombre de sample ou il y a eu un headroom supérieur a 1000 |
|
290 else |
|
291 cnt = 0; |
|
292 |
|
293 if (lserver == server) // on compte le nombre de boucle que l'on a fait sans qu'aucun sample n'ai été joué. |
|
294 cnt2++; |
|
295 else |
|
296 cnt2 = 0; |
|
297 |
|
298 if (cnt > 1000 * codec_divider && cnt2 < 2) { // si la position de la tete de lecture n'a pas bougé durant 2 tours et que le nombre de sample ou le headroom a été supérieur à 1000 est > 1000 |
|
299 todo = headroom - 625 * codec_divider; |
|
300 try { |
|
301 //android.util.Log.d("RECV", "cut"); |
|
302 sleep(20); |
|
303 } catch (InterruptedException e) { |
|
304 // TODO Auto-generated catch block |
|
305 e.printStackTrace(); |
|
306 } |
|
307 } |
|
308 user += track.write(lin,0,len); |
|
309 m = 1; |
|
310 seq = (frame.getPkt()[0]).getSeqNumber(); |
|
311 DataFramePool.getInstance().returnFrame(frame); |
|
312 //println("headroom " + headroom + " user " + user + " server " + server + " luser " + luser + " lserver " + lserver); |
|
313 if (user >= luser + sampling_rate) { |
|
314 if (am.getMode() != speakermode) { |
|
315 am.setMode(speakermode); |
|
316 switch (speakermode) { |
|
317 case AudioManager.MODE_IN_CALL: |
|
318 /*am.setStreamVolume(AudioManager.STREAM_RING,(int)( |
|
319 am.getStreamMaxVolume(AudioManager.STREAM_RING)* |
|
320 com.mbdsys.sfrdroid.ui.Settings.getEarGain()), 0); |
|
321 track.setStereoVolume(AudioTrack.getMaxVolume()* |
|
322 com.mbdsys.sfrdroid.ui.Settings.getEarGain() |
|
323 ,AudioTrack.getMaxVolume()* |
|
324 com.mbdsys.sfrdroid.ui.Settings.getEarGain());*/ |
|
325 //running = false; |
|
326 case AudioManager.MODE_NORMAL: |
|
327 track.setStereoVolume(AudioTrack.getMaxVolume(),AudioTrack.getMaxVolume()); |
|
328 //running = false; |
|
329 } |
|
330 } |
|
331 luser = user; |
|
332 } |
|
333 lserver = server; |
|
334 System.arraycopy(lin, 0, lin2, 0, REAL_BUFFER_SIZE); |
|
335 } |
|
336 } |
|
337 println("POOL SIZE " + DatagramPool.getInstance().getPoolSize()); |
|
338 track.stop(); |
|
339 //if (Receiver.pstn_state == null || Receiver.pstn_state.equals("IDLE")) |
|
340 // am.setMode(AudioManager.MODE_NORMAL); |
|
341 saveVolume(); |
|
342 am.setStreamVolume(AudioManager.STREAM_MUSIC,oldvol,0); |
|
343 restoreSettings(); |
|
344 ToneGenerator tg = new ToneGenerator(AudioManager.STREAM_RING,ToneGenerator.MAX_VOLUME/4*3); |
|
345 tg.startTone(ToneGenerator.TONE_PROP_PROMPT); |
|
346 try { |
|
347 Thread.sleep(500); |
|
348 } catch (InterruptedException e) { |
|
349 } |
|
350 tg.stopTone(); |
|
351 rtpSession = null; |
|
352 track = null; |
|
353 codec.cleanDecoder(codecCtx); |
|
354 codec = null; |
|
355 println("rtp receiver terminated"); |
|
356 } |
|
357 |
|
358 public void endReceiver() { |
|
359 track.stop(); |
|
360 //if (Receiver.pstn_state == null || Receiver.pstn_state.equals("IDLE")) |
|
361 // am.setMode(AudioManager.MODE_NORMAL); |
|
362 saveVolume(); |
|
363 am.setStreamVolume(AudioManager.STREAM_MUSIC,oldvol,0); |
|
364 restoreSettings(); |
|
365 ToneGenerator tg = new ToneGenerator(AudioManager.STREAM_RING,ToneGenerator.MAX_VOLUME/4*3); |
|
366 tg.startTone(ToneGenerator.TONE_PROP_PROMPT); |
|
367 try { |
|
368 Thread.sleep(500); |
|
369 } catch (InterruptedException e) { |
|
370 } |
|
371 tg.stopTone(); |
|
372 rtpSession = null; |
|
373 track = null; |
|
374 codec.cleanDecoder(codecCtx); |
|
375 codec = null; |
|
376 println("rtp receiver terminated"); |
|
377 } |
|
378 |
|
379 /** Debug output */ |
|
380 static int i = 0; |
|
381 private static void println(String str) { |
|
382 System.out.println("RtpStreamReceiver "+ i++ +": " + str); |
|
383 } |
|
384 |
|
385 public static int byte2int(byte b) { // return (b>=0)? b : -((b^0xFF)+1); |
|
386 // return (b>=0)? b : b+0x100; |
|
387 return (b + 0x100) % 0x100; |
|
388 } |
|
389 |
|
390 public static int byte2int(byte b1, byte b2) { |
|
391 return (((b1 + 0x100) % 0x100) << 8) + (b2 + 0x100) % 0x100; |
|
392 } |
|
393 |
|
394 @Override |
|
395 public int frameSize(int payloadType) { |
|
396 // TODO Auto-generated method stub |
|
397 return 0; |
|
398 } |
|
399 |
|
400 @Override |
|
401 public void receiveData(DataFrame frame, Participant participant) { |
|
402 // TODO Auto-generated method stub |
|
403 |
|
404 } |
|
405 |
|
406 @Override |
|
407 public void userEvent(int type, Participant[] participant) { |
|
408 // TODO Auto-generated method stub |
|
409 |
|
410 } |
|
411 |
|
412 } |