HarmonyOS鸿蒙Next中屏幕录制如何获取每一帧图片并保存到本地
HarmonyOS鸿蒙Next中屏幕录制如何获取每一帧图片并保存到本地 详细描述:屏幕录制包含录屏存文件和录屏取码流两种模式,录屏取码流方式可获取录屏数据的原始码流,如何根据获取的原始码流获取每一帧图片并保存到本地进行分析,必要时提供代码实现demo,能够让用户根据demo获取屏幕录制的每一帧图片。
所处环境:录屏取码流模式,获取每一帧图片。
期望:得到录屏取码流模式下获取每一帧图片的demo代码,用户能根据代码方便获取每一帧图片。
可以在录屏取码流模式的OnBufferAvailable()函数中函数中将buffer转换为pixelmap格式再将pixelmap转存为jpg图片格式保存到本地,然后在arkts侧打开图片
获取每一帧的buffer:
void OnBufferAvailableS05(OH_AVScreenCapture *capture, OH_AVBuffer *buffer, OH_AVScreenCaptureBufferType bufferType,
int64_t timestamp, void *userData) {
if (m_isExcludeSCRunning) {
if (bufferType == OH_SCREEN_CAPTURE_BUFFERTYPE_VIDEO) {
OH_NativeBuffer *nativeBuffer = OH_AVBuffer_GetNativeBuffer(buffer);
if (nativeBuffer != nullptr && capture != nullptr) {
int bufferLen = OH_AVBuffer_GetCapacity(buffer);
DEMO_LOGD("==DEMO== ScreenCapture bufferLen %{public}d", bufferLen);
OH_NativeBuffer_Config config;
OH_NativeBuffer_GetConfig(nativeBuffer, &config);
g_bufferConfig = config;
uint8_t *buf = OH_AVBuffer_GetAddr(buffer);
if (buf == nullptr) {
return;
}
// std::this_thread::sleep_for(std::chrono::milliseconds(1000));
g_index++;
string loadPath = "data/storage/el2/base/files/picture" + std::to_string(g_index) + ".jpg";
int fd = open(loadPath.c_str(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
DEMO_LOGI("the fd is : %{public}d", fd);
if (fd == -1) {
perror("open");
DEMO_LOGI("Error code: %{public}d", errno);
DEMO_LOGI("Error message: %{public}s", strerror(errno));
return;
}
packToFileFromPixelmapTest(buf, bufferLen, fd);
close(fd);
fwrite(buf, 1, bufferLen, vFile_);
OH_NativeBuffer_Unreference(nativeBuffer);
}
}
}
}
将buffer转换为pixelmap格式并保存到本地:
Image_ErrorCode packToFileFromPixelmapTest(uint8_t *buffer, size_t bufferSize, int fd)
{
// 创建ImagePacker实例。
OH_ImagePackerNative *testPacker = nullptr;
Image_ErrorCode errCode = OH_ImagePackerNative_Create(&testPacker);
if (errCode != IMAGE_SUCCESS) {
OH_LOG_ERROR(LOG_APP, "ImagePackerNativeCTest CreatePacker OH_ImagePackerNative_Create failed, errCode: %{public}d.", errCode);
return errCode;
}
// 创建Pixelmap实例。
OH_Pixelmap_InitializationOptions *createOpts;
OH_PixelmapInitializationOptions_Create(&createOpts);
OH_PixelmapInitializationOptions_SetWidth(createOpts, g_bufferConfig.width);
DEMO_LOGI("the width of buffer is : %{public}d", g_bufferConfig.width);
OH_PixelmapInitializationOptions_SetHeight(createOpts, g_bufferConfig.height);
DEMO_LOGI("the height of buffer is : %{public}d", g_bufferConfig.height);
OH_PixelmapInitializationOptions_SetPixelFormat(createOpts, 3);
OH_PixelmapInitializationOptions_SetAlphaType(createOpts, 0);
OH_PixelmapNative *pixelmap = nullptr;
OH_PixelmapInitializationOptions_SetSrcPixelFormat(createOpts, 3);
DEMO_LOGI("the size of buffer is : %{public}d", bufferSize);
errCode = OH_PixelmapNative_CreatePixelmap(buffer, bufferSize, createOpts, &pixelmap);
if (errCode != IMAGE_SUCCESS) {
OH_LOG_ERROR(LOG_APP, "ImagePackerNativeCTest OH_PixelmapNative_CreatePixelmap failed, errCode: %{public}d.", errCode);
return errCode;
}
// napi_value* pixelmapNapi = nullptr;
//
// OH_PixelmapNative_ConvertPixelmapNativeToNapi(env, pixelmap, pixelmapNapi);
// 指定编码参数,将PixelMap直接编码进文件。
OH_PackingOptions *option = nullptr;
OH_PackingOptions_Create(&option);
char type[] = "image/jpeg";
Image_MimeType image_MimeType = {type, strlen(type)};
OH_PackingOptions_SetMimeType(option, &image_MimeType);
errCode = OH_ImagePackerNative_PackToFileFromPixelmap(testPacker, option, pixelmap, fd);
if (errCode != IMAGE_SUCCESS) {
OH_LOG_ERROR(LOG_APP, "ImagePackerNativeCTest OH_ImagePackerNative_PackToFileFromPixelmap failed, errCode: %{public}d.", errCode);
return errCode;
}
// 释放ImagePacker实例。
errCode = OH_ImagePackerNative_Release(testPacker);
if (errCode != IMAGE_SUCCESS)
{
OH_LOG_ERROR(LOG_APP, "ImagePackerNativeCTest ReleasePacker OH_ImagePackerNative_Release failed, errCode: %{public}d.", errCode);
return errCode;
}
// 释放Pixelmap实例。
errCode = OH_PixelmapNative_Release(pixelmap);
if (errCode != IMAGE_SUCCESS)
{
OH_LOG_ERROR(LOG_APP, "ImagePackerNativeCTest ReleasePacker OH_PixelmapNative_Release failed, errCode: %{public}d.", errCode);
return errCode;
}
return IMAGE_SUCCESS;
}
arkts侧读取本地图片并进行操作:
// 导入相关模块
import fileIo from '@ohos.fileio';
import { ImageSource } from '@ohos.multimedia.image';
// 读取图片文件
async function loadAndDisplayImage() {
// 图片的存储路径和文件名
const filePath = 'data/storage/el2/base/files/picture1.jpg'; // 替换为实际的文件路径
// 读取文件
const file = await fileIo.open(filePath, fileIo.OpenMode.READ_ONLY);
const fileStat = await fileIo.fstat(file.fd);
const bufferSize = fileStat.size;
const buffer = new ArrayBuffer(bufferSize);
const bytesRead = await fileIo.read(file.fd, buffer);
if (bytesRead !== bufferSize) {
console.error('读取文件失败');
return;
}
// 关闭文件
await fileIo.close(file.fd);
// 创建 ImageSource 并解析图片数据
const imageSource = await ImageSource.createPixelMapFromBytes(buffer);
const pixelMap = await imageSource.createPixelMap();
}
参考:
使用AVScreenCapture录屏取码流(C/C++)
https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/using-avscreencapture-for-buffer
使用Image_NativeModule完成图片编码
https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/image-packer-c
更多关于HarmonyOS鸿蒙Next中屏幕录制如何获取每一帧图片并保存到本地的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
楼主可以看下
需要在录制过程中获取音视频帧数据转换为对应格式的图片或者音频,可以在AVScreenCapture的OH_AVBuffer中获取buffer流数据保存,再将保存的数据进行转换。
从buffer流中获取数据,获取数据为PCM格式码流,可参考:
void Buffer2Png(OH_AVBuffer* buffer){
int bufferLen = OH_AVBuffer_GetCapacity(buffer);
OH_LOG_INFO (LOG_APP,"png length: %{public}d",bufferLen);
OH_NativeBuffer *nativeBuffer = OH_AVBuffer_GetNativeBuffer(buffer);
OH_NativeBuffer_Config config;
OH_NativeBuffer_GetConfig(nativeBuffer, &config);
int32_t videoSize = config.height * config.width * 4;
uint8_t *buf = OH_AVBuffer_GetAddr(buffer);
testSavePNG(config.width, config.height, (uint8_t *)buf);
}
void OnBufferAvailable(OH_AVScreenCapture *capture, OH_AVBuffer *buffer, OH_AVScreenCaptureBufferType bufferType, int64_t timestamp, void *userData) {
// 处于录屏取码流状态。
if (IsCaptureStreamRunning) {
// 视频buffer。
Buffer2Png(buffer);
OH_LOG_INFO(LOG_APP, "this is an info level log");
}
}
struct OH_AVScreenCapture *capture;
// 开始录屏时调用StartScreenCapture。
static napi_value StartScreenCapture(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value args[1] = {nullptr};
// 获取参数。
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
int32_t Frame_Rate;
napi_get_value_int32(env, args[0], &Frame_Rate);
IsCaptureStreamRunning = true;
// 实例化ScreenCapture。
capture = OH_AVScreenCapture_Create();
// 设置回调 。
OH_AVScreenCapture_SetErrorCallback(capture, OnError, nullptr);
OH_AVScreenCapture_SetStateCallback(capture, OnStateChange, nullptr);
OH_AVScreenCapture_SetDataCallback(capture, OnBufferAvailable, nullptr);
// 初始化录屏,传入配置信息OH_AVScreenRecorderConfig。
OH_AudioCaptureInfo miccapinfo = {.audioSampleRate = 16000, .audioChannels = 2, .audioSource = OH_MIC};
OH_VideoCaptureInfo videocapinfo = {.videoFrameWidth = 768, .videoFrameHeight = 1280, .videoSource = OH_VIDEO_SOURCE_SURFACE_RGBA};
OH_AudioInfo audioinfo = {.micCapInfo = miccapinfo,};
OH_VideoInfo videoinfo = {.videoCapInfo = videocapinfo};
OH_AVScreenCaptureConfig config = {.captureMode = OH_CAPTURE_HOME_SCREEN,.dataType = OH_ORIGINAL_STREAM,.audioInfo = audioinfo,.videoInfo = videoinfo};
OH_AVScreenCapture_Init(capture, config);
// 开始录屏。
OH_AVScreenCapture_StartScreenCapture(capture);
// mic开关设置。
OH_AVScreenCapture_SetMicrophoneEnabled(capture, false);
// 可选 设置录屏时的最大帧率 需在启动后调用。
OH_AVScreenCapture_SetMaxVideoFrameRate(capture, Frame_Rate);
// 返回调用结果,仅返回随意值。
napi_value sum;
napi_create_double(env, 0, &sum);
return sum;
}
保存转换后的PNG图片,并在ArkTS侧通过保存文件路径展示图片:
void testSavePNG(unsigned int w, unsigned int h, const unsigned char *img)
{
static int num = 0;
std::string tmpDir = std::getenv("TMPDIR"); // /data/storage/el2/base/cache
std::string filename = tmpDir + "/" + GETPNGNAME + std::to_string(num) + ".png";
FILE *fp = fopen(filename.c_str(), "wb");
svpng(fp, w, h, img, 1);
fclose(fp);
fp = nullptr;
}
完整的demo可以参考下:
void svpng (FILE* out, unsigned w, unsigned h, const unsigned char * img, int alpha);
SVPNG_LINKAGE void svpng(SVPNG_OUTPUT, unsigned w, unsigned h, const unsigned char* img, int alpha) {
static const unsigned t[] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
/* CRC32校验码表 */ 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c };
unsigned a = 1, b = 0, c, p = w * (alpha ? 4 : 3) + 1, x, y, i; /* 初始化CRC校验码的变量 */
#define SVPNG_U8A(ua, l) for (i = 0; i < l; i++) SVPNG_PUT((ua)[i]);
#define SVPNG_U32(u) do { SVPNG_PUT((u) >> 24); SVPNG_PUT(((u) >> 16) & 255); SVPNG_PUT(((u) >> 8) & 255); SVPNG_PUT((u) & 255); } while(0)
#define SVPNG_U8C(u) do { SVPNG_PUT(u); c ^= (u); c = (c >> 4) ^ t[c & 15]; c = (c >> 4) ^ t[c & 15]; } while(0)
#define SVPNG_U8AC(ua, l) for (i = 0; i < l; i++) SVPNG_U8C((ua)[i])
#define SVPNG_U16LC(u) do { SVPNG_U8C((u) & 255); SVPNG_U8C(((u) >> 8) & 255); } while(0)
#define SVPNG_U32C(u) do { SVPNG_U8C((u) >> 24); SVPNG_U8C(((u) >> 16) & 255); SVPNG_U8C(((u) >> 8) & 255); SVPNG_U8C((u) & 255); } while(0)
#define SVPNG_U8ADLER(u) do { SVPNG_U8C(u); a = (a + (u)) % 65521; b = (b + a) % 65521; } while(0)
#define SVPNG_BEGIN(s, l) do { SVPNG_U32(l); c = ~0U; SVPNG_U8AC(s, 4); } while(0)
#define SVPNG_END() SVPNG_U32(~c)
SVPNG_U8A("\x89PNG\r\n\32\n", 8); /* 写入PNG文件头 */
SVPNG_BEGIN("IHDR", 13); /* 将图像头信息写入IHDR块(图像头信息)中 */
SVPNG_U32C(w); SVPNG_U32C(h); /* 图像宽高(8字节) */
SVPNG_U8C(8); SVPNG_U8C(alpha ? 6 : 2); /* 深度为8,颜色类型为带透明度(RGBA)或不带透明度(RGB) */
SVPNG_U8AC("\0\0\0", 3); /* 压缩方法、滤波器和交错类型固定为默认值 */
SVPNG_END(); /* 结束图像文件头写入 */
SVPNG_BEGIN("IDAT", 2 + h * (5 + p) + 4); /* 将图像数据写入IDAT块中 */
SVPNG_U8AC("\x78\1", 2); /* 写入压缩块起始头(2字节) */
for (y = 0; y < h; y++) { /* 每行作为一个压缩块(Default Block) */
SVPNG_U8C(y == h - 1); /* 写入一个字节判断是否到达最后一行,若是最后一行写1,否则写0 */
SVPNG_U16LC(p); SVPNG_U16LC(~p); /* 写入两个16位整数(共4字节),分别表示块大小及其1的补码,用于校验完整性 */
SVPNG_U8ADLER(0); /* 写入一个字节表示未使用滤波 */
for (x = 0; x < p - 1; x++, img++)
SVPNG_U8ADLER(*img); /* 将图像的每个像素依次写入压缩块中,完成图片数据压缩 */
}
SVPNG_U32C((b << 16) | a); /* 写入4字节整数用于表示压缩块的结束和Adler-32校验和 */
SVPNG_END(); /* } */
SVPNG_BEGIN("IEND", 0); SVPNG_END(); /* 写入图像结束块标识IEND */
}
#endif /* SVPNG_INC_ */
这种网上都有相关的内容的:https://blog.csdn.net/zhizhengguan/article/details/106012115
在HarmonyOS Next中,使用ScreenCapture
类进行屏幕录制。通过ImageReceiver
接收图像数据,可获取每一帧的Image
对象。使用Image
的getComponent
方法访问图像组件数据,转换为像素数组或PixelMap
。通过ImageSaver
将PixelMap
保存为JPEG或PNG格式文件到本地。整个过程需申请屏幕录制和存储权限。
在HarmonyOS Next中,通过录屏取码流模式获取每一帧图片并保存到本地,可以基于ScreenRecorder
和VideoCapture
类实现。以下是一个完整的代码示例,展示如何获取原始码流并提取每一帧图片保存为JPEG文件:
import screenRecorder from '@ohos.multimedia.screenRecorder';
import image from '@ohos.multimedia.image';
import media from '@ohos.multimedia.media';
import fileIo from '@ohos.fileio';
// 配置录屏参数
let videoProf: screenRecorder.VideoProfile = {
videoBitrate: 2000000,
videoCodec: media.CodecMimeType.VIDEO_AVC,
videoFrameWidth: 720,
videoFrameHeight: 1080,
videoFrameRate: 30
};
// 创建ScreenRecorder实例
let screenRecorder: screenRecorder.ScreenRecorder = await screenRecorder.createScreenRecorder();
// 设置录屏流回调
screenRecorder.on('videoFrameAvailable', (frame: screenRecorder.VideoFrame) => {
// 将VideoFrame转换为Image对象
let imageSource: image.ImageSource = image.createImageSource(frame.surface);
imageSource.createImage((err, img) => {
if (err) {
console.error('Failed to create image');
return;
}
// 配置图片编码参数
let packOpts: image.PackingOption = {
format: "image/jpeg",
quality: 100
};
// 将图片打包为JPEG格式
img.packing(packOpts, (err, data) => {
if (err) {
console.error('Failed to pack image');
return;
}
// 生成文件名并保存到本地
let fileName = `frame_${Date.now()}.jpg`;
let filePath = `/data/storage/el2/base/files/${fileName}`;
fileIo.writeFile(filePath, data, (err) => {
if (err) {
console.error('Failed to write file');
} else {
console.log(`Frame saved: ${filePath}`);
}
});
});
});
});
// 开始录屏
await screenRecorder.start(videoProf);
关键步骤说明:
- 创建
ScreenRecorder
实例并配置视频参数 - 通过
videoFrameAvailable
事件监听获取原始视频帧 - 使用
createImageSource
将视频帧转换为Image对象 - 通过
packing
方法将图像编码为JPEG格式 - 使用文件IO接口将图像数据保存到本地
注意事项:
- 需要申请
ohos.permission.CAPTURE_SCREEN
录屏权限 - 文件保存路径需使用应用可访问的目录
- 实际使用中建议添加帧率控制和内存管理
- 可根据需要调整图像格式(如PNG)和质量参数
此代码可直接运行在HarmonyOS Next设备上,能够实时捕获屏幕帧并保存为独立的图片文件。