这篇博客应该是相当有分量的博客了。篇幅会比较长,因为内容很多。我尽力的想写的详细,而又不至于繁琐。这之间的程度是很难把握的,话不多说 进入主题。
首先,在这之前,需要对几个类,以及他们的方法的有所了解。

MediaCodec

谷歌对这个类的描述如下,MediaCodec类可用于访问底层媒体编解码器,即编码器/解码器组件。它是Android底层多媒体支持基础架构的一部分(通常与MediaExtractor、MediaSync、MediaMuxer、MediaCrypto、MediaDrm、Image、Surface和AudioTrack一起使用)。重点是 编码解码器,因为系统产生的数据 ,都是原始的数据,需要他进行处理。

原理:
下面这张图,只需要粗略看一看,你只需要知道 MediaCodec 有两个ByteBuffer,一个是输入,一个是输出。这也很好理解。毕竟编码解码器,肯定是要你给它旧数据,它编码解码完,还给你一个新数据。两个ByteBuffer 就相当于两个篮子,接受发送数据。

重要方法:

//返回要用有效数据填充的输入缓冲区的索引,如果当前没有可用的缓冲区,则返回-1。如果timeoutUs == 0,该方法将立即返回;如果timeoutUs < 0,则无限期等待输入缓冲区的可用性;如果timeoutUs > 0,则等待“timeoutUs”微秒。
public int dequeueInputBuffer (long timeoutUs)

这个方法呢,就是返回 输入缓冲区的索引(mediaCodec可以通过索引找到缓冲区)。也就是上面的ByteBuffer。

//通过上面的索引,找到输入缓冲区。
public ByteBuffer getInputBuffer (int index)

注意 上面这个是input

//返回输出缓冲区队列索引,最多阻塞“timeoutUs”微秒。返回已成功解码的输出缓冲区和INFO_*常量之一的索引。
//info 就是描述输出缓冲区数据的,例如时间,大小
public int dequeueOutputBuffer (MediaCodec.BufferInfo info,  long timeoutUs)
//通过上面的索引,找到输出缓冲区。
public ByteBuffer getOutputBuffer (int index)

注意 上面这个是output

//释放输出缓冲区 ,这个也好理解,你从输出缓冲区取完数据了,得要把缓冲区清空,放回去,取下一次的数据
public void releaseOutputBuffer (int index,  boolean render)

MediaMuxer

谷歌描述:MediaMuxer为muxing基本流提供便利。目前MediaMuxer支持MP4、Webm和3GP文件作为输出。它还支持muxing b帧在MP4自从Android牛轧糖。
上面编码解码完的数据,还需要写入到文件里面,这个类呢,主要就是帮助我们写文件的。

重要方法

//添加具有指定格式的跟踪。
public int addTrack (MediaFormat format)

上面这个呢,如果你了解视频的话就知道,视频里面画面 和声音 是两个不同的东西,但是都在一个文件里面。所以,他们有一个叫信道的东西。比如,声音在1信道,画面在2信道之类的。MediaMuxer只有知道信道,才知道接下来的数据要写到哪里。

//写入数据的方法
public void writeSampleData (int trackIndex, ByteBuffer byteBuf, MediaCodec.BufferInfo bufferInfo)

上面这个 byteBuf参数 就是mediaCodec的输出缓冲区,info 也是mediaCodec输出缓冲区的,我们可以info里面的值进行更改,例如时间,这样就可以暂停视频

附加:
录制视频画面的方向。

public void setOrientationHint (int degrees)

要注意,应该在start()之前调用这个方法

MediaRecorder

这个类呢,综合了上面mediaCodec和mediaMuxer,使用这个类,你可以很轻松的录制到本地。这个只能视频录制到本地,不能用于直播,因为你取不到编码后的数据。

权限申请

录屏权限

录屏权限是需要申请的,每次录制都必须要申请。

           //获取MediaProjectionManager ,通过这个类 申请权限,// 录屏是一个危险的权限,所以每次录屏的时候都得这么申请,用户同意了才行MediaProjectionManager  projectionManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);//开启activity 然后在onActivityResult回调里面判断 用户是否同意录屏权限startActivityForResult(projectionManager.createScreenCaptureIntent(), REQUSET_VIDEO);

申请后需要查看用户是否同意,在onActivityResult周期里面查看

  @Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);if (requestCode == REQUSET_VIDEO && resultCode == RESULT_OK) {//已经成功获取到权限   //这个是生成虚拟屏幕所需要的MediaProjection projection = projectionManager.getMediaProjection(resultCode, data);}}

注意上面的 MediaProjection我下面所有的代码,都会用它,一定要记得了。

录音和读写文件权限

在androidManiFest.xml 里面

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.RECORD_AUDIO" />

从android6.0以后还需要 动态申请一下。

   /*** 请求录音读写权限*/void requestPermission() {if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PERMISSION_DENIED) {String[] permission = {Manifest.permission.RECORD_AUDIO};requestPermissions(permission, REQUSET_AUDIO);}}

在onRequestPermissionsResult周期里面得到结果

   @Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);if (requestCode == REQUSET_AUDIO) {for (int i = 0; i < grantResults.length; i++) {if (grantResults[i] == PERMISSION_GRANTED) {Toast.makeText(this, "获得权限" + permissions[i], Toast.LENGTH_SHORT).show();}}}}

MediaRecorder使用范例

注意将上面申请录屏后生成的MediaProjection 传入

    //录制视频 放在子线程最好,所以线程class MediaRecordThread extends Thread {private int mWidth;//录制视频的宽private int mHeight;//录制视频的高private int mBitRate;//比特率 bits per second  这个经过我测试 并不是 一定能达到这个值private int mDpi;//视频的DPIprivate String mDstPath;//录制视频文件存放地点private MediaRecorder mMediaRecorder;//通过这个类录制private MediaProjection mediaProjection;//通过这个类 生成虚拟屏幕private final int FRAME_RATE = 60;//视频帧数 一秒多少张画面 并不一定能达到这个值private VirtualDisplay virtualDisplay;MediaRecordThread(int width, int height, int bitrate, int dpi, MediaProjection mediaProjection, String dstPath) {mWidth = width;mHeight = height;mBitRate = bitrate;mDpi = dpi;this.mediaProjection = mediaProjection;mDstPath = dstPath;}@Overridepublic void run() {try {//先实例化initMediaRecorder();//下面这个方法的 width height  并不是录制视频的宽高。他更明显是虚拟屏幕的宽高//注意 mediaRecorder.getSurface() 这里我们mediaRecorder的surface 传递给虚拟屏幕,// 虚拟屏幕显示的内容就会反映在这个surface上面,自然也就可以录制了virtualDisplay = mediaProjection.createVirtualDisplay("luing", mWidth, mHeight, mDpi,DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, mediaRecorder.getSurface(), null, null);//开始mMediaRecorder.start();Zprint.log(this.getClass(), "录屏线程内部开始工作");} catch (IllegalStateException | IOException e) {//                e.printStackTrace();Zprint.log(this.getClass(), " 异常  ", e.toString());}}//实例化MediaRecordorvoid initMediaRecorder() throws IOException {mMediaRecorder = mediaRecorder = new MediaRecorder();//设置视频来源  录屏嘛 肯定是使用一个Surface作为视频源,如果录制视频的话 就是使用摄像头作为来源了 CAMERAmediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);//设置要用于录制的音频源,必须在setOutputFormat()之前调用。参数有很多,英文注释也很简单,下面这个是录制麦克风,也就是外放的//记住 这个必要获得录制视频权限才行,要不然 报错mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);//设置录制期间生成的输出文件的格式。必须在prepare()之前调用mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);//录制文件存放位置mediaRecorder.setOutputFile(mDstPath);//录制视频的宽高mediaRecorder.setVideoSize(mWidth, mHeight);//FPSmediaRecorder.setVideoFrameRate(FRAME_RATE);//比特率mediaRecorder.setVideoEncodingBitRate(mBitRate);//视频编码格式mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);//音频编码格式mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);//准备 到这里 就可以开始录制视频了mediaRecorder.prepare();}/*** 释放资源*/void release() {if (mediaRecorder != null) {mediaRecorder.setOnErrorListener(null);mediaRecorder.stop();mediaRecorder.reset();mediaRecorder.release();mediaRecorder = null;//help GC}if (virtualDisplay != null) {virtualDisplay.release();virtualDisplay = null;//help GC}if (mediaProjection != null) {mediaProjection.stop();mediaProjection = null;//help GC}}}

这个类很简单 ,没什么说的 ,注释也很多。

MediaCodec , MediaMuxer,AudioRecord

实现录屏加录音 同步方式, 可直播,可推流

//录制屏幕 加声音 同步方式class AudioRecorderThread extends Thread {private AudioRecord mAudiorecord;//录音类private MediaMuxer mMediaMuxer;//通过这个将视频流写入本地文件,如果直播的话 不需要这个// 音频源:音频输入-麦克风  我使用其他格式 就会报错private final static int AUDIO_SOURCE = MediaRecorder.AudioSource.MIC;// 采样率// 44100是目前的标准,但是某些设备仍然支持22050,16000,11025// 采样频率一般共分为22.05KHz、44.1KHz、48KHz三个等级private final static int AUDIO_SAMPLE_RATE = 44100;// 音频通道 默认的 可以是单声道 立体声道private final int AUDIO_CHANNEL = AudioFormat.CHANNEL_IN_DEFAULT;// 音频格式:PCM编码   返回音频数据的格式private final int AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT;//记录期间写入音频数据的缓冲区的总大小(以字节为单位)。private int audioBufferSize = 0;//缓冲数组 ,用来读取audioRecord的音频数据private byte[] byteBuffer;private int audioIndex;//通过MediaMuxer 向本地文件写入数据时候,这个标志是用来确定信道的private int videoIndex;//上同private MediaCodec mAudioMediaCodec;//音频编码器private MediaCodec mVideoMediaCodec;//视频编码器private MediaFormat audioFormat;//音频编码器 输出数据的格式private MediaFormat videoFormat;//视频编码器 输出数据的格式private MediaProjection mediaProjection;//通过这个类 生成虚拟屏幕private Surface surface;//视频编码器 生成的surface ,用于充当 视频编码器的输入源private VirtualDisplay virtualDisplay; //虚拟屏幕//这个是每次在编码器 取数据的时候,这个info 携带取出数据的信息,例如 时间,大小 类型之类的  关键帧 可以通过这里的flags辨别private MediaCodec.BufferInfo audioInfo = new MediaCodec.BufferInfo();private MediaCodec.BufferInfo videoInfo = new MediaCodec.BufferInfo();private volatile boolean isRun = true;//用于控制 是否录制,这个无关紧要private int mWidth;//录制视频的宽private int mHeight;//录制视频的高private int mBitRate;//比特率 bits per second  这个经过我测试 并不是 一定能达到这个值private int mDpi;//视频的DPIprivate String mDstPath;//录制视频文件存放地点private final int FRAME_RATE = 60;//视频帧数 一秒多少张画面 并不一定能达到这个值public AudioRecorderThread(int width, int height, int bitrate, int dpi, MediaProjection mediaProjection, String dstPath) {this.mediaProjection = mediaProjection;this.mWidth = width;this.mHeight = height;mBitRate = bitrate;mDpi = dpi;mDstPath = dstPath;}@Overridepublic void run() {super.run();try {//实例化 AudioRecordinitAudioRecord();//实例化 写入文件的类initMediaMuxer();//实例化 音频编码器initAudioMedicode();//实例化 视频编码器initVideoMedicodec();//开始mAudioMediaCodec.start();mVideoMediaCodec.start();int timeoutUs = -1;//这个主要是为了 第一次进入while循环 视频编码器 能阻塞到 有视频数据输出 才运行String TAG = "audio";while (isRun) {//获取 输出缓冲区的索引 通过索引 可以去到缓冲区,缓冲区里面存着 编码后的视频数据 。 timeoutUs为负数的话,会一直阻塞到有缓冲区索引,0的话 立刻返回int videoOutputID = mVideoMediaCodec.dequeueOutputBuffer(videoInfo, timeoutUs);Log.d(TAG, "video flags " + videoInfo.flags);timeoutUs = 0;//第二次 视频编码器 就不需要 阻塞了  0 立刻返回//索引大于等于0 就代表有数据了if (videoOutputID >= 0) {Zprint.log(this.getClass(), "VIDEO 输出", videoOutputID, videoInfo.presentationTimeUs);//flags是2的时候 代表输出的数据 是配置信息,不是媒体信息if (videoInfo.flags != 2) {//得到缓冲区//这里就可以取出数据 进行网络传输ByteBuffer outBuffer = mVideoMediaCodec.getOutputBuffer(videoOutputID);outBuffer.flip();//准备读取//写入文件中  注意 videoIndexmMediaMuxer.writeSampleData(videoIndex, outBuffer, videoInfo);}//释放缓冲区,毕竟缓冲区一共就两个 一个输入 一个输出,用完是要还回去的mVideoMediaCodec.releaseOutputBuffer(videoOutputID, false);} else if (videoOutputID == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {//输出格式有变化Zprint.log(this.getClass(), "video Format 改变");videoFormat = mVideoMediaCodec.getOutputFormat();//得到新的输出格式videoIndex = mMediaMuxer.addTrack(videoFormat);//重新确定信道}//得到 输入缓冲区的索引int audioInputID = mAudioMediaCodec.dequeueInputBuffer(0);//也是大于等于0 代表 可以输入数据啦if (audioInputID >= 0) {Zprint.log(this.getClass(), "audio 输入", audioInputID);ByteBuffer audioInputBuffer = mAudioMediaCodec.getInputBuffer(audioInputID);audioInputBuffer.clear();//从 audiorecord 里面 读取原始的音频数据int read = mAudiorecord.read(byteBuffer, 0, audioBufferSize);if (read < audioBufferSize) {System.out.println(" 读取的数据" + read);}//上面read可能小于audioBufferSize  要注意audioInputBuffer.put(byteBuffer, 0, read);//入列  注意下面的时间,这个是确定这段数据 时间的 ,视频音频 都是一段段的数据,每个数据都有时间 ,这样播放器才知道 先播放那个数据// 串联起来 就是连续的了mAudioMediaCodec.queueInputBuffer(audioInputID, 0, read, System.nanoTime() / 1000L, 0);}//音频输出int audioOutputID = mAudioMediaCodec.dequeueOutputBuffer(audioInfo, 0);Log.d(TAG, "audio flags " + audioInfo.flags);if (audioOutputID >= 0) {Zprint.log(this.getClass(), "audio 输出", audioOutputID, audioInfo.presentationTimeUs);audioInfo.presentationTimeUs = videoInfo.presentationTimeUs;//保持 视频和音频的统一,防止 时间画面声音 不同步if (audioInfo.flags != 2) {//这里就可以取出数据 进行网络传输ByteBuffer audioOutBuffer = mAudioMediaCodec.getOutputBuffer(audioOutputID);audioOutBuffer.limit(audioInfo.offset + audioInfo.size);//这是另一种 和上面的 flip 没区别audioOutBuffer.position(audioInfo.offset);mMediaMuxer.writeSampleData(audioIndex, audioOutBuffer, audioInfo);//写入}//释放缓冲区mAudioMediaCodec.releaseOutputBuffer(audioOutputID, false);} else if (audioOutputID == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {Zprint.log(this.getClass(), "audio Format 改变");audioFormat = mAudioMediaCodec.getOutputFormat();audioIndex = mMediaMuxer.addTrack(audioFormat);//注意 这里  只在start  视频哪里没有这个,这个方法只能调用一次mMediaMuxer.start();}}//释放资源stopRecorder();} catch (Exception e) {e.printStackTrace();}}//实例化 AUDIO 的编码器void initAudioMedicode() throws IOException {audioFormat = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, AUDIO_SAMPLE_RATE, AUDIO_CHANNEL);audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, 96000);//比特率//描述要使用的AAC配置文件的键(仅适用于AAC音频格式)。audioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);audioFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, audioBufferSize << 1);//最大输入//这里注意  如果 你不确定 你要生成的编码器类型,就通过下面的 通过类型生成编码器mAudioMediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);//配置mAudioMediaCodec.configure(audioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);}//实例化 VIDEO 的编码器void initVideoMedicodec() throws IOException {//这里的width height 就是录制视频的分辨率,可以更改  如果这里的分辨率小于 虚拟屏幕的分辨率 ,你会发现 视频只录制了 屏幕部分内容videoFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, mWidth, mHeight);videoFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);videoFormat.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);//比特率 bit单位videoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 60);//FPSvideoFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);//关键帧  是完整的一张图片,其他的都是部分图片//通过类型创建编码器  同理 创建解码器也是一样mVideoMediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);//配置mVideoMediaCodec.configure(videoFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);//让视频编码器 生成一个弱引用的surface, 这个surface不会保证视频编码器 不被回收,这样 编码视频的时候 就不需要 传输数据进去了surface = mVideoMediaCodec.createInputSurface();//创建虚拟屏幕,让虚拟屏幕内容 渲染在上面的surface上面 ,这样 才能 不用传输数据进去virtualDisplay = mediaProjection.createVirtualDisplay("video", mWidth, mHeight, dpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, surface, null, null);}//录音的类,用于给音频编码器 提供原始数据void initAudioRecord() {//得到 音频录制时候 最小的缓冲区大小audioBufferSize = AudioRecord.getMinBufferSize(AUDIO_SAMPLE_RATE, AUDIO_CHANNEL, AUDIO_ENCODING);byteBuffer = new byte[audioBufferSize];//两种方式 都可以
//            mAudiorecord = new AudioRecord(AUDIO_SOURCE, AUDIO_SAMPLE_RATE, AUDIO_CHANNEL, AUDIO_ENCODING, audioBufferSize);//通过builder方式创建mAudiorecord = new AudioRecord.Builder().setAudioSource(MediaRecorder.AudioSource.MIC).setAudioFormat(new AudioFormat.Builder().setEncoding(AudioFormat.ENCODING_PCM_16BIT).setSampleRate(32000).setChannelMask(AudioFormat.CHANNEL_IN_MONO).build()).setBufferSizeInBytes(audioBufferSize).build();//开始录制,这里可以检查一下状态,但只要代码无误,检查是无需的 statemAudiorecord.startRecording();}//如果 要录制mp4文件的话,需要调用这个方法 创建 MediaMuxerprivate void initMediaMuxer() throws Exception {//注意格式  创建录制的文件String filePath = filePath("luyi.mp4");//实例化 MediaMuxer 编码器取出的数据,通过它写入文件中mMediaMuxer = new MediaMuxer(filePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);}//释放资源void stopRecorder() {if (mVideoMediaCodec != null) {mVideoMediaCodec.stop();mVideoMediaCodec.release();mVideoMediaCodec = null;}if (mAudioMediaCodec != null) {mAudioMediaCodec.stop();mAudioMediaCodec.release();mAudioMediaCodec = null;}if (mAudiorecord != null) {mAudiorecord.stop();mAudiorecord.release();mAudiorecord = null;}mMediaMuxer.stop();mMediaMuxer.release();mMediaMuxer = null;virtualDisplay.release();virtualDisplay = null;}}

这个类呢,首先用AudioRecord来获取音频数据,用virtualDisplay来获取视频数据。因为,有音频和视频,所以 要用两个mediaCodec 来进行工作。mediaCodec有两种方式,一种是同步的,像上面这样,还有一种是异步的。两个codec配合的话,我感觉还是同步的比较好。异步也是可以实现的,只是较为繁琐。需要用到队列之类的。
细心的读者会发现,上面videoCodec 是没有取出输入缓冲区的。因为 ,它的输入工作 被surface 代替了。virtualDisplay的内容直接渲染在videoCodec的surface 里面,充当了 输入数据。

还有如果,你想进行直播的话,取出输出缓冲区的数据传输,要注意 还有头部信息,这个下面例子中。

MediaCodec 录屏 异步方式实现

//录屏 异步模式  没有声音的class ScreenAsyn implements Runnable {private MediaProjection mediaProjection;private MediaFormat videoFormat;private MediaCodec mVideoMediaCodec;private VirtualDisplay virtualDisplay;private MediaMuxer mediaMuxer;private int videoIndex;private int mWidth;//录制视频的宽private int mHeight;//录制视频的高private int mBitRate;//比特率 bits per second  这个经过我测试 并不是 一定能达到这个值private int mDpi;//视频的DPIprivate String mDstPath;//录制视频文件存放地点private final int FRAME_RATE = 60;//视频帧数 一秒多少张画面 并不一定能达到这个值public ScreenAsyn(int width, int height, int bitrate, int dpi, MediaProjection mediaProjection, String dstPath) {this.mWidth = width;mHeight = height;mBitRate = bitrate;mDpi = dpi;this.mediaProjection = mediaProjection;mDstPath = dstPath;}@Overridepublic void run() {try {mediaMuxer = new MediaMuxer(mDstPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);Surface surface;//这里的width height 就是录制视频的分辨率,可以更改  如果这里的分辨率小于 虚拟屏幕的分辨率 ,你会发现 视频只录制了 屏幕部分内容videoFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, mWidth, mHeight);videoFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);videoFormat.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);//比特率 bit单位videoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 60);//FPSvideoFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);//关键帧  是完整的一张图片,其他的都是部分图片//通过类型创建编码器  同理 创建解码器也是一样mVideoMediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);mVideoMediaCodec.setCallback(new MediaCodec.Callback() {@Overridepublic void onInputBufferAvailable(@NonNull MediaCodec codec, int index) {}@Overridepublic void onOutputBufferAvailable(@NonNull MediaCodec codec, int index, @NonNull MediaCodec.BufferInfo info) {Zprint.log(this.getClass(), "info", info.offset, info.size, info.presentationTimeUs);ByteBuffer outBuffer = codec.getOutputBuffer(index);outBuffer.flip();mediaMuxer.writeSampleData(videoIndex, outBuffer, info);codec.releaseOutputBuffer(index, false);}@Overridepublic void onError(@NonNull MediaCodec codec, @NonNull MediaCodec.CodecException e) {}@Overridepublic void onOutputFormatChanged(@NonNull MediaCodec codec, @NonNull MediaFormat format) {//这是h.264需要的ByteBuffer sps = format.getByteBuffer("csd-0");    // SPSByteBuffer pps = format.getByteBuffer("csd-1");    // PPS//VP9 需要的ByteBuffer CodecPrivate = format.getByteBuffer("csd-0"); //videoIndex = mediaMuxer.addTrack(format);mediaMuxer.start();}});mVideoMediaCodec.configure(videoFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);surface = mVideoMediaCodec.createInputSurface();virtualDisplay = mediaProjection.createVirtualDisplay("asyn", mWidth, mHeight, mDpi, mBitRate, surface, null, null);mVideoMediaCodec.start();} catch (IOException e) {e.printStackTrace();}}//暂停录制void stopRecord() {if (mVideoMediaCodec != null) {mVideoMediaCodec.stop();mVideoMediaCodec.release();mVideoMediaCodec = null;}if (mediaMuxer != null) {mediaMuxer.release();mediaMuxer = null;}if (virtualDisplay != null) {virtualDisplay.release();}}}

这是异步实现的,个人感觉异步的实现比较好。
下面这个就是进行网络直播前,流的一些必要信息。
流的数据:

取出上面的 outBuffer 数据,就是媒体数据了,上面这个是H.264格式的数据。

总结

网上的这方面资料比较少,我研究了一段时间,写出来。在这里说一下个人心得吧。一开始可以看看网上的博客,然后,你如果想深入,那么还是看开发者文档。

附带 截图

   /*** 截图* @param mediaProjection* @return bitmap */public Bitmap screenShot(MediaProjection mediaProjection){Objects.requireNonNull(mediaProjection);ImageReader imageReader = ImageReader.newInstance(1080, 1920, PixelFormat.RGBA_8888, 60);VirtualDisplay virtualDisplay = mediaProjection.createVirtualDisplay("screen", width, height, dpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,imageReader.getSurface(), null, null);//这个是取 现在最新的图片Image image = imageReader.acquireLatestImage();//也可以挨个取图片
//        Image image = imageReader.acquireNextImage();return image2Bitmap(image);}public static Bitmap image2Bitmap(Image image) {if (image == null) {System.out.println("image 为空");return null;}int width = image.getWidth();int height = image.getHeight();System.out.println(width+"    "+height);final Image.Plane[] planes = image.getPlanes();final ByteBuffer buffer = planes[0].getBuffer();int pixelStride = planes[0].getPixelStride();int rowStride = planes[0].getRowStride();int rowPadding = rowStride - pixelStride * width;Bitmap bitmap = Bitmap.createBitmap(width+ rowPadding / pixelStride , height, Bitmap.Config.ARGB_8888);bitmap.copyPixelsFromBuffer(buffer);/*    //压缩图片Matrix matrix = new Matrix();matrix.setScale(0.5F, 0.5F);System.out.println(bitmap.isMutable());bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, false);
*/image.close();return bitmap;}

代码地址

如果有不懂的,可以关注我的公众号 “知我饭否” 向我留言。我会每天更新一些文章,有兴趣的可以 微信 搜索"知我饭否" or 扫描我的 博客头像

android 录制屏幕 带声音 可直播方案 截屏相关推荐

  1. 电脑怎么录制屏幕带声音?其实几步就可以完成电脑录屏

    电脑怎么录制屏幕带声音?相信大家在日常的工作和学习中都是需要录制屏幕的,有的时候录制屏幕带不带声音都可以.但是有的时候我们需要录制屏幕的同时也需要加入声音.如果没有录制带声音的屏幕将会十分麻烦.下面小 ...

  2. android屏幕 录制检测,Android 录制屏幕的实现方法

    Android 录制屏幕的实现方法,长久以来,我一直希望能够直接从Android屏幕上进行录制并将其编码为多种格式,以便将录制内容嵌入在任意位置,而不需要安装任何软件. 如今,我们已经接近这个目标.C ...

  3. 计算机录屏幕和声音的软件是什么,可以录制屏幕视频声音的电脑录屏软件是什么?...

    可以录制屏幕视频声音的电脑录屏软件是什么? 2019年10月30日 13:20作者:黄页编辑:黄页 分享 录制屏幕视频的声音,一直以来都是广大录屏用户的一项硬性需求.比方说:录制屏幕游戏时需要录制游戏 ...

  4. 两个类实现Android录制屏幕功能

    两个类实现Android录制屏幕功能 原理 Android4.4开始支持录制屏幕,但首先需要获取Root权限才可以运行. Android5.0及之后Android API 开放了视频录制的接口,其实严 ...

  5. RK3288[android 7.1]调试笔记 隐藏底部状态栏截屏按钮

    RK3288[android 7.1]调试笔记 隐藏底部状态栏截屏按钮 修改frameworks/base/packages/SystemUI/src/com/android/systemui/sta ...

  6. Chrome自带的截图与滚动截屏

    Chrome自带的截图与滚动截屏 打开Chrome的控制台 F12 然后快捷键 mac: control + command + p windows: ctrl + shift + p 在里面输入ca ...

  7. android 屏幕录制方案,Android录制屏幕的实现方法

    原文:Paul Kinlan 翻译:Agora.io 长久以来,我一直希望能够直接从Android屏幕上进行录制并将其编码为多种格式,以便将录制内容嵌入在任意位置,而不需要安装任何软件. 如今,我们已 ...

  8. Android限制录制屏幕无声音,哪一个安卓录屏软件可以录制系统的声音

    今天介绍的这款软件许多宝宝在之前就已经问过我很多次了,因为没有什么特别好用,所以一直就没有给大家推荐什么,直到今天才来给大家推荐,是因为小编使用了几天之后发现可以介绍给你们录屏大师,因为它操作起来挺简 ...

  9. vivo手机怎么弄html,vivo录制屏幕设置声音(手机怎么录制内部声音)

    如今不少手机都带有录屏功能,而vivo手机也有哦,但是无奈知道的人实在太少了.那么今天我们就一起来看看vivo手机强大的录屏功能以及vivo录屏完成后如何进一步进行编辑吧~ 一.vivo录屏 1.屏幕 ...

最新文章

  1. 【iCore4 双核心板_ARM】例程十七:USB_MSC实验——读/写U盘(大容量存储器)
  2. phpmyadmin同时连接多个服务器的数据库
  3. python教程:datetime与字符串互转
  4. 移动开发:iphone开发之触摸事件详解
  5. MongoDB数据库设计备忘
  6. php开发地图导航,php 实现百度地图(很详细出来的地图)
  7. 一个超赞又好用的PHP技巧!如何生成PHP扩展的类手册
  8. 微软的报表工具 SQL Server 2000 Reporting Services 评估版
  9. php爬取js对象,php如何用正则解析html中的js对象
  10. Microsoft+R:Microsoft R Open (MRO)安装和多核运作
  11. 游戏开发入行大师攻略
  12. 安捷伦or是德信号源+频谱仪操作: 从程控到自动测试 (四)平坦度检测的程控实现
  13. 别再用 offset 和 limit 分页了,性能太差!
  14. C++MFC画图工具
  15. 使用Java分割大文件
  16. (以三星S8为例)安卓全面屏手势设置教程
  17. 原来证件照还可以这样拍?美炸了!再也不怕证件照丑到家了!
  18. pvlan与vlan的区别是什么?
  19. 【STM32+cubemx】0020 HAL库开发:以太网ENC28J60芯片和TCP、UDP简单应用
  20. 支付FM——支付宝收款码配置

热门文章

  1. php一句话-D盾绕过小技巧
  2. Kafka丢数据、重复消费、顺序消费的问题
  3. 【C#基础】数据结构
  4. 第14章可迭代的对象,迭代器和生成器
  5. ESPHome 和 Home Assistant点灯握手
  6. [讲座论坛] 竹资源培育与中国竹产业
  7. 几位深受妇科病困扰的女明星
  8. 计算机登陆后如何防止自动注销,电脑自动注销怎么处理
  9. 电脑html文件删除,电脑plugin可以删除吗
  10. 如何将qsv格式视频转换为MP4格式?qsv文件怎么转换成mp4