[鸿蒙从零到一] 鸿蒙媒体能力实战:图片、音频与视频处理

简介: 本文系统讲解HarmonyOS媒体开发实战:涵盖图片选择/压缩/显示、音频播放/录制、视频播放/拍摄,详解Picker、AVPlayer、AVRecorder等核心API用法,并提供权限配置、性能优化及完整播放器案例,助开发者快速构建专业媒体应用

前言

在移动应用开发中,媒体能力是核心功能之一。HarmonyOS 提供了完整的媒体框架,覆盖图片选择、音频播放、视频录制等场景。本文将从实战角度出发,带你掌握 HarmonyOS 的媒体能力。


一、图片选择与处理

1.1 使用 Picker 选择图片

HarmonyOS 提供了统一的 Picker API,支持从相册选择图片:

import {
    picker } from '@kit.CoreFileKit';
import {
    BusinessError } from '@kit.BasicServicesKit';

async function pickImage(): Promise<string> {
   
  try {
   
    const photoSelectOptions = new picker.PhotoSelectOptions();
    photoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
    photoSelectOptions.maxSelectNumber = 1;

    const photoViewPicker = new picker.PhotoViewPicker();
    const result = await photoViewPicker.select(photoSelectOptions);

    if (result && result.photoUris.length > 0) {
   
      return result.photoUris[0];
    }
    return '';
  } catch (err) {
   
    console.error('选择图片失败:', JSON.stringify(err));
    return '';
  }
}

1.2 图片解码与显示

获取图片 URI 后,使用 Image 组件显示:

import {
    image } from '@kit.ImageKit';

@Entry
@Component
struct ImageDemo {
   
  @State imageUri: string = '';

  build() {
   
    Column() {
   
      Button('选择图片')
        .onClick(async () => {
   
          this.imageUri = await pickImage();
        })

      if (this.imageUri) {
   
        Image(this.imageUri)
          .width('100%')
          .height(300)
          .objectFit(ImageFit.Contain)
      }
    }
    .padding(20)
  }
}

1.3 图片压缩与保存

处理大图时需要压缩:

import {
    image } from '@kit.ImageKit';
import {
    fileIo } from '@kit.CoreFileKit';

async function compressImage(sourceUri: string, targetPath: string): Promise<void> {
   
  try {
   
    const imageSource = image.createImageSource(sourceUri);
    const imageInfo = await imageSource.getImageInfo();
    console.info(`原始尺寸: ${
     imageInfo.size.width}x${
     imageInfo.size.height}`);

    const decodingOptions: image.DecodingOptions = {
   
      desiredSize: {
    width: 800, height: 800 },
      desiredPixelFormat: image.PixelMapFormat.RGBA_8888
    };

    const pixelMap = await imageSource.createPixelMap(decodingOptions);
    const imagePacker = image.createImagePacker();
    const packOpts: image.PackingOption = {
   
      format: 'image/jpeg',
      quality: 80
    };

    const buffer = await imagePacker.packing(pixelMap, packOpts);
    const file = fileIo.openSync(targetPath, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY);
    fileIo.writeSync(file.fd, buffer);
    fileIo.closeSync(file);

    console.info('图片压缩完成');
  } catch (err) {
   
    console.error('压缩失败:', JSON.stringify(err));
  }
}

二、音频播放与录制

2.1 音频播放

使用 AVPlayer 播放音频:

import {
    media } from '@kit.MediaKit';

@Component
export struct AudioPlayer {
   
  private avPlayer?: media.AVPlayer;
  @State isPlaying: boolean = false;
  @State currentTime: number = 0;
  @State duration: number = 0;

  async initPlayer(audioUri: string) {
   
    try {
   
      this.avPlayer = await media.createAVPlayer();

      this.avPlayer.on('stateChange', (state: string) => {
   
        console.info(`播放器状态: ${
     state}`);
      });

      this.avPlayer.on('timeUpdate', (time: number) => {
   
        this.currentTime = time;
      });

      this.avPlayer.on('durationUpdate', (duration: number) => {
   
        this.duration = duration;
      });

      this.avPlayer.url = audioUri;
    } catch (err) {
   
      console.error('初始化播放器失败:', JSON.stringify(err));
    }
  }

  async play() {
   
    await this.avPlayer?.play();
    this.isPlaying = true;
  }

  async pause() {
   
    await this.avPlayer?.pause();
    this.isPlaying = false;
  }

  build() {
   
    Column() {
   
      Text(`${
     this.formatTime(this.currentTime)} / ${
     this.formatTime(this.duration)}`)

      Row() {
   
        Button(this.isPlaying ? '暂停' : '播放')
          .onClick(() => {
   
            if (this.isPlaying) {
   
              this.pause();
            } else {
   
              this.play();
            }
          })
      }
    }
  }

  formatTime(ms: number): string {
   
    const seconds = Math.floor(ms / 1000);
    const min = Math.floor(seconds / 60);
    const sec = seconds % 60;
    return `${
     min}:${
     sec.toString().padStart(2, '0')}`;
  }

  aboutToDisappear() {
   
    this.avPlayer?.release();
  }
}

2.2 音频录制

使用 AVRecorder 录制音频:

import {
    media } from '@kit.MediaKit';
import {
    fileIo } from '@kit.CoreFileKit';

@Component
export struct AudioRecorder {
   
  private avRecorder?: media.AVRecorder;
  @State isRecording: boolean = false;
  private outputPath: string = '';

  async initRecorder() {
   
    try {
   
      this.avRecorder = await media.createAVRecorder();

      this.avRecorder.on('stateChange', (state: string) => {
   
        console.info(`录制器状态: ${
     state}`);
      });

      const context = getContext(this);
      this.outputPath = `${
     context.cacheDir}/audio_${
     Date.now()}.m4a`;

      const config: media.AVRecorderConfig = {
   
        audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC,
        profile: {
   
          audioBitrate: 128000,
          audioChannels: 2,
          audioCodec: media.CodecMimeType.AUDIO_AAC,
          audioSampleRate: 48000,
          fileFormat: media.ContainerFormatType.CFT_MPEG_4A
        },
        url: `fd://${
     fileIo.openSync(this.outputPath, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY).fd}`
      };

      await this.avRecorder.prepare(config);
    } catch (err) {
   
      console.error('初始化录制器失败:', JSON.stringify(err));
    }
  }

  async startRecord() {
   
    await this.avRecorder?.start();
    this.isRecording = true;
  }

  async stopRecord() {
   
    await this.avRecorder?.stop();
    this.isRecording = false;
    console.info('录音已保存:', this.outputPath);
  }

  build() {
   
    Column() {
   
      Button(this.isRecording ? '停止录音' : '开始录音')
        .onClick(() => {
   
          if (this.isRecording) {
   
            this.stopRecord();
          } else {
   
            this.startRecord();
          }
        })
    }
  }

  aboutToDisappear() {
   
    this.avRecorder?.release();
  }
}

三、视频播放与录制

3.1 视频播放

使用 AVPlayer + XComponent 播放视频:

import {
    media } from '@kit.MediaKit';

@Entry
@Component
struct VideoPlayer {
   
  private avPlayer?: media.AVPlayer;
  private surfaceId: string = '';
  @State isPlaying: boolean = false;

  async initPlayer(videoUri: string) {
   
    try {
   
      this.avPlayer = await media.createAVPlayer();

      this.avPlayer.on('stateChange', (state: string) => {
   
        console.info(`播放器状态: ${
     state}`);
      });

      this.avPlayer.url = videoUri;
      this.avPlayer.surfaceId = this.surfaceId;
    } catch (err) {
   
      console.error('初始化播放器失败:', JSON.stringify(err));
    }
  }

  build() {
   
    Column() {
   
      XComponent({
   
        id: 'video_surface',
        type: XComponentType.SURFACE,
        controller: new XComponentController()
      })
        .onLoad((context?: object) => {
   
          this.surfaceId = (context as {
    surfaceId: string }).surfaceId;
          this.initPlayer('file://...');
        })
        .width('100%')
        .height(300)

      Button(this.isPlaying ? '暂停' : '播放')
        .onClick(async () => {
   
          if (this.isPlaying) {
   
            await this.avPlayer?.pause();
          } else {
   
            await this.avPlayer?.play();
          }
          this.isPlaying = !this.isPlaying;
        })
    }
  }
}

3.2 视频录制

使用相机 API 录制视频:

import {
    camera } from '@kit.CameraKit';

@Component
export struct VideoRecorder {
   
  private cameraManager?: camera.CameraManager;
  private videoOutput?: camera.VideoOutput;
  @State isRecording: boolean = false;

  async initCamera() {
   
    try {
   
      this.cameraManager = camera.getCameraManager(getContext(this));
      const cameras = this.cameraManager.getSupportedCameras();

      if (cameras.length === 0) {
   
        console.error('没有可用相机');
        return;
      }

      const cameraInput = this.cameraManager.createCameraInput(cameras[0]);
      await cameraInput.open();

      const profile: camera.VideoProfile = {
   
        format: camera.CameraFormat.CAMERA_FORMAT_YUV_420_SP,
        size: {
    width: 1920, height: 1080 },
        frameRateRange: {
    min: 30, max: 30 }
      };

      this.videoOutput = this.cameraManager.createVideoOutput(profile, 'fd://...');

      const session = this.cameraManager.createSession(camera.SceneMode.NORMAL_VIDEO);
      session.beginConfig();
      session.addInput(cameraInput);
      session.addOutput(this.videoOutput);
      await session.commitConfig();
      await session.start();

      console.info('相机初始化完成');
    } catch (err) {
   
      console.error('初始化相机失败:', JSON.stringify(err));
    }
  }

  async startRecord() {
   
    await this.videoOutput?.start();
    this.isRecording = true;
  }

  async stopRecord() {
   
    await this.videoOutput?.stop();
    this.isRecording = false;
  }

  build() {
   
    Column() {
   
      Button(this.isRecording ? '停止录制' : '开始录制')
        .onClick(() => {
   
          if (this.isRecording) {
   
            this.stopRecord();
          } else {
   
            this.startRecord();
          }
        })
    }
  }
}

四、权限申请

媒体功能需要申请相应权限:

4.1 module.json5 配置

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.READ_IMAGEVIDEO",
        "reason": "$string:permission_read_media",
        "usedScene": { "when": "inuse" }
      },
      {
        "name": "ohos.permission.WRITE_IMAGEVIDEO",
        "reason": "$string:permission_write_media",
        "usedScene": { "when": "inuse" }
      },
      {
        "name": "ohos.permission.MICROPHONE",
        "reason": "$string:permission_microphone",
        "usedScene": { "when": "inuse" }
      },
      {
        "name": "ohos.permission.CAMERA",
        "reason": "$string:permission_camera",
        "usedScene": { "when": "inuse" }
      }
    ]
  }
}

4.2 运行时申请

import {
    abilityAccessCtrl, Permissions } from '@kit.AbilityKit';

async function requestPermissions(): Promise<boolean> {
   
  const permissions: Permissions[] = [
    'ohos.permission.READ_IMAGEVIDEO',
    'ohos.permission.MICROPHONE',
    'ohos.permission.CAMERA'
  ];

  const context = getContext(this);
  const atManager = abilityAccessCtrl.createAtManager();

  try {
   
    const result = await atManager.requestPermissionsFromUser(context, permissions);
    return result.authResults.every(r => r === 0);
  } catch (err) {
   
    console.error('权限申请失败:', JSON.stringify(err));
    return false;
  }
}

五、实战案例:完整的媒体播放器

import {
    media } from '@kit.MediaKit';
import {
    picker } from '@kit.CoreFileKit';

@Entry
@Component
struct MediaPlayerDemo {
   
  private avPlayer?: media.AVPlayer;
  @State mediaUri: string = '';
  @State isPlaying: boolean = false;
  @State currentTime: number = 0;
  @State duration: number = 0;

  async selectMedia() {
   
    try {
   
      const options = new picker.PhotoSelectOptions();
      options.MIMEType = picker.PhotoViewMIMETypes.VIDEO_TYPE;
      options.maxSelectNumber = 1;

      const photoPicker = new picker.PhotoViewPicker();
      const result = await photoPicker.select(options);

      if (result.photoUris.length > 0) {
   
        this.mediaUri = result.photoUris[0];
        await this.initPlayer();
      }
    } catch (err) {
   
      console.error('选择媒体失败:', JSON.stringify(err));
    }
  }

  async initPlayer() {
   
    this.avPlayer = await media.createAVPlayer();

    this.avPlayer.on('timeUpdate', (time: number) => {
   
      this.currentTime = time;
    });

    this.avPlayer.on('durationUpdate', (duration: number) => {
   
      this.duration = duration;
    });

    this.avPlayer.url = this.mediaUri;
  }

  build() {
   
    Column() {
   
      Text('HarmonyOS 媒体播放器')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)

      Button('选择视频')
        .onClick(() => this.selectMedia())
        .margin({
    top: 20 })

      if (this.mediaUri) {
   
        Text(`播放中: ${
     this.mediaUri.split('/').pop()}`)
          .margin({
    top: 10 })

        Row() {
   
          Button(this.isPlaying ? '暂停' : '播放')
            .onClick(async () => {
   
              if (this.isPlaying) {
   
                await this.avPlayer?.pause();
              } else {
   
                await this.avPlayer?.play();
              }
              this.isPlaying = !this.isPlaying;
            })

          Button('停止')
            .onClick(async () => {
   
              await this.avPlayer?.stop();
              this.isPlaying = false;
            })
        }
        .margin({
    top: 20 })
      }
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }

  aboutToDisappear() {
   
    this.avPlayer?.release();
  }
}

六、性能优化建议

6.1 图片加载优化

  • 使用缩略图预览大图
  • 异步解码避免主线程阻塞
  • 实现图片缓存机制

6.2 音视频播放优化

  • 预加载下一个媒体文件
  • 实现播放进度保存与恢复
  • 监听系统中断事件(来电等)

6.3 内存管理

  • 及时释放 PixelMap 和 AVPlayer
  • 使用弱引用避免内存泄漏
  • 监控内存占用并主动回收

总结

本文系统介绍了 HarmonyOS 的媒体能力,涵盖:

  1. 图片处理 — Picker 选择、解码显示、压缩保存
  2. 音频能力 — AVPlayer 播放、AVRecorder 录制
  3. 视频能力 — 视频播放、相机录制
  4. 权限管理 — 静态声明与动态申请
  5. 实战案例 — 完整媒体播放器实现
  6. 性能优化 — 内存管理与加载优化

掌握这些能力后,你就可以开发功能完整的媒体类应用了。


本文基于 HarmonyOS NEXT(API 12+)编写,部分 API 可能随版本更新而变化。

相关文章
|
7天前
|
存储 弹性计算 缓存
阿里云服务器租赁费用:新版租赁收费标准及活动报价参考
本文更新了2026年阿里云全系列云服务器租赁活动报价,所有特惠资源均可前往阿里云活动中心选购,整体覆盖从个人入门到企业级高性能场景的全梯度需求。其中轻量应用服务器主打极致性价比,2核2G峰值200M带宽配置每日10点、15点限时抢购价仅38元/年,2核4G配置379元/年起;高性价比的经济型e实例、通用算力型u2i实例覆盖2核4G至4核32G全档位,适配开发测试与中小型企业业务;搭载英特尔至强6处理器的第九代c9i企业级实例算力较上代提升20%,支撑高并发生产环境,不同实例规格价差清晰,用户可根据自身业务负载与预算灵活选型。
1728 116
|
8天前
|
人工智能 程序员 API
Codex 接入 DeepSeek-V4-Flash:还能补上识图,提供两套方案
Codex 接入 DeepSeek-V4-Flash 怎么配?本文覆盖 CLI 与桌面端,再用 qwen3-vl-flash 补识图,两套方案可直接照做
1195 7
|
13天前
|
云安全 人工智能 运维
阿里云联动百位企业安全专家,共识Agent防御最佳实践
当Agent成为新员工,你的安全边界在哪里?
1955 9
阿里云联动百位企业安全专家,共识Agent防御最佳实践
|
7天前
|
编解码 人工智能 安全
2核4G/4核8G/8核16G阿里云服务器如何选择实例?经济型e、通用算力型u2i与计算型c9i选哪个?
本文介绍了阿里云2核4G、4核8G、8核16G三档主流配置下经济型e、通用算力型u2i和计算型c9i三种实例的最新活动价格与适用场景。同配置下三者价差显著,以2核4G为例,经济型e低至599.93元/年,计算型c9i则高达1742.08元/年。文章详细解析了各实例的性能定位:经济型e适合轻负载入门场景,u2i兼顾稳定算力与性价比,c9i凭借第9代至强处理器与芯片级安全能力支撑高性能业务。同时提示用户可叠加满减优惠券享受折上折,建议根据业务负载与预算综合决策。
541 112
缓存 安全 IDE
750 2
|
20天前
|
人工智能 前端开发 Linux
Codex 桌面版安装 + CC Switch 接入第三方 API 完整教程(2026 最新)
2026最新教程:手把手教你安装Codex桌面版,通过CC Switch v3.17.0一键接入Fenno等国产API(兼容OpenAI Responses格式),跳过账号登录,完整启用代码审查、多步任务与上下文感知功能。零基础友好,全程图文实操。(239字)
2874 4
|
8天前
|
人工智能 JSON Shell
2026AI漫剧本地全开源方案(附各个软件模型链接),8G显卡也能流畅运行
这是一套完全本地化部署的AI漫剧生成技术链路:涵盖LLM剧本分镜生成、FLUX文生图(IP-Adapter人脸锁定)、StoryDiffusion时序连贯控制、LTX-2.3唇形同步视频生成,及ComfyUI全流程调度。零云端费用,仅耗硬件算力,单集2–4小时可产出竖屏短视频,适配抖音/B站分发。
|
5天前
|
编解码 弹性计算 云计算
MiniMax-H3 视频生成模型 — 一键部署与使用指南
MiniMax-H3是MiniMax开源的33B全模态视频生成模型,支持文生视频、图生视频、参考生视频三种模式,原生输出2K/15秒带立体声音频视频,已原生适配ComfyUI,并可通过阿里云计算巢一键部署。(239字)
|
12天前
|
存储 人工智能 关系型数据库
阿里云AI产品与云产品最新组合套餐:Token Plan、AI coding及云服务器和建站等组合优惠价
阿里云推出全新“算力+模型+应用”一站式云与AI组合套餐活动,覆盖从个人开发者到中大型企业的全场景需求。核心亮点为分三档定价的Token Plan订阅服务,支持Qwen3.8-Max-Preview大模型调用,错峰时段最低可享0.2折优惠。活动同步推出AI Coding、智能体部署、云电脑托管、0代码建站等十余类场景化组合,搭配99元/年的普惠云服务器、88元/年的入门数据库等经典特惠产品,还为企业提供1V1定制化AI转型方案,大幅降低了不同用户群体拥抱AI的技术门槛与采购成本。
734 111