本篇博客主要介绍以下内容
- 摄像头打开和帧回调
- 采集YUV数据,编码为H264
- H264数据解码播放
- H264数据写入文件
其实摄像头的数据,是可以直接通过SurfaceView进行预览的
这里绕了一个弯,纯粹是为了给大家演示YUV编码,H264解码,H264播放的所有知识点
毕竟,在实际应用中,提供数据的可能不是摄像头,有很多种可能性,所以我们有必要掌握每种方法
如果仅仅是为了显示摄像头视频,请看这个专栏的前几篇博客
实现代码
//摄像头管理类
@SuppressWarnings("all")
public class Cameras {
//打卡正面相机
public static Camera openFrontCamera() {
try {
int cameraCount = Camera.getNumberOfCameras();
for (int i = 0; i = 0) {
ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
byte[] outData = new byte[bufferInfo.size];
outputBuffer.get(outData);
if (spNalu != null) {
System.arraycopy(outData, 0, output, pos, outData.length);
pos += outData.length;
} else {
//保存SPS和PPS
ByteBuffer spsPpsBuffer = ByteBuffer.wrap(outData);
if (spsPpsBuffer.getInt() == 0x00000001) {
spNalu = new byte[outData.length];
System.arraycopy(outData, 0, spNalu, 0, outData.length);
} else return -1;
}
mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
}
//判断NALU类型是否为IDR
if (output[4] == 0x65) {
//给IDR帧添加SPS和PPS
System.arraycopy(output, 0, yuv, 0, pos);
System.arraycopy(spNalu, 0, output, 0, spNalu.length);
System.arraycopy(yuv, 0, output, spNalu.length, pos);
pos += spNalu.length;
}
return pos;
}
//判断是否存在指定OUTPUT_MEDIA_FORMAT的编码器
private static MediaCodecInfo selectMediaCodec() {
int numCodecs = MediaCodecList.getCodecCount();
for (int i = 0; i = 0) {
mediaCodec.releaseOutputBuffer(outputBufferIndex, true);
outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
}
return true;
}
private long computePresentationTime(long frameIndex) {
return 132 + frameIndex * 1000000 / frameRate;
}
}
//处理编码出的H264数据
//在work方法中定义自己的业务代码,比如拿来播放,写文件,推流等
public abstract class AvcWorker {
Queue queue = new LinkedList();
Lock lock = new ReentrantLock();
boolean working = false;
public AvcWorker() {
Threads.post(() -> {
onInit();
while (true) {
lock.lock();
if (working) work();
else Threads.sleep(500);
lock.unlock();
}
});
}
//添加NALU到队列
public void enqueue(byte[] nalu, int len) {
ByteBuffer buffer = ByteBuffer.allocate(len);
buffer.put(nalu, 0, len);
queue.offer(buffer);
}
//NALU出列
public ByteBuffer dequeue() {
return queue.poll();
}
public AvcWorker start() {
lock.lock();
onStart();
working = true;
lock.unlock();
return this;
}
public AvcWorker stop() {
lock.lock();
queue.clear();
onStop();
working = false;
lock.unlock();
return this;
}
protected abstract void work();
protected abstract void onInit();
protected abstract void onStart();
protected abstract void onStop();
}
@SuppressWarnings("all")
public class LoginActivity extends CommonActivity {
@BindView(R.id.v_surface_view)
SurfaceView previewSurfaceView;
@BindView(R.id.v_surface_view_2)
SurfaceView avcDecodeSurfaceView;
Camera camera;
int w = 640;
int h = 480;
AvcEncoder avcEncoder;
AvcDecoder avcDecoder;
AvcWorker avcWorker;
RandomAccessFile raf;
byte[] h264 = new byte[1024 * 1024];
protected void create() {
setContentView(R.layout.activity_main);
ButterKnife.bind(this, ctx);
requestAllPermissionWithCallback();
}
@Override
protected void onPermissionOk() {
//等待Surface创建完毕,再打开摄像头
postLater(this::openCamera, 2000);
}
@SneakyThrows
private void openCamera() {
//初始化编码器解码器
avcEncoder = AvcEncoder.create(w, h, 30, 2500000).start();
avcDecoder = AvcDecoder.create(w, h, 30, avcDecodeSurfaceView.getHolder().getSurface()).start();
//创建H264工作线程
avcWorker = new AvcWorker() {
@Override
@SneakyThrows
protected void work() {
ByteBuffer byteBuffer = dequeue();
if (byteBuffer == null) {
Threads.sleep(100);
return;
}
//ByteBuffer转byte[]
int length = byteBuffer.capacity();
byte[] buffer = new byte[length];
System.arraycopy(byteBuffer.array(), 0, buffer, 0, length);
//解码播放
avcDecoder.decodeNalu(buffer);
//写入H264文件
raf.seek(raf.length());
raf.write(buffer);
}
@Override
@SneakyThrows
protected void onInit() {
File file = new File("/sdcard/cam.h264");
if (file.exists()) file.delete();
raf = new RandomAccessFile(file, "rw");
}
@Override
protected void onStart() {
}
@Override
@SneakyThrows
protected void onStop() {
}
}.start();
//打卡摄像头
camera = Cameras.openBackCamera();
Camera.Parameters parameters = camera.getParameters();
parameters.setPreviewSize(w, h);
parameters.setPreviewFrameRate(30);
parameters.setPreviewFormat(ImageFormat.NV21);
camera.setParameters(parameters);
camera.setPreviewDisplay(previewSurfaceView.getHolder());
camera.setPreviewCallback((nv21, camera) -> {
//NV21转成H264,保存至队列,等待线程处理
int len = avcEncoder.encodeFrame(nv21, h264);
if (len > 0) avcWorker.enqueue(h264, len);
});
camera.startPreview();
}
}
package com.easing.commons.android.MediaCodec;
import android.graphics.Bitmap;
//YUV各种子格式之间进行转换
//未经严格测试,部分方法可能有问题
@SuppressWarnings("all")
public class YUVHandler {
//计算YUV的buffer长度
public static int yuvBufferLength(int width, int height) {
int stride = (int) Math.ceil(width / 16.0) * 16;
int y_size = stride * height;
int c_stride = (int) Math.ceil(width / 32.0) * 16;
int c_size = c_stride * height / 2;
return y_size + c_size * 2;
}
//镜像化YUV帧
public static void yuvMirror(byte[] yuv, int w, int h) {
int a, b;
byte temp;
//镜像化Y
for (int i = 0; i
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?