在这里插入图片描述

每日一句正能量

真正值得的人和事,不会成为你的绊脚石,而是会化作你前行的动力。
滋养还是消耗? 健康的关系与热爱的事业,给你的感觉应该是“充电”而非“断电”。如果一段关系总让你疲惫、内耗,或许就该重新审视其价值了。


一、引言:为什么媒体格式转换是刚需?

在 HarmonyOS 生态中,媒体文件格式转换是贯穿应用开发全周期的核心能力。用户拍摄的 4K H.265 视频需要在社交平台上分享,但平台仅支持 H.264;专业录音设备导出的 FLAC 无损音频需要转换为 MP3 以便在车载系统播放;相册中的 HEIF 照片需要转为 JPEG 才能被老旧设备识别;视频编辑应用需要将多段素材统一封装为 MP4 进行合成输出。

这些场景的共同诉求是:在保证质量的前提下,将媒体文件从源格式转换为目标格式,同时支持分辨率缩放、码率调整、编码升级/降级等精细化控制。HarmonyOS 6(API 23)提供了完整的媒体格式转换能力体系,本文将从架构设计到代码实战,全面解析这一工业化方案。


二、整体架构:四层转换引擎体系

在这里插入图片描述

图1:HarmonyOS 媒体格式转换整体架构

HarmonyOS 的媒体格式转换体系分为四层:

  • 输入层:支持视频(MP4/MOV/MKV/MPEG-TS/AVI)、音频(MP3/AAC/FLAC/WAV/OGG)、图片(JPEG/PNG/HEIF/WEBP/BMP/GIF)以及原始码流(YUV/PCM/RGBA)四类数据源。
  • 转换引擎层:四大核心引擎协同工作——AVTranscoder 负责音视频封装格式转换与参数调整,AVCodec 负责编解码器级别的转换(如 H.264↔H.265),ImagePacker 负责图片格式转换与压缩,MediaMuxer 负责音视频复用与多轨道封装。
  • 输出层:生成标准兼容的目标文件,包括 MP4(H.264+AAC,通用兼容)、M4A(AAC,高音质音频)、MP3(最大兼容音频)、JPEG/PNG/HEIF(Web 优化图片)。
  • 硬件加速层:底层调用 MediaCodec 硬件编解码器,相比纯软件实现性能提升 5-10 倍。

三、音视频转码:AVTranscoder 深度实战

3.1 转码流程与状态机

在这里插入图片描述

图2:AVTranscoder 音视频转码完整流程

标准转码流程遵循八步状态机:创建转码器 → 设置输入源 → 配置转码参数 → 准备转码 → 开始转码 → 进度回调 → 完成/取消 → 释放资源。整个流程为纯异步设计,通过事件回调机制与调用方通信,避免阻塞 UI 线程。

3.2 完整代码:音视频转码器
import { media } from '@kit.MediaKit';
import { fileIo as fs } from '@kit.CoreFileKit';
import { BusinessError } from '@kit.BasicServicesKit';

/**
 * 转码配置选项
 */
interface TranscodeOptions {
  outputPath: string;                    // 输出文件路径
  fileFormat?: media.ContainerFormatType; // 输出封装格式
  videoCodecType?: media.CodecMimeType;   // 视频编码类型
  audioCodecType?: media.CodecMimeType;   // 音频编码类型
  videoBitrate?: number;                  // 视频码率(bps)
  audioBitrate?: number;                  // 音频码率(bps)
  width?: number;                         // 输出宽度
  height?: number;                        // 输出高度
  frameRate?: number;                     // 输出帧率
}

/**
 * 音视频转码引擎
 * 支持:封装格式转换、分辨率缩放、码率调整、编码升级/降级
 */
class AVTranscodeEngine {
  private transcoder: media.AVTranscoder | null = null;
  private isTranscoding: boolean = false;

  /**
   * 执行音视频转码
   * @param inputPath 输入文件路径
   * @param options 转码配置
   * @param callbacks 进度与状态回调
   */
  async transcode(
    inputPath: string,
    options: TranscodeOptions,
    callbacks: {
      onProgress?: (percent: number) => void;
      onComplete?: () => void;
      onError?: (code: number, message: string) => void;
    }
  ): Promise<void> {
    try {
      // 第一步:创建转码器实例
      this.transcoder = await media.createAVTranscoder();

      // 第二步:设置输入源(文件描述符方式)
      const inputFile = fs.openSync(inputPath, fs.OpenMode.READ_ONLY);
      const stat = fs.statSync(inputPath);
      this.transcoder.fdSrc = {
        fd: inputFile.fd,
        offset: 0,
        length: stat.size,
      };

      // 第三步:配置转码参数
      const config: media.AVTranscoderConfig = {
        fileFormat: options.fileFormat ?? media.ContainerFormatType.CFT_MPEG_4,
        videoCodecType: options.videoCodecType ?? media.CodecMimeType.VIDEO_AVC,
        audioCodecType: options.audioCodecType ?? media.CodecMimeType.AUDIO_AAC,
      };

      // 可选:视频码率(不设置则自动根据输入计算)
      if (options.videoBitrate) {
        config.videoBitrate = options.videoBitrate;
      }

      // 可选:音频码率
      if (options.audioBitrate) {
        config.audioBitrate = options.audioBitrate;
      }

      // 可选:输出分辨率(实现视频缩放)
      if (options.width && options.height) {
        config.width = options.width;
        config.height = options.height;
      }

      // 可选:输出帧率
      if (options.frameRate) {
        config.frameRate = options.frameRate;
      }

      this.transcoder.config = config;

      // 第四步:准备转码(预加载资源、初始化编解码器)
      await this.transcoder.prepare();

      // 第五步:注册事件监听
      this.transcoder.on('progressUpdate', (progress: number) => {
        callbacks.onProgress?.(progress);
      });

      this.transcoder.on('complete', () => {
        this.isTranscoding = false;
        callbacks.onComplete?.();
        this.release(inputFile);
      });

      this.transcoder.on('error', (error: BusinessError) => {
        this.isTranscoding = false;
        callbacks.onError?.(error.code, error.message);
        this.release(inputFile);
      });

      // 第六步:开始转码(异步执行)
      this.isTranscoding = true;
      await this.transcoder.transcode();

    } catch (err) {
      this.isTranscoding = false;
      const error = err as BusinessError;
      callbacks.onError?.(error.code, error.message);
      throw error;
    }
  }

  /**
   * 取消正在进行的转码任务
   */
  async cancel(): Promise<void> {
    if (this.isTranscoding && this.transcoder) {
      await this.transcoder.stop();
      this.isTranscoding = false;
      console.info('[TranscodeEngine] 转码已取消');
    }
  }

  /**
   * 释放资源
   */
  private release(inputFile: fs.File): void {
    this.transcoder?.release();
    this.transcoder = null;
    fs.closeSync(inputFile);
    console.info('[TranscodeEngine] 资源已释放');
  }
}

// ========== 使用示例 ==========

/**
 * 示例1:视频压缩(4K→1080p,降低码率以便分享)
 */
async function compressVideoForShare(inputPath: string, outputPath: string): Promise<void> {
  const engine = new AVTranscodeEngine();
  await engine.transcode(inputPath, {
    outputPath,
    fileFormat: media.ContainerFormatType.CFT_MPEG_4,
    videoCodecType: media.CodecMimeType.VIDEO_AVC,
    audioCodecType: media.CodecMimeType.AUDIO_AAC,
    width: 1920,
    height: 1080,
    videoBitrate: 4 * 1024 * 1024,  // 4Mbps
    audioBitrate: 128 * 1024,        // 128Kbps
  }, {
    onProgress: (p) => console.log(`压缩进度: ${p}%`),
    onComplete: () => console.log('压缩完成'),
    onError: (c, m) => console.error(`压缩失败: ${c} - ${m}`),
  });
}

/**
 * 示例2:编码升级(H.264→H.265,节省存储空间)
 */
async function upgradeToH265(inputPath: string, outputPath: string): Promise<void> {
  const engine = new AVTranscodeEngine();
  await engine.transcode(inputPath, {
    outputPath,
    fileFormat: media.ContainerFormatType.CFT_MPEG_4,
    videoCodecType: media.CodecMimeType.VIDEO_HEVC,  // H.265
    audioCodecType: media.CodecMimeType.AUDIO_AAC,
    videoBitrate: 2 * 1024 * 1024,  // H.265 相同质量下码率可降低 50%
  }, {
    onProgress: (p) => console.log(`转码进度: ${p}%`),
    onComplete: () => console.log('H.265 转码完成'),
    onError: (c, m) => console.error(`转码失败: ${c} - ${m}`),
  });
}

/**
 * 示例3:封装格式转换(MKV→MP4,提升兼容性)
 */
async function convertMkvToMp4(inputPath: string, outputPath: string): Promise<void> {
  const engine = new AVTranscodeEngine();
  await engine.transcode(inputPath, {
    outputPath,
    fileFormat: media.ContainerFormatType.CFT_MPEG_4,
    videoCodecType: media.CodecMimeType.VIDEO_AVC,
    audioCodecType: media.CodecMimeType.AUDIO_AAC,
  }, {
    onProgress: (p) => console.log(`转换进度: ${p}%`),
    onComplete: () => console.log('MKV→MP4 转换完成'),
    onError: (c, m) => console.error(`转换失败: ${c} - ${m}`),
  });
}
3.3 转码参数速查表
参数类型默认值说明
fileFormatContainerFormatTypeMPEG_4输出封装格式:MPEG_4、M4A、MPEG_2_TS
videoCodecTypeCodecMimeTypeVIDEO_AVC视频编码:H.264(AVC)、H.265(HEVC)
audioCodecTypeCodecMimeTypeAUDIO_AAC音频编码:AAC、MP3
videoBitratenumber自适应视频码率(bps),不设置则自动计算
audioBitratenumber自适应音频码率(bps),推荐 128K~320K
width/heightnumber原始尺寸输出分辨率,可实现缩放
frameRatenumber原始帧率输出帧率,可实现降帧

四、图片格式转换:ImagePacker 实战

在这里插入图片描述

图3:ImagePacker 图片格式转换与压缩流程

图片格式转换是移动应用中最高频的媒体处理场景。HarmonyOS 的 ImagePacker 支持从 PixelMap、文件路径或 ArrayBuffer 输入,输出为 JPEG、PNG、WEBP 或 HEIF 格式,同时支持质量压缩、尺寸缩放等精细控制。

4.1 完整代码:图片转换与压缩引擎
import { image } from '@kit.ImageKit';
import { fileIo as fs } from '@kit.CoreFileKit';

/**
 * 图片压缩策略
 */
enum CompressStrategy {
  QUALITY_FIRST = 'quality',    // 质量优先(95~100)
  BALANCED = 'balanced',        // 平衡模式(75~85)
  SIZE_FIRST = 'size',          // 体积优先(50~65)
  THUMBNAIL = 'thumbnail',      // 缩略图模式(尺寸缩放)
}

/**
 * 图片转换选项
 */
interface ImageConvertOptions {
  format: 'image/jpeg' | 'image/png' | 'image/webp' | 'image/heif';
  quality?: number;              // 压缩质量 1~100
  size?: { width: number; height: number };  // 目标尺寸
  bufferSize?: number;           // 输出缓冲区大小
}

class ImageConvertEngine {

  /**
   * 图片格式转换(文件→文件)
   * @param inputPath 输入图片路径
   * @param outputPath 输出图片路径
   * @param options 转换选项
   */
  async convertFile(
    inputPath: string,
    outputPath: string,
    options: ImageConvertOptions
  ): Promise<void> {
    // 创建图像源
    const imageSource = image.createImageSource(inputPath);

    // 解码为 PixelMap(如果需要缩放,可在此指定目标尺寸)
    const decodeOpts: image.DecodingOptions = {};
    if (options.size) {
      decodeOpts.desiredSize = options.size;
    }
    const pixelMap = await imageSource.createPixelMap(decodeOpts);

    // 创建 Packer
    const packer = image.createImagePacker();

    // 配置打包选项
    const packOpts: image.PackingOption = {
      format: options.format,
      quality: options.quality ?? 85,
      bufferSize: options.bufferSize ?? 10 * 1024 * 1024, // 默认 10MB 缓冲
    };

    // 打包到文件
    await packer.packingToFile(pixelMap, outputPath, packOpts);

    // 释放资源
    packer.release();
    pixelMap.release();
    imageSource.release();

    console.info(`[ImageConvert] 转换完成: ${outputPath}`);
  }

  /**
   * 图片格式转换(文件→内存 Buffer)
   * 适合网络上传前压缩场景
   */
  async convertToBuffer(
    inputPath: string,
    options: ImageConvertOptions
  ): Promise<ArrayBuffer> {
    const imageSource = image.createImageSource(inputPath);
    const pixelMap = await imageSource.createPixelMap(
      options.size ? { desiredSize: options.size } : {}
    );

    const packer = image.createImagePacker();
    const packOpts: image.PackingOption = {
      format: options.format,
      quality: options.quality ?? 85,
    };

    const buffer = await packer.packing(pixelMap, packOpts);

    packer.release();
    pixelMap.release();
    imageSource.release();

    return buffer;
  }

  /**
   * 智能压缩:根据目标文件大小自动计算最佳 quality
   * @param inputPath 输入图片路径
   * @param outputPath 输出图片路径
   * @param maxSizeBytes 目标最大文件大小(字节)
   * @param format 输出格式
   */
  async smartCompress(
    inputPath: string,
    outputPath: string,
    maxSizeBytes: number,
    format: 'image/jpeg' | 'image/webp' = 'image/jpeg'
  ): Promise<void> {
    let minQuality = 1;
    let maxQuality = 100;
    let bestQuality = 85;

    // 二分查找最佳 quality
    while (minQuality <= maxQuality) {
      const midQuality = Math.floor((minQuality + maxQuality) / 2);
      const buffer = await this.convertToBuffer(inputPath, {
        format,
        quality: midQuality,
      });

      if (buffer.byteLength <= maxSizeBytes) {
        bestQuality = midQuality;
        minQuality = midQuality + 1; // 尝试更高质量
      } else {
        maxQuality = midQuality - 1; // 需要更低质量
      }
    }

    // 使用最佳 quality 输出最终文件
    await this.convertFile(inputPath, outputPath, {
      format,
      quality: bestQuality,
    });

    console.info(`[ImageConvert] 智能压缩完成: quality=${bestQuality}, 目标≤${maxSizeBytes}B`);
  }

  /**
   * 批量图片压缩(相册场景)
   */
  async batchCompress(
    filePaths: string[],
    outputDir: string,
    strategy: CompressStrategy
  ): Promise<string[]> {
    const qualityMap: Record<CompressStrategy, number> = {
      [CompressStrategy.QUALITY_FIRST]: 95,
      [CompressStrategy.BALANCED]: 80,
      [CompressStrategy.SIZE_FIRST]: 60,
      [CompressStrategy.THUMBNAIL]: 75,
    };

    const sizeMap: Record<CompressStrategy, { width: number; height: number } | undefined> = {
      [CompressStrategy.QUALITY_FIRST]: undefined,
      [CompressStrategy.BALANCED]: undefined,
      [CompressStrategy.SIZE_FIRST]: undefined,
      [CompressStrategy.THUMBNAIL]: { width: 512, height: 512 },
    };

    const results: string[] = [];
    for (const filePath of filePaths) {
      const fileName = filePath.substring(filePath.lastIndexOf('/') + 1);
      const outputPath = `${outputDir}/compressed_${fileName}`;

      await this.convertFile(filePath, outputPath, {
        format: 'image/jpeg',
        quality: qualityMap[strategy],
        size: sizeMap[strategy],
      });

      results.push(outputPath);
    }

    return results;
  }
}

// ========== 使用示例 ==========

/**
 * 示例1:HEIF→JPEG 转换(兼容老旧设备)
 */
async function convertHeifToJpeg(inputPath: string, outputPath: string): Promise<void> {
  const engine = new ImageConvertEngine();
  await engine.convertFile(inputPath, outputPath, {
    format: 'image/jpeg',
    quality: 90,
  });
}

/**
 * 示例2:图片压缩至 200KB 以内(社交平台上传)
 */
async function compressForSocial(inputPath: string, outputPath: string): Promise<void> {
  const engine = new ImageConvertEngine();
  await engine.smartCompress(inputPath, outputPath, 200 * 1024, 'image/jpeg');
}

/**
 * 示例3:生成缩略图网格
 */
async function generateThumbnails(
  photoPaths: string[],
  outputDir: string
): Promise<string[]> {
  const engine = new ImageConvertEngine();
  return await engine.batchCompress(photoPaths, outputDir, CompressStrategy.THUMBNAIL);
}

五、音频提取与音视频复用

5.1 从视频中提取音频(MP4→M4A/MP3)
import { media } from '@kit.MediaKit';

/**
 * 从视频文件中提取音频轨道
 * @param videoPath 输入视频路径
 * @param outputPath 输出音频路径
 * @param format 输出格式:M4A 或 MP3
 */
async function extractAudio(
  videoPath: string,
  outputPath: string,
  format: 'm4a' | 'mp3' = 'm4a'
): Promise<void> {
  const engine = new AVTranscodeEngine();

  const fileFormat = format === 'm4a'
    ? media.ContainerFormatType.CFT_MPEG_4
    : media.ContainerFormatType.CFT_MPEG_4; // MP3 需特殊处理

  const audioCodec = format === 'm4a'
    ? media.CodecMimeType.AUDIO_AAC
    : media.CodecMimeType.AUDIO_MP3;

  await engine.transcode(videoPath, {
    outputPath,
    fileFormat,
    audioCodecType: audioCodec,
    // 不设置 videoCodecType,表示仅提取音频
  }, {
    onProgress: (p) => console.log(`音频提取进度: ${p}%`),
    onComplete: () => console.log('音频提取完成'),
    onError: (c, m) => console.error(`提取失败: ${c} - ${m}`),
  });
}
5.2 音视频复用:合并多轨道
import { media } from '@kit.MediaKit';

/**
 * 音视频复用器:将独立的视频流和音频流合并为单一文件
 */
class MediaMuxerEngine {

  /**
   * 合并视频文件与音频文件
   * @param videoPath 视频文件路径(含画面)
   * @param audioPath 音频文件路径(含声音)
   * @param outputPath 输出文件路径
   */
  async muxVideoAudio(
    videoPath: string,
    audioPath: string,
    outputPath: string
  ): Promise<void> {
    // 创建复用器
    const muxer = await media.createAVMuxer();

    // 设置输出文件
    const outputFile = fs.openSync(outputPath, fs.OpenMode.WRITE_ONLY | fs.OpenMode.CREATE);
    muxer.fd = outputFile.fd;

    // 添加视频轨道
    const videoFormat: media.AVMuxerFormat = {
      mimeType: media.CodecMimeType.VIDEO_AVC,
      width: 1920,
      height: 1080,
      frameRate: 30,
    };
    const videoTrackIndex = muxer.addTrack(videoFormat);

    // 添加音频轨道
    const audioFormat: media.AVMuxerFormat = {
      mimeType: media.CodecMimeType.AUDIO_AAC,
      sampleRate: 44100,
      channelCount: 2,
    };
    const audioTrackIndex = muxer.addTrack(audioFormat);

    // 开始复用
    muxer.start();

    // 读取视频样本并写入
    // ...(实际实现需通过 AVCodec 解码后重新编码为目标格式)

    // 结束复用
    muxer.stop();
    muxer.release();
    fs.closeSync(outputFile);

    console.info('[MediaMuxer] 复用完成');
  }
}

六、格式支持矩阵与性能优化

在这里插入图片描述

图4:HarmonyOS 6 媒体格式支持矩阵与转码性能对比

6.1 格式兼容性速查
输入格式→ MP4→ M4A→ MP3→ MPEG-TS
MP4 (H.264+AAC)✓ 完整部分(音频提取)—✓ 完整
MOV (H.264+AAC)✓ 完整部分—✓ 完整
MKV (H.265+AC3)✓ 完整部分部分—
MPEG-TS✓ 完整部分—✓ 完整
MP3——✓ 完整—
FLAC—✓ 完整✓ 完整—
WAV—✓ 完整✓ 完整—
6.2 性能优化策略
优化手段具体措施效果
硬件加速启用 MediaCodec 硬件编解码速度提升 5-10 倍
分辨率预缩放在解码阶段直接输出目标尺寸减少 50% 计算量
音频直通源与目标音频编码相同时直接复制速度提升 20 倍
后台线程转码任务放入 Worker 线程避免 UI 卡顿
批量队列串行处理避免多任务争抢编解码器提升整体吞吐
缓存复用复用编解码器实例(同参数场景)减少初始化开销
6.3 权限声明
// module.json5
"requestPermissions": [
  {
    "name": "ohos.permission.READ_MEDIA",
    "reason": "$string:read_media_reason"
  },
  {
    "name": "ohos.permission.WRITE_MEDIA",
    "reason": "$string:write_media_reason"
  }
]

七、总结与展望

本文基于 HarmonyOS 6(API 23),系统梳理了媒体格式转换的全链路技术方案。核心要点总结如下:

  1. AVTranscoder 是音视频转码的核心引擎,支持封装格式转换、分辨率缩放、码率调整、编码升级/降级四大能力,通过异步事件回调机制实现非阻塞转码;
  2. ImagePacker 提供图片格式转换与压缩能力,支持 JPEG/PNG/WEBP/HEIF 四种输出格式,通过智能二分查找算法实现目标文件大小的精准控制;
  3. 音频提取可通过 AVTranscoder 不设置视频编码参数实现,将视频中的音频轨道单独导出为 M4A 或 MP3;
  4. MediaMuxer 支持音视频多轨道复用,适用于视频编辑、字幕合并等高级场景;
  5. 性能优化方面,硬件加速、分辨率预缩放、音频直通等手段可将转码效率提升至实时倍速的 8-45 倍。

未来可探索的方向包括:结合 AI 超分技术实现低分辨率视频的智能画质增强转码、利用分布式能力将转码任务卸载到算力更强的设备(如 PC/智慧屏)、以及对接云端转码服务实现超大文件的云端异步处理与本地结果回传。


转载自:https://blog.csdn.net/u014727709/article/details/163645696
欢迎 👍点赞✍评论⭐收藏,欢迎指正

Logo

邀请您加入社区

更多推荐