安卓-录屏录音录像
由于工作关系又得熟悉一下大学的安卓基础了,完成安卓的录屏录音录像,目前已完成demo分别单独能录制了,之后再完成功能性开发。由此记录一下,感谢github和csdn以及公司大佬的指导。1、录屏主要涉及知识点VirtualDisplay 虚拟屏幕创建从而可以提供屏幕数据MediaCodec 硬编码将屏幕数据编码成H264MediaMuxer 多媒体混合完成mp4文件的封装输出2、录音...
由于工作关系又得熟悉一下大学的安卓基础了,完成安卓的录屏录音录像,目前已完成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
更多推荐
所有评论(0)