项目工程demo地址https://github.com/liluojun/PlayVideo
demo包含硬编解h264、libyuv裁剪图像、opengles渲染yuv数据、ffmpeg解码裸h264数据等功能,故仅供参考测试。
硬编码首先设置编码器
MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", width, height); mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);//色彩空间 mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);//码率 mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, m_framerate);//帧率 mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2);//I帧间隔 try { mediaCodec = MediaCodec.createEncoderByType("video/avc"); } catch (Exception e) { e.printStackTrace(); } mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); mediaCodec.start();//启动编码器
这块基本是模板代码,编码器常用的参数都在这里。
喂数据编码,数据来源手机相机
public void encoder(byte[] input) { if (isRuning) { try { if (input != null && input.length != 0) { if (colorFormat <= 20) { JavaToNativeMethod.getInstence().nv21ToI420(input, yuv420sp, m_width, m_height, yByte, uByte, vByte); } else { JavaToNativeMethod.getInstence().nv21ToNv12(input, yuv420sp, m_width, m_height, yByte, uByte, vByte); } } if (!encode) { return; } if (input != null && input.length != 0) { try { ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers(); ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers(); int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1); Log.e("index", "" + inputBufferIndex); if (inputBufferIndex >= 0) { pts = computePresentationTime(generateIndex); ByteBuffer inputBuffer = inputBuffers[inputBufferIndex]; inputBuffer.clear(); inputBuffer.put(input); mediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, pts, 0); generateIndex += 1; } else { return; } MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC); while (outputBufferIndex >= 0) { UiVideoData u = new UiVideoData(); ByteBuffer outputBuffer = outputBuffers[outputBufferIndex]; byte[] outData = new byte[bufferInfo.size]; outputBuffer.get(outData); } bufferInfo = null; } catch (Exception e) { Log.e("Encoder", "编码错误" + e.getMessage()); } } } catch (Exception e) { e.printStackTrace(); } } }
转换数据格式我使用的是Android手机默认的nv21格式,但是受到手机存储yuv数据格式的限制故需要转换格式,格式转换用的是libyuv库,代码如下:
if (colorFormat <= 20) { JavaToNativeMethod.getInstence().nv21ToI420(input, yuv420sp, m_width, m_height, yByte, uByte, vByte); } else { JavaToNativeMethod.getInstence().nv21ToNv12(input, yuv420sp, m_width, m_height, yByte, uByte, vByte); }
编码基本上是模板代码在 outputBuffer.get(outData);这里outdata就是编码后的数据了。
关于sps与pps的获取:
if (spsPpsBuffer.getInt() == 0x00000001) { byte[] mMediaHead = new byte[outData.length]; System.arraycopy(outData, 0, mMediaHead, 0, outData.length); configbyte = outData; mMediaHead = null; outData = null; break; }
在编码器启动后出的第一个数据就是sps与pps信息。
关于I帧的判断:
(outData[4] & 0x1f) == 5
这里我采用的是上面的判定方式,网上也有其他的判定方式如outData[4]==65||outData[4]==25之类的。
强制I帧有时候我们会遇到需要强行出一帧I帧的情况,只需要调用编码器的flush()方法即可。
硬解码首先也是设置解码器
mediaformat = MediaFormat.createVideoFormat("video/avc", w, h); mediaformat.setByteBuffer("csd-0", ByteBuffer.wrap(sps));//设置sps mediaformat.setByteBuffer("csd-1", ByteBuffer.wrap(pps));//设置pps mediaformat.setInteger(MediaFormat.KEY_FRAME_RATE, m_framerate);//帧率 mediaformat.setInteger(MediaFormat.KEY_BIT_RATE, 1024 * 1000);//码率 mediaformat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2);//I帧间隔 mCodec.configure(mediaformat, null, null, 0);//注意第二个参数可以填入surface,将解码数据直接渲染,这里我用的自己的渲染故填null mCodec.start();//启动解码器
编解码器的设置差不多,只是多了sps和pps的参数设定。
喂数据解码
public void onFrame(UiVideoData uiVideoData) { byte[] buf = uiVideoData._data; ByteBuffer[] inputBuffers = mCodec.getInputBuffers();//MediaCodec在此ByteBuffer[]中获取输入数据 ByteBuffer[] outputBuffers = mCodec.getOutputBuffers(); // 解码后的数据 int inputBufferIndex = mCodec.dequeueInputBuffer(100);//获取输入缓冲区的索引 if (inputBufferIndex >= 0) { long pts = computePresentationTime(generateIndex); ByteBuffer inputBuffer = inputBuffers[inputBufferIndex]; inputBuffer.clear(); inputBuffer.put(buf);//先获取缓冲区,再放入值 mCodec.queueInputBuffer(inputBufferIndex, 0, buf.length, pts, 0);//四个参数,第一个是输入缓冲区的索引,第二个是放入的数据大小,第三个是时间戳,保证递增就是 generateIndex += 1; } else { return; } MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();//用于描述解码得到的byte[]数据的相关信息 int outputBufferIndex = mCodec.dequeueOutputBuffer(bufferInfo, 1000);//-1代表一直等待,0表示不等待 Log.e(TAG, "outputBufferIndex=" + outputBufferIndex); while (outputBufferIndex >= 0) {//大于等于0表示解码器有数据输出 ByteBuffer outputBuffer = outputBuffers[outputBufferIndex]; byte[] outData = new byte[bufferInfo.size]; outputBuffer.get(outData);//将Buffer内的数据取出到字节数组中 mCodec.releaseOutputBuffer(outputBufferIndex, true); outputBufferIndex = -1; } } 这里也基本是模板代码 outputBuffer.get(outData)获取到解码后的数据然后就可以自己去渲染的。
注:Android硬编硬解受限于设备并不保证所有设备都能运行成功。