Skip to content

Commit

Permalink
commit
Browse files Browse the repository at this point in the history
  • Loading branch information
HelloHuDi committed May 29, 2018
1 parent f9add97 commit 3808213
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 44 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
<a href="" target="_blank"><img src="https://img.shields.io/badge/release-v1.0-blue.svg"></img></a>
</p>

## create mp4 with MediaProjection,there is no voice for the time being
## Implement screen capture without root on Android 5.0+ by using MediaProjectionManager, VirtualDisplay, AudioRecord, MediaCodec and MediaMuxer APIs

## according to [ScreenRecorder][1] adaptation,thanks [Yrom Wang][2]
## There is no voice for the time being

## usage:
## According to [ScreenRecorder][1] adaptation,thanks [Yrom Wang][2]

## Usage:

### dependencies :

Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
package com.hd.screencapture.capture;

import android.annotation.TargetApi;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.media.MediaRecorder;
import android.os.Build;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.util.Log;
import android.util.SparseLongArray;

import com.hd.screencapture.callback.RecorderCallback;
import com.hd.screencapture.config.AudioConfig;
import com.hd.screencapture.config.ScreenCaptureConfig;
import com.hd.screencapture.help.ExecutorUtil;
import com.hd.screencapture.observer.CaptureObserver;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Locale;


Expand All @@ -26,22 +31,28 @@
public final class AudioRecorder extends Recorder {

private AudioRecord audioRecord;

private AudioConfig audioConfig;

private ExecutorUtil executorUtil;

public AudioRecorder(@NonNull CaptureObserver observer, @NonNull ScreenCaptureConfig config,//
@NonNull RecorderCallback callback) {
super(AUDIO_RECORDER, observer, config, callback);
TAG = "AudioRecorder";
audioConfig=config.getAudioConfig();
audioConfig = config.getAudioConfig();
executorUtil = new ExecutorUtil();
mChannelsSampleRate = audioConfig.getSamplingRate() * audioConfig.getChannelCount();
if (config.allowLog())
Log.i(TAG, "in bitrate " + (mChannelsSampleRate << getBit()));
mPollRate = 2048_000 / audioConfig.getSamplingRate(); // poll per 2048 samples
}

@Override
public boolean prepare() {
try {
if (initAudioRecord()) {
initMediaCodec(createMediaFormat());
startReadData();
return true;
}
} catch (Exception e) {
Expand All @@ -50,6 +61,22 @@ public boolean prepare() {
return false;
}

@Override
public boolean record() {
try {
if (mEncoder != null) {
record.set(true);
mEncoder.start();
startReadData();
return true;
}
return false;
} catch (Exception ignored) {
ignored.printStackTrace();
}
return false;
}

@Override
public void release() {
super.release();
Expand All @@ -71,8 +98,8 @@ private boolean initAudioRecord() {
audioConfig.getChannelCount(), audioConfig.getBitrate());
if (minBufferSize == AudioRecord.ERROR_BAD_VALUE) {
if (config.allowLog())
Log.e(TAG, "Invalid parameter ! "+String.format(Locale.US, "Bad arguments: getMinBufferSize(%d, %d, %d)",//
audioConfig.getSamplingRate(), audioConfig.getChannelCount(), audioConfig.getBitrate()));
Log.e(TAG, "Invalid parameter ! " + String.format(Locale.US, "Bad arguments: getMinBufferSize(%d, %d, %d)",//
audioConfig.getSamplingRate(), audioConfig.getChannelCount(), audioConfig.getBitrate()));
return false;
}
audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, audioConfig.getSamplingRate(),//
Expand All @@ -84,7 +111,7 @@ private boolean initAudioRecord() {
}
try {
if (config.allowLog() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Log.d(TAG, " size in frame " + audioRecord.getBufferSizeInFrames());
Log.d(TAG, " size in frame " + audioRecord.getBufferSizeInFrames());
}
audioRecord.startRecording();
if (audioRecord.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING) {
Expand All @@ -103,57 +130,150 @@ private boolean initAudioRecord() {
}

@Override
MediaFormat createMediaFormat() {
String type = MediaFormat.MIMETYPE_AUDIO_AAC;
MediaFormat format = new MediaFormat();
format.setString(MediaFormat.KEY_MIME, type);
format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, audioConfig.getChannelCount());
format.setInteger(MediaFormat.KEY_SAMPLE_RATE, audioConfig.getSamplingRate());
format.setInteger(MediaFormat.KEY_BIT_RATE, audioConfig.getBitrate());
format.setInteger(MediaFormat.KEY_AAC_PROFILE, audioConfig.getLevel() == null ?//
MediaCodecInfo.CodecProfileLevel.AACObjectMain : audioConfig.getLevel().profile);
MediaFormat createMediaFormat() {
String type = MediaFormat.MIMETYPE_AUDIO_AAC;
MediaFormat format = new MediaFormat();
format.setString(MediaFormat.KEY_MIME, type);
format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, audioConfig.getChannelCount());
format.setInteger(MediaFormat.KEY_SAMPLE_RATE, audioConfig.getSamplingRate());
format.setInteger(MediaFormat.KEY_BIT_RATE, audioConfig.getBitrate());
format.setInteger(MediaFormat.KEY_AAC_PROFILE, audioConfig.getLevel() == null ?//
MediaCodecInfo.CodecProfileLevel.AACObjectMain : audioConfig.getLevel().profile);
if (config.allowLog())
Log.d(TAG, "created audio format: " + format);
return format;
}

@Override
void initMediaCodec(MediaFormat format)throws IOException {
void initMediaCodec(MediaFormat format) throws IOException {
super.initMediaCodec(format);
mEncoder = createEncoder(format.getString(MediaFormat.KEY_MIME));
mEncoder.setCallback(mCodecCallback);
mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
}

private MediaCodec.Callback mCodecCallback = new MediaCodec.Callback() {
@Override
public void onInputBufferAvailable(@NonNull MediaCodec codec, int index) {
// callback.onInputBufferAvailable(index);
Log.d(TAG,"onInputBufferAvailable :"+codec+"="+index);
private void startReadData() {
executorUtil.diskIO().execute(() -> {
while (record.get()) {
try {
feedInput();
feedOutput();
} catch (Exception e) {
e.printStackTrace();
callback.onError(e);
}
}
});
}

private void feedInput() {
int inputBufferIndex = dequeueInputBuffer(0);
// if (config.allowLog())
// Log.d(TAG, "audio encoder returned input buffer index=" + inputBufferIndex);
final boolean eos = audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_STOPPED;
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = getInputBuffer(inputBufferIndex);
int offset = inputBuffer.position();
int limit = inputBuffer.limit();
int size = 0;
int flags;
if (!eos) {
flags = MediaCodec.BUFFER_FLAG_KEY_FRAME;
size = audioRecord.read(inputBuffer, limit);
if (config.allowLog())
Log.d(TAG, "Read frame data size " + size //
+ " for index " + inputBufferIndex + " buffer : " + offset + ", " + limit);
if (size < 0) {
size = 0;
}
} else {
flags = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
}
long presentationTimeUs = calculateFrameTimestamp(size << 3);
queueInputBuffer(inputBufferIndex, offset, size, presentationTimeUs, flags);
callback.onInputBufferAvailable(inputBufferIndex);
} else {
// if (config.allowLog())
// Log.i(TAG, "try later to poll input buffer:"+inputBufferIndex);
sleepSomeTime();
if (!record.get())
return;
feedInput();
}
}

@Override
public void onOutputBufferAvailable(@NonNull MediaCodec codec, int index, @NonNull MediaCodec.BufferInfo info) {
// callback.onOutputBufferAvailable(index, info);
Log.d(TAG,"onOutputBufferAvailable :"+codec+"="+index+"=="+info);
private MediaCodec.BufferInfo bufferInfo = null;

private void feedOutput() {
while (record.get()) {
if (null == bufferInfo) {
bufferInfo = new MediaCodec.BufferInfo();
}
int outputBufferIndex = dequeueOutputBuffer(bufferInfo, 1);
if (config.allowLog())
Log.d(TAG, "audio encoder returned output buffer index=" + outputBufferIndex);
if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
callback.onOutputFormatChanged(getEncoder().getOutputFormat());
}
if (outputBufferIndex < 0) {
bufferInfo.set(0, 0, 0, 0);
break;
} else {
callback.onOutputBufferAvailable(outputBufferIndex, bufferInfo);
}
}
}

@Override
public void onError(@NonNull MediaCodec codec, @NonNull MediaCodec.CodecException e) {
// callback.onError(e);
Log.d(TAG,"onError :"+codec+"="+e);
private static final int LAST_FRAME_ID = -1;

}
private SparseLongArray mFramesUsCache = new SparseLongArray(2);

private int mChannelsSampleRate;

@Override
public void onOutputFormatChanged(@NonNull MediaCodec codec, @NonNull MediaFormat format) {
// callback.onOutputFormatChanged(format);
Log.d(TAG,"onOutputFormatChanged :"+codec+"="+format);
private long calculateFrameTimestamp(int totalBits) {
int samples = totalBits >> getBit();
long frameUs = mFramesUsCache.get(samples, -1);
if (frameUs == -1) {
frameUs = samples * 1000_000 / mChannelsSampleRate;
mFramesUsCache.put(samples, frameUs);
}
long timeUs = SystemClock.elapsedRealtimeNanos() / 1000;
// accounts the delay of polling the audio sample data
timeUs -= frameUs;
long currentUs;
long lastFrameUs = mFramesUsCache.get(LAST_FRAME_ID, -1);
if (lastFrameUs == -1) { // it's the first frame
currentUs = timeUs;
} else {
currentUs = lastFrameUs;
}
if (config.allowLog())
Log.i(TAG, "count samples pts: " + currentUs + ", time pts: " + timeUs + ", samples: " + samples);
// maybe too late to acquire sample data
if (timeUs - currentUs >= (frameUs << 1)) {
// reset
currentUs = timeUs;
}
};
mFramesUsCache.put(LAST_FRAME_ID, currentUs + frameUs);
return currentUs;
}

private void startReadData() {
private int getBit() {
if (audioConfig.getBitrate() == AudioFormat.ENCODING_PCM_16BIT)
return 4;
else if (audioConfig.getBitrate() == AudioFormat.ENCODING_PCM_8BIT)
return 3;
return 1;
}

private int mPollRate;

private void sleepSomeTime() {
try {
Thread.sleep(mPollRate);
} catch (InterruptedException e) {
e.printStackTrace();
}
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
* Created by hd on 2018/5/20 .
*/
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
public abstract class Recorder {
public abstract class Recorder{

static final int VIDEO_RECORDER = 0;

Expand Down Expand Up @@ -97,8 +97,16 @@ public final ByteBuffer getInputBuffer(int index) {
return getEncoder().getInputBuffer(index);
}

public final void queueInputBuffer(int index, int offset, int size, long pstTs, int flags) {
getEncoder().queueInputBuffer(index, offset, size, pstTs, flags);
public final void queueInputBuffer(int index, int offset, int size, long presentationTimeUs, int flags) {
getEncoder().queueInputBuffer(index, offset, size, presentationTimeUs, flags);
}

public final int dequeueInputBuffer(long timeoutUs) {
return getEncoder().dequeueInputBuffer(timeoutUs);
}

public final int dequeueOutputBuffer(@NonNull MediaCodec.BufferInfo info, long timeoutUs) {
return getEncoder().dequeueOutputBuffer(info,timeoutUs);
}

public final void releaseOutputBuffer(int index) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ public void onInputBufferAvailable(@NonNull MediaCodec codec, int index) {
@Override
public void onOutputBufferAvailable(@NonNull MediaCodec codec, int index, @NonNull MediaCodec.BufferInfo info) {
callback.onOutputBufferAvailable(index, info);
Log.d(TAG,"我来测video:"+index);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.hd.screencapture.help;

import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull;

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

/**
* Created by hd on 2018/5/21 .
*/
public class ExecutorUtil {

private final Executor diskIO;

private final Executor networkIO;

private final Executor mainThread;

public ExecutorUtil(Executor diskIO, Executor networkIO, Executor mainThread) {
this.diskIO = diskIO;
this.networkIO = networkIO;
this.mainThread = mainThread;
}

public ExecutorUtil() {
this(Executors.newSingleThreadExecutor(), //
Executors.newFixedThreadPool(3), new MainThreadExecutor());
}

public Executor diskIO() {
return diskIO;
}

public Executor networkIO() {
return networkIO;
}

public Executor mainThread() {
return mainThread;
}

private static class MainThreadExecutor implements Executor {
private Handler mainThreadHandler = new Handler(Looper.getMainLooper());
@Override
public void execute(@NonNull Runnable command) {
mainThreadHandler.post(command);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ private void resetAudioOutputFormat(MediaFormat newFormat) {

private void startMuxerIfReady() {
if (mMuxerStarted || mVideoOutputFormat == null || //
(config.hasAudio() && (audioRecorder != null || mAudioOutputFormat == null))) {
(audioRecorder != null && mAudioOutputFormat == null)) {
return;
}
mVideoTrackIndex = mMuxer.addTrack(mVideoOutputFormat);
Expand Down

0 comments on commit 3808213

Please sign in to comment.