@@ -43,36 +43,23 @@ public class RecorderThread implements Runnable {
4343 private static final String TAG = "RecorderThread" ;
4444
4545 private final MediaProjection mediaProjection ;
46- private VirtualDisplay virtualDisplay ;
47- private Surface surface ;
48- private MediaCodec audioEncoder ;
49- private MediaCodec videoEncoder ;
50- private AudioRecord audioRecord ;
51- private MediaMuxer muxer ;
46+ private final String outputFilePath ;
47+ private final int videoWidth ;
48+ private final int videoHeight ;
49+ private final int recordingRotation ;
50+
5251 private boolean muxerStarted ;
5352 private boolean isStartTimestampInitialized = false ;
5453 private long startTimestampUs ;
55- private final Handler handler ;
56- private Thread audioRecordThread ;
54+ private long lastAudioTimestampUs = -1 ;
5755
5856 private int videoTrackIndex = -1 ;
5957 private int audioTrackIndex = -1 ;
6058
61-
62- private final String outputFilePath ;
63- private final String videoMime ;
64- private int videoWidth ;
65- private int videoHeight ;
66- private final int sampleRate ;
67- private final int recordingRotation ;
68-
6959 private volatile boolean stopped = false ;
7060 private volatile boolean audioStopped ;
7161 private volatile boolean hasAsyncError = false ;
7262
73- private MediaCodec .BufferInfo bufferInfo ;
74- private long lastAudioTimestampUs = -1 ;
75-
7663 @ RequiresApi (api = Build .VERSION_CODES .LOLLIPOP )
7764 private final VirtualDisplay .Callback displayCallback = new VirtualDisplay .Callback () {
7865 @ Override
@@ -93,14 +80,10 @@ public void onStopped() {
9380 public RecorderThread (MediaProjection mediaProjection , String outputFilePath ,
9481 int videoWidth , int videoHeight , int recordingRotation ) {
9582 this .mediaProjection = mediaProjection ;
96- handler = new Handler ();
9783 this .outputFilePath = outputFilePath ;
9884 this .videoWidth = videoWidth ;
9985 this .videoHeight = videoHeight ;
100- this .sampleRate = RecorderConstant .AUDIO_CODEC_SAMPLE_RATE_HZ ;
10186 this .recordingRotation = recordingRotation ;
102- videoMime = MediaFormat .MIMETYPE_VIDEO_AVC ;
103- bufferInfo = new MediaCodec .BufferInfo ();
10487 }
10588
10689 public void startRecording () {
@@ -117,20 +100,9 @@ public boolean isRecordingRunning() {
117100 return !stopped ;
118101 }
119102
120- @ RequiresApi (api = Build .VERSION_CODES .LOLLIPOP )
121- private void setupVideoCodec () throws IOException {
122- // Encoded video resolution MUST match virtual display.
123- videoEncoder = MediaCodec .createEncoderByType (videoMime );
124-
125- /* Clamp raw width/height of device screen with video encoder's capabilities
126- * to avoid crash.
127- * */
128- MediaCodecInfo .VideoCapabilities videoEncoderCapabilities = videoEncoder .getCodecInfo ().
129- getCapabilitiesForType (videoMime ).getVideoCapabilities ();
130- this .videoWidth = videoEncoderCapabilities .getSupportedWidths ().clamp (this .videoWidth );
131- this .videoHeight = videoEncoderCapabilities .getSupportedHeights ().clamp (this .videoHeight );
132- int videoBitrate = calcBitRate (videoWidth , videoHeight );
133-
103+ @ RequiresApi (api = Build .VERSION_CODES .KITKAT )
104+ private MediaFormat setupVideoEncoderFormat (String videoMime , int videoWidth ,
105+ int videoHeight , int videoBitrate ) {
134106 MediaFormat encoderFormat = MediaFormat .createVideoFormat (videoMime , videoWidth ,
135107 videoHeight );
136108 encoderFormat .setInteger (MediaFormat .KEY_COLOR_FORMAT ,
@@ -142,36 +114,35 @@ private void setupVideoCodec() throws IOException {
142114 RecorderConstant .AUDIO_CODEC_REPEAT_PREV_FRAME_AFTER_MS );
143115 encoderFormat .setInteger (MediaFormat .KEY_I_FRAME_INTERVAL ,
144116 RecorderConstant .AUDIO_CODEC_I_FRAME_INTERVAL_MS );
145-
146- videoEncoder .configure (encoderFormat , null , null ,
147- MediaCodec .CONFIGURE_FLAG_ENCODE );
148- surface = videoEncoder .createInputSurface ();
149- videoEncoder .start ();
117+ return encoderFormat ;
150118 }
151119
152120 @ RequiresApi (api = Build .VERSION_CODES .LOLLIPOP )
153- private VirtualDisplay setupVirtualDisplay () {
121+ private VirtualDisplay setupVirtualDisplay (MediaProjection mediaProjection ,
122+ Surface surface , Handler handler ,
123+ int videoWidth , int videoHeight ) {
154124 return mediaProjection .createVirtualDisplay ("Appium Screen Recorder" ,
155125 videoWidth , videoHeight , DisplayMetrics .DENSITY_HIGH ,
156126 DisplayManager .VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR ,
157127 surface , displayCallback , handler );
158128 }
159129
160- private void setupAudioCodec () throws IOException {
130+ @ RequiresApi (api = Build .VERSION_CODES .LOLLIPOP )
131+ private MediaCodec setupAudioCodec (int sampleRate ) throws IOException {
161132 // Encoded video resolution matches virtual display. TODO set channelCount 2 try stereo quality
162133 MediaFormat encoderFormat = MediaFormat .createAudioFormat (MediaFormat .MIMETYPE_AUDIO_AAC ,
163134 sampleRate , RecorderConstant .AUDIO_CODEC_CHANNEL_COUNT );
164135 encoderFormat .setInteger (MediaFormat .KEY_BIT_RATE ,
165136 RecorderConstant .AUDIO_CODEC_DEFAULT_BITRATE );
166137
167- audioEncoder = MediaCodec .createEncoderByType (MediaFormat .MIMETYPE_AUDIO_AAC );
138+ MediaCodec audioEncoder = MediaCodec .createEncoderByType (MediaFormat .MIMETYPE_AUDIO_AAC );
168139 audioEncoder .configure (encoderFormat , null , null ,
169140 MediaCodec .CONFIGURE_FLAG_ENCODE );
170- audioEncoder . start () ;
141+ return audioEncoder ;
171142 }
172143
173144 @ RequiresApi (api = Build .VERSION_CODES .Q )
174- private void setupAudioRecord () {
145+ private AudioRecord setupAudioRecord (int sampleRate ) {
175146 int channelConfig = AudioFormat .CHANNEL_IN_MONO ;
176147 int minBufferSize = AudioRecord .getMinBufferSize (sampleRate , channelConfig ,
177148 AudioFormat .ENCODING_PCM_16BIT );
@@ -187,15 +158,15 @@ private void setupAudioRecord() {
187158 new AudioPlaybackCaptureConfiguration .Builder (this .mediaProjection )
188159 .addMatchingUsage (AudioAttributes .USAGE_MEDIA )
189160 .build ();
190- audioRecord = audioRecordBuilder .setAudioFormat (audioFormat )
161+ return audioRecordBuilder .setAudioFormat (audioFormat )
191162 .setBufferSizeInBytes (4 * minBufferSize )
192163 .setAudioPlaybackCaptureConfig (apcc )
193164 .build ();
194165 }
195166
196167 @ RequiresApi (api = Build .VERSION_CODES .LOLLIPOP )
197- private void startAudioRecord () {
198- audioRecordThread = new Thread (new Runnable () {
168+ private Thread startAudioRecord (MediaCodec audioEncoder , final AudioRecord audioRecord ) {
169+ return new Thread (new Runnable () {
199170 @ Override
200171 public void run () {
201172 Thread .currentThread ().setPriority (Thread .MAX_PRIORITY );
@@ -240,11 +211,9 @@ public void run() {
240211 } finally {
241212 audioRecord .stop ();
242213 audioRecord .release ();
243- audioRecord = null ;
244214 }
245215 }
246216 });
247- audioRecordThread .start ();
248217 }
249218
250219 private long getPresentationTimeUs () {
@@ -264,25 +233,26 @@ private int calcBitRate(int width, int height) {
264233 return bitrate ;
265234 }
266235
267- private void startMuxerIfSetUp () {
236+ private void startMuxerIfSetUp (MediaMuxer muxer ) {
268237 if (audioTrackIndex >= 0 && videoTrackIndex >= 0 ) {
269238 muxer .start ();
270239 muxerStarted = true ;
271240 }
272241 }
273242
274243 @ RequiresApi (api = Build .VERSION_CODES .LOLLIPOP )
275- private boolean writeAudioBufferToFile () {
244+ private boolean writeAudioBufferToFile (MediaCodec audioEncoder , MediaMuxer muxer ,
245+ MediaCodec .BufferInfo bufferInfo ) {
276246 int encoderStatus ;
277247
278- encoderStatus = audioEncoder .dequeueOutputBuffer (this . bufferInfo , 0 );
248+ encoderStatus = audioEncoder .dequeueOutputBuffer (bufferInfo , 0 );
279249 if (encoderStatus == MediaCodec .INFO_OUTPUT_FORMAT_CHANGED ) {
280250 if (audioTrackIndex > 0 ) {
281251 Log .e (TAG , "audioTrackIndex greater than zero" );
282252 return false ;
283253 }
284254 audioTrackIndex = muxer .addTrack (audioEncoder .getOutputFormat ());
285- startMuxerIfSetUp ();
255+ startMuxerIfSetUp (muxer );
286256 } else if (encoderStatus < 0 && encoderStatus != MediaCodec .INFO_TRY_AGAIN_LATER ) {
287257 Log .w (TAG , "unexpected result from audio encoder.dequeueOutputBuffer: "
288258 + encoderStatus );
@@ -293,16 +263,16 @@ private boolean writeAudioBufferToFile() {
293263 return false ;
294264 }
295265
296- if (this . bufferInfo .presentationTimeUs > this .lastAudioTimestampUs
297- && muxerStarted && this . bufferInfo .size != 0 &&
298- (this . bufferInfo .flags & MediaCodec .BUFFER_FLAG_CODEC_CONFIG ) == 0 ) {
299- this .lastAudioTimestampUs = this . bufferInfo .presentationTimeUs ;
300- muxer .writeSampleData (audioTrackIndex , encodedData , this . bufferInfo );
266+ if (bufferInfo .presentationTimeUs > this .lastAudioTimestampUs
267+ && muxerStarted && bufferInfo .size != 0 &&
268+ (bufferInfo .flags & MediaCodec .BUFFER_FLAG_CODEC_CONFIG ) == 0 ) {
269+ this .lastAudioTimestampUs = bufferInfo .presentationTimeUs ;
270+ muxer .writeSampleData (audioTrackIndex , encodedData , bufferInfo );
301271 }
302272
303273 audioEncoder .releaseOutputBuffer (encoderStatus , false );
304274
305- if ((this . bufferInfo .flags & MediaCodec .BUFFER_FLAG_END_OF_STREAM ) != 0 ) {
275+ if ((bufferInfo .flags & MediaCodec .BUFFER_FLAG_END_OF_STREAM ) != 0 ) {
306276 Log .v (TAG , "buffer eos" );
307277 return false ;
308278 }
@@ -311,7 +281,8 @@ private boolean writeAudioBufferToFile() {
311281 }
312282
313283 @ RequiresApi (api = Build .VERSION_CODES .LOLLIPOP )
314- private boolean writeVideoBufferToFile () {
284+ private boolean writeVideoBufferToFile (MediaCodec videoEncoder , MediaMuxer muxer ,
285+ MediaCodec .BufferInfo bufferInfo ) {
315286 int encoderStatus ;
316287
317288 encoderStatus = videoEncoder .dequeueOutputBuffer (bufferInfo ,
@@ -322,7 +293,7 @@ private boolean writeVideoBufferToFile() {
322293 return false ;
323294 }
324295 videoTrackIndex = muxer .addTrack (videoEncoder .getOutputFormat ());
325- startMuxerIfSetUp ();
296+ startMuxerIfSetUp (muxer );
326297 } else if (encoderStatus < 0 && encoderStatus != MediaCodec .INFO_TRY_AGAIN_LATER ) {
327298 Log .w (TAG , "unexpected result from encoder.dequeueOutputBuffer: "
328299 + encoderStatus );
@@ -352,12 +323,45 @@ private boolean writeVideoBufferToFile() {
352323 @ RequiresApi (api = Build .VERSION_CODES .Q )
353324 @ Override
354325 public void run () {
326+ VirtualDisplay virtualDisplay = null ;
327+ MediaCodec videoEncoder = null ;
328+ MediaCodec audioEncoder = null ;
329+ Surface surface = null ;
330+ AudioRecord audioRecord = null ;
331+ Thread audioRecordThread = null ;
332+ MediaMuxer muxer = null ;
355333 try {
356- setupVideoCodec ();
357- virtualDisplay = setupVirtualDisplay ();
358-
359- setupAudioCodec ();
360- setupAudioRecord ();
334+ String videoMime = MediaFormat .MIMETYPE_VIDEO_AVC ;
335+ videoEncoder = MediaCodec .createEncoderByType (videoMime );
336+
337+ /* Clamp raw width/height of device screen with video encoder's capabilities
338+ * to avoid crash.
339+ * */
340+ MediaCodecInfo .VideoCapabilities videoEncoderCapabilities = videoEncoder .getCodecInfo ().
341+ getCapabilitiesForType (videoMime ).getVideoCapabilities ();
342+ int finalVideoWidth =
343+ videoEncoderCapabilities .getSupportedWidths ().clamp (this .videoWidth );
344+ int finalVideoHeight =
345+ videoEncoderCapabilities .getSupportedHeights ().clamp (this .videoHeight );
346+ int videoBitrate = calcBitRate (videoWidth , videoHeight );
347+
348+ MediaFormat videoEncoderFormat = setupVideoEncoderFormat (videoMime ,
349+ finalVideoWidth , finalVideoHeight , videoBitrate );
350+
351+ videoEncoder .configure (videoEncoderFormat , null , null ,
352+ MediaCodec .CONFIGURE_FLAG_ENCODE );
353+ surface = videoEncoder .createInputSurface ();
354+ videoEncoder .start ();
355+
356+ Handler handler = new Handler ();
357+ virtualDisplay = setupVirtualDisplay (this .mediaProjection , surface , handler ,
358+ finalVideoWidth , finalVideoHeight );
359+
360+ int sampleRate = RecorderConstant .AUDIO_CODEC_SAMPLE_RATE_HZ ;
361+ audioEncoder = setupAudioCodec (sampleRate );
362+ audioEncoder .start ();
363+
364+ audioRecord = setupAudioRecord (sampleRate );
361365
362366 muxer = new MediaMuxer (this .outputFilePath ,
363367 MediaMuxer .OutputFormat .MUXER_OUTPUT_MPEG_4 );
@@ -366,21 +370,22 @@ public void run() {
366370 // note: this method must be run before muxer.start()
367371 muxer .setOrientationHint (recordingRotation );
368372
369- startAudioRecord ();
373+ audioRecordThread = startAudioRecord (audioEncoder , audioRecord );
374+ audioRecordThread .start ();
370375
371- bufferInfo = new MediaCodec .BufferInfo ();
376+ MediaCodec . BufferInfo bufferInfo = new MediaCodec .BufferInfo ();
372377 lastAudioTimestampUs = -1 ;
373378
374379 while (!stopped && !hasAsyncError ) {
375- if (!writeAudioBufferToFile ()) {
380+ if (!writeAudioBufferToFile (audioEncoder , muxer , bufferInfo )) {
376381 break ;
377382 }
378383
379384 if (videoTrackIndex >= 0 && audioTrackIndex < 0 ) {
380385 continue ; // wait for audio config before processing any video data frames
381386 }
382387
383- if (!writeVideoBufferToFile ()) {
388+ if (!writeVideoBufferToFile (videoEncoder , muxer , bufferInfo )) {
384389 break ;
385390 }
386391 }
0 commit comments