如何进行视频压缩
========

**问题现象**

视频压缩处理是影音娱乐类应用中的典型场景之一，如上传、发送或保存视频时存在大小限制，需要对视频进行压缩，保证视频质量及大小满足用户需要。

**解决方案**

**方案一**

1.  利用三方库videocompressor的视频压缩功能来实现，具体步骤如下：
    
    static async selectVideo(selectNumber: number): Promise<photoAccessHelper.PhotoSelectResult> {
      let photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
      // 设置要选择的媒体文件类型
      photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.VIDEO\_TYPE;
      // 设置选择文件最大数量
      photoSelectOptions.maxSelectNumber = selectNumber;
      let photoPicker = new photoAccessHelper.PhotoViewPicker();
      // PhotoViewPicker的select方法不需要特殊权限即可读取到图片或者视频文件
      return await photoPicker.select(photoSelectOptions);
    }
    
    // 获取视频路径
    async selectAndCompressVideo() {
      let selectResult: photoAccessHelper.PhotoSelectResult = await PictureUtils.selectVideo(1);
      // 得到选中视频的uri
      this.videoPath = selectResult.photoUris\[0\];
      hilog.info(0x0000, TAG, 'videoCompressor select selectFilePath:' + this.videoPath)
    }
    
2.  调用compressVideo方法并指定相应质量进行压缩。
    
    compressVideo(quality: CompressQuality, qualityTag: string) {
      this.quantity = qualityTag;
      let videoCompressor = new VideoCompressor();
      // 调用compressVideo方法并指定相应质量进行压缩
      videoCompressor.compressVideo(this.uiContext.getHostContext()!, this.videoPath, quality)
        .then((data) => {
          if (data.code == CompressorResponseCode.SUCCESS) {
            hilog.info(0x0000, TAG,
              'videoCompressor ' + qualityTag + ' message:' + data.message + '--outputPath:' + data.outputPath);
            // 获取存在应用沙箱路径下的压缩后的视频文件，点击保存按钮后将其写入相册
            this.buffer = PictureUtils.compressVideo(data.outputPath);
            hilog.info(0x0000, TAG, \`The buffer: ${JSON.stringify(this.buffer)}\`);
            this.uiContext.showAlertDialog({ message: '已压缩完成，可执行下载!' });
          } else {
            hilog.info(0x0000, TAG,
              'videoCompressor ' + qualityTag + ' code:' + data.code + '--error message:' + data.message);
          }
        }).catch((err: Error) => {
        hilog.info(0x0000, TAG, 'videoCompressor ' + qualityTag + ' get error message' + err.message);
      })
    }
    
3.  压缩完成后，视频保存至沙箱。
    
    try {
      let parentDir = this.uiContext.getHostContext()!.filesDir;
      let fileName: string = Date.now() + '-' + this.quantity + '.mp4';
      this.fileDir = parentDir + '/' + fileName;
      let file = await fileIo.open(this.fileDir, fileIo.OpenMode.READ\_WRITE | fileIo.OpenMode.CREATE);
      // 写入文件
      await fileIo.write(file.fd, this.buffer);
      this.compressedSize = (await fileIo.stat(file.fd)).size;
      // 关闭文件
      await fileIo.close(file.fd);
      this.uiContext.showAlertDialog({ message: '保存成功!' });
    } catch (error) {
      hilog.error(0x0000, TAG, 'error is' + JSON.stringify(error));
      this.uiContext.showAlertDialog({ message: '保存失败!' });
    }
    

**方案二**

使用AVCodec Kit视频编码功能，将未经压缩的视频数据传输至视频编码器进行编码处理，通过设定所需的编码格式、比特率、帧率等参数，并对编码输出进行控制，从而实现视频文件的压缩目的，编码器参数配置范围请参考：[获取支持的编解码能力](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/obtain-supported-codecs)。

int32\_t VideoEncoder::Configure(const SampleInfo &sampleInfo) {
    OH\_AVFormat \*format = OH\_AVFormat\_Create();
    CHECK\_AND\_RETURN\_RET\_LOG(format != nullptr, AVCODEC\_SAMPLE\_ERR\_ERROR, "AVFormat create failed");

    OH\_AVFormat\_SetIntValue(format, OH\_MD\_KEY\_WIDTH, sampleInfo.videoWidth);
    OH\_AVFormat\_SetIntValue(format, OH\_MD\_KEY\_HEIGHT, sampleInfo.videoHeight);
    OH\_AVFormat\_SetDoubleValue(format, OH\_MD\_KEY\_FRAME\_RATE, sampleInfo.frameRate);
    OH\_AVFormat\_SetIntValue(format, OH\_MD\_KEY\_PIXEL\_FORMAT, sampleInfo.pixelFormat);
    OH\_AVFormat\_SetIntValue(format, OH\_MD\_KEY\_VIDEO\_ENCODE\_BITRATE\_MODE, sampleInfo.bitrateMode);
    OH\_AVFormat\_SetLongValue(format, OH\_MD\_KEY\_BITRATE, sampleInfo.bitrate);
    OH\_AVFormat\_SetIntValue(format, OH\_MD\_KEY\_PROFILE, sampleInfo.hevcProfile);
    // Setting HDRVivid-related parameters
    if (sampleInfo.isHDRVivid) {
        OH\_AVFormat\_SetIntValue(format, OH\_MD\_KEY\_I\_FRAME\_INTERVAL, sampleInfo.iFrameInterval);
        OH\_AVFormat\_SetIntValue(format, OH\_MD\_KEY\_RANGE\_FLAG, sampleInfo.rangFlag);
        OH\_AVFormat\_SetIntValue(format, OH\_MD\_KEY\_COLOR\_PRIMARIES, sampleInfo.primary);
        OH\_AVFormat\_SetIntValue(format, OH\_MD\_KEY\_TRANSFER\_CHARACTERISTICS, sampleInfo.transfer);
        OH\_AVFormat\_SetIntValue(format, OH\_MD\_KEY\_MATRIX\_COEFFICIENTS, sampleInfo.matrix);
    }
    // ...

    // Setting the Encoder
    int ret = OH\_VideoEncoder\_Configure(encoder\_, format);
    OH\_AVFormat\_Destroy(format);
    format = nullptr;
    CHECK\_AND\_RETURN\_RET\_LOG(ret == AV\_ERR\_OK, AVCODEC\_SAMPLE\_ERR\_ERROR, "Config failed, ret: %{public}d", ret);
    return AVCODEC\_SAMPLE\_ERR\_OK;
}

示例代码参考：[基于AVCodec能力的视频编解码](https://gitcode.com/HarmonyOS_Samples/AVCodecVideo)。

**方案三**

如果是视频拍摄过程中的场景，可通过AVRecorder设立录像输出流并调整自定义录像参数，从而调控视频文件的比特率与分辨率等特性，进而实现对视频文件大小的有效管理。

let videoProfilesArray: Array<camera.VideoProfile> = cameraOutputCap.videoProfiles;
if (!videoProfilesArray) {
  Logger.error(TAG, 'createOutput videoProfilesArray === null || undefined');
}
// ...

let aVRecorderProfile: media.AVRecorderProfile = {
  // ...
  fileFormat: media.ContainerFormatType.CFT\_MPEG\_4,
  videoBitrate: 2000000,
  videoCodec: media.CodecMimeType.VIDEO\_AVC,
  videoFrameWidth: videoSize.width,
  videoFrameHeight: videoSize.height,
  videoFrameRate: 30
};

let aVRecorderConfig: media.AVRecorderConfig = {
  // ...
  videoSourceType: media.VideoSourceType.VIDEO\_SOURCE\_TYPE\_SURFACE\_YUV,
  profile: aVRecorderProfile,
  url: this.url,
  rotation: 90,
  // ...
};

示例代码参考：[基于CameraKit通过AVRecorder录像](https://gitcode.com/harmonyos_samples/camera-kit-avrecorder)。

**方案四**

ArkTS提供了AVTranscoder接口用于视频转码。视频的码率和分辨率是影响视频画质和文件大小的关键因素，通过AVTranscoder进行视频转码，可以实现节省视频存储空间和提高视频文件的设备兼容性。

AVTranscoder使用方式及常见问题请参考文档：[视频转码](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/using-avtranscoder-for-transcodering)。