本篇博客主要介紹以下內容
- 攝像頭打開和幀回調
- 採集YUV數據,編碼爲H264
- H264數據解碼播放
- H264數據寫入文件
實現代碼
//攝像頭管理類
@SuppressWarnings("all")
public class Cameras {
//打卡正面相機
public static Camera openFrontCamera() {
try {
int cameraCount = Camera.getNumberOfCameras();
for (int i = 0; i < cameraCount; i++) {
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(i, info);
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) return Camera.open(i);
}
} catch (Exception e) {
Console.error(e);
}
return null;
}
//打卡背面相機
public static Camera openBackCamera() {
try {
int cameraCount = Camera.getNumberOfCameras();
for (int i = 0; i < cameraCount; i++) {
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(i, info);
if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) return Camera.open(i);
}
} catch (Exception e) {
Console.error(e);
}
return null;
}
}
//將NV21編碼爲AVC(YUV-H264)
@SuppressWarnings("all")
public class AvcEncoder {
static final int INPUT_COLOR_FORMAT = MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible;
static final String OUTPUT_MEDIA_FORMAT = MediaFormat.MIMETYPE_VIDEO_AVC;
MediaCodec mediaCodec;
int width;
int height;
int frameRate;
int bitRate;
byte[] spNalu; //SPS和PPS
byte[] originYuv; //攝像頭YUV數據,橫屏
byte[] yuv; //旋轉後的YUV,豎屏
long frameIndex = 0;
//幀率越高,畫面越流暢
//比特率越高,畫面越清晰
//但是幀率和比特率過大,會造網絡數據阻塞
@SneakyThrows
public static AvcEncoder create(int width, int height, int frameRate, int bitRate) {
AvcEncoder avcEncoder = new AvcEncoder();
avcEncoder.width = width;
avcEncoder.height = height;
avcEncoder.frameRate = frameRate;
avcEncoder.bitRate = bitRate;
//判斷是否有合適的編碼器
MediaCodecInfo mediaCodecInfo = selectMediaCodec();
if (mediaCodecInfo == null) return null;
int colorFormat = selectColorFormat(mediaCodecInfo);
if (colorFormat == -1) return null;
int bufferLength = YUVHandler.yuvBufferLength(width, height);
avcEncoder.yuv = new byte[bufferLength];
avcEncoder.originYuv = new byte[bufferLength];
return avcEncoder;
}
@SneakyThrows
public AvcEncoder configure() {
//創建對應編碼器
mediaCodec = MediaCodec.createEncoderByType(OUTPUT_MEDIA_FORMAT);
MediaFormat mediaFormat = MediaFormat.createVideoFormat(OUTPUT_MEDIA_FORMAT, height, width);
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate);
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, INPUT_COLOR_FORMAT);
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5); //IDR幀刷新時間
mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
return this;
}
@SneakyThrows
public AvcEncoder start() {
if (mediaCodec == null) configure();
mediaCodec.start();
return this;
}
public AvcEncoder pause() {
mediaCodec.stop();
return this;
}
public AvcEncoder close() {
mediaCodec.stop();
mediaCodec.release();
mediaCodec = null;
return this;
}
//編碼YUV數據
@SneakyThrows
public int encodeFrame(byte[] input, byte[] output) {
//攝像頭是橫屏採集的,要旋轉才能正常顯示
YUVHandler.nv21ToNv12(input, originYuv, width, height);
YUVHandler.yuv420spRotate90(originYuv, yuv, width, height);
ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();
int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1);
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
inputBuffer.clear();
inputBuffer.put(yuv);
long presentationTimeUs = computePresentationTime(frameIndex);
mediaCodec.queueInputBuffer(inputBufferIndex, 0, yuv.length, presentationTimeUs, 0);
frameIndex ++;
}
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
int pos = 0;
while (outputBufferIndex >= 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 < numCodecs; i++) {
MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
if (!codecInfo.isEncoder()) continue;
String[] types = codecInfo.getSupportedTypes();
for (int j = 0; j < types.length; j++)
if (types[j].equalsIgnoreCase(OUTPUT_MEDIA_FORMAT))
return codecInfo;
}
return null;
}
//判斷是否支持將指定INPUT_COLOR_FORMAT轉爲想要的OUTPUT_MEDIA_FORMAT
private static int selectColorFormat(MediaCodecInfo codecInfo) {
MediaCodecInfo.CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(OUTPUT_MEDIA_FORMAT);
for (int colorFormat : capabilities.colorFormats)
if (colorFormat == INPUT_COLOR_FORMAT)
return colorFormat;
return -1;
}
private long computePresentationTime(long frameIndex) {
return 132 + frameIndex * 1000000 / frameRate;
}
}
//將AVC解碼爲圖像
@SuppressWarnings("all")
public class AvcDecoder {
//編碼類型
static final String MEDIA_FORMAT = MediaFormat.MIMETYPE_VIDEO_AVC;
MediaCodec mediaCodec;
Surface surface;
int width;
int height;
int frameRate;
long frameIndex = 0;
public static AvcDecoder create(int width, int height, int frameRate, Surface surface) {
AvcDecoder avcDecoder = new AvcDecoder();
avcDecoder.width = width;
avcDecoder.height = height;
avcDecoder.frameRate = frameRate;
avcDecoder.surface = surface;
return avcDecoder;
}
@SneakyThrows
public AvcDecoder configure() {
MediaFormat mediaFormat = MediaFormat.createVideoFormat(MEDIA_FORMAT, width, height);
mediaCodec = MediaCodec.createDecoderByType(MEDIA_FORMAT);
mediaCodec.configure(mediaFormat, surface, null, 0);
return this;
}
@SneakyThrows
public AvcDecoder start() {
if (mediaCodec == null) configure();
mediaCodec.start();
return this;
}
public AvcDecoder pause() {
mediaCodec.stop();
return this;
}
public AvcDecoder close() {
mediaCodec.stop();
mediaCodec.release();
mediaCodec = null;
return this;
}
public boolean decodeNalu(byte[] h264) {
ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
int inputBufferIndex = mediaCodec.dequeueInputBuffer(100);
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
inputBuffer.clear();
inputBuffer.put(h264);
long presentationTimeUs = computePresentationTime(frameIndex);
mediaCodec.queueInputBuffer(inputBufferIndex, 0, h264.length, presentationTimeUs, 0);
frameIndex++;
} else return false;
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 100);
while (outputBufferIndex >= 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<ByteBuffer> 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<LoginActivity> {
@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();
}
}
Cameras,AvcEncoder,AvcDecoder,AvcWorker都是封裝好的框架類,無需任何修改
LoginActivity總共只有幾十行代碼,YUV和H264的回調數據都有,很方便自己修改