/*
* Copyright (C) 2009 The Sipdroid Open Source Project
* Copyright (C) 2005 Luca Veltri - University of Parma - Italy
*
* This file is part of Sipdroid (http://www.sipdroid.org)
*
* Sipdroid is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this source code; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.sipdroid.media;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.util.Random;
import org.sipdroid.net.RtpPacket;
import org.sipdroid.net.RtpSocket;
import org.sipdroid.net.SipdroidSocket;
import org.sipdroid.pjlib.Codec;
import android.content.Context;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.preference.PreferenceManager;
import android.telephony.TelephonyManager;
import com.beem.project.beem.ui.Call;
/**
* RtpStreamSender is a generic stream sender. It takes an InputStream and sends
* it through RTP.
*/
public class RtpStreamSender extends Thread {
/** Whether working in debug mode. */
public static boolean DEBUG = true;
/** The RtpSocket */
RtpSocket rtp_socket = null;
/** Payload type */
int p_type;
/** Number of frame per second */
long frame_rate;
/** Number of bytes per frame */
int frame_size;
/**
* Whether it works synchronously with a local clock, or it it acts as slave
* of the InputStream
*/
boolean do_sync = true;
/**
* Synchronization correction value, in milliseconds. It accellarates the
* sending rate respect to the nominal value, in order to compensate program
* latencies.
*/
int sync_adj = 0;
/** Whether it is running */
boolean running = false;
boolean muted = false;
/**
* Constructs a RtpStreamSender.
*
* @param input_stream
* the stream to be sent
* @param do_sync
* whether time synchronization must be performed by the
* RtpStreamSender, or it is performed by the InputStream (e.g.
* the system audio input)
* @param payload_type
* the payload type
* @param frame_rate
* the frame rate, i.e. the number of frames that should be sent
* per second; it is used to calculate the nominal packet time
* and,in case of do_sync==true, the next departure time
* @param frame_size
* the size of the payload
* @param src_socket
* the socket used to send the RTP packet
* @param dest_addr
* the destination address
* @param dest_port
* the destination port
*/
public RtpStreamSender(boolean do_sync,
int payload_type, long frame_rate, int frame_size,
SipdroidSocket src_socket, String dest_addr, int dest_port) {
init(do_sync, payload_type, frame_rate, frame_size,
src_socket, dest_addr, dest_port);
}
/** Inits the RtpStreamSender */
private void init(boolean do_sync,
int payload_type, long frame_rate, int frame_size,
SipdroidSocket src_socket, String dest_addr,
int dest_port) {
this.p_type = payload_type;
this.frame_rate = frame_rate;
this.frame_size = frame_size;
this.do_sync = do_sync;
try {
rtp_socket = new RtpSocket(src_socket, InetAddress
.getByName(dest_addr), dest_port);
} catch (Exception e) {
e.printStackTrace();
}
}
/** Sets the synchronization adjustment time (in milliseconds). */
public void setSyncAdj(int millisecs) {
sync_adj = millisecs;
}
/** Whether is running */
public boolean isRunning() {
return running;
}
public boolean mute() {
return muted = !muted;
}
public static int delay = 0;
/** Stops running */
public void halt() {
running = false;
}
Random random;
double smin = 200,s;
int nearend;
void calc(short[] lin,int off,int len) {
int i,j;
double sm = 30000,r;
for (i = 0; i < len; i += 5) {
j = lin[i+off];
s = 0.03*Math.abs(j) + 0.97*s;
if (s < sm) sm = s;
if (s > smin) nearend = 3000/5;
else if (nearend > 0) nearend--;
}
for (i = 0; i < len; i++) {
j = lin[i+off];
if (j > 6550)
lin[i+off] = 6550*5;
else if (j < -6550)
lin[i+off] = -6550*5;
else
lin[i+off] = (short)(j*5);
}
r = (double)len/100000;
smin = sm*r + smin*(1-r);
}
void calc1(short[] lin,int off,int len) {
int i,j;
for (i = 0; i < len; i++) {
j = lin[i+off];
lin[i+off] = (short)(j>>1);
}
}
void calc5(short[] lin,int off,int len) {
int i,j;
for (i = 0; i < len; i++) {
j = lin[i+off];
if (j > 16350)
lin[i+off] = 16350<<1;
else if (j < -16350)
lin[i+off] = -16350<<1;
else
lin[i+off] = (short)(j<<1);
}
}
void calc10(short[] lin,int off,int len) {
int i,j;
for (i = 0; i < len; i++) {
j = lin[i+off];
if (j > 8150)
lin[i+off] = 8150<<2;
else if (j < -8150)
lin[i+off] = -8150<<2;
else
lin[i+off] = (short)(j<<2);
}
}
void noise(short[] lin,int off,int len,double power) {
int i,r = (int)(power*2);
short ran;
if (r == 0) r = 1;
for (i = 0; i < len; i += 4) {
ran = (short)(random.nextInt(r*2)-r);
lin[i+off] = ran;
lin[i+off+1] = ran;
lin[i+off+2] = ran;
lin[i+off+3] = ran;
}
}
public static float getMicGain() {
if (Call.headset > 0)
return Float.valueOf(PreferenceManager.getDefaultSharedPreferences(Call.mContext).getString("hmicgain", "1.0"));
return Float.valueOf(PreferenceManager.getDefaultSharedPreferences(Call.mContext).getString("micgain", "0.25"));
}
/** Runs it in a new Thread. */
public void run() {
if (rtp_socket == null)
return;
byte[] buffer = new byte[frame_size + 12];
RtpPacket rtp_packet = new RtpPacket(buffer, 0);
rtp_packet.setPayloadType(p_type);
int seqn = 0;
long time = 0;
double p = 0;
TelephonyManager tm = (TelephonyManager) Call.mContext.getSystemService(Context.TELEPHONY_SERVICE);
boolean improve = PreferenceManager.getDefaultSharedPreferences(Call.mContext).getBoolean("improve",false);
boolean useGSM = !PreferenceManager.getDefaultSharedPreferences(Call.mContext).getString("compression","edge").equals("never");
int micgain = (int)(getMicGain()*10);
running = true;
if (DEBUG)
println("Reading blocks of " + buffer.length + " bytes");
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
AudioRecord record = new AudioRecord(MediaRecorder.AudioSource.MIC, 8000, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT,
AudioRecord.getMinBufferSize(8000,
AudioFormat.CHANNEL_CONFIGURATION_MONO,
AudioFormat.ENCODING_PCM_16BIT)*3/2);
short[] lin = new short[frame_size*11];
int num,ring = 0;
random = new Random();
InputStream alerting = null;
try {
alerting = Call.mContext.getAssets().open("alerting");
} catch (IOException e2) {
e2.printStackTrace();
}
switch (p_type) {
case 3:
Codec.init();
break;
case 0:
case 8:
G711.init();
break;
}
record.startRecording();
while (running) {
if (muted || Call.call_state == Call.UA_STATE_HOLD) {
record.stop();
while (running && (muted || Call.call_state == Call.UA_STATE_HOLD)) {
try {
sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
record.startRecording();
}
num = record.read(lin,(ring+delay)%(frame_size*11),frame_size);
if (RtpStreamReceiver.speakermode == AudioManager.MODE_NORMAL) {
calc(lin,(ring+delay)%(frame_size*11),num);
if (RtpStreamReceiver.nearend != 0)
noise(lin,(ring+delay)%(frame_size*11),num,p);
else if (RtpStreamReceiver.nearend == 0)
p = 0.9*p + 0.1*s;
} else switch (micgain) {
case 1:
calc1(lin,(ring+delay)%(frame_size*11),num);
break;
case 5:
calc5(lin,(ring+delay)%(frame_size*11),num);
break;
case 10:
calc10(lin,(ring+delay)%(frame_size*11),num);
break;
}
if (Call.call_state != Call.UA_STATE_INCALL && alerting != null) {
try {
if (alerting.available() < num)
alerting.reset();
alerting.read(buffer,12,num);
} catch (IOException e) {
e.printStackTrace();
}
switch (p_type) {// have to add ulaw case?
case 3:
G711.alaw2linear(buffer, lin, num);
num = Codec.encode(lin, 0, buffer, num);
break;
case 0:
G711.alaw2linear(buffer, lin, num);
G711.linear2ulaw(lin, 0, buffer, num);
break;
}
} else {
switch (p_type) {
case 3:
num = Codec.encode(lin, ring%(frame_size*11), buffer, num);
break;
case 0:
G711.linear2ulaw(lin, ring%(frame_size*11), buffer, num);
break;
case 8:
G711.linear2alaw(lin, ring%(frame_size*11), buffer, num);
break;
}
}
ring += frame_size;
rtp_packet.setSequenceNumber(seqn++);
rtp_packet.setTimestamp(time);
rtp_packet.setPayloadLength(num);
try {
rtp_socket.send(rtp_packet);
} catch (IOException e) {
e.printStackTrace();
}
time += frame_size;
/*if (useGSM && p_type == 8 && !BeemConnectivity.isWifi(Call.mContext) && tm.getNetworkType() == TelephonyManager.NETWORK_TYPE_EDGE) {
rtp_packet.setPayloadType(p_type = 3);
if (frame_size == 1024) {
frame_size = 960;
ring = 0;
}
}*/
}
record.stop();
rtp_socket.close();
rtp_socket = null;
if (DEBUG)
println("rtp sender terminated");
}
/** Debug output */
private static void println(String str) {
System.out.println("RtpStreamSender: " + str);
}
}