• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.screenrecord;
18 
19 import static android.content.Context.MEDIA_PROJECTION_SERVICE;
20 
21 import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.INTERNAL;
22 import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.MIC;
23 import static com.android.systemui.screenrecord.ScreenRecordingAudioSource.MIC_AND_INTERNAL;
24 
25 import android.annotation.Nullable;
26 import android.content.ContentResolver;
27 import android.content.ContentValues;
28 import android.content.Context;
29 import android.graphics.Bitmap;
30 import android.hardware.display.DisplayManager;
31 import android.hardware.display.VirtualDisplay;
32 import android.media.MediaCodecInfo;
33 import android.media.MediaMuxer;
34 import android.media.MediaRecorder;
35 import android.media.ThumbnailUtils;
36 import android.media.projection.IMediaProjection;
37 import android.media.projection.IMediaProjectionManager;
38 import android.media.projection.MediaProjection;
39 import android.media.projection.MediaProjectionManager;
40 import android.net.Uri;
41 import android.os.IBinder;
42 import android.os.RemoteException;
43 import android.os.ServiceManager;
44 import android.provider.MediaStore;
45 import android.util.DisplayMetrics;
46 import android.util.Log;
47 import android.util.Size;
48 import android.view.Surface;
49 import android.view.WindowManager;
50 
51 import java.io.File;
52 import java.io.IOException;
53 import java.io.OutputStream;
54 import java.nio.file.Files;
55 import java.text.SimpleDateFormat;
56 import java.util.Date;
57 
58 /**
59  * Recording screen and mic/internal audio
60  */
61 public class ScreenMediaRecorder {
62     private static final int TOTAL_NUM_TRACKS = 1;
63     private static final int VIDEO_FRAME_RATE = 30;
64     private static final int VIDEO_FRAME_RATE_TO_RESOLUTION_RATIO = 6;
65     private static final int AUDIO_BIT_RATE = 196000;
66     private static final int AUDIO_SAMPLE_RATE = 44100;
67     private static final int MAX_DURATION_MS = 60 * 60 * 1000;
68     private static final long MAX_FILESIZE_BYTES = 5000000000L;
69     private static final String TAG = "ScreenMediaRecorder";
70 
71 
72     private File mTempVideoFile;
73     private File mTempAudioFile;
74     private MediaProjection mMediaProjection;
75     private Surface mInputSurface;
76     private VirtualDisplay mVirtualDisplay;
77     private MediaRecorder mMediaRecorder;
78     private int mUser;
79     private ScreenRecordingMuxer mMuxer;
80     private ScreenInternalAudioRecorder mAudio;
81     private ScreenRecordingAudioSource mAudioSource;
82 
83     private Context mContext;
84     MediaRecorder.OnInfoListener mListener;
85 
ScreenMediaRecorder(Context context, int user, ScreenRecordingAudioSource audioSource, MediaRecorder.OnInfoListener listener)86     public ScreenMediaRecorder(Context context,
87             int user, ScreenRecordingAudioSource audioSource,
88             MediaRecorder.OnInfoListener listener) {
89         mContext = context;
90         mUser = user;
91         mListener = listener;
92         mAudioSource = audioSource;
93     }
94 
prepare()95     private void prepare() throws IOException, RemoteException {
96         //Setup media projection
97         IBinder b = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
98         IMediaProjectionManager mediaService =
99                 IMediaProjectionManager.Stub.asInterface(b);
100         IMediaProjection proj = null;
101         proj = mediaService.createProjection(mUser, mContext.getPackageName(),
102                     MediaProjectionManager.TYPE_SCREEN_CAPTURE, false);
103         IBinder projection = proj.asBinder();
104         mMediaProjection = new MediaProjection(mContext,
105                 IMediaProjection.Stub.asInterface(projection));
106 
107         File cacheDir = mContext.getCacheDir();
108         cacheDir.mkdirs();
109         mTempVideoFile = File.createTempFile("temp", ".mp4", cacheDir);
110 
111         // Set up media recorder
112         mMediaRecorder = new MediaRecorder();
113 
114         // Set up audio source
115         if (mAudioSource == MIC) {
116             mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
117         }
118         mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
119 
120         mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
121 
122 
123         // Set up video
124         DisplayMetrics metrics = new DisplayMetrics();
125         WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
126         wm.getDefaultDisplay().getRealMetrics(metrics);
127         int screenWidth = metrics.widthPixels;
128         int screenHeight = metrics.heightPixels;
129         int refereshRate = (int) wm.getDefaultDisplay().getRefreshRate();
130         int vidBitRate = screenHeight * screenWidth * refereshRate / VIDEO_FRAME_RATE
131                 * VIDEO_FRAME_RATE_TO_RESOLUTION_RATIO;
132         mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
133         mMediaRecorder.setVideoEncodingProfileLevel(
134                 MediaCodecInfo.CodecProfileLevel.AVCProfileHigh,
135                 MediaCodecInfo.CodecProfileLevel.AVCLevel42);
136         mMediaRecorder.setVideoSize(screenWidth, screenHeight);
137         mMediaRecorder.setVideoFrameRate(refereshRate);
138         mMediaRecorder.setVideoEncodingBitRate(vidBitRate);
139         mMediaRecorder.setMaxDuration(MAX_DURATION_MS);
140         mMediaRecorder.setMaxFileSize(MAX_FILESIZE_BYTES);
141 
142         // Set up audio
143         if (mAudioSource == MIC) {
144             mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.HE_AAC);
145             mMediaRecorder.setAudioChannels(TOTAL_NUM_TRACKS);
146             mMediaRecorder.setAudioEncodingBitRate(AUDIO_BIT_RATE);
147             mMediaRecorder.setAudioSamplingRate(AUDIO_SAMPLE_RATE);
148         }
149 
150         mMediaRecorder.setOutputFile(mTempVideoFile);
151         mMediaRecorder.prepare();
152         // Create surface
153         mInputSurface = mMediaRecorder.getSurface();
154         mVirtualDisplay = mMediaProjection.createVirtualDisplay(
155                 "Recording Display",
156                 screenWidth,
157                 screenHeight,
158                 metrics.densityDpi,
159                 DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
160                 mInputSurface,
161                 null,
162                 null);
163 
164         mMediaRecorder.setOnInfoListener(mListener);
165         if (mAudioSource == INTERNAL ||
166                 mAudioSource == MIC_AND_INTERNAL) {
167             mTempAudioFile = File.createTempFile("temp", ".aac",
168                     mContext.getCacheDir());
169             mAudio = new ScreenInternalAudioRecorder(mTempAudioFile.getAbsolutePath(),
170                     mMediaProjection, mAudioSource == MIC_AND_INTERNAL);
171         }
172 
173     }
174 
175     /**
176     * Start screen recording
177     */
start()178     void start() throws IOException, RemoteException, IllegalStateException {
179         Log.d(TAG, "start recording");
180         prepare();
181         mMediaRecorder.start();
182         recordInternalAudio();
183     }
184 
185     /**
186      * End screen recording
187      */
end()188     void end() {
189         mMediaRecorder.stop();
190         mMediaProjection.stop();
191         mMediaRecorder.release();
192         mMediaRecorder = null;
193         mMediaProjection = null;
194         mInputSurface.release();
195         mVirtualDisplay.release();
196         stopInternalAudioRecording();
197 
198         Log.d(TAG, "end recording");
199     }
200 
stopInternalAudioRecording()201     private void stopInternalAudioRecording() {
202         if (mAudioSource == INTERNAL || mAudioSource == MIC_AND_INTERNAL) {
203             mAudio.end();
204             mAudio = null;
205         }
206     }
207 
recordInternalAudio()208     private  void recordInternalAudio() throws IllegalStateException {
209         if (mAudioSource == INTERNAL || mAudioSource == MIC_AND_INTERNAL) {
210             mAudio.start();
211         }
212     }
213 
214     /**
215      * Store recorded video
216      */
save()217     protected SavedRecording save() throws IOException {
218         String fileName = new SimpleDateFormat("'screen-'yyyyMMdd-HHmmss'.mp4'")
219                 .format(new Date());
220 
221         ContentValues values = new ContentValues();
222         values.put(MediaStore.Video.Media.DISPLAY_NAME, fileName);
223         values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");
224         values.put(MediaStore.Video.Media.DATE_ADDED, System.currentTimeMillis());
225         values.put(MediaStore.Video.Media.DATE_TAKEN, System.currentTimeMillis());
226 
227         ContentResolver resolver = mContext.getContentResolver();
228         Uri collectionUri = MediaStore.Video.Media.getContentUri(
229                 MediaStore.VOLUME_EXTERNAL_PRIMARY);
230         Uri itemUri = resolver.insert(collectionUri, values);
231 
232         Log.d(TAG, itemUri.toString());
233         if (mAudioSource == MIC_AND_INTERNAL || mAudioSource == INTERNAL) {
234             try {
235                 Log.d(TAG, "muxing recording");
236                 File file = File.createTempFile("temp", ".mp4",
237                         mContext.getCacheDir());
238                 mMuxer = new ScreenRecordingMuxer(MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4,
239                         file.getAbsolutePath(),
240                         mTempVideoFile.getAbsolutePath(),
241                         mTempAudioFile.getAbsolutePath());
242                 mMuxer.mux();
243                 mTempVideoFile.delete();
244                 mTempVideoFile = file;
245             } catch (IOException e) {
246                 Log.e(TAG, "muxing recording " + e.getMessage());
247                 e.printStackTrace();
248             }
249         }
250 
251         // Add to the mediastore
252         OutputStream os = resolver.openOutputStream(itemUri, "w");
253         Files.copy(mTempVideoFile.toPath(), os);
254         os.close();
255         if (mTempAudioFile != null) mTempAudioFile.delete();
256         DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
257         Size size = new Size(metrics.widthPixels, metrics.heightPixels);
258         SavedRecording recording = new SavedRecording(itemUri, mTempVideoFile, size);
259         mTempVideoFile.delete();
260         return recording;
261     }
262 
263     /**
264     * Object representing the recording
265     */
266     public class SavedRecording {
267 
268         private Uri mUri;
269         private Bitmap mThumbnailBitmap;
270 
SavedRecording(Uri uri, File file, Size thumbnailSize)271         protected SavedRecording(Uri uri, File file, Size thumbnailSize) {
272             mUri = uri;
273             try {
274                 mThumbnailBitmap = ThumbnailUtils.createVideoThumbnail(
275                         file, thumbnailSize, null);
276             } catch (IOException e) {
277                 Log.e(TAG, "Error creating thumbnail", e);
278             }
279         }
280 
getUri()281         public Uri getUri() {
282             return mUri;
283         }
284 
getThumbnail()285         public @Nullable Bitmap getThumbnail() {
286             return mThumbnailBitmap;
287         }
288     }
289 }
290