Android5.0以上版本錄屏實(shí)現(xiàn)代碼(完整代碼)
我錄屏的方式是分別錄制音頻和視頻,最后合并成mp4格式,比較麻煩,因?yàn)榫W(wǎng)上完整的教程比較少,所以我打算寫一個(gè)完整版的,照著我的代碼寫完之后,至少是能夠?qū)崿F(xiàn)功能的,而不是簡單的介紹下用法。
1既然是錄制視頻,我們應(yīng)該有一個(gè)按鈕控制開始和結(jié)束。
2在錄制之前,需要先判斷一下Android系統(tǒng)的版本是否大于5.0,并且動(dòng)態(tài)申請(qǐng)一下權(quán)限(讀寫,錄音,照相機(jī)),這一步可以在點(diǎn)開始按鈕的時(shí)候執(zhí)行
if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 102); } if (ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, 103); } if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, 104); } Intent intent = null; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { intent = mediaProjectionManager.createScreenCaptureIntent(); startActivityForResult(intent, 101);//正常情況是要執(zhí)行到這里的,作用是申請(qǐng)捕捉屏幕 } else { ShowUtil.showToast(context, "Android版本太低,無法使用該功能"); }
3定義MediaProjection和MediaProjectionManager等一些其他必要的變量
boolean isrun = false;//用來標(biāo)記錄屏的狀態(tài)private MediaProjectionManager mediaProjectionManager; private MediaProjection mediaProjection;//錄制視頻的工具private int width, height, dpi;//屏幕寬高和dpi,后面會(huì)用到 private ScreenRecorder screenRecorder;//這個(gè)是自己寫的錄視頻的工具類,下文會(huì)放完整的代碼 Thread thread;//錄視頻要放在線程里去執(zhí)行
在onCreat里寫好實(shí)例化
mediaProjectionManager = (MediaProjectionManager) context.getSystemService(MEDIA_PROJECTION_SERVICE); WindowManager manager = this.getWindowManager(); DisplayMetrics outMetrics = new DisplayMetrics(); manager.getDefaultDisplay().getMetrics(outMetrics); width = outMetrics.widthPixels; height = outMetrics.heightPixels; dpi = outMetrics.densityDpi;
4我們?cè)趏nActivityResult回調(diào)方法中,來處理返回的事件
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == 102) { Toast.makeText(context, "缺少讀寫權(quán)限", Toast.LENGTH_SHORT).show(); return; } if (requestCode == 103) { Toast.makeText(context, "缺少錄音權(quán)限", Toast.LENGTH_SHORT).show(); return; } if (requestCode == 104) { Toast.makeText(context, "缺少相機(jī)權(quán)限", Toast.LENGTH_SHORT).show(); return; } if (requestCode != 101) { Log.e("HandDrawActivity", "error requestCode =" + requestCode); } if (resultCode != RESULT_OK) { Toast.makeText(context, "捕捉屏幕被禁止", Toast.LENGTH_SHORT).show(); return; } mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data); if (mediaProjection != null) { screenRecorder = new ScreenRecorder(width, height, mediaProjection, dpi); } thread = new Thread() { @Override public void run() { screenRecorder.startRecorder();//跟ScreenRecorder有關(guān)的下文再說,總之這句話的意思就是開始錄屏的意思 } }; thread.start(); binding.startPlayer.setText("停止");//開始和停止我用的同一個(gè)按鈕,所以開始錄屏之后把按鈕文字改一下 isrun = true;//錄屏狀態(tài)改成真 }
5先放上ScreenRecorder代碼,只想要結(jié)果的朋友呢,直接把類粘貼走,把報(bào)錯(cuò)的地方改一改(在我自己的項(xiàng)目里可是不報(bào)錯(cuò)的),就實(shí)現(xiàn)了錄制屏幕的功能了,還想看看的,可以往下看看
import android.hardware.display.DisplayManager; import android.media.MediaCodec; import android.media.MediaCodecInfo; import android.media.MediaFormat; import android.media.MediaMuxer; import android.media.MediaRecorder; import android.media.projection.MediaProjection; import android.os.Build; import android.os.Environment; import android.text.TextUtils; import android.util.Log; import android.view.Surface; import com.coremedia.iso.boxes.Container; import com.googlecode.mp4parser.authoring.Movie; import com.googlecode.mp4parser.authoring.Track; import com.googlecode.mp4parser.authoring.builder.DefaultMp4Builder; import com.googlecode.mp4parser.authoring.container.mp4.MovieCreator; import com.googlecode.mp4parser.authoring.tracks.AppendTrack; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; public class ScreenRecorder { private int mWidth, mHeight, mDensty; private MediaProjection mediaProjection; private MediaCodec.BufferInfo mBufferInfo; private MediaCodec mEncorder; private Surface mInputSurface; private MediaMuxer mMuxer; private boolean isQuit = false; private boolean mMuxerStarted = false; private int mTrackIndex; private String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/cache"; private MediaRecorder mediaRecorder; public ScreenRecorder(int mWidth, int mHeight, MediaProjection mediaProjection, int mDensty) { this.mWidth = mWidth; this.mHeight = mHeight; this.mediaProjection = mediaProjection; this.mDensty = mDensty; File file = new File(path); if (!file.exists()) { file.mkdirs(); } } public void startRecorder() { prepareRecorder(); startLuYin(); startRecording(); } public void stop() { isQuit = true; releaseEncorders(1); List<String> filePath = new ArrayList<>(); filePath.add(path + "/APlanyinpin.amr"); filePath.add(path + "/APlanshipin.mp4"); joinVideo(filePath, path); } public void destory() { releaseEncorders(0); } private void startLuYin() { File file = new File(path, "APlanyinpin.amr"); mediaRecorder = new MediaRecorder(); mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT); mediaRecorder.setOutputFile(file.getAbsolutePath()); try { mediaRecorder.prepare(); mediaRecorder.start(); Log.e("HandDrawActivity", "已經(jīng)開始錄音"); } catch (IOException e) { e.printStackTrace(); } } private void prepareRecorder() { mBufferInfo = new MediaCodec.BufferInfo(); //元數(shù)據(jù),描述bytebuffer的數(shù)據(jù),尺寸,偏移 //創(chuàng)建格式化對(duì)象 MIMI_TYPE 傳入的 video/avc 是H264編碼格式 MediaFormat format = MediaFormat.createVideoFormat("video/avc", mWidth, mHeight); int frameRate = 45; format.setInteger(MediaFormat.KEY_BIT_RATE, 3000000); format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10); format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate); format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); format.setInteger(MediaFormat.KEY_CAPTURE_RATE, frameRate); format.setInteger(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, 1000000 / frameRate); try { mEncorder = MediaCodec.createEncoderByType("video/avc"); mEncorder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); mInputSurface = mEncorder.createInputSurface(); mEncorder.start(); } catch (IOException e) { e.printStackTrace(); releaseEncorders(0); } } private void startRecording() { File saveFile = new File(path, "APlanshipin.mp4"); try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { mMuxer = new MediaMuxer(saveFile.getAbsolutePath(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); mediaProjection.createVirtualDisplay("SCREENRECORDER", mWidth, mHeight, mDensty, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, mInputSurface, null, null); drainEncoder(); } } catch (Exception e) { e.printStackTrace(); } } private void drainEncoder() { while (!isQuit) { Log.e("TAG", "drain....."); int bufferIndex = mEncorder.dequeueOutputBuffer(mBufferInfo, 0); if (bufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } if (bufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { mTrackIndex = mMuxer.addTrack(mEncorder.getOutputFormat()); if (!mMuxerStarted && mTrackIndex >= 0) { mMuxer.start(); mMuxerStarted = true; Log.e("HandDrawActivity", "已經(jīng)開始錄屏"); } } if (bufferIndex >= 0) { Log.e("TAG", "drain...write.."); ByteBuffer bufferData = mEncorder.getOutputBuffer(bufferIndex); if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { mBufferInfo.size = 0; } if (mBufferInfo.size != 0) { if (mMuxerStarted) { bufferData.position(mBufferInfo.offset); bufferData.limit(mBufferInfo.offset + mBufferInfo.size); mMuxer.writeSampleData(mTrackIndex, bufferData, mBufferInfo); } } mEncorder.releaseOutputBuffer(bufferIndex, false); if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { break; } } } Log.e("HandDrawActivity", "已經(jīng)結(jié)束錄屏"); } private void releaseEncorders(int i) { if (mediaProjection != null) { mediaProjection.stop(); } mBufferInfo = null; if (mEncorder != null) { mEncorder.stop(); } mInputSurface = null; if (mMuxer != null && i == 1) { mMuxer.stop(); } if (mediaRecorder != null) { mediaRecorder.stop(); mediaRecorder.reset(); mediaRecorder.release(); } } private boolean joinVideo(List<String> filePaths, String resultPath) { Log.e("HandDrawActivity", "準(zhǔn)備合成中"); boolean result = false; if (filePaths == null || filePaths.size() <= 0 || TextUtils.isEmpty(resultPath)) { throw new IllegalArgumentException(); } if (filePaths.size() == 1) { // 只有一個(gè)視頻片段,不需要合并 return true; } try { Movie[] inMovies = new Movie[filePaths.size()]; for (int i = 0; i < filePaths.size(); i++) { Log.e("HandDrawActivity", "filePaths=" + filePaths.get(i)); File f = new File(filePaths.get(i)); if (f.exists()) { inMovies[i] = MovieCreator.build(filePaths.get(i)); } } // 分別取出音軌和視頻 List<Track> videoTracks = new LinkedList<>(); List<Track> audioTracks = new LinkedList<>(); for (Movie m : inMovies) { for (Track t : m.getTracks()) { if (t.getHandler().equals("soun")) { audioTracks.add(t); } if (t.getHandler().equals("vide")) { videoTracks.add(t); } } } // 合并到最終的視頻文件 Movie outMovie = new Movie(); if (audioTracks.size() > 0) { outMovie.addTrack(new AppendTrack(audioTracks.toArray(new Track[audioTracks.size()]))); } if (videoTracks.size() > 0) { outMovie.addTrack(new AppendTrack(videoTracks.toArray(new Track[videoTracks.size()]))); } Container mp4file = new DefaultMp4Builder().build(outMovie); // 將文件輸出 File resultFile = new File(resultPath, "APlanTeacherAnswer.mp4"); if (resultFile.exists() && resultFile.isFile()) { resultFile.delete(); } FileChannel fc = new RandomAccessFile(resultFile, "rw").getChannel(); mp4file.writeContainer(fc); fc.close(); Log.e("HandDrawActivity", "合成完畢"); // 合成完成后把原片段文件刪除 for (String filePath : filePaths) { File file = new File(filePath); file.delete(); } result = true; HandDrawActivity.sendVideo(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } return result; } }
6從startRecorder方法說起
public void startRecorder() { prepareRecorder();//錄視頻前的準(zhǔn)備 startLuYin();//直接錄音頻(不用準(zhǔn)備) startRecording();//錄視頻 }
錄音的方法
private void startLuYin() { File file = new File(path, "APlanyinpin.amr"); mediaRecorder = new MediaRecorder(); mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);//聲音來源,麥克 mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);//音頻格式,默認(rèn),其實(shí)就是上面定義好的amr了,除此之外還有mp4 mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);//編碼格式,問題是我不知道編碼格式對(duì)什么有影響,是音質(zhì)高低還是文件大小還是解析快慢,等我有時(shí)間去專門研究一下 mediaRecorder.setOutputFile(file.getAbsolutePath()); try { mediaRecorder.prepare(); mediaRecorder.start(); Log.e("HandDrawActivity", "已經(jīng)開始錄音"); } catch (IOException e) { e.printStackTrace(); } }
//錄視頻前的準(zhǔn)備工作 private void prepareRecorder() { mBufferInfo = new MediaCodec.BufferInfo(); //元數(shù)據(jù),描述bytebuffer的數(shù)據(jù),尺寸,偏移 //創(chuàng)建格式化對(duì)象 MIMI_TYPE 傳入的 video/avc 是H264編碼格式 MediaFormat format = MediaFormat.createVideoFormat("video/avc", mWidth, mHeight); int frameRate = 45; format.setInteger(MediaFormat.KEY_BIT_RATE, 3000000); format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10); format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate); format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); format.setInteger(MediaFormat.KEY_CAPTURE_RATE, frameRate); format.setInteger(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, 1000000 / frameRate);//編碼器的設(shè)置,具體是設(shè)置的啥我也不太清楚,但是網(wǎng)上查一查都是這么寫的?。。? try { mEncorder = MediaCodec.createEncoderByType("video/avc"); mEncorder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); mInputSurface = mEncorder.createInputSurface(); mEncorder.start();//讓編碼器先跑起來 } catch (IOException e) { e.printStackTrace(); releaseEncorders(0); } }
這里也是準(zhǔn)備工作
private void startRecording() { File saveFile = new File(path, "APlanshipin.mp4"); try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { mMuxer = new MediaMuxer(saveFile.getAbsolutePath(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);//百度一下MediaMuxer,講的很詳細(xì)的 mediaProjection.createVirtualDisplay("SCREENRECORDER", mWidth, mHeight, mDensty, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, mInputSurface, null, null); drainEncoder(); } } catch (Exception e) { e.printStackTrace(); } }
這個(gè)就是開始寫視頻文件了
private void drainEncoder() { while (!isQuit) { Log.e("TAG", "drain....."); int bufferIndex = mEncorder.dequeueOutputBuffer(mBufferInfo, 0); if (bufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } if (bufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { mTrackIndex = mMuxer.addTrack(mEncorder.getOutputFormat()); if (!mMuxerStarted && mTrackIndex >= 0) { mMuxer.start(); mMuxerStarted = true; Log.e("HandDrawActivity", "已經(jīng)開始錄屏"); } } if (bufferIndex >= 0) { Log.e("TAG", "drain...write.."); ByteBuffer bufferData = mEncorder.getOutputBuffer(bufferIndex); if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { mBufferInfo.size = 0; } if (mBufferInfo.size != 0) { if (mMuxerStarted) { bufferData.position(mBufferInfo.offset); bufferData.limit(mBufferInfo.offset + mBufferInfo.size); mMuxer.writeSampleData(mTrackIndex, bufferData, mBufferInfo); } } mEncorder.releaseOutputBuffer(bufferIndex, false); if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { break; } } } Log.e("HandDrawActivity", "已經(jīng)結(jié)束錄屏"); }
這個(gè)就是把錄好的音頻和視頻合并成mp4的方法了,也是點(diǎn)擊停止錄屏的時(shí)候用到的
private boolean joinVideo(List<String> filePaths, String resultPath) { Log.e("HandDrawActivity", "準(zhǔn)備合成中"); boolean result = false; if (filePaths == null || filePaths.size() <= 0 || TextUtils.isEmpty(resultPath)) { throw new IllegalArgumentException(); } if (filePaths.size() == 1) { // 只有一個(gè)視頻片段,不需要合并 return true; } try { Movie[] inMovies = new Movie[filePaths.size()]; for (int i = 0; i < filePaths.size(); i++) { Log.e("HandDrawActivity", "filePaths=" + filePaths.get(i)); File f = new File(filePaths.get(i)); if (f.exists()) { inMovies[i] = MovieCreator.build(filePaths.get(i)); } } // 分別取出音軌和視頻 List<Track> videoTracks = new LinkedList<>(); List<Track> audioTracks = new LinkedList<>(); for (Movie m : inMovies) { for (Track t : m.getTracks()) { if (t.getHandler().equals("soun")) { audioTracks.add(t); } if (t.getHandler().equals("vide")) { videoTracks.add(t); } } } // 合并到最終的視頻文件 Movie outMovie = new Movie(); if (audioTracks.size() > 0) { outMovie.addTrack(new AppendTrack(audioTracks.toArray(new Track[audioTracks.size()]))); } if (videoTracks.size() > 0) { outMovie.addTrack(new AppendTrack(videoTracks.toArray(new Track[videoTracks.size()]))); } Container mp4file = new DefaultMp4Builder().build(outMovie); // 將文件輸出 File resultFile = new File(resultPath, "APlanTeacherAnswer.mp4"); if (resultFile.exists() && resultFile.isFile()) { resultFile.delete(); } FileChannel fc = new RandomAccessFile(resultFile, "rw").getChannel(); mp4file.writeContainer(fc); fc.close(); Log.e("HandDrawActivity", "合成完畢"); // 合成完成后把原片段文件刪除 for (String filePath : filePaths) { File file = new File(filePath); file.delete(); } result = true; HandDrawActivity.sendVideo(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } return result; }
這個(gè)就是結(jié)束的時(shí)候了,該清空的清空,該注銷的注銷, i是用來判斷錄沒錄的,有可能剛進(jìn)入這個(gè)頁面都沒錄過,直接就返回到別的頁面了,那就有可能空指針異常,因?yàn)橛行┳兞慷紱]初始化,所以用i判斷一下,也可以自己寫別的方法判端
private void releaseEncorders(int i) { if (mediaProjection != null) { mediaProjection.stop(); } mBufferInfo = null; if (mEncorder != null) { mEncorder.stop(); } mInputSurface = null; if (mMuxer != null && i == 1) { mMuxer.stop(); } if (mediaRecorder != null) { mediaRecorder.stop(); mediaRecorder.reset(); mediaRecorder.release(); } }
7部分代碼也是我從網(wǎng)上扒的,但是網(wǎng)上的代碼就沒怎么見過比較完整的版本的,我上面寫的都是經(jīng)過我自己測(cè)試絕對(duì)沒問題的而且代碼也沒什么遺漏的,要是發(fā)現(xiàn)有遺漏的代碼我后續(xù)再補(bǔ)上。
相關(guān)文章
Android Fragment+FragmentTabHost組件實(shí)現(xiàn)常見主頁面(仿微信新浪)
本文主要介紹Fragment+FragmentTabHost組件實(shí)現(xiàn)常見主頁面,這里整理了詳細(xì)資料及簡單示例代碼,有興趣的小伙伴可以參考下2016-09-09Android Retrofit2網(wǎng)路編程實(shí)現(xiàn)方法詳解
這篇文章主要介紹了Android Retrofit2網(wǎng)路編程實(shí)現(xiàn)方法詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-12-12Android 表情面板和軟鍵盤切換時(shí)跳閃問題的解決方法
這篇文章主要介紹了Android 表情面板和軟鍵盤切換時(shí)跳閃問題的解決方法,需要的朋友可以參考下2017-08-08Android自定義View實(shí)現(xiàn)照片裁剪框與照片裁剪功能
這篇文章主要介紹了Android自定義View實(shí)現(xiàn)照片裁剪框與照片裁剪功能的相關(guān)資料,需要的朋友可以參考下2016-07-07Android NDK 開發(fā)中 SO 包大小壓縮方法詳解
這篇文章主要為為大家介紹了Android NDK 開發(fā)中 SO 包大小壓縮方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09Android ViewPagerIndicator詳解及實(shí)例代碼
這篇文章主要介紹了Android ViewPagerIndicator詳解及實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-05-05懸浮對(duì)話框Android代碼實(shí)現(xiàn)
這篇文章主要為大家詳細(xì)介紹了懸浮對(duì)話框Android代碼實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-08-08Android基于Xposed修改微信運(yùn)動(dòng)步數(shù)實(shí)例
這篇文章主要介紹了Android基于Xposed修改微信運(yùn)動(dòng)步數(shù)實(shí)例,需要的朋友可以參考下2017-06-06