直播二维码生成速查
直播二维码生成速查
1. 项目中二维码的生成流程
项目对应代码:
liveStep2Component.ets -> 拼接直播二维码内容
liveStep3Component.ets -> 生成二维码图片并显示
LiveStorage.ets -> 保存二维码内容
完整流程:
选择 Wi-Fi
-> 设置直播地址、分辨率、码率
-> 拼接二维码字符串
-> ZXing 生成 BitMatrix
-> 转换成 RGBA 黑白像素数据
-> 创建 PixelMap
-> Image 显示二维码
2. 直播二维码内容格式
项目当前使用换行符分隔字段:
SJ + 直播平台类型
Wi-Fi 名称
Wi-Fi 密码
直播地址
分辨率
码率
固定参数 7
代码:
this.liveStorage.liveQRCode =
'SJ' + this.live_platform_type + '\n' +
this.liveStorage.wifiSSID + '\n' +
this.liveStorage.wifiPassword + '\n' +
this.liveStorage.liveStreamUrl + '\n' +
this.live_resolution_type + '\n' +
this.live_bitrate + '\n7';
生成结果类似:
SJPLATFORM_TYPE
CAMERA_WIFI_SSID
CAMERA_WIFI_PASSWORD
LIVE_STREAM_URL
RESOLUTION_TYPE
BITRATE
7
相机端扫码后按照行读取:
const values =
qrContent.split('\n');
const platformType = values[0];
const wifiSsid = values[1];
const wifiPassword = values[2];
const streamUrl = values[3];
const resolution = values[4];
const bitrate = values[5];
const fixedValue = values[6];
解析前要检查字段数量:
const values =
qrContent.split('\n');
if (values.length < 7) {
return;
}
3. 生成二维码图片
项目使用:
import {
BarcodeFormat,
BitMatrix,
EncodeHintType,
MultiFormatWriter
} from '@ohos/zxing';
生成二维码矩阵:
const size = 500;
const hints:
Map<EncodeHintType, Object> =
new Map();
hints.set(
EncodeHintType.MARGIN,
0
);
const matrix: BitMatrix =
new MultiFormatWriter().encode(
qrContent,
BarcodeFormat.QR_CODE,
size,
size,
hints
);
这里的 matrix 可以理解成二维码黑白格子:
true -> 黑色模块
false -> 白色模块
4. 将 BitMatrix 转成 PixelMap
鸿蒙 Image 不能直接显示 ZXing 的 BitMatrix,需要先创建像素缓冲区:
const buffer =
new ArrayBuffer(size * size * 4);
const data =
new Uint8Array(buffer);
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
const index =
(y * size + x) * 4;
if (matrix.get(x, y)) {
// 黑色,RGBA
data[index] = 0;
data[index + 1] = 0;
data[index + 2] = 0;
data[index + 3] = 255;
} else {
// 白色,RGBA
data[index] = 255;
data[index + 1] = 255;
data[index + 2] = 255;
data[index + 3] = 255;
}
}
}
创建 PixelMap:
const options:
image.InitializationOptions = {
size: {
width: size,
height: size
},
pixelFormat:
image.PixelMapFormat.RGBA_8888
};
this.qrcodeImage =
await image.createPixelMap(
buffer,
options
);
页面显示:
Image(this.qrcodeImage ?? '')
.width('95%')
.backgroundColor(Color.White)
.padding(10);
5. 直接可复制的二维码生成方法
async function createQrCode(
content: string
): Promise<PixelMap | null> {
try {
if (!content) {
return null;
}
const size = 500;
const hints:
Map<EncodeHintType, Object> =
new Map();
hints.set(
EncodeHintType.MARGIN,
0
);
const matrix =
new MultiFormatWriter().encode(
content,
BarcodeFormat.QR_CODE,
size,
size,
hints
);
const buffer =
new ArrayBuffer(size * size * 4);
const data =
new Uint8Array(buffer);
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
const index =
(y * size + x) * 4;
const color =
matrix.get(x, y)
? 0
: 255;
data[index] = color;
data[index + 1] = color;
data[index + 2] = color;
data[index + 3] = 255;
}
}
return await image.createPixelMap(
buffer,
{
size: {
width: size,
height: size
},
pixelFormat:
image.PixelMapFormat.RGBA_8888
}
);
} catch (error) {
console.error(
`二维码生成失败: ${
JSON.stringify(error)
}`
);
return null;
}
}
使用:
this.qrcodeImage =
await createQrCode(
this.liveStorage.liveQRCode
);
6. 二维码生成失败重试
项目当前最多重试 3 次:
private tryAgain: number = 0;
async generateQrCode(): Promise<void> {
try {
this.qrcodeImage =
await createQrCode(
this.liveStorage.liveQRCode
);
this.tryAgain = 0;
} catch (error) {
if (this.tryAgain < 3) {
this.tryAgain++;
setTimeout(() => {
this.generateQrCode();
}, 300);
}
}
}
建议重试时先判断内容没有变化:
private qrContent: string = '';
async generateQrCode(
content: string
): Promise<void> {
this.qrContent = content;
const image =
await createQrCode(content);
if (this.qrContent === content) {
this.qrcodeImage = image;
}
}
这样页面快速切换直播配置时,旧二维码不会覆盖新二维码。
7. 二维码内容不要打印密码
项目当前如果直接打印完整二维码内容:
LogUtils.debug(
'liveQRCode: ' +
this.liveStorage.liveQRCode
);
二维码内容里包含 Wi-Fi 密码,这样会把密码写进日志。
不建议:
console.info(
JSON.stringify(this.liveStorage.liveQRCode)
);
建议只打印摘要:
LogUtils.debug(
TAG,
`直播二维码已生成,长度=${
this.liveStorage.liveQRCode.length
}`
);
或者隐藏敏感字段:
const values =
this.liveStorage.liveQRCode.split('\n');
LogUtils.debug(
TAG,
`平台=${values[0]}, `
+ `SSID=${values[1]}, `
+ `字段数量=${values.length}`
);
二维码本身也属于敏感数据,因为拿到二维码的人可以获取:
Wi-Fi 名称
Wi-Fi 密码
直播地址
直播配置
8. 二维码字段建议增加版本号
当前第一行使用:
SJ + 直播平台类型
后续如果字段格式变化,旧版本可能无法解析。
可以改成:
SJCAM_LIVE_V1
直播平台类型
Wi-Fi 名称
Wi-Fi 密码
直播地址
分辨率
码率
固定参数
生成:
const qrContent = [
'SJCAM_LIVE_V1',
livePlatformType,
wifiSsid,
wifiPassword,
liveStreamUrl,
resolution,
bitrate,
'7'
].join('\n');
解析:
const values =
qrContent.split('\n');
if (
values.length < 8 ||
values[0] !== 'SJCAM_LIVE_V1'
) {
ToastUtil.showToast(
'不支持的直播二维码'
);
return;
}
这样以后增加字段时可以根据版本处理:
switch (values[0]) {
case 'SJCAM_LIVE_V1':
parseLiveQrV1(values);
break;
case 'SJCAM_LIVE_V2':
parseLiveQrV2(values);
break;
default:
ToastUtil.showToast(
'二维码版本不支持'
);
}
9. 二维码内容较长时的注意事项
二维码内容越长,二维码格子越密,扫码距离和容错能力会变差。
当前直播二维码包含:
Wi-Fi 名称
Wi-Fi 密码
直播地址
直播配置
如果直播地址特别长,建议:
减少无用字段
缩短固定参数
避免重复保存同一个值
提高二维码图片尺寸
给二维码保留白边
二维码尺寸:
const size = 500;
如果内容较长,可以提高:
const size = 800;
但尺寸越大,内存和生成时间也会增加。
10. Margin 为什么重要
项目当前设置:
hints.set(
EncodeHintType.MARGIN,
0
);
MARGIN 是二维码外部留白。
留白太小:
二维码贴近边缘
扫码设备不容易识别
裁剪后可能无法扫描
更稳妥的写法:
hints.set(
EncodeHintType.MARGIN,
4
);
如果页面已经通过白色背景和 padding 提供了留白,可以保持:
hints.set(
EncodeHintType.MARGIN,
0
);
但要确保最终图片四周仍然有白边。
11. 官方二维码能力和项目 ZXing 的区别
鸿蒙官方目前主要提供扫码能力,例如:
import {
scanBarcode,
scanCore
} from '@kit.ScanKit';
官方扫码:
const options:
scanBarcode.ScanOptions = {
scanTypes: [
scanCore.ScanType.ALL
],
enableMultiMode: false,
enableAlbum: false
};
const result =
await scanBarcode.startScanForResult(
context,
options
);
需要注意:
ScanKit 主要负责识别二维码
不是一个直接生成二维码图片的 UI 组件
项目 ZXing 负责生成:
new MultiFormatWriter().encode(
content,
BarcodeFormat.QR_CODE,
width,
height,
hints
);
两者区别:
项目 ZXing:
- 可以生成二维码
- 可以控制尺寸
- 可以控制 Margin
- 可以直接拿到二维码矩阵
- 需要自己转换 PixelMap
- 需要自己处理内存和显示
鸿蒙 ScanKit:
- 主要负责扫码识别
- 调起系统扫码能力
- 不需要自己解析黑白像素
- 不适合直接生成二维码图片
- 生成二维码仍需要第三方库或自己实现
简单记忆:
生成二维码 -> ZXing
扫描二维码 -> ScanKit
12. ZXing 和手写二维码的区别
不建议自己手写二维码编码算法:
二维码编码规则复杂
需要处理纠错等级
需要处理数据分块
需要处理掩码
需要处理版本号
使用 ZXing:
const matrix =
new MultiFormatWriter().encode(
content,
BarcodeFormat.QR_CODE,
500,
500,
hints
);
ZXing 已经处理了:
数据编码
二维码版本
纠错码
掩码
黑白模块排列
项目只需要负责:
内容拼接
矩阵转图片
PixelMap 显示
13. 扫码识别项目生成的直播二维码
const result =
await scanBarcode.startScanForResult(
context,
options
);
const content =
result.originalValue;
const values =
content.split('\n');
if (values.length < 7) {
ToastUtil.showToast(
'二维码格式错误'
);
return;
}
const platformType = values[0];
const ssid = values[1];
const password = values[2];
const streamUrl = values[3];
const resolution = values[4];
const bitrate = values[5];
不要把密码写入日志:
LogUtils.info(
TAG,
`platform=${platformType}, `
+ `ssid=${ssid}, `
+ `url=${streamUrl}`
);
14. 脱敏后的完整直播二维码案例
async function buildLiveQrCode(): Promise<void> {
const qrContent = [
'SJCAM_LIVE_V1',
'LIVE_PLATFORM',
'CAMERA_WIFI_SSID',
'CAMERA_WIFI_PASSWORD',
'LIVE_STREAM_URL',
'RESOLUTION_TYPE',
'BITRATE_VALUE',
'7'
].join('\n');
const qrImage =
await createQrCode(qrContent);
if (!qrImage) {
ToastUtil.showToast(
'直播二维码生成失败'
);
return;
}
this.qrcodeImage = qrImage;
}
页面显示:
Image(this.qrcodeImage ?? '')
.width('95%')
.backgroundColor(Color.White)
.padding(10);
隐私字段统一使用:
CAMERA_WIFI_SSID
CAMERA_WIFI_PASSWORD
LIVE_STREAM_URL
LIVE_PLATFORM
RESOLUTION_TYPE
BITRATE_VALUE
DEVICE_SERIAL
浙公网安备 33010602011771号