最近一直在研究直播功能,播放画面和播放音频是大头,弄了很久一直没弄好,视频播放这边主要是,使用HLS功能实现的播放功能。
在这里主要说下音频流直播的实现,在和同事不断的努力和交流下,终于播放出了声音,万份惊喜啊。有种屌丝逆袭的感觉。好的不扯那么多了进入正题:
首先,是基于大牛破解国外的juv之后,继续实现的连接red5服务器的功能;
连接的代码,这边是在主activity里面写的:
public class MainActivity extends Activity {
// fields
private Button btnStart, finish;
String url = "you red5 server";
String externUID = "userId";
String username = "userName";
private Object callId = "75050";//回掉时服务器提供的
final UltraNetConnection connection = new UltraNetConnection();
private UltraNetStream stream;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
connection.addEventListener(new NetConnectionListener());
connection.client(MainActivity.this);
btnStart = (Button) findViewById(R.id.btnStart);
btnStart.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
start();
}
});
finish = (Button) findViewById(R.id.finish);
finish.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (stream != null) {
stream.close();
}
MainActivity.this.finish();
}
});
}
/**
* 1 、Starts the example.
*/
public void start() {
new AsyncTask<Void, Void, Boolean>() {
@Override
protected Boolean doInBackground(Void... params) {
connection.connect(url, externUID, username);
// connection.connect(url);
// 1 wait till connected
while (!connection.connected() && !disconnected) {
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
}
if (!disconnected) {
stream = new UltraNetStream(connection);
stream.addEventListener(new NetStream.ListenerAdapter() {
@Override
public void onNetStatus(final INetStream source,
final Map<String, Object> info) {
System.out
.println("NetStream#onNetStatus: " + info);
}
});
}
while (!disconnected) {
try {
Thread.sleep(100);
} catch (Exception e) {/* ignore */
Log.e("ThreadException", "连接失败\n\n" + e.toString());
}
}
connection.close();
return null;
};
}.execute((Void) null);
}
/**
* 2 、 <code>NetConnectionListener</code> - {@link UltraNetConnection}
* listener implementation.
*/
public class NetConnectionListener extends NetConnection.ListenerAdapter {
/**
* Constructor.
*/
public NetConnectionListener() {
}
@Override
public void onAsyncError(final INetConnection source,
final String message, final Exception e) {
System.out.println("NetConnection#onAsyncError: " + message + " "
+ e);
}
@Override
public void onIOError(final INetConnection source, final String message) {
System.out.println("NetConnection#onIOError: " + message);
}
@Override
public void onNetStatus(final INetConnection source,
final Map<String, Object> info) {
// 2
System.out.println("NetConnection#onNetStatus: " + info);
final Object code = info.get("code");
if (NetConnection.CONNECT_SUCCESS.equals(code)) {
connection.call("voiceconf.call", null, "default", username,
callId);
} else {
disconnected = true;
}
}
}
// 3、
public void successfullyJoinedVoiceConferenceCallback(String publishName,
String playName, String codec) {
System.out.println("publishName:" + publishName + "\n\nplayName:"
+ playName + "\n\ncodec:" + codec);
try {
// stream.receiveAudio(true);
// stream.receiveVideo(false);
if (stream == null) {
stream = new UltraNetStream(connection);
}
//调用该方法可以传递直播音频流,playname是连接成功后,red5返回给的speex音频名称
stream.play(new FlvVideo(playName + ".flv"), playName);
} catch (Exception e) {
e.printStackTrace();
}
}
// fields
private volatile boolean disconnected = false;
public void getSystemOfflineMsg(String str) {
System.out.println(str);
}
public void getSystemMSG(String str) {
System.out.println(str);
}
public void updatePublicInfo(String str) {
System.out.println(str);
}
public void newMessageShow() {
System.out.println("新信件");
}
public void newUpdateSocialityInfo() {
System.out.println("新pk");
}
public void updateAutoUndercityInfo(String str) {
System.out.println("自动闯关完成" + str);
}
public int onBWCheck(Object o) {
return 0;
}
public int onBWCheck() {
return 0;
}
public void onBWDone(Object[] paramArrayOfObject) {
System.out.println("test onBWDone");
}
public static void create_newRoom(String str) {
System.out.println("\n\n\nserverbackof_room --------: [" + str
+ "]\n\n\n");
}
public static void callJS_back_app(String str) {
System.out.println("\n\n\nserverbackof_app --------: [" + str
+ "]\n\n\n");
}
}
这个方法是连接服务器的,其中用到的两个UltraNetConnection 和 UltraNetStream 两个类的代码如下:
package com.weedong.net.rtmp;
import java.io.File;
import java.lang.reflect.Method;
import java.util.Calendar;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import com.smaxe.logger.ILogger;
import com.smaxe.uv.ProtocolLayerInfo;
import com.smaxe.uv.Responder;
import com.smaxe.uv.UrlInfo;
import com.smaxe.uv.client.INetConnection;
import com.smaxe.uv.client.a.d;
import com.smaxe.uv.client.a.h;
import com.smaxe.uv.client.a.i;
import com.smaxe.uv.client.a.k;
public final class UltraNetConnection extends i
implements INetConnection
{
private final h a;
private d b = null;
private a c = null;
private ExecutorService d = null;
private ScheduledExecutorService e = null;
private boolean f = false;
private boolean g = false;
private static final int h = 19;
private static final int i = 9;
private static final int[] j = { 5, 2, 7, 1, 0, 3, 6, 4 };
private static byte[] k = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25 };
public static void setSwfFileSizeAndHash(Map<String, Object> paramMap, File paramFile)
throws Exception
{
a(paramMap, paramFile);
}
public UltraNetConnection()
{
this(null);
}
public UltraNetConnection(Map<String, Object> paramMap)
{
this(paramMap, null, null);
}
public UltraNetConnection(Map<String, Object> paramMap, ExecutorService paramExecutorService, ScheduledExecutorService paramScheduledExecutorService)
{
super(paramMap);
this.d = (paramExecutorService == null ? Executors.newCachedThreadPool() : paramExecutorService);
this.e = (paramScheduledExecutorService == null ? Executors.newSingleThreadScheduledExecutor() : paramScheduledExecutorService);
this.f = (paramExecutorService == null);
this.g = (paramScheduledExecutorService == null);
this.a = new k(this.e);
}
public void addHeader(String paramString, boolean paramBoolean, Object paramObject)
{
this.b.a(paramString, paramBoolean, paramObject);
}
public void call(String paramString, Responder paramResponder, Object[] paramArrayOfObject)
{
if (!connected())
return;
this.b.a(paramString, paramResponder, paramArrayOfObject);
}
public void close()
{
b(com.smaxe.uv.a.e.b("NetConnection.Connect.Closed", "Connection is closed."));
}
public void connect(String paramString, Object... paramArrayOfObject)
{
// b(k);
UrlInfo localUrlInfo = UrlInfo.parseUrl(paramString);
com.smaxe.uv.client.a.e locale = new com.smaxe.uv.client.a.e();
locale.a(this.d);
locale.a((ILogger)configuration().get("logger"));
this.b = locale;
this.c = new a();
try {
Method bitchMethod = com.smaxe.uv.client.a.h.class.getMethod("a", String.class, String.class, int.class, Map.class);
Object btichResult = bitchMethod.invoke(this.a, localUrlInfo.protocol, localUrlInfo.host, localUrlInfo.port, configuration());
Method[] aryMethod = com.smaxe.uv.client.a.d.class.getMethods();
for(Method method : aryMethod) {
if(method.getName().equals("a") && method.getParameterTypes().length == 6) {
method.invoke(this.b, this, btichResult, paramString, localUrlInfo.getApp(), this.c, paramArrayOfObject);
break;
}
}
} catch(Exception ex) {
ex.printStackTrace();
}
super.connect(paramString, paramArrayOfObject);
}
public boolean connected()
{
if (this.b == null)
return false;
return this.b.a() == 3;
}
public String connectedProxyType()
{
return connected() ? this.b.b() : null;
}
public boolean usingTLS()
{
return connected() ? this.b.c() : false;
}
public ProtocolLayerInfo getInfo()
{
return this.b.d();
}
public int getUploadBufferSize()
{
return this.b.e();
}
public void setMaxUploadBandwidth(int paramInt)
{
if (paramInt < 0)
throw new IllegalArgumentException("Parameter 'bandwidth' is negative: " + paramInt);
this.b.a(paramInt);
}
public void onBWDone()
{
}
public void onBWDone(Object[] paramArrayOfObject)
{
}
private void b(Map<String, Object> paramMap)
{
if (this.b == null)
return;
this.b.a(paramMap);
if ((this.f) && (this.d != null))
this.d.shutdown();
if ((this.g) && (this.e != null))
this.e.shutdown();
this.d = null;
this.e = null;
}
static void a(byte[] paramArrayOfByte)
{
if ((paramArrayOfByte == null) || (paramArrayOfByte.length != 25))
return;
k = paramArrayOfByte;
}
d a()
{
return this.b;
}
private static void b(byte abyte0[])
throws IllegalArgumentException
{
int l = 0;
for(int i1 = 1; i1 < abyte0.length - 1; i1++)
l += abyte0[i1] & 0xff;
l &= 0xff;
int j1 = abyte0[1] & 0xf;
if((abyte0[0] & 0xff) != (byte)(l >> 0 & 0xf) || (abyte0[abyte0.length - 1] & 0xff) != (byte)(l >> 4 & 0xf) || abyte0[1] + abyte0[abyte0.length - 2] != 15)
a(16);
boolean aflag[] = new boolean[21];
byte abyte1[] = new byte[8];
int k1 = 1;
int l1 = j1;
for(int i2 = 0; i2 < abyte1.length; i2++)
{
for(; aflag[l1 % aflag.length]; l1++);
aflag[l1 % aflag.length] = true;
abyte1[i2] = abyte0[2 + l1 % aflag.length];
k1 += 2;
l1 += k1;
}
if((abyte1[1] & 0xf) != 3)
a(32);
boolean flag = (abyte1[3] & 0xf) >= 8;
int j2 = (flag ? abyte1[3] - 8 : abyte1[3]) & 0xf;
if(j2 < 1)
a(1);
if(flag)
{
Calendar calendar = Calendar.getInstance();
calendar.set(1, 2000 + (abyte1[4] & 0xf));
calendar.set(2, (abyte1[5] & 0xf) - 1);
calendar.set(5, ((abyte1[6] & 0xf) << 4) + (abyte1[7] & 0xf));
if(System.currentTimeMillis() - calendar.getTimeInMillis() > 0L)
a(18);
}
}
private static void a(int paramInt)
{
switch (paramInt & 0xF)
{
case 0:
throw new IllegalArgumentException(a(new long[] { 8460391658548064800L, 8315163859177334048L, 8319872964449869929L, 7205878151055483136L }));
case 1:
throw new IllegalArgumentException(a(new long[] { 8460391658548064800L, 8315163859177334048L, 8319309735340351598L, 7811060823377406308L, 7162256601089340786L, 8532478991051810162L, 120946281218048L }));
case 2:
throw new IllegalArgumentException(a(new long[] { 8462924959242482208L, 2314957309810076517L, 2335505025909089656L, 2378011653932580864L }));
}
}
private static String a(long[] paramArrayOfLong)
{
byte[] arrayOfByte = new byte[paramArrayOfLong.length * 8];
int m = 0;
for (int n = 0; n < paramArrayOfLong.length; n++)
for (int i1 = 0; i1 < 8; i1++)
{
byte i2 = (byte)(int)(paramArrayOfLong[n] >> j[i1] * 8 & 0xFF);
if (i2 == 0)
break;
arrayOfByte[(n * 8 + i1)] = i2;
m++;
}
return new String(arrayOfByte, 0, m);
}
static void a(UltraNetConnection netconnection, String s, Exception exception)
{
netconnection.a(s, exception);
}
static void a(UltraNetConnection netconnection, String s)
{
netconnection.a(s);
}
static void a(UltraNetConnection netconnection, Map map)
{
netconnection.b(map);
}
static void b(UltraNetConnection netconnection, Map map)
{
netconnection.a(map);
}
static void c(UltraNetConnection netconnection, Map map)
{
netconnection.a(map);
}
private class a extends d.a
{
public a()
{
}
public void a(String paramString, Exception paramException)
{
UltraNetConnection.a(UltraNetConnection.this, paramString, paramException);
}
public void a(String paramString)
{
UltraNetConnection.a(UltraNetConnection.this, paramString);
}
public void a(Map<String, Object> paramMap)
{
String str = (String)paramMap.get("code");
if ((!"NetConnection.Connect.Success".equals(str)) && (!"NetConnection.Connect.Bandwidth".equals(str)) && (!"NetConnection.Call.Failed".equals(str)))
UltraNetConnection.a(UltraNetConnection.this, paramMap);
UltraNetConnection.b(UltraNetConnection.this, paramMap);
}
public void a(long paramLong1, long paramLong2)
{
if (!((Boolean)UltraNetConnection.this.configuration().get("enableAcknowledgementEventNotification")).booleanValue())
return;
Map localMap = com.smaxe.uv.a.e.b("NetConnection.Connect.Bandwidth", "'Acknowledgement' event notification.");
localMap.put("acknowledgement", Long.valueOf(paramLong1));
localMap.put("info", new ProtocolLayerInfo(UltraNetConnection.this.getInfo()));
localMap.put("uploadBufferSize", Long.valueOf(paramLong2));
UltraNetConnection.c(UltraNetConnection.this, localMap);
}
}
}
package com.weedong.net.rtmp;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Map;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.util.Log;
import com.gauss.speex.encode.Speex;
import com.smaxe.uv.client.ICamera;
import com.smaxe.uv.client.IMicrophone;
import com.smaxe.uv.client.INetConnection;
import com.smaxe.uv.client.INetStream;
import com.smaxe.uv.client.IVideo;
import com.smaxe.uv.client.a.c;
import com.smaxe.uv.client.video.EmptyVideo;
import com.smaxe.uv.stream.MediaData;
public final class UltraNetStream extends c implements INetStream {
private com.smaxe.uv.client.a.d videoControl = null;
private IVideo ivideo = new EmptyVideo();
private volatile boolean c = false;
private IMicrophone d = null;
private ICamera e = null;
private e f = null;
private c g = null;
private VideoOrAudioInfo audioData = null;
private VideoOrAudioInfo videoData = null;
public UltraNetStream(INetConnection paramINetConnection) {
this.videoControl = ((UltraNetConnection) paramINetConnection).a();
this.videoControl.a(paramINetConnection, this, new d());
this.audioData = new VideoOrAudioInfo();
this.videoData = new VideoOrAudioInfo();
}
public INetStream.Info getInfo() {
// return video/audio frame;size;
System.out.println("h.a = " + this.audioData.duration);
System.out.println("h.b = " + this.audioData.frames);
System.out.println("h.c = " + this.audioData.size);
return new INetStream.Info(this.audioData.duration,
this.audioData.frames, this.audioData.size,
this.videoData.duration, this.videoData.frames,
this.videoData.size);
}
public ICamera getCamera() {
return this.e;
}
public IMicrophone getMicrophone() {
return this.d;
}
public IVideo getVideo() {
if ((this.ivideo instanceof MyVideo))
return ((MyVideo) this.ivideo).a();
return this.ivideo;
}
public double bufferLength() {
return this.c ? this.videoControl.b(this) : this.ivideo.bufferLength();
}
public int bufferSize() {
return this.c ? this.videoControl.c(this) : 0;
}
public long bytesLoaded() {
return this.ivideo.bytesLoaded();
}
public long bytesTotal() {
return this.ivideo.bytesTotal();
}
public int clearBuffer() {
return this.videoControl.a(this);
}
public void attachAudio(IMicrophone paramIMicrophone) {
if (this.d == paramIMicrophone)
return;
if (this.d != null) {
this.d.removeListener(this.g);
this.g = null;
}
this.d = paramIMicrophone;
if (this.d != null)
this.d.addListener(this.g = new c());
}
public void attachCamera(ICamera paramICamera, int paramInt) {
if (this.e == paramICamera)
return;
if (this.e != null) {
this.e.removeListener(this.f);
this.f = null;
}
this.e = paramICamera;
if (this.e != null)
this.e.addListener(this.f = new e(paramInt));
}
public void publish(String paramString1, String paramString2) {
paramString2 = paramString2 == null ? "live" : paramString2;
if ((!"append".equalsIgnoreCase(paramString2))
&& (!"live".equalsIgnoreCase(paramString2))
&& (!"record".equalsIgnoreCase(paramString2)))
throw new IllegalArgumentException("Wrong publish type '"
+ paramString2 + "'. Use 'live','record' or 'append' type");
this.videoControl.a(this, paramString1, paramString2);
this.c = true;
}
public void send(String paramString, Object... paramArrayOfObject) {
this.videoControl.a(this, paramString, paramArrayOfObject);
}
public double currentFPS() {
return this.ivideo.fps();
}
public double liveDelay() {
return this.ivideo.liveDelay();
}
public double time() {
return this.ivideo.time();
}
public void play(IVideo paramIVideo, Object... paramArrayOfObject) {
if (paramIVideo == null)
throw new IllegalArgumentException("Parameter 'video' is null");
this.ivideo = new MyVideo(paramIVideo);
this.videoControl.a(this, this.ivideo, paramArrayOfObject);
}
public void pause() {
this.videoControl.d(this);
}
public void receiveAudio(boolean paramBoolean) {
this.videoControl.a(this, paramBoolean);
}
public void receiveVideo(boolean paramBoolean) {
this.videoControl.b(this, paramBoolean);
}
public void resume() {
this.videoControl.e(this);
}
public void seek(double paramDouble) {
this.videoControl.a(this, paramDouble);
}
public void togglePause() {
if (this.c)
pause();
else
resume();
}
public void close() {
attachAudio(null);
attachCamera(null, 0);
this.videoControl.f(this);
super.close();
}
public void _RtmpSampleAccess(boolean paramBoolean1, boolean paramBoolean2) {
}
static void a(UltraNetStream netstream, String s, Exception exception) {
netstream.a(s, exception);
}
static void a(UltraNetStream netstream, String s) {
netstream.a(s);
}
static void a(UltraNetStream netstream, Map map) {
netstream.c(map);
}
static void b(UltraNetStream netstream, Map map) {
netstream.a(map);
}
static IVideo a(UltraNetStream netstream) {
return netstream.ivideo;
}
static void c(UltraNetStream netstream, Map map) {
netstream.b(map);
}
static void d(UltraNetStream netstream, Map map) {
netstream.d(map);
}
static boolean b(UltraNetStream netstream) {
return netstream.c;
}
static VideoOrAudioInfo c(UltraNetStream netstream) {
return netstream.videoData;
}
static com.smaxe.uv.client.a.d d(UltraNetStream netstream) {
return netstream.videoControl;
}
// TASK System callback method
static VideoOrAudioInfo e(UltraNetStream netstream) {
System.out.println("netstream.getVideo:" + netstream.getInfo());
return netstream.audioData;
}
private final class VideoOrAudioInfo {
public int duration = 0;
public int frames = 0;
public int size = 0;
// File file = new File("//mnt//sdcard//rtmpStream.txt");
// OutputStream os = null;
// private FileOutputStream fos;
byte[] b;
private Speex speexDecoder;
private AudioTrack track;
private int minBufferSize;
int decsize = 0;
short[] decoded = new short[160];
public VideoOrAudioInfo() {
speexDecoder = new Speex();
speexDecoder.init();
minBufferSize = AudioTrack.getMinBufferSize(8000,
AudioFormat.CHANNEL_OUT_MONO,
AudioFormat.ENCODING_PCM_16BIT);
track = new AudioTrack(AudioManager.STREAM_MUSIC, 8000,
AudioFormat.CHANNEL_OUT_MONO,
AudioFormat.ENCODING_PCM_16BIT, minBufferSize,
AudioTrack.MODE_STREAM);
}
// TODO MediaData
public MediaData a(MediaData paramMediaData) {
this.duration = (this.frames == 0 ? 0 : this.duration
+ paramMediaData.rtime);
this.frames += 1;
this.size += paramMediaData.size();
try {
InputStream mdate = paramMediaData.read();
int dataLen = mdate.available() - 1;
//Log.d("audioSize", dataLen+"");
b = new byte[dataLen];
mdate.read(b, 0, 1);
mdate.read(b, 0, dataLen);
if (dataLen > 11) {
if ((decsize = speexDecoder.decode(b, decoded, dataLen)) > 0) {
track.write(decoded, 0, decsize);
track.setStereoVolume(0.7f, 0.7f);
track.play();
}
}
} catch (IOException e) {
Log.e("IOException", e.toString());
}
return paramMediaData;
}
}
private final class MyVideo implements IVideo {
private final IVideo v;
public MyVideo(IVideo arg2) {
this.v = arg2;
}
public IVideo a() {
return this.v;
}
public double bufferLength() {
return this.v.bufferLength();
}
public long bytesLoaded() {
return this.v.bytesLoaded();
}
public long bytesTotal() {
return this.v.bytesTotal();
}
public void clear() {
this.v.clear();
}
public void clearPlayBuffer() {
this.v.clearPlayBuffer();
}
public double fps() {
return this.v.fps();
}
public double liveDelay() {
return this.v.liveDelay();
}
public void reset() {
this.v.reset();
}
public double time() {
return this.v.time();
}
public void onAudioData(MediaData paramMediaData) {
this.v.onAudioData(UltraNetStream.e(UltraNetStream.this).a(
paramMediaData));
System.out.println("" + paramMediaData);
}
public void onVideoData(MediaData paramMediaData) {
this.v.onVideoData(UltraNetStream.c(UltraNetStream.this).a(
paramMediaData));
}
public void onFlvData(MediaData paramMediaData) {
this.v.onFlvData(paramMediaData);
}
public void onCuePoint(Object paramObject) {
this.v.onCuePoint(paramObject);
}
public void onMetaData(Object paramObject) {
this.v.onMetaData(paramObject);
}
public void onSetDataFrame(String paramString, Object paramObject) {
this.v.onSetDataFrame(paramString, paramObject);
}
}
private final class c extends IMicrophone.ListenerAdapter {
public c() {
}
public void onAudioData(MediaData paramMediaData) {
if (!UltraNetStream.b(UltraNetStream.this))
return;
UltraNetStream.d(UltraNetStream.this).a(UltraNetStream.this,
UltraNetStream.e(UltraNetStream.this).a(paramMediaData));
}
}
private final class e extends ICamera.ListenerAdapter {
private final int b;
private long c = 0L;
public e(int arg2) {
this.b = arg2;
}
public void onVideoData(MediaData paramMediaData) {
if (!UltraNetStream.b(UltraNetStream.this))
return;
if (this.b > 0) {
try {
Class<?> clzF = Class.forName("com.smaxe.uv.a.a.f");
Method m = clzF.getMethod("a", int.class);
long l = System.currentTimeMillis();
if (l - this.c < this.b)
return;
int value = (Integer) m.invoke(clzF, paramMediaData.tag());
switch (value) {
case 1:
case 5:
this.c = l;
break;
default:
return;
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
UltraNetStream.d(UltraNetStream.this).b(UltraNetStream.this,
UltraNetStream.c(UltraNetStream.this).a(paramMediaData));
}
public void onFlvData(MediaData paramMediaData) {
if (!UltraNetStream.b(UltraNetStream.this))
return;
UltraNetStream.d(UltraNetStream.this).c(UltraNetStream.this,
paramMediaData);
}
}
private class d extends com.smaxe.uv.client.a.d.a
{
public d() {
}
public void a(String paramString, Exception paramException) {
UltraNetStream.a(UltraNetStream.this, paramString, paramException);
}
public void a(String paramString) {
UltraNetStream.a(UltraNetStream.this, paramString);
}
public void a(Map<String, Object> paramMap) {
UltraNetStream.a(UltraNetStream.this, paramMap);
}
public void b(Map<String, Object> paramMap) {
UltraNetStream.b(UltraNetStream.this, paramMap);
UltraNetStream.a(UltraNetStream.this).onCuePoint(paramMap);
}
public void c(Map<String, Object> paramMap) {
UltraNetStream.c(UltraNetStream.this, paramMap);
UltraNetStream.a(UltraNetStream.this).onMetaData(paramMap);
}
public void d(Map<String, Object> paramMap) {
UltraNetStream.d(UltraNetStream.this, paramMap);
}
public void a(String paramString, Map<String, Object> paramMap) {
UltraNetStream.a(UltraNetStream.this).onSetDataFrame(paramString,
paramMap);
}
}
private final static byte[] hex = "0123456789ABCDEF".getBytes();
public static String Bytes2HexString(byte[] b) {
byte[] buff = new byte[2 * b.length];
for (int i = 0; i < b.length; i++) {
buff[2 * i] = hex[(b[i] >> 4) & 0x0f];
buff[2 * i + 1] = hex[b[i] & 0x0f];
}
return new String(buff);
}
}
以上两个是辅助的被破解的包,其中 UltraNetStream 中的 VideoOrAudioInfo 类是不断的服务器回掉的类,里面有个方法:
// TODO MediaData
public MediaData a(MediaData paramMediaData) {
this.duration = (this.frames == 0 ? 0 : this.duration
+ paramMediaData.rtime);
this.frames += 1;
this.size += paramMediaData.size();
try {
InputStream mdate = paramMediaData.read();
int dataLen = mdate.available() - 1;
//Log.d("audioSize", dataLen+"");
b = new byte[dataLen];
mdate.read(b, 0, 1);
mdate.read(b, 0, dataLen);
if (dataLen > 11) {
if ((decsize = speexDecoder.decode(b, decoded, dataLen)) > 0) {
track.write(decoded, 0, decsize);
track.setStereoVolume(0.7f, 0.7f);
track.play();
}
}
} catch (IOException e) {
Log.e("IOException", e.toString());
}
return paramMediaData;
}
该方法是red5不断一直回掉的函数,在这个函数里, paramMediaData 是我们一直要的音频流,这里我将它转成了ImputStream ,并且交给speex进行decode,decode完成后,交给手机的声卡进行播放;在不断的调试中,发现 paramMediaData 这个每次传递过来的都是43位数据,通过抓包,输出log发现,
每一行开头都会有个B2,查阅另一位大神(http://blog.csdn.net/cssmhyl/article/details/8128705)的帖子,发现B2为speex音频格式的数据头,播放音频时需要去除该音频头信息,
于是就使用该方法,调试,
<span style="white-space:pre"></span>int dataLen = mdate.available() - 1;将头位置不放到b里面,直接从B2后面的位置开始读取数据;不然的话,会一直听见各种杂音;这样就可以播放正常的声音了;
//Log.d("audioSize", dataLen+"");
b = new byte[dataLen];
mdate.read(b, 0, 1);
mdate.read(b, 0, dataLen);
如果碰见声音音调不正常的,估计是采样率设置的问题,我使用的是8kHz,声音播放正常了;
这边需要注意的是,VideoOrAudioInfo(){}这个构造函数中,需要初始化speex,一个minBufferSize 和一个 声卡对象;