zoukankan      html  css  js  c++  java
  • Android 音视频开发(七): 音视频录制流程总结

    在前面我们学习和使用了AudioRecordAudioTrackCamera MediaExtractorMediaMuxer APIMediaCodec。 学习和使用了上述的API之后,相信对Android系统的音视频处理有一定的经验和心得了。本文及后面的几篇文章做的事情就是将这些知识串联起来,做一些稍微复杂的事情。

    一、流程分析

    1.1 需求说明

    我们需要做的事情就是:串联整个音视频录制流程,完成音视频的采集、编码、封包成 mp4 输出。

    1.2 实现方式

    Android音视频采集的方法:预览用SurfaceView,视频采集用Camera类,音频采集用AudioRecord。

    1.3 数据处理思路

    使用MediaCodec 类进行编码压缩,视频压缩为H.264,音频压缩为aac,使用MediaMuxer 将音视频合成为MP4。

    二、 实现过程

    2.1 收集Camera数据,并转码为H264存储到文件

    在收集数据之前,对Camera设置一些参数,方便收集后进行数据处理:

    Camera.Parameters parameters = camera.getParameters();
    parameters.setPreviewFormat(ImageFormat.NV21);
    parameters.setPreviewSize(1280, 720);

    然后设置PreviewCallback:

    camera.setPreviewCallback(this);

    就可以获取到Camera的原始NV21数据:

    onPreviewFrame(byte[] bytes, Camera camera)

    在创建一个H264Encoder类,在里面进行编码操作,并将编码后的数据存储到文件:

    new Thread(new Runnable() {
    
        @Override
        public void run() {
            isRuning = true;
            byte[] input = null;
            long pts = 0;
            long generateIndex = 0;
    
            while (isRuning) {
                if (yuv420Queue.size() > 0) {
                    input = yuv420Queue.poll();
                    byte[] yuv420sp = new byte[width * height * 3 / 2];
                    // 必须要转格式,否则录制的内容播放出来为绿屏
                    NV21ToNV12(input, yuv420sp, width, height);
                    input = yuv420sp;
                }
                if (input != null) {
                    try {
                        ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
                        ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();
                        int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1);
                        if (inputBufferIndex >= 0) {
                          pts = computePresentationTime(generateIndex);
                          ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
                          inputBuffer.clear();
                          inputBuffer.put(input);
                          mediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, System.currentTimeMillis(), 0);
                          generateIndex += 1;
                        }
    
                        MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
                        int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC);
                        while (outputBufferIndex >= 0) {
                            ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
                            byte[] outData = new byte[bufferInfo.size];
                            outputBuffer.get(outData);
                            if (bufferInfo.flags == MediaCodec.BUFFER_FLAG_CODEC_CONFIG) {
                                configbyte = new byte[bufferInfo.size];
                                configbyte = outData;
                            } else if (bufferInfo.flags == MediaCodec.BUFFER_FLAG_SYNC_FRAME) {
                                byte[] keyframe = new byte[bufferInfo.size + configbyte.length];
                                System.arraycopy(configbyte, 0, keyframe, 0, configbyte.length);
                                System.arraycopy(outData, 0, keyframe, configbyte.length, outData.length);
                                outputStream.write(keyframe, 0, keyframe.length);
                            } else {
                                outputStream.write(outData, 0, outData.length);
                            }
    
                            mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
                            outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC);
                        }
    
                    } catch (Throwable t) {
                        t.printStackTrace();
                    }
                } else {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
    
            // 停止编解码器并释放资源
            try {
                mediaCodec.stop();
                mediaCodec.release();
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            // 关闭数据流
            try {
                outputStream.flush();
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }).start(); 

    当结束编码的时候,需要将相关的资源释放掉:

    // 停止编解码器并释放资源
    try {
        mediaCodec.stop();
        mediaCodec.release();
    } catch (Exception e) {
        e.printStackTrace();
    }
    
    // 关闭数据流
    try {
        outputStream.flush();
        outputStream.close();
    } catch (IOException e) {
        e.printStackTrace();
    }

    此时,我们做到了将视频内容采集-->编码-->存储文件。但这个仅仅是对Android 音视频开发(四):使用 Camera API 采集视频数据的延伸,但是很有必要。因为在前面学习了如何采集音频,如何使用MediaCodec去处理音视频,如何使用MediaMuxer去混合音视频。

    示例代码:https://github.com/renhui/AndroidRecorder/releases/tag/only_h264_video

    下面我们在当前的的基础上继续完善,即将音视频采集并混合为音视频。

    2.2 音视频采集+混合,存储到文件

    基本完成思路已经在2.1的结尾处坐了说明,下面贴一下demo的链接:

    示例代码:https://github.com/renhui/AndroidRecorder/releases/tag/h264_video_audio

    想做更多的事情,只有学习了OpenGL之后才能继续了。录制音视频的学习先到此为止了。

  • 相关阅读:
    Sqli-labs Less-47 order by后的注入
    Sqli-labs Less-46 order by后的注入
    Sqli-labs Background-9 order by后的injection
    Sqli-labs Less-45 堆叠注入
    jsp,servlet知识点
    jsp页面编码不统一可能会出问题
    jsp页面找不到,jsp页面乱码
    BZOJ 2843: 极地旅行社 lct splay
    2018/3/23 省选模拟赛
    bzoj 4573: [Zjoi2016]大森林 lct splay
  • 原文地址:https://www.cnblogs.com/renhui/p/7520690.html
Copyright © 2011-2022 走看看