主要用的安卓类有MediaCodec和MediaMuxer,MediaCodec负责视频数据编解码,MediaMuxer负责将编码后的数据封装成MP4文件,采集摄像头用的是camera,并且用surfaceview进行预览
1、初始化surfaceview与camera,预览摄像头的画面
private void initSurfaceHolder() {
surfaceHolder = surfaceView.getHolder();
surfaceHolder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
initCamera();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
mCamera.startPreview();
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
destroyCamera();
}
});
}
在surfaceview surfaceCreated中初始化摄像机配置。
private void initCamera() {
mCamera = Camera.open(1);
mCamera.setPreviewCallback(this);
mCamera.setDisplayOrientation(90);
if (parameters == null) {
parameters = mCamera.getParameters();
}
parameters.setPreviewFormat(ImageFormat.NV21);
parameters.setPreviewSize(1280, 720);
mCamera.setParameters(parameters);
try {
mCamera.setPreviewDisplay(surfaceHolder);
} catch (IOException e) {
e.printStackTrace();
}
}
注意由于摄像头默认是横着的,所以必须先调用setDisplayOrientation 翻转下,ImageFormat.NV21是摄像机默认的图像采样格式。
获取原始YUV数据:
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
if (YUVQueue.size() > 10) {
YUVQueue.poll();
}
YUVQueue.add(data);
}
onPreviewFrame 回调中获取到的就是原始YUV数据,这里,我先将它放到队列中,以方便后面编码从中取出。
2、摄像头采集到的画面的帧数据拿到后,下一步当然是进行编码工作,编码用到的是MediaCodec,我们先初步了解下MediaCodec。
MediaCodec类可以获取底层媒体编码/解码库,是Android底层多媒体支持库的一部分(一般和MediaExtractor、MediaSync、MediaMuxer、MediaCrypto、MediaDrm、Image、Surface、AudioTrack搭配使用)。
简单来说,MediaCodec 工作时候有输入工作队列和输出工作队列,我们将摄像头原始画面数据放进空的buffer中,并且送给编码器,编码器对其进行编码,编码后的数据又通过output buffer队列输出来,我们取出编码后的buffer,并将其释放掉,再送给编码器。
下面开始代码初始化MediaCodec.
//编码格式,AVC对应的是H264
MediaFormat mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 1280, 720);
//YUV 420 对应的是图片颜色采样格式
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
//比特率
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 3000000);
//帧率
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 20);
//I 帧间隔
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
try {
mediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
//创建生成MP4初始化对象
mediaMuxer = new MediaMuxer(MP4_PATH, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
} catch (IOException e) {
e.printStackTrace();
}
//进入配置状态
mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
//进行生命周期执行状态
mediaCodec.start();
比特率,帧率根据需要自己设置。
另外 摄像头采集到的NV21数据,即YYYYYYYY VUVU,编码器需要的是NV12,即
YYYYYYY UVUV
private void NV21ToNV12(byte[] nv21, byte[] nv12, int width, int height) {
if (nv21 == null || nv12 == null) {
return;
}
int framesize = width * height;
int i, j;
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];
}
}
好了,YUV数据也转换好了,下面我们就进行编码工作了,这里我先介绍同步方式,开启一个线程专门做编码任务。
class EncoderThread extends Thread {
@Override
public void run() {
long pts = 0;
super.run();
while (!isMuxFinish) {
if (mediaCodec == null) {
break;
}
// 拿到有空闲的输入缓存区下标
int inputBufferId = mediaCodec.dequeueInputBuffer(-1);
if (inputBufferId >= 0) {
pts = computePresentationTime();
//有效的空的缓存区
ByteBuffer inputBuffer = mediaCodec.getInputBuffer(inputBufferId);
byte[] tempByte = getInputBuffer();
if(isMuxFinish){
break;
}
inputBuffer.put(tempByte);
//将数据放到编码队列
mediaCodec.queueInputBuffer(inputBufferId, 0, tempByte.length, pts, 0);
}
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
//得到成功编码后输出的out buffer Id
int outputBufferId = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
if (outputBufferId >= 0) {
ByteBuffer outputBuffer = mediaCodec.getOutputBuffer(outputBufferId);
byte[] out = new byte[bufferInfo.size];
outputBuffer.get(out);
writeBytesToFile(out);
outputBuffer.position(bufferInfo.offset);
outputBuffer.limit(bufferInfo.offset + bufferInfo.size);
// 将编码后的数据写入到MP4复用器
mediaMuxer.writeSampleData(mTrackIndex, outputBuffer, bufferInfo);
//释放output buffer
mediaCodec.releaseOutputBuffer(outputBufferId, false);
} else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
MediaFormat mediaFormat = mediaCodec.getOutputFormat();
mTrackIndex = mediaMuxer.addTrack(mediaFormat);
mediaMuxer.start();
}
}
注意事项:
1、dequeueInputBuffer 得到input buffer ,如果返回-1表示没有可用的buffer,参数timeoutUs ,如果传的是0,将会立即返回,传的是-1,将会一直等待。
2、mediaCodec.queueInputBuffer(inputBufferId, 0, tempByte.length, pts, 0);
将装满帧数据的buffer放进输入队列,该方法第四个参数表示时间戳,这个参数最好给下值,否则output后面不会输出,这个是我实践中发现的。
3、MP4合成器mediaMuxer.addTrack(mediaFormat)一定要在INFO_OUTPUT_FORMAT_CHANGED条件满足后再调用,否则不起作用。
4、编码后调用mediaMuxer.writeSampleData(mTrackIndex, outputBuffer, info) 写入一帧数据或者一片数据
5、编码好一帧后记得releaseOutputBuffer
代码下载地址:https://download.csdn.net/download/zxd_android/10584840