直播二维码生成速查

直播二维码生成速查

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
posted @ 2026-09-01 18:44  带头大哥d小弟  阅读(15)  评论(0)    收藏  举报