数据源为摄像头
int framerate = 5;
int width = 640;
int height = 480;
NV21Convertor mConvertor;
MediaCodec mMediaCodec;
// 初始化/设置/开始执行 ---> 编码器
public static void initMediaCodec() {
int dgree = getDgree(mContext);
// bitrate 比特率
int bitrate = 2 * width * height * framerate / 50;
// bitrate = 300000;
// EncoderDebugger 编码器调试器
EncoderDebugger debugger = EncoderDebugger.debug(mContext, width, height);
//mConVertor 转换器 转换成 NV21格式
mConvertor = debugger.getNV21Convertor();
try {
//创建MediaCodec, 此时是Uninitialized状态
mMediaCodec = MediaCodec.createByCodecName(debugger.getEncoderName());
//MediaFormat 的一些设置
//初始化编码器 MediaFormat 媒体格式
MediaFormat mediaFormat;
//createVideoFormat 创建视频格式 预设宽高
//“video/avc” - H.264/AVC
mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, width, height);
//设置马率
//码率(编码每秒编出多少帧画面) : 设置比特率 : 每秒传送的比特(bit)数。
// 单位为bps,比特率越高,传送数据速度越快
// bitrate = 2 * width * height * framerate / 20;
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
//设置帧率
//关键帧 速率 : 每秒显示的帧数 影响画面流畅度
// int framerate = 10; -- > 帧速率为10
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, framerate);
//EncoderColorFormat 编码器颜色格式
//NV21格式
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, debugger.getEncoderColorFormat());
//设置关键帧间隔时间
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);//关键帧间隔时间
//调用configure 进入Configured状态
mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
//调用start 进入Executing状态, 开始编解码工作
//打开编码器 : 调用 start 进入 Executing 状态,开始编解码工作
mMediaCodec.start();
} catch (IOException e) {
e.printStackTrace();
}
}
//定义 Camera相机预览回调的方法, 使用Buffer数组的同步处理方式
private Camera.PreviewCallback previewCallback = new Camera.PreviewCallback() {
byte[] mPpsSps = new byte[0];
@Override
//PreviewFrame 预览框架
public void onPreviewFrame(byte[] data, Camera camera) {
synchronized (this) {
//获取输入输出缓冲区
//从输入缓冲区队列中取出可用缓冲区, 并填充数据
ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers();
//从输入缓冲区队列中拿到编解码后的内容, 进行相应操作后释放, 供下一次使用
ByteBuffer[] outputBuffers = mMediaCodec.getOutputBuffers();
byte[] dst = new byte[data.length];
//拿到相机数据: parameters 获取照相机参数 previewSize 获取预览尺寸
Camera.Size previewSize = mCamera.getParameters().getPreviewSize();
byte[] yuv420sp = new byte[previewSize.width * previewSize.height * 3/2];
//转换颜色格式
// NV21ToNV12(data, yuv420sp,previewSize.width , previewSize.height);
dst = yuv420sp;
dst = data;
try {
/*
* 从输入缓冲区队列取出可用缓冲区, 并填充数据
* queueInputBuffer 和 dequeueInputBuffer是一对方法,两个要在一起使用
* 这一对函数的应用场合是对输入的数据流进行编码或者解码处理的时候
* */
/*
* dequeueInputBuffer返回缓冲区索引,如果索引小于 0 ,则表示当前没有可用的缓冲区。
* 它的参数 timeoutUs表示超时时间(请求是否可用的) ,毕竟用的是 MediaCodec 的同步模式,
* 如果没有可用缓冲区,就会阻塞指定参数时间; 如果参数为负数,则会一直阻塞下去。
* */
int bufferIndex = mMediaCodec.dequeueInputBuffer(1);
if (bufferIndex >= 0) {
// 清除原来的内容以接收新的内容, 获取一个 ByteBuffer的数组(inputBuffers), 这些数据就是准备处理的数据
inputBuffers[bufferIndex].clear();
// 传入原始数据, 调用 dequeueInputBuffer方法提取出要处理的部分, 放到缓冲区
mConvertor.convert(dst, inputBuffers[bufferIndex]);
/*
* 提交数据给 Codec
* queueInputBuffer方法将数据入队时,除了要传递出队时的索引值(bufferIndex),
* 然后还需要传入当前缓冲区的时间戳 presentationTimeUs 和 当前缓冲区的一个标识 flag,
* 把盒子和原料一起放回到传送带上原来的位置
* */
mMediaCodec.queueInputBuffer(bufferIndex, 0,
//position 位置
inputBuffers[bufferIndex].position(),
System.nanoTime() / 10, 0);
/*
* 把数据传入给 MediaCodec之后,通过 dequeueOutputBuffer方法取出编解码后的数据,除了指定超时时间外,
* 还需要传入 MediaCodec.BufferInfo 对象,这个对象里面有着编码后数据的长度、偏移量以及标识符。
* 获取输出 buffer和 index
* */
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
/*
* 从输入缓冲区队列中拿到编解码后的内容, 进行相应操作后释放, 供下一次使用
* timeoutUS 设为0, 设为-1时图像不动
* dequeueOutputBuffer 与上面的方法差不多一样
* 获取可用的输出缓冲区, 取出加工好的数据
* */
int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, 0);
// 从输出缓冲区队列中拿到编码好的内容,对内容进行相应处理后在释放
//true 将解码的数据显示到surface上
while (outputBufferIndex >= 0) {
ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
byte[] outData = new byte[bufferInfo.size];
outputBuffer.get(outData);
// 取出 MediaCodec.BufferInfo 内的数据(outputBufferIndex)之后,根据不同的标识符进行不同的操作
//记录pps和sps: 这个数据是必须要有的,它里面有着视频的宽、高信息
if (outData[0] == 0 && outData[1] == 0 && outData[2] == 0 && outData[3] == 1 && outData[4] == 103) {
mPpsSps = outData;
} else if (outData[0] == 0 && outData[1] == 0 && outData[2] == 0 && outData[3] == 1 && outData[4] == 101) {
//在关键帧前面加上pps和sps数据
//关键帧比普通帧多了个帧头, 保存了编码的信息
byte[] iframeData = new byte[mPpsSps.length + outData.length];
//System.arraycopy() 方法复制指定的源数组的数组,
// 在指定的位置开始,到目标数组的指定位置。
System.arraycopy(mPpsSps, 0, iframeData, 0, mPpsSps.length);
System.arraycopy(outData, 0, iframeData, mPpsSps.length, outData.length);
outData = iframeData;
}
byte[] length = intToByteArray(outData.length);
byte[] testdata = new byte[length.length + outData.length + 2];
byte[] head = {(byte) 0xff, 0x00};
System.arraycopy(head, 0, testdata, 0, 2);
System.arraycopy(length, 0, testdata, 2, length.length);
System.arraycopy(outData, 0, testdata, length.length + 2, outData.length);
//发送视频 testdata为我们想要的数据
// TcpVideo.getInstance().sendImage(testdata);
//通过releaseOutputBuffer 方法释放对应的缓冲区,
// 第二个参数表示是否要渲染到surface上, 不需要为false
mMediaCodec.releaseOutputBuffer(outputBufferIndex, false);
outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, 0);
}
} else {
Log.e("easypusher", "No buffer available !");
}
} catch (Exception e) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
String stack = sw.toString();
Log.e("save_log", stack);
e.printStackTrace();
} finally {
mCamera.addCallbackBuffer(dst);
}
}
}
};
private void NV21ToNV12(byte[] nv21, byte[] nv12, int width, int height) {
if (nv21 == null || nv12 == null)
return;
int framesize = width * height;
int i = 0, j = 0;
System.arraycopy(nv21, 0, nv12, 0, framesize);
for (i = 0; i < framesize; i++) {
nv12[i] = nv21[i];
}
for (j = 0; j < framesize / 2; j += 2) {
nv12[framesize + j - 1] = nv21[j + framesize];
}
for (j = 0; j < framesize / 2; j += 2) {
nv12[framesize + j] = nv21[j + framesize - 1];
}
}