項(xiàng)目工程demo地址https://github.com/liluojun/PlayVideo
demo包含硬編解h264筏餐、libyuv裁剪圖像、opengles渲染yuv數(shù)據(jù)捂寿、ffmpeg解碼裸h264數(shù)據(jù)等功能口四,故僅供參考測(cè)試。
硬編碼首先設(shè)置編碼器
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();//啟動(dòng)編碼器
這塊基本是模板代碼秦陋,編碼器常用的參數(shù)都在這里蔓彩。
喂數(shù)據(jù)編碼,數(shù)據(jù)來(lái)源手機(jī)相機(jī)
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", "編碼錯(cuò)誤" + e.getMessage());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
轉(zhuǎn)換數(shù)據(jù)格式我使用的是Android手機(jī)默認(rèn)的nv21格式驳概,但是受到手機(jī)存儲(chǔ)yuv數(shù)據(jù)格式的限制故需要轉(zhuǎn)換格式赤嚼,格式轉(zhuǎn)換用的是libyuv庫(kù),代碼如下:
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就是編碼后的數(shù)據(jù)了顺又。
關(guān)于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;
}
在編碼器啟動(dòng)后出的第一個(gè)數(shù)據(jù)就是sps與pps信息。
關(guān)于I幀的判斷:
(outData[4] & 0x1f) == 5
這里我采用的是上面的判定方式稚照,網(wǎng)上也有其他的判定方式如outData[4]==65||outData[4]==25之類的蹂空。
強(qiáng)制I幀有時(shí)候我們會(huì)遇到需要強(qiáng)行出一幀I幀的情況,只需要調(diào)用編碼器的flush()方法即可果录。
硬解碼首先也是設(shè)置解碼器
mediaformat = MediaFormat.createVideoFormat("video/avc", w, h);
mediaformat.setByteBuffer("csd-0", ByteBuffer.wrap(sps));//設(shè)置sps
mediaformat.setByteBuffer("csd-1", ByteBuffer.wrap(pps));//設(shè)置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);//注意第二個(gè)參數(shù)可以填入surface上枕,將解碼數(shù)據(jù)直接渲染,這里我用的自己的渲染故填null
mCodec.start();//啟動(dòng)解碼器
編解碼器的設(shè)置差不多雕憔,只是多了sps和pps的參數(shù)設(shè)定姿骏。
喂數(shù)據(jù)解碼
public void onFrame(UiVideoData uiVideoData) {
byte[] buf = uiVideoData._data;
ByteBuffer[] inputBuffers = mCodec.getInputBuffers();//MediaCodec在此ByteBuffer[]中獲取輸入數(shù)據(jù)
ByteBuffer[] outputBuffers = mCodec.getOutputBuffers(); // 解碼后的數(shù)據(jù)
int inputBufferIndex = mCodec.dequeueInputBuffer(100);//獲取輸入緩沖區(qū)的索引
if (inputBufferIndex >= 0) {
long pts = computePresentationTime(generateIndex);
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
inputBuffer.clear();
inputBuffer.put(buf);//先獲取緩沖區(qū),再放入值
mCodec.queueInputBuffer(inputBufferIndex, 0, buf.length, pts, 0);//四個(gè)參數(shù)斤彼,第一個(gè)是輸入緩沖區(qū)的索引分瘦,第二個(gè)是放入的數(shù)據(jù)大小,第三個(gè)是時(shí)間戳琉苇,保證遞增就是
generateIndex += 1;
} else {
return;
}
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();//用于描述解碼得到的byte[]數(shù)據(jù)的相關(guān)信息
int outputBufferIndex = mCodec.dequeueOutputBuffer(bufferInfo, 1000);//-1代表一直等待嘲玫,0表示不等待
Log.e(TAG, "outputBufferIndex=" + outputBufferIndex);
while (outputBufferIndex >= 0) {//大于等于0表示解碼器有數(shù)據(jù)輸出
ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
byte[] outData = new byte[bufferInfo.size];
outputBuffer.get(outData);//將Buffer內(nèi)的數(shù)據(jù)取出到字節(jié)數(shù)組中
mCodec.releaseOutputBuffer(outputBufferIndex, true);
outputBufferIndex = -1;
}
}
這里也基本是模板代碼 outputBuffer.get(outData)獲取到解碼后的數(shù)據(jù)然后就可以自己去渲染的。
注:Android硬編硬解受限于設(shè)備并不保證所有設(shè)備都能運(yùn)行成功并扇。