【Android音視頻開發】【006】攝像頭YUV幀圖像轉H264碼流並播放

本篇博客主要介紹以下內容

  • 攝像頭打開和幀回調
  • 採集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的回調數據都有,很方便自己修改

發佈了429 篇原創文章 · 獲贊 43 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章