android下实时传输h264并播放

这周给安排了个任务,在android端读取H264原始视频数据,传一帧播一帧,播就播吧,还要Socket实时传输实时播,虽然并没有接触过,都无从下手,不过想到直播都是这么个形式,说明肯定是行得通的,而且之前解码过音频,知道android有一个音视频很强大的类MediaCodec,搞了几天,终于解决了,决定写篇博客把这个感人的故事记录下来。

什么是H264,简单来说,h264是一种视频编码格式它是一种很高明的视频算法,视频播放的时候,差不多每秒会播24帧,如果以一部1280*720的2小时的电影为例,差不多会有148GB大小,显然这太庞大了,而采用h264编码,它会首先传一个关键帧(I 帧),然后比较下一帧跟上一帧之间的差异,将这两帧的差异作为一个补偿帧(P/B帧),这样的话,只需要传补偿帧就可以进行画面的更新,除非整个场景的切换,才会又传一个关键帧,不然的话只传一个补偿帧,数据量还是很小的,所以才把一部电影压缩到只有1-2GB这么大。

H264的数据结构。

差不多是这样

头两帧是SPS(Sequence Parameter Set:序列参数集)和PPS(Picture Parameter Set:图像参数集)数据,这是视频的格式,包含了各种信息,我也没做深究,需要详细了解H264数据结构的朋友可以百度一下,有很多解释得很详细的,SPS,PPS后就是视频数据了,无论是I帧还是P帧,格式都是一样的,包括SPS和PPS,都是作为一帧数据,每一帧都是以0001或者001开头的,0001居多。所以我们只需要判断从0001开始,到下一个0001为止,就是一帧数据。大概需要了解的也就这么多,下面说说我是如何实现的。

建议大家一下,普通人用百度,程序员还是用Google的好,之前研究不会的东西的时候也是Google一下,顺利解决,百度上答案太多重复的了,所以虽然麻烦一点,不过能解决问题就好,还是推荐Google。

我是参考的这位大神:

http://www.itdadao.com/articles/c15a280703p0.html

不过他的帖子不太完全,贴一下我的代码:

首先是服务端:


一帧的封装类:

package com.example.server;

/**
 * @brief 一帧的封装
 */
public class Frame {
	public byte[] mData;
	public int offset;
	public int length;

	public Frame(byte[] data, int offset, int size) {
		mData = data;
		this.offset = offset;
		this.length = size;
	}

	public void setFrame(byte[] data, int offset, int size) {
		mData = data;
		this.offset = offset;
		this.length = size;
	}
}


VideoCodec接口:

package com.example.server;

public interface VideoCodec {

	String MIME_TYPE = "video/avc";
	int VIDEO_FRAME_PER_SECOND = 15;
	int VIDEO_I_FRAME_INTERVAL = 5;
	int VIDEO_BITRATE = 500 * 8 * 1000;
}


Socket服务器:

package com.example.server;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

import android.util.Log;

public class Server {
	private static Server server;
	private static ServerSocket mServerSocket;
	private Socket socket;
	/* 服务器端口 */
	private final static int SERVER_HOST_PORT = 12580;
	private boolean release;
	private static final String TAG = "daolema";

	public boolean hasRelease() {
		return release;
	}

	public void setRelease(boolean release) {
		this.release = release;
	}

	public static Server getInstance() {
		if (server == null) {
			server = new Server();
		}
		return server;
	}

	private static ServerSocket getServerSocket() {
		if (mServerSocket == null) {
			try {
				mServerSocket = new ServerSocket(SERVER_HOST_PORT);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return mServerSocket;
	}

	public void connect() {
		// TODO Auto-generated method stub
		try {
			mServerSocket = getServerSocket();
			Log.i(TAG, "socket已开启,等待连接");
			while (true) {
				socket = mServerSocket.accept();
				if (socket.isConnected()) {
					Log.i(TAG, "socket连接成功");
					break;
				}
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public void disconnect() {
		// TODO Auto-generated method stub
		try {
			if (socket != null && socket.isConnected()) {
				socket.close();
				mServerSocket.close();
				Log.i(TAG, "socket断开了");
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public byte[] readLength() {
		try {
			if (!socket.isConnected()) {
				socket = mServerSocket.accept();
			}
			InputStream is = socket.getInputStream();
			return readBytes(is, 4);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	public byte[] readSPSPPS(int length) {
		// TODO Auto-generated method stub
		try {
			if (!socket.isConnected()) {
				socket = mServerSocket.accept();
			}
			InputStream is = socket.getInputStream();
			return readBytes(is, length);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	public Frame readFrame(int frameLength) {
		// TODO Auto-generated method stub
		try {
			if (!socket.isConnected()) {
				socket = mServerSocket.accept();
			}
			InputStream is = socket.getInputStream();
			Frame frame = new Frame(readBytes(is, frameLength), 0, frameLength);
			return frame;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 从socket读byte数组
	 * 
	 * @param in
	 * @param length
	 * @return
	 */
	public static byte[] readBytes(InputStream in, long length) {
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		byte[] buffer = new byte[1024];
		int read = 0;
		while (read < length) {
			int cur = 0;
			try {
				cur = in.read(buffer, 0, (int) Math.min(1024, length - read));
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			if (cur < 0) {
				break;
			}
			read += cur;
			baos.write(buffer, 0, cur);
		}
		return baos.toByteArray();
	}
}


然后最主要的H264解码器类:
package com.example.server;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;

import android.annotation.SuppressLint;
import android.media.MediaCodec;
import android.media.MediaFormat;
import android.util.Log;
import android.view.Surface;

public class VideoDecoder implements VideoCodec {
	private Surface mSurface;
	private static final int TYPE_SPS = 7;
	private static final int TYPE_PPS = 8;
	private static final int TYPE_FRAME_DATA = 5;
	private static final int NO_FRAME_DATA = -1;
	private final int TIMEOUT_US = 10000;
	private static final String TAG = "daolema";
	private Server mServer;
	private Worker mWorker;
	private byte[] mSps;
	private byte[] mPps;
	private final static int HEAD_OFFSET = 512;

	public VideoDecoder(Surface surface, Server server)
	// throws DecoderServerNullException
	{
		if (server == null) {
			// throw new DecoderServerNullException();
		}
		mSurface = surface;
		mServer = server;
	}

	public void start() {
		if (mWorker == null) {
			mWorker = new Worker();
			mWorker.setRunning(true);
			mWorker.start();
		}
	}

	public void stop() {
		if (mWorker != null) {
			mWorker.setRunning(false);
			mWorker = null;
		}
		if (mServer != null) {
			if (!mServer.hasRelease()) {
				mServer.disconnect();
			}
		}
	}

	private class Worker extends Thread {
		volatile boolean isRunning;
		private MediaCodec decoder;
		private int mWidth;
		private int mHeight;
		MediaCodec.BufferInfo mBufferInfo;

		/**
		 * 等待客户端连接,解码器配置
		 * 
		 * @return
		 */
		public boolean prepare() {
			mServer.connect();
			mBufferInfo = new MediaCodec.BufferInfo();
			// 首先读取编码的视频的长度和宽度
			// try {
			// mWidth = mServer.readInt();
			// mHeight = mServer.readInt();
			mWidth = 1280;
			mHeight = 720;
			// } catch (IOException e) {
			// e.printStackTrace();
			// return false;
			// }
			// 编码器那边会先发sps和pps来,头一帧就由sps和pps组成
			int spsLength = bytesToInt(mServer.readLength());
			byte[] sps = mServer.readSPSPPS(spsLength);
			mSps = Arrays.copyOfRange(sps, 4, spsLength);
			int ppsLength = bytesToInt(mServer.readLength());
			byte[] pps = mServer.readSPSPPS(ppsLength);
			mPps = Arrays.copyOfRange(pps, 4, ppsLength);
			MediaFormat format = MediaFormat.createVideoFormat(
					MediaFormat.MIMETYPE_VIDEO_AVC, mWidth, mHeight);
			format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, mHeight * mWidth);
			format.setInteger(MediaFormat.KEY_MAX_HEIGHT, mHeight);
			format.setInteger(MediaFormat.KEY_MAX_WIDTH, mWidth);
			format.setByteBuffer("csd-0", ByteBuffer.wrap(mSps));
			format.setByteBuffer("csd-1", ByteBuffer.wrap(mPps));
			try {
				decoder = MediaCodec
						.createDecoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
			} catch (IOException e) {
				e.printStackTrace();
			}
			decoder.configure(format, mSurface, null, 0);
			decoder.start();
			return true;
		}

		public void setRunning(boolean running) {
			isRunning = running;
		}

		@Override
		public void run() {
			if (!prepare()) {
				Log.i(TAG, "视频解码器初始化失败");
				isRunning = false;
			}
			while (isRunning) {
				decode();
			}
			release();

		}

		private void decode() {
			byte[] data = new byte[100000];
			byte[] frameData = new byte[200000];
			boolean isEOS = false;
			while (!isEOS) {
				// 判断是否是流的结尾
				int inIndex = decoder.dequeueInputBuffer(TIMEOUT_US);
				if (inIndex >= 0) {
					/**
					 * 测试
					 */
					// byte[] frame=mServer.readFrame();
					int frameLength = bytesToInt(mServer.readLength());
					Frame frame = mServer.readFrame(frameLength);
					ByteBuffer buffer = decoder.getInputBuffer(inIndex);
					if (buffer == null) {
						Log.i(TAG, "buffer=null");
						return;
					}
					buffer.clear();
					if (frame == null) {
						Log.d(TAG, "InputBuffer BUFFER_FLAG_END_OF_STREAM");
						decoder.queueInputBuffer(inIndex, 0, 0, 0,
								MediaCodec.BUFFER_FLAG_END_OF_STREAM);
						isEOS = true;
						isRunning = false;
						// 服务已经断开,释放服务端
						mServer.disconnect();
					} else {
						buffer.put(frame.mData, 0, frame.length);
						buffer.clear();
						buffer.limit(frame.length);
						decoder.queueInputBuffer(inIndex, 0, frame.length, 0,
								MediaCodec.BUFFER_FLAG_SYNC_FRAME);
					}
				} else {
					isEOS = true;
				}
				int outIndex = decoder.dequeueOutputBuffer(mBufferInfo,
						TIMEOUT_US);
				// Log.i(TAG, "video decoding .....");
				while (outIndex >= 0) {
					// ByteBuffer buffer =
					decoder.getOutputBuffer(outIndex);
					decoder.releaseOutputBuffer(outIndex, true);
					outIndex = decoder.dequeueOutputBuffer(mBufferInfo,
							TIMEOUT_US);// 再次获取数据,如果没有数据输出则outIndex=-1
										// 循环结束
				}
			}

		}

		/**
		 * 释放资源
		 */
		@SuppressLint("NewApi")
		private void release() {
			if (decoder != null) {
				decoder.stop();
				decoder.release();
			}
		}
	}

	public int bytesToInt(byte[] bytes) {
		int i;
		i = (int) ((bytes[0] & 0xff) | ((bytes[1] & 0xff) << 8)
				| ((bytes[2] & 0xff) << 16) | ((bytes[3] & 0xff) << 24));
		return i;
	}
}
在MainActivity中通过一个SurfaceView播放接收到的数据即可:
package com.example.server;

import android.app.Activity;
import android.os.Bundle;
import android.view.SurfaceView;

public class MainActivity extends Activity {

	private SurfaceView mSurfaceView;
	private Server mServer;
	private VideoDecoder mVideoDecoder;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		mSurfaceView = (SurfaceView) findViewById(R.id.surfaceView1);
		mServer = Server.getInstance();
		mVideoDecoder = new VideoDecoder(mSurfaceView.getHolder().getSurface(),
				mServer);
		mVideoDecoder.start();
	}
}

服务器XML:



    




------------------------------------------------------------我是分隔线------------------------------------------------------------

再来是客户端,客户端就比较简单了:

Socket客户端:

package com.example.client;

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

import android.util.Log;

/**
 * Socket通信的工具类(客户端)
 * Created by 禽兽先生
 * Created on 2016.03.06
 */
public class Client {
	private static Client mClient;
	private static Socket mSocket;
	// 要连接的服务器IP地址
	private static final String HOST_ADDRESS = "192.168.1.108";
	// 要连接的服务器端口号
	private static final int HOST_PORT = 12580;

	/**
	 * 单例模式
	 */
	public static Client getInstance() {
		if (mClient == null) {
			mClient = new Client();
		}
		return mClient;
	}

	/**
	 * 打开Socket通道,连接服务器
	 */
	private static Socket getSocket() {
		if (mSocket == null) {
			try {
				mSocket = new Socket(HOST_ADDRESS, HOST_PORT);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return mSocket;
	}

	/**
	 * 打开Socket通道,连接服务器
	 */
	public void connect() {
		// TODO Auto-generated method stub
		mSocket = getSocket();
		Log.i("daolema", "socket成功连接");
	}

	/**
	 * 断开Socket连接
	 */
	public void disconnect() {
		if (mSocket != null && mSocket.isConnected()) {
			try {
				mSocket.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	public void sendLength(byte[] bytes) {
		try {
			if (!mSocket.isConnected()) {
				mSocket = getSocket();
			}
			OutputStream os = mSocket.getOutputStream();
			os.write(bytes);
			os.flush();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public void sendSPSPPS(byte[] bytes) {
		try {
			if (!mSocket.isConnected()) {
				mSocket = getSocket();
			}
			OutputStream os = mSocket.getOutputStream();
			os.write(bytes);
			os.flush();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public void sendFrame(byte[] bytes) {
		try {
			if (!mSocket.isConnected()) {
				mSocket = getSocket();
			}
			OutputStream os = mSocket.getOutputStream();
			os.write(bytes, 0, bytes.length);
			os.flush();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
客户端的MainActivity:

package com.example.client;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Arrays;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MainActivity extends Activity {
	private Client mClient;
	private byte[] fileBytes;
	private int count = 0;
	private Button bt_spspps;
	private Button bt_h264;

	@SuppressLint("NewApi")
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		bt_spspps = (Button) findViewById(R.id.bt_spspps);
		bt_h264 = (Button) findViewById(R.id.bt_h264);
		fileBytes = getByte("/storage/emulated/0/Movies/1280_720.h264");
		bt_spspps.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				// TODO Auto-generated method stub
				new Thread(new Runnable() {
					@SuppressWarnings("unused")
					@SuppressLint("NewApi")
					@Override
					public void run() {
						mClient = Client.getInstance();
						mClient.connect();
						start: for (int i = 0; i < fileBytes.length; i++) {
							if (fileBytes[i] == 0 && fileBytes[i + 1] == 0
									&& fileBytes[i + 2] == 0
									&& fileBytes[i + 3] == 1) {
								end: for (int j = i + 4; j < fileBytes.length; j++) {
									if (fileBytes[j] == 0
											&& fileBytes[j + 1] == 0
											&& fileBytes[j + 2] == 0
											&& fileBytes[j + 3] == 1) {
										byte[] temp = Arrays.copyOfRange(
												fileBytes, i, j);
										mClient.sendLength(intToBytes(temp.length));
										mClient.sendSPSPPS(temp);
										count++;
										if (count == 2) {
											runOnUiThread(new Runnable() {
												public void run() {
													bt_spspps.setEnabled(false);
												}
											});
											return;
										}
										break end;
									}
								}
							}
						}
					}
				}).start();

			}
		});
		bt_h264.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				// TODO Auto-generated method stub
				new Thread(new Runnable() {
					@SuppressWarnings("unused")
					@Override
					public void run() {
						// TODO Auto-generated method stub
						start: for (int i = 0; i < fileBytes.length; i++) {
							if (fileBytes[i] == 0 && fileBytes[i + 1] == 0
									&& fileBytes[i + 2] == 0
									&& fileBytes[i + 3] == 1) {
								end: for (int j = i + 4; j < fileBytes.length; j++) {
									if (fileBytes[j] == 0
											&& fileBytes[j + 1] == 0
											&& fileBytes[j + 2] == 0
											&& fileBytes[j + 3] == 1) {
										byte[] temp = Arrays.copyOfRange(
												fileBytes, i, j);
										mClient.sendLength(intToBytes(temp.length));
										mClient.sendFrame(temp);
										break end;
									}
								}
							}
						}
					}
				}).start();
			}
		});
	}

	private byte[] getByte(String path) {
		File f = new File(path);
		InputStream in;
		byte bytes[] = null;
		try {
			in = new FileInputStream(f);
			bytes = new byte[(int) f.length()];
			in.read(bytes);
			in.close();
		} catch (Exception e) {
			e.printStackTrace();
		}

		return bytes;
	}

	public byte[] intToBytes(int i) {
		byte[] bytes = new byte[4];
		bytes[0] = (byte) (i & 0xff);
		bytes[1] = (byte) ((i >> 8) & 0xff);
		bytes[2] = (byte) ((i >> 16) & 0xff);
		bytes[3] = (byte) ((i >> 24) & 0xff);
		return bytes;
	}
}
客户端XML:


    
这就是所有的代码了,记得在两边都加网络权限,不然没办法通信,读写内存卡的权限在客户端添加就行:

    
    
    
    
然后把h264文件放到对应目录下即可,你也可以自己随便放一个目录,代码中目录对应也行。


我写的这个是通过Socket实时传输数据的,实时播放的,如果需要直接播放本地的H264视频的朋友,可以参考这篇文章:

http://www.cnblogs.com/superping/p/4884689.html

本文的Demo:

http://download.csdn.net/detail/zgcqflqinhao/9688735








你可能感兴趣的:(走在自己的Android之路上)