Skip to content

Commit 7e438ec

Browse files
committed
screen record example
1 parent 74be818 commit 7e438ec

8 files changed

Lines changed: 677 additions & 0 deletions

File tree

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
2+
3+
set(NAME screenrecord)
4+
5+
set(SRC_DIR ../../../examples/${NAME})
6+
set(BASE_DIR ../../../base)
7+
set(EXTERNAL_DIR ../../../external)
8+
9+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DVK_USE_PLATFORM_ANDROID_KHR -DVK_NO_PROTOTYPES")
10+
11+
file(GLOB EXAMPLE_SRC "${SRC_DIR}/*.cpp")
12+
13+
add_library(native-lib SHARED ${EXAMPLE_SRC})
14+
15+
add_library(native-app-glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
16+
17+
add_subdirectory(../base ${CMAKE_SOURCE_DIR}/../base)
18+
19+
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
20+
21+
include_directories(${BASE_DIR})
22+
include_directories(${EXTERNAL_DIR})
23+
include_directories(${EXTERNAL_DIR}/glm)
24+
include_directories(${EXTERNAL_DIR}/imgui)
25+
include_directories(${EXTERNAL_DIR}/tinygltf)
26+
include_directories(${ANDROID_NDK}/sources/android/native_app_glue)
27+
28+
target_link_libraries(
29+
native-lib
30+
native-app-glue
31+
libbase
32+
android
33+
log
34+
z
35+
mediandk
36+
)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
This example demonstrates video screen recording on Android by using AMediaCodec API
2+
3+
0 Create codec surface AMediaCodec_createInputSurface
4+
1 Create Vulkan surface from codec surface
5+
2 Blit images from display surface to codec surface
6+
7+
8+
# How to retrieve and play the encoded file?
9+
10+
```shell
11+
adb shell run-as de.saschawillems.vulkanScreenRecord cat video.h264 >video.h264
12+
ffplay video.h264
13+
```
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
apply plugin: 'com.android.application'
2+
apply from: '../gradle/outputfilename.gradle'
3+
4+
android {
5+
compileSdkVersion rootProject.ext.compileSdkVersion
6+
defaultConfig {
7+
applicationId "de.saschawillems.vulkanScreenRecord"
8+
minSdkVersion 26 //AMediaCodec_createInputSurface was introduced in API 26
9+
targetSdkVersion rootProject.ext.targetSdkVersion
10+
versionCode 1
11+
versionName "1.0"
12+
ndk {
13+
abiFilters rootProject.ext.abiFilters
14+
}
15+
externalNativeBuild {
16+
cmake {
17+
cppFlags "-std=c++14"
18+
arguments "-DANDROID_STL=c++_shared", '-DANDROID_TOOLCHAIN=clang'
19+
}
20+
}
21+
}
22+
sourceSets {
23+
main.assets.srcDirs = ['assets']
24+
}
25+
buildTypes {
26+
release {
27+
minifyEnabled false
28+
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
29+
}
30+
}
31+
externalNativeBuild {
32+
cmake {
33+
path "CMakeLists.txt"
34+
}
35+
}
36+
}
37+
38+
task copyTask {
39+
copy {
40+
from '../../common/res/drawable'
41+
into "src/main/res/drawable"
42+
include 'icon.png'
43+
}
44+
45+
copy {
46+
from rootProject.ext.shaderPath + 'glsl/base'
47+
into 'assets/shaders/glsl/base'
48+
include '*.spv'
49+
}
50+
51+
copy {
52+
from rootProject.ext.shaderPath + 'glsl/screenshot'
53+
into 'assets/shaders/glsl/screenshot'
54+
include '*.*'
55+
}
56+
57+
copy {
58+
from rootProject.ext.assetPath + 'models'
59+
into 'assets/models'
60+
include 'chinesedragon.gltf'
61+
}
62+
63+
64+
}
65+
66+
preBuild.dependsOn copyTask
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
3+
4+
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
5+
6+
<application
7+
android:label="Vulkan Video Screen Recorder"
8+
android:icon="@drawable/icon"
9+
android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
10+
<activity android:name="de.saschawillems.vulkanSample.VulkanActivity"
11+
android:screenOrientation="portrait"
12+
android:configChanges="orientation|keyboardHidden"
13+
android:exported="true">
14+
<meta-data android:name="android.app.lib_name"
15+
android:value="native-lib" />
16+
<intent-filter>
17+
<action android:name="android.intent.action.MAIN" />
18+
<category android:name="android.intent.category.LAUNCHER" />
19+
</intent-filter>
20+
</activity>
21+
</application>
22+
23+
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
24+
<uses-feature android:name="android.hardware.gamepad" android:required="false" />
25+
26+
</manifest>
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright (C) 2018 by Sascha Willems - www.saschawillems.de
3+
*
4+
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
5+
*/
6+
package de.saschawillems.vulkanSample;
7+
8+
import android.app.AlertDialog;
9+
import android.app.NativeActivity;
10+
import android.content.DialogInterface;
11+
import android.content.pm.ApplicationInfo;
12+
import android.os.Bundle;
13+
14+
import java.util.concurrent.Semaphore;
15+
16+
public class VulkanActivity extends NativeActivity {
17+
18+
static {
19+
// Load native library
20+
System.loadLibrary("native-lib");
21+
}
22+
@Override
23+
protected void onCreate(Bundle savedInstanceState) {
24+
super.onCreate(savedInstanceState);
25+
}
26+
27+
// Use a semaphore to create a modal dialog
28+
29+
private final Semaphore semaphore = new Semaphore(0, true);
30+
31+
public void showAlert(final String message)
32+
{
33+
final VulkanActivity activity = this;
34+
35+
ApplicationInfo applicationInfo = activity.getApplicationInfo();
36+
final String applicationName = applicationInfo.nonLocalizedLabel.toString();
37+
38+
this.runOnUiThread(new Runnable() {
39+
public void run() {
40+
AlertDialog.Builder builder = new AlertDialog.Builder(activity, android.R.style.Theme_Material_Dialog_Alert);
41+
builder.setTitle(applicationName);
42+
builder.setMessage(message);
43+
builder.setPositiveButton("Close", new DialogInterface.OnClickListener() {
44+
public void onClick(DialogInterface dialog, int id) {
45+
semaphore.release();
46+
}
47+
});
48+
builder.setCancelable(false);
49+
AlertDialog dialog = builder.create();
50+
dialog.show();
51+
}
52+
});
53+
try {
54+
semaphore.acquire();
55+
}
56+
catch (InterruptedException e) { }
57+
}
58+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#include "codecutils.h"
2+
3+
const char *amErrorString(media_status_t status) {
4+
/** The requested media operation completed successfully. */
5+
switch (status) {
6+
case AMEDIA_OK:
7+
return "AMEDIA_OK";
8+
case AMEDIACODEC_ERROR_INSUFFICIENT_RESOURCE:
9+
return "AMEDIACODEC_ERROR_INSUFFICIENT_RESOURCE";
10+
case AMEDIACODEC_ERROR_RECLAIMED:
11+
return "AMEDIACODEC_ERROR_RECLAIMED";
12+
case AMEDIA_ERROR_UNKNOWN:
13+
return "AMEDIA_ERROR_UNKNOWN";
14+
case AMEDIA_ERROR_MALFORMED:
15+
return "AMEDIA_ERROR_MALFORMED";
16+
case AMEDIA_ERROR_UNSUPPORTED:
17+
return "AMEDIA_ERROR_UNSUPPORTED";
18+
case AMEDIA_ERROR_INVALID_OBJECT:
19+
return "AMEDIA_ERROR_INVALID_OBJECT";
20+
case AMEDIA_ERROR_INVALID_PARAMETER:
21+
return "AMEDIA_ERROR_INVALID_PARAMETER";
22+
case AMEDIA_ERROR_INVALID_OPERATION:
23+
return "AMEDIA_ERROR_INVALID_OPERATION";
24+
case AMEDIA_ERROR_END_OF_STREAM:
25+
return "AMEDIA_ERROR_END_OF_STREAM";
26+
case AMEDIA_ERROR_IO:
27+
return "AMEDIA_ERROR_IO";
28+
case AMEDIA_ERROR_WOULD_BLOCK:
29+
return "AMEDIA_ERROR_WOULD_BLOCK";
30+
case AMEDIA_DRM_ERROR_BASE:
31+
return "AMEDIA_DRM_ERROR_BASE";
32+
case AMEDIA_DRM_NOT_PROVISIONED:
33+
return "AMEDIA_DRM_NOT_PROVISIONED";
34+
case AMEDIA_DRM_RESOURCE_BUSY:
35+
return "AMEDIA_DRM_RESOURCE_BUSY";
36+
case AMEDIA_DRM_DEVICE_REVOKED:
37+
return "AMEDIA_DRM_DEVICE_REVOKED";
38+
case AMEDIA_DRM_SHORT_BUFFER:
39+
return "AMEDIA_DRM_SHORT_BUFFER";
40+
case AMEDIA_DRM_SESSION_NOT_OPENED:
41+
return "AMEDIA_DRM_SESSION_NOT_OPENED";
42+
case AMEDIA_DRM_TAMPER_DETECTED:
43+
return "AMEDIA_DRM_TAMPER_DETECTED";
44+
case AMEDIA_DRM_VERIFY_FAILED:
45+
return "AMEDIA_DRM_VERIFY_FAILED";
46+
case AMEDIA_DRM_NEED_KEY:
47+
return "AMEDIA_DRM_NEED_KEY";
48+
case AMEDIA_DRM_LICENSE_EXPIRED:
49+
return "AMEDIA_DRM_LICENSE_EXPIRED";
50+
case AMEDIA_IMGREADER_ERROR_BASE:
51+
return "AMEDIA_IMGREADER_ERROR_BASE";
52+
case AMEDIA_IMGREADER_NO_BUFFER_AVAILABLE:
53+
return "AMEDIA_IMGREADER_NO_BUFFER_AVAILABLE";
54+
case AMEDIA_IMGREADER_MAX_IMAGES_ACQUIRED:
55+
return "AMEDIA_IMGREADER_MAX_IMAGES_ACQUIRED";
56+
case AMEDIA_IMGREADER_CANNOT_LOCK_IMAGE:
57+
return "AMEDIA_IMGREADER_CANNOT_LOCK_IMAGE";
58+
case AMEDIA_IMGREADER_CANNOT_UNLOCK_IMAGE:
59+
return "AMEDIA_IMGREADER_CANNOT_UNLOCK_IMAGE";
60+
case AMEDIA_IMGREADER_IMAGE_NOT_LOCKED:
61+
return "AMEDIA_IMGREADER_IMAGE_NOT_LOCKED";
62+
}
63+
return "UNKNOWN";
64+
65+
}

examples/screenrecord/codecutils.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#ifndef ANDROID_CODECUTILS_H
2+
#define ANDROID_CODECUTILS_H
3+
4+
#include "VulkanAndroid.h"
5+
#include <media/NdkMediaCodec.h>
6+
7+
static constexpr int COLOR_FormatSurface = 0x7F000789;
8+
9+
const char *amErrorString(media_status_t status);
10+
11+
#define AM_CHECK_RESULT(ctx, f) \
12+
{ \
13+
media_status_t res = (f); \
14+
if (res != AMEDIA_OK) \
15+
{ \
16+
LOGE("Fatal : %s \"%s\" in %s at line %d", ctx, amErrorString(res), __FILE__, __LINE__);\
17+
} else { \
18+
LOGI("OK : %s \"%s\" in %s at line %d", ctx, amErrorString(res), __FILE__, __LINE__); \
19+
} \
20+
}
21+
22+
#define AM_CHECK_RESULT_ERR(ctx, f) \
23+
{ \
24+
media_status_t res = (f); \
25+
if (res != AMEDIA_OK) \
26+
{ \
27+
LOGE("Fatal : %s \"%s\" in %s at line %d", ctx, amErrorString(res), __FILE__, __LINE__);\
28+
} \
29+
}
30+
31+
#endif //ANDROID_CODECUTILS_H

0 commit comments

Comments
 (0)