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之后才能继续了。录制音视频的学习先到此为止了。

  • 相关阅读:
    web服务器-Apache
    nginx优化
    nginx下载限速
    nginx-URL重写
    HDU 5358 First One 求和(序列求和,优化)
    HDU 5360 Hiking 登山 (优先队列,排序)
    HDU 5353 Average 糖果分配(模拟,图)
    UVALive 4128 Steam Roller 蒸汽式压路机(最短路,变形) WA中。。。。。
    HDU 5348 MZL's endless loop 给边定向(欧拉回路,最大流)
    HDU 5344 MZL's xor (水题)
  • 原文地址:https://www.cnblogs.com/renhui/p/7520690.html
Copyright © 2011-2022 走看看