Skip to content

Commit ce4e1ae

Browse files
committed
io.appium.settings: recording: Fixup dependency hell
Signed-off-by: sirmordred <[email protected]>
1 parent cb83b09 commit ce4e1ae

File tree

1 file changed

+78
-73
lines changed

1 file changed

+78
-73
lines changed

app/src/main/java/io/appium/settings/RecorderThread.java

Lines changed: 78 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)