安卓视频编解码

   自Android4.1之后增加了MediaCodec 类,通过该类可以调用系统所支持的编解码器(包括软和硬)。最近有项目需要在安卓上接收PC端推送过来的视频,实现多屏互动。其中用到了MediaCodec 。 本文简单的介绍MediaCodec 的使用,当然只是先讲解视频解码。

   首先定义一个SurfaceView用来显示解码后的视频。这里简单介绍一下SurfaceView。SurfaceView是View的继承类,这个视图里内嵌了一个专门用于绘制的Surface。

使用时需要重写3个方法:

   //在surface的大小发生改变时激发

 (1)public void surfaceChanged(SurfaceHolder holder,int format,int width,int height){}

   //在创建时激发,一般在这里调用画图的线程

 (2)public void surfaceCreated(SurfaceHolder holder){}

   //销毁时激发,一般在这里将画图的线程停止、释放

  (3)public void surfaceDestroyed(SurfaceHolder holder) {}

SurfaceHolder是surface的控制器,用来操纵surface。通过SurfaceHolder.getSurface()可以获得该SurfaceView对象。

较详细的关于SurfaceView 可以参考 http://www.cnblogs.com/xuling/archive/2011/06/06/android.html

    在public void surfaceCreated(SurfaceHolder holder){}方法里面可以初始化和配置解码器。

     mMediaCodec.configure(mMediaFormat, holder.getSurface(), null, 0);

     mMediaCodec.start();

这样一来,解码器解码出来的视频数据就会自动输出到了SurfaceView上面,当然在 surfaceDestroyed (SurfaceHolder holder) {} 里面要记得stop掉解码器或者release掉解码器。

代码中使用了一个线程来接收PC端推送过来的数据和处理数据。当然你可以使用一个线程来接收数据,一个线程来处理数据,这样效率可能会好些。

首先是读取帧头,判断校验码和获取一帧长度。

代码如下:

        InputStream is = null;
	byte[] header = new byte[12];
	byte[] readData = new byte[4 << 19];

	int len = 0;
	int rlen = 0;
	int hlen = 0;
	while (!isExit) {

		if (mClientSocket == null || mClientSocket.isClosed()) {
		try {					
			Thread.sleep(300);
		} catch (InterruptedException e) {}
			continue;
		}

		try {
			is = mClientSocket.getInputStream();
		} catch (IOException e) {
		closeSocket(0);
		<span style="white-space:pre">	</span>continue;
		}
		try {
		<span style="white-space:pre">	</span>len = is.read(header, 0, 12);
		} catch (IOException e) {
		}
		if (len <= 0) {
		<span style="white-space:pre">	</span>closeSocket(1);
			continue;
		}		
		/* 校验 */
		if (header[0] == (byte) 0x80 && header[1] == 0x60) {

		<span style="white-space:pre">	</span>// get frame length
			hlen = (header[8] & 0xFF);
			hlen += (header[9] & 0xFF) << 8;
			hlen += (header[10] & 0xFF) << 16;
			hlen += (header[11] & 0xFF) << 24;
		} else {
			closeSocket(2);
			continue;
		}

接着,再从输入流中读取一帧数据

<span style="white-space:pre">	</span>rlen = 0;
	// read one frame from InputStream
	while (hlen > 0) {
	try {
	len = is.read(readData, rlen, hlen);
	} catch (IOException e) {
		e.printStackTrace();
	}
	if (len <= 0) {
		closeSocket(3);
		break;
	}

	hlen -= len;
	rlen += len;
	}


    读取完一帧数据之后进行解码,使用的是h.264解码 video/avc。

    解码的过程大概是这样:

    1.获取空闲的输入缓冲区

    2.向输入缓冲区喂数据

    3.开始解码

    4.向输出缓冲区获取解码后的数据,释放缓冲区。

    

	int ibidx = -1;
	ByteBuffer[] inputBuffers = null;
	if (mMediaCodec != null) {

	try {
	<span style="white-space:pre">	</span>inputBuffers = mMediaCodec.getInputBuffers();
		/* wait indefinitely an input buffer */
		ibidx = mMediaCodec.dequeueInputBuffer(100000);		
	} catch (IllegalStateException e) {
	<span style="white-space:pre">	</span>continue;
	}
	if (ibidx >= 0 ) {
		ByteBuffer tmp = ByteBuffer.wrap(readData, 0, rlen);
		inputBuffers[ibidx].clear();
		inputBuffers[ibidx].put(tmp);
		mMediaCodec.queueInputBuffer(ibidx, 0, rlen, 0, 0);
		tmp.clear();
		releaseOutBuf(mMediaCodec);
	} 

   

	dequeueInputBuffer(long timeoutUs)

    方法用于申请输入缓冲区,参数timeoutUs >0 ,表示等待 timeoutUs  微妙, = 0 表示不等待 , -1 表示阻塞等待有效的输入缓冲区。 我测试时发现有些平板在这个地方总出现获取不到有效的缓冲区,而且出现的概率蛮大的,有可能是资源还没有释放完全造成的。

    因为直接让解码出来的数据显示SurfaceView上面,就不需要解码出来的数据了。不过还是得释放掉缓冲区,否则解码器始终保留着。

	private void releaseOutBuf(MediaCodec mc) {
	<span style="white-space:pre">	</span>MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
		int obidx = mc.dequeueOutputBuffer(info, 0);
		do {
		<span style="white-space:pre">	</span>if (obidx == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
				break;
			} else if (obidx == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {	
				break;
			} else if (obidx > 0) {
				mc.releaseOutputBuffer(obidx, true);
				obidx = mc.dequeueOutputBuffer(info, 0);			
			}
		} while (obidx > 0);
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

苦茶子12138

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值