由于工作关系又得熟悉一下大学的安卓基础了,完成安卓的录屏录音录像,目前已完成demo分别单独能录制了,之后再完成功能性开发。由此记录一下,感谢github和csdn以及公司大佬的指导。

注意:安装的时候要勾选权限好,直接点击运行可能存在各种各样的问题都是由权限导致的(如mMuxer.addTrack(mediaCodec.getOutputFormat());返回-1、Camera.open失败)

1、录屏

主要涉及知识点
VirtualDisplay 虚拟屏幕创建从而可以提供屏幕数据
MediaCodec 硬编码将屏幕数据编码成H264
MediaMuxer 多媒体混合完成mp4文件的封装输出

在这里插入图片描述

首先是创建开始VirtualDisplay()

public void StartVirtualDisplay() {
      if (mbVirtual == null) {
           mbVirtual = new SmartSeeVirtualDisplay();
           mbVirtual.SetListener((RCSVirtualListener) this);
       }
       if (mbVirtual != null) {
           mbVirtual.SetProjectManager((MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE));
           mbVirtual.StartVirtualDisplay();
           startProjection();
       }
   }

并在提示开始录屏的控件的结束onActivityResult函数中创建虚拟层

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (mbVirtual != null) {
            mbVirtual.SetProjectManager((MediaProjectionManager)getSystemService(Context.MEDIA_PROJECTION_SERVICE));
            mbVirtual.StartVirtualDisplay();
            File externalFilesDir = getExternalFilesDir(null);
            // display metrics
            DisplayMetrics metrics = getResources().getDisplayMetrics();
            int mDensity = metrics.densityDpi;
            Display mDisplay = getWindowManager().getDefaultDisplay();
            mbVirtual.OnActivityResult(requestCode, resultCode, data, externalFilesDir,
                    mDensity, mDisplay, this);
        }
        super.onActivityResult(requestCode, resultCode, data);
    }

2、录音

相关技术就是:MediaCodec硬编码和MediaMuxer生成mp3文件

package com.example.myapplication;

import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.media.MediaMuxer;
import android.media.MediaRecorder;
import android.os.Build;
import android.util.Log;

import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;

public class AudioEncoder implements Runnable {

    private String mime = "audio/mp4a-latm";
    private AudioRecord mRecorder;
    private MediaCodec mEnc;
    private int rate=256000;

    //录音设置
    private int sampleRate=44100;   //采样率,默认44.1k
    private int channelCount=2;     //音频采样通道,默认2通道
    private int channelConfig= AudioFormat.CHANNEL_IN_STEREO;        //通道设置,默认立体声
    private int audioFormat=AudioFormat.ENCODING_PCM_16BIT;     //设置采样数据格式,默认16比特PCM
    private FileOutputStream fos;

    private byte[] buffer;
    private boolean isRecording;
    private Thread mThread;
    private int bufferSize;

    private String mSavePath;

    private MediaMuxer mMuxer;  //多路复用器,用于音视频混合
    private int mAudioTrack=-1;

    public AudioEncoder(){

    }
    public void setMuxer(MediaMuxer mMuxer){this.mMuxer=mMuxer;}
    public void setMime(String mime){
        this.mime=mime;
    }

    public void setRate(int rate){
        this.rate=rate;
    }

    public void setSampleRate(int sampleRate){
        this.sampleRate=sampleRate;
    }

    public void setSavePath(String path){
        this.mSavePath=path;
    }

    public boolean prepare() throws IOException {
        //fos=new FileOutputStream(mSavePath);
        //音频编码相关
        MediaFormat format= MediaFormat.createAudioFormat(mime,sampleRate,channelCount);
        format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
//        format.setInteger(MediaFormat.KEY_CHANNEL_MASK, AudioFormat.CHANNEL_IN_MONO);
        format.setInteger(MediaFormat.KEY_BIT_RATE, rate);
        mEnc=MediaCodec.createEncoderByType(mime);
        mEnc.configure(format,null,null,MediaCodec.CONFIGURE_FLAG_ENCODE);
        //音频录制相关`
        bufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat)*2;
        buffer=new byte[bufferSize];
        mRecorder=new AudioRecord(MediaRecorder.AudioSource.MIC,sampleRate,channelConfig,audioFormat,bufferSize);
        if (mRecorder.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING) {
            return false;
        }

        mMuxer=new MediaMuxer(mSavePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
        return true;
    }

    public void start() throws InterruptedException {
        mEnc.start();
        //开始录制音频
        try{
            // 防止某些手机崩溃,例如联想
            mRecorder.startRecording();
        }catch (IllegalStateException e){
            e.printStackTrace();
        }
        if(mThread!=null&&mThread.isAlive()){
            isRecording=false;
            mThread.join();
        }
        isRecording=true;
        mThread=new Thread(this);
        mThread.start();
    }

    private ByteBuffer getInputBuffer(int index){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            return mEnc.getInputBuffer(index);
        }else{
            return mEnc.getInputBuffers()[index];
        }
    }

    private ByteBuffer getOutputBuffer(int index){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            return mEnc.getOutputBuffer(index);
        }else{
            return mEnc.getOutputBuffers()[index];
        }
    }

    //TODO Add End Flag
    private void readOutputData() throws IOException{
        int index=mEnc.dequeueInputBuffer(-1);
        if(index>=0){
            final ByteBuffer buffer=getInputBuffer(index);
            buffer.clear();
            int length=mRecorder.read(buffer,bufferSize);
            if(length>0){
                mEnc.queueInputBuffer(index,0,length,System.nanoTime()/1000,0);
            }else{
                Log.e("wuwang","length-->"+length);
            }
        }
        MediaCodec.BufferInfo mInfo=new MediaCodec.BufferInfo();
        int outIndex;
        do{
            outIndex=mEnc.dequeueOutputBuffer(mInfo,0);
            Log.e("wuwang","audio flag---->"+mInfo.flags+"/"+outIndex);
            if(outIndex>=0){
                ByteBuffer buffer=getOutputBuffer(outIndex);
                buffer.position(mInfo.offset);
//                byte[] temp=new byte[mInfo.size+7];
//                buffer.get(temp,7,mInfo.size);
//                addADTStoPacket(temp,temp.length);
                if(mAudioTrack>=0&&mInfo.size>0&&mInfo.presentationTimeUs>0){
                    try {
                        mMuxer.writeSampleData(mAudioTrack,buffer,mInfo);
                    }catch (Exception e){
                        //Log.e(TAG,"audio error:size="+mInfo.size+"/offset="
                          //      +mInfo.offset+"/timeUs="+mInfo.presentationTimeUs);
                        e.printStackTrace();
                    }
                }
                mEnc.releaseOutputBuffer(outIndex,false);
                if((mInfo.flags&MediaCodec.BUFFER_FLAG_END_OF_STREAM)!=0){
                    //Log.e(TAG,"audio end");
                    return ;
                }
            }else if(outIndex ==MediaCodec.INFO_TRY_AGAIN_LATER){

            }else if(outIndex==MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){
                mAudioTrack=mMuxer.addTrack(mEnc.getOutputFormat());
                //Log.e(TAG,"add audio track-->"+mAudioTrack);
                if(mAudioTrack>=0){
                    mMuxer.start();
                }
            }
        }while (outIndex>=0);
    }

    /**
     * 给编码出的aac裸流添加adts头字段
     * @param packet 要空出前7个字节,否则会搞乱数据
     * @param packetLen
     */
    private void addADTStoPacket(byte[] packet, int packetLen) {
        int profile = 2;  //AAC LC
        int freqIdx = 4;  //44.1KHz
        int chanCfg = 2;  //CPE
        packet[0] = (byte)0xFF;
        packet[1] = (byte)0xF9;
        packet[2] = (byte)(((profile-1)<<6) + (freqIdx<<2) +(chanCfg>>2));
        packet[3] = (byte)(((chanCfg&3)<<6) + (packetLen>>11));
        packet[4] = (byte)((packetLen&0x7FF) >> 3);
        packet[5] = (byte)(((packetLen&7)<<5) + 0x1F);
        packet[6] = (byte)0xFC;
    }

    /**
     * 停止录制
     */
    public void stop(){
        try {
            isRecording=false;
            mThread.join();
            mRecorder.stop();
            mEnc.stop();
            mEnc.release();
//            fos.flush();
//            fos.close();
            mAudioTrack=-1;
            mMuxer.stop();
            mMuxer.release();
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while (isRecording){
            try {
                readOutputData();
//                fos.write(buffer,0,length);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

3、录像

涉及技术点:Camera摄像机类、MediaCodec硬编码,需要注意的是一些格式的转换,最好是使用C++jni调用,单纯用java有点花屏
注意:需要surfaceView控件,如果不需要显示则可以设置为单位1
在这里插入图片描述

private void openCamera() throws InterruptedException {
        camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_FRONT);
        //获取相机参数
        Camera.Parameters parameters = camera.getParameters();
        //获取相机支持的预览的大小
//        Camera.Size previewSize = getCameraPreviewSize(parameters);
//        int width = previewSize.width;
//        int height = previewSize.height;
        int width = 320;
        int height = 240;
        //设置预览格式(也就是每一帧的视频格式)YUV420下的NV21
        parameters.setPreviewFormat(ImageFormat.NV21);
        //设置预览图像分辨率
        parameters.setPreviewSize(width, height);
        //相机旋转90度  注意需要旋转90度才是观看视角
        camera.setDisplayOrientation(90);
        //配置camera参数
        camera.setParameters(parameters);
        try {
            camera.setPreviewDisplay(holder);
        } catch (IOException e) {
            e.printStackTrace();
        }

        long time=System.currentTimeMillis();
        String savePath=getPath("video/",time+".mp4");
        nv21EncoderH264 = new NV21EncoderH264(width, height);
        nv21EncoderH264.setSavePath(savePath);
        nv21EncoderH264.initMediaCodec();
        nv21EncoderH264.start();
        //设置监听获取视频流的每一帧  数据回调函数
        camera.setPreviewCallback(new Camera.PreviewCallback() {
            @Override
            public void onPreviewFrame(byte[] data, Camera camera) {
                long time = System.currentTimeMillis();
                nv21EncoderH264.feedData(data, time);//传递到处理线程进行处理
            }
        });
        //调用startPreview()用以更新preview的surface
        camera.startPreview();
    }

资源已上传CSDN

参考链接
onActivityResult传值的使用
https://www.cnblogs.com/hutie1980/p/4548263.html
如果想在Activity中得到新打开Activity 关闭后返回的数据,
需要使用系统提供的startActivityForResult(Intent intent, int requestCode)方法打开新的Activity,
新的Activity 关闭后会向前面的Activity传回数据,为了得到传回的数据,必
须在前面的Activity中重写onActivityResult(int requestCode, int resultCode, Intent data)方法。

public void onSmartSeeRcsVirtual(byte[] var1, int var2, int var3, int var4) 屏幕数据
http://www.360doc.com/content/18/0327/17/14013244_740651286.shtml 硬编码
https://view.inews.qq.com/a/20201211A0D9DJ00
https://time.geekbang.org/dailylesson/detail/100056832 如何实现Android端的录屏采集?
https://so.csdn.net/so/search?q=%E7%A1%AC%E7%BC%96%E7%A0%81&t=blog&u=junzia 硬编码
https://www.jianshu.com/p/30e7de494a7f?from=timeline Android 硬编码(MediaCodec)

https://www.cnblogs.com/haibindev/p/8408598.html Android Camera2采集摄像头原始数据并手动预览
https://blog.csdn.net/nonmarking/article/details/48601317 ffmpeg综合应用示例(三)——安卓手机摄像头编码
Android采集摄像头的视频流数据并使用MediaCodec编码为H264格式
https://azhon.blog.csdn.net/article/details/104527016?spm=1001.2101.3001.6650.13&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-13.no_search_link&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-13.no_search_link
https://www.jianshu.com/p/58fbab11145a Android 摄像头采集与数据处理
https://blog.csdn.net/chailongger/article/details/84675574 NV21 旋转+转为NV12 及各种旋转
https://www.cnblogs.com/haibindev/p/8408598.html Android Camera2采集摄像头原始数据并手动预览+调用libyuv做RGB之间的数据转换
https://blog.csdn.net/nonmarking/article/details/48601317 ffmpeg综合应用示例(三)——安卓手机摄像头编码
https://www.jianshu.com/p/1f072a248a37
注意:安装的时候要勾选权限好,直接点击运行可能存在各种各样的问题都是由权限导致的(如mMuxer.addTrack(mediaCodec.getOutputFormat());返回-1、Camera.open失败)

4、录屏叠加录像

主要涉及ndk编译so及jni调用一些视频格式转换与叠加的视频帧的函数都是由C++实现的

5、完成安卓背后服务录像

主要是涉及服务与广播进行实现的
参考:
https://download.csdn.net/download/u014048791/10290965?utm_medium=distribute.pc_relevant_download.none-task-download-2defaultbaidujsdefault-2.test_version_3&depth_1-utm_source=distribute.pc_relevant_download.none-task-download-2defaultbaidujsdefault-2.test_version_3&dest=https%3A%2F%2Fdownload.csdn.net%2Fdownload%2Fu014048791%2F10290965&spm=1003.2020.3001.6616.5

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐