├── .gitignore
├── AndroidManifest.xml
├── LICENSE
├── README.md
├── doc
└── flow.png
├── project.properties
├── res
├── drawable-hdpi
│ └── ic_app.png
├── drawable-mdpi
│ └── ic_app.png
├── layout
│ └── source_activity.xml
└── values
│ └── strings.xml
└── src
└── com
└── a30corner
└── screenrecoder
├── ControlActivity.java
└── ScreenRecoder.java
/.gitignore:
--------------------------------------------------------------------------------
1 | # built application files
2 | *.apk
3 | *.ap_
4 |
5 | # files for the dex VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # generated files
12 | bin/
13 | gen/
14 |
15 | # Local configuration file (sdk path, etc)
16 | local.properties
17 |
18 | # Eclipse project files
19 | .classpath
20 | .project
21 |
22 | # Proguard folder generated by Eclipse
23 | proguard/
24 |
25 | # Intellij project files
26 | *.iml
27 | *.ipr
28 | *.iws
29 | .idea/
30 |
--------------------------------------------------------------------------------
/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Szu-Hsien Lee
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ScreenRecoder
2 | =============
3 | A screenrecorder in Android KitKat.
4 |
5 |
6 |
7 | This is a proof-of-concept project.
8 |
9 | I just try to use some new stuffs in KitKat to implement a screen record function in pure Java.
10 |
11 | Capturing screen out of your app need one protected permission.(CAPTURE_VIDEO_OUTPUT)
12 | It works well but you need to sign apk with platform key.
13 |
14 | How it works
15 | =============
16 |
17 | 
18 |
19 | License
20 | =============
21 |
22 | The MIT License (MIT)
23 | Copyright (c) 2014 Szu-Hsien Lee
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/doc/flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/misgod/ScreenRecoder/b9537b7a910933002fda97c3fc0d77992192d02f/doc/flow.png
--------------------------------------------------------------------------------
/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-19
15 |
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/misgod/ScreenRecoder/b9537b7a910933002fda97c3fc0d77992192d02f/res/drawable-hdpi/ic_app.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/ic_app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/misgod/ScreenRecoder/b9537b7a910933002fda97c3fc0d77992192d02f/res/drawable-mdpi/ic_app.png
--------------------------------------------------------------------------------
/res/layout/source_activity.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
23 |
24 |
32 |
33 |
--------------------------------------------------------------------------------
/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ScreenRecoder
6 |
7 |
--------------------------------------------------------------------------------
/src/com/a30corner/screenrecoder/ControlActivity.java:
--------------------------------------------------------------------------------
1 |
2 | package com.a30corner.screenrecoder;
3 |
4 | import com.a30corner.screenrecoder.R;
5 |
6 | import android.app.Activity;
7 | import android.os.Bundle;
8 | import android.view.View;
9 | import android.widget.ToggleButton;
10 |
11 | public class ControlActivity extends Activity {
12 | private ScreenRecoder mDisplaySourceService;
13 |
14 | @Override
15 | protected void onCreate(Bundle savedInstanceState) {
16 | super.onCreate(savedInstanceState);
17 |
18 | setContentView(R.layout.source_activity);
19 |
20 | mDisplaySourceService = new ScreenRecoder(this);
21 |
22 | }
23 |
24 | @Override
25 | protected void onDestroy() {
26 | super.onDestroy();
27 | stopServices();
28 | }
29 |
30 | private void startServices() {
31 |
32 | mDisplaySourceService.start("/sdcard/temp.mp4");
33 | }
34 |
35 | private void stopServices() {
36 | if (mDisplaySourceService != null && mDisplaySourceService.isStarted()) {
37 | mDisplaySourceService.stop();
38 | mDisplaySourceService = null;
39 | }
40 | }
41 |
42 | public void onToggleClicked(View view) {
43 | boolean on = ((ToggleButton) view).isChecked();
44 | if (on) {
45 | startServices();
46 | } else {
47 | stopServices();
48 | }
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/src/com/a30corner/screenrecoder/ScreenRecoder.java:
--------------------------------------------------------------------------------
1 | package com.a30corner.screenrecoder;
2 |
3 | import java.io.IOException;
4 | import java.nio.ByteBuffer;
5 |
6 | import android.content.Context;
7 | import android.hardware.display.DisplayManager;
8 | import android.hardware.display.VirtualDisplay;
9 | import android.media.MediaCodec;
10 | import android.media.MediaCodec.BufferInfo;
11 | import android.media.MediaCodecInfo;
12 | import android.media.MediaFormat;
13 | import android.media.MediaMuxer;
14 | import android.media.MediaMuxer.OutputFormat;
15 | import android.util.Log;
16 | import android.view.Surface;
17 |
18 | public class ScreenRecoder {
19 | private static final String DISPLAY_NAME = "ScreenRecoder";
20 | private final DisplayManager mDisplayManager;
21 |
22 | private int videoTrackIndex;
23 |
24 | private VirtualDisplayThread mVirtualDisplayThread;
25 |
26 |
27 | private volatile boolean started;
28 |
29 | public ScreenRecoder(Context context) {
30 |
31 | mDisplayManager = (DisplayManager) context
32 | .getSystemService(Context.DISPLAY_SERVICE);
33 | }
34 |
35 | public synchronized void start(String path) {
36 | if (!started) {
37 | mVirtualDisplayThread = new VirtualDisplayThread(720,
38 | 1280, 320,path);
39 | mVirtualDisplayThread.start();
40 | started = true;
41 | }
42 | }
43 |
44 | public synchronized void stop() {
45 | if (started) {
46 | started = false;
47 | mVirtualDisplayThread.quit();
48 | }
49 |
50 | }
51 |
52 | public boolean isStarted(){
53 | return started;
54 | }
55 |
56 |
57 | private final class VirtualDisplayThread extends Thread {
58 | private static final int TIMEOUT_USEC = 1000000;
59 | private static final int BIT_RATE = 6000000;
60 | private static final int FRAME_RATE = 30;
61 | private static final int I_FRAME_INTERVAL = 10;
62 |
63 | private final int mWidth;
64 | private final int mHeight;
65 | private final int mDensityDpi;
66 | private MediaMuxer muxer;
67 |
68 | private volatile boolean mQuitting;
69 |
70 | private boolean mMuxerStarted;
71 |
72 | public VirtualDisplayThread(int width, int height, int densityDpi,String path) {
73 | mWidth = width;
74 | mHeight = height;
75 | mDensityDpi = densityDpi;
76 |
77 | try {
78 | muxer = new MediaMuxer(path, OutputFormat.MUXER_OUTPUT_MPEG_4);
79 | } catch (IOException e) {
80 | Log.e("sam", e.getMessage(), e);
81 |
82 | }
83 |
84 | }
85 |
86 | @Override
87 | public void run() {
88 | MediaFormat format = MediaFormat.createVideoFormat("video/avc",
89 | mWidth, mHeight);
90 | format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
91 | MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
92 | format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
93 | format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
94 | format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,
95 | I_FRAME_INTERVAL);
96 |
97 | MediaCodec codec = MediaCodec.createEncoderByType("video/avc");
98 | codec.configure(format, null, null,
99 | MediaCodec.CONFIGURE_FLAG_ENCODE);
100 | Surface surface = codec.createInputSurface();
101 | codec.start();
102 |
103 | VirtualDisplay virtualDisplay = mDisplayManager
104 | .createVirtualDisplay(DISPLAY_NAME, mWidth, mHeight,
105 | mDensityDpi, surface,
106 | DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC);
107 |
108 | if (virtualDisplay != null) {
109 | stream(codec);
110 | virtualDisplay.release();
111 | }
112 |
113 | codec.signalEndOfInputStream();
114 | codec.stop();
115 | }
116 |
117 | public void quit() {
118 | mQuitting = true;
119 | }
120 |
121 | private void stream(MediaCodec codec) {
122 | BufferInfo info = new BufferInfo();
123 | ByteBuffer[] buffers = null;
124 |
125 | while (!mQuitting) {
126 | int index = codec.dequeueOutputBuffer(info, TIMEOUT_USEC);
127 | if (index >= 0) {
128 | if (buffers == null) {
129 | buffers = codec.getOutputBuffers();
130 | }
131 |
132 | ByteBuffer buffer = buffers[index];
133 | buffer.limit(info.offset + info.size);
134 | buffer.position(info.offset);
135 |
136 | muxer.writeSampleData(videoTrackIndex, buffer, info);
137 |
138 | codec.releaseOutputBuffer(index, false);
139 | } else if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
140 | if (mMuxerStarted) {
141 | throw new RuntimeException("format changed twice");
142 | }
143 |
144 | MediaFormat newFormat = codec.getOutputFormat();
145 |
146 | // now that we have the Magic Goodies, start the muxer
147 | videoTrackIndex = muxer.addTrack(newFormat);
148 | muxer.start();
149 |
150 | mMuxerStarted = true;
151 |
152 | buffers = null;
153 | } else if (index == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
154 | buffers = null;
155 | } else if (index == MediaCodec.INFO_TRY_AGAIN_LATER) {
156 | Log.e("sam", "Codec dequeue buffer timed out.");
157 | }
158 | }
159 |
160 | muxer.stop();
161 | muxer.release();
162 | }
163 | }
164 | }
165 |
--------------------------------------------------------------------------------