Harmony学习之多媒体处理

Harmony学习之多媒体处理

一、场景引入

小明正在开发一个社交应用,需要实现拍照、录制视频、播放音乐等功能。他发现多媒体处理是移动应用开发的核心能力之一,涉及相机调用、音视频录制播放、图片选择编辑等多个方面。在HarmonyOS中,多媒体子系统提供了丰富的API来支持这些功能。

二、核心概念

2.1 多媒体子系统架构

HarmonyOS多媒体子系统采用分层架构设计,核心包括:

  • 媒体播放器(AVPlayer):负责音视频播放管理
  • 媒体录制器(AVRecorder):提供音视频录制能力
  • 屏幕录制(AVScreenCapture):支持屏幕录制功能
  • 相机服务(CameraKit):相机硬件访问和操作
  • 音频服务(OHAudio):音频采集和渲染

2.2 权限体系

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

权限名称权限说明使用场景
ohos.permission.CAMERA相机权限拍照、录像
ohos.permission.MICROPHONE麦克风权限录音、视频录制
ohos.permission.READ_MEDIA读取媒体文件访问相册
ohos.permission.WRITE_MEDIA写入媒体文件保存图片视频

三、相机开发

3.1 相机初始化

相机开发需要先创建相机实例并配置参数:

// entry/src/main/ets/common/utils/CameraManager.ts
import camera from '@kit.CameraKit';
import { BusinessError } from '@ohos.base';

export class CameraManager {
  private cameraKit: camera.CameraKit;
  private cameraDevice: camera.CameraDevice | null = null;
  private context: common.UIAbilityContext;

  constructor(context: common.UIAbilityContext) {
    this.context = context;
    this.cameraKit = camera.getCameraManager();
  }

  // 获取相机列表
  async getCameraList(): Promise<string[]> {
    try {
      const cameraIds = this.cameraKit.getCameraIds();
      return cameraIds;
    } catch (error) {
      console.error('获取相机列表失败:', error);
      return [];
    }
  }

  // 创建相机实例
  async createCamera(cameraId: string): Promise<boolean> {
    try {
      this.cameraDevice = await this.cameraKit.createCamera(
        cameraId,
        this.createCameraStateCallback(),
        this.createEventHandler()
      );
      return true;
    } catch (error) {
      console.error('创建相机失败:', error);
      return false;
    }
  }

  // 相机状态回调
  private createCameraStateCallback(): camera.CameraStateCallback {
    return {
      onCreated: (camera: camera.Camera) => {
        console.log('相机创建成功');
        this.configureCamera(camera);
      },
      onConfigured: (camera: camera.Camera) => {
        console.log('相机配置完成');
        this.startPreview(camera);
      },
      onReleased: (camera: camera.Camera) => {
        console.log('相机已释放');
      }
    };
  }

  // 配置相机参数
  private configureCamera(camera: camera.Camera) {
    const configBuilder = camera.getCameraConfigBuilder();
    const config = configBuilder
      .setPreviewFrameRateRange([30, 30])
      .setPreviewSize({ width: 1920, height: 1080 })
      .build();
    
    camera.configure(config);
  }

  // 开始预览
  private startPreview(camera: camera.Camera) {
    const frameConfigBuilder = camera.getFrameConfigBuilder(
      camera.FrameConfigType.FRAME_CONFIG_PREVIEW
    );
    
    const frameConfig = frameConfigBuilder
      .addSurface(this.createPreviewSurface())
      .build();
    
    camera.triggerLoopingCapture(frameConfig);
  }

  // 创建预览Surface
  private createPreviewSurface(): Surface {
    // 创建预览Surface并返回
    return new Surface();
  }

  // 释放相机资源
  async releaseCamera() {
    if (this.cameraDevice) {
      await this.cameraDevice.release();
      this.cameraDevice = null;
    }
  }
}

3.2 拍照功能

// entry/src/main/ets/common/utils/CameraManager.ts
export class CameraManager {
  // 拍照
  async takePhoto(): Promise<image.PixelMap | null> {
    if (!this.cameraDevice) {
      console.error('相机未初始化');
      return null;
    }

    try {
      const frameConfigBuilder = this.cameraDevice.getFrameConfigBuilder(
        camera.FrameConfigType.FRAME_CONFIG_PHOTO_OUTPUT
      );
      
      const imageReceiver = image.createImageReceiver(
        1920, 1080, image.ImageFormat.JPEG, 8
      );
      
      const frameConfig = frameConfigBuilder
        .addSurface(imageReceiver.getReceivingSurface())
        .build();
      
      // 触发单帧捕获
      await this.cameraDevice.triggerSingleCapture(frameConfig);
      
      // 获取拍照结果
      const imageArray = await imageReceiver.readNextImage();
      const pixelMap = await imageArray.createPixelMap();
      
      return pixelMap;
    } catch (error) {
      console.error('拍照失败:', error);
      return null;
    }
  }

  // 保存图片到相册
  async savePhotoToGallery(pixelMap: image.PixelMap, fileName: string): Promise<boolean> {
    try {
      const imagePacker = image.createImagePacker();
      const imageData = await imagePacker.packing(pixelMap, {
        format: 'image/jpeg',
        quality: 90
      });
      
      const mediaLibrary = mediaLibraryKit.getMediaLibrary(this.context);
      const publicPath = await mediaLibrary.getPublicDirectory(
        mediaLibraryKit.DirectoryType.DIR_IMAGE
      );
      
      const imageAsset = await mediaLibrary.createAsset(
        mediaLibraryKit.MediaType.IMAGE,
        fileName,
        publicPath
      );
      
      const file = await imageAsset.open(mediaLibraryKit.OpenMode.WRITE_ONLY);
      await fileIo.write(file.fd, imageData);
      await imageAsset.close(file.fd);
      
      return true;
    } catch (error) {
      console.error('保存图片失败:', error);
      return false;
    }
  }
}

四、音视频录制

4.1 音频录制

// entry/src/main/ets/common/utils/AudioRecorder.ts
import media from '@kit.MediaKit';
import fileIo from '@kit.CoreFileKit';
import { BusinessError } from '@ohos.base';

export class AudioRecorder {
  private avRecorder: media.AVRecorder | null = null;
  private filePath: string = '';
  private fd: number = 0;

  // 开始录音
  async startRecord(context: common.UIAbilityContext): Promise<boolean> {
    try {
      // 创建录音文件
      this.filePath = context.filesDir + '/record_' + Date.now() + '.m4a';
      const file = fileIo.openSync(this.filePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE);
      this.fd = file.fd;

      // 配置录音参数
      const config: media.AVRecorderConfig = {
        audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC,
        profile: {
          audioBitrate: 100000,
          audioChannels: 1,
          audioCodec: media.CodecMimeType.AUDIO_AAC,
          audioSampleRate: 48000,
          fileFormat: media.ContainerFormatType.CFT_MPEG_4A
        },
        url: `fd://${this.fd}`
      };

      // 创建录制器
      this.avRecorder = await media.createAVRecorder();
      await this.avRecorder.prepare(config);
      await this.avRecorder.start();

      return true;
    } catch (error) {
      console.error('开始录音失败:', error);
      return false;
    }
  }

  // 停止录音
  async stopRecord(): Promise<string> {
    try {
      if (this.avRecorder) {
        await this.avRecorder.stop();
        await this.avRecorder.release();
        this.avRecorder = null;
      }
      
      if (this.fd) {
        fileIo.closeSync(this.fd);
      }
      
      return this.filePath;
    } catch (error) {
      console.error('停止录音失败:', error);
      return '';
    }
  }

  // 暂停录音
  async pauseRecord(): Promise<void> {
    if (this.avRecorder) {
      await this.avRecorder.pause();
    }
  }

  // 恢复录音
  async resumeRecord(): Promise<void> {
    if (this.avRecorder) {
      await this.avRecorder.resume();
    }
  }
}

4.2 视频录制

// entry/src/main/ets/common/utils/VideoRecorder.ts
import media from '@kit.MediaKit';
import fileIo from '@kit.CoreFileKit';
import { BusinessError } from '@ohos.base';

export class VideoRecorder {
  private avRecorder: media.AVRecorder | null = null;
  private filePath: string = '';
  private fd: number = 0;

  // 开始录像
  async startRecord(context: common.UIAbilityContext): Promise<boolean> {
    try {
      // 创建录像文件
      this.filePath = context.filesDir + '/video_' + Date.now() + '.mp4';
      const file = fileIo.openSync(this.filePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE);
      this.fd = file.fd;

      // 配置录像参数
      const config: media.AVRecorderConfig = {
        audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC,
        videoSourceType: media.VideoSourceType.VIDEO_SOURCE_TYPE_SURFACE_YUV,
        profile: {
          audioBitrate: 128000,
          audioChannels: 2,
          audioCodec: media.CodecMimeType.AUDIO_AAC,
          audioSampleRate: 48000,
          videoBitrate: 2000000,
          videoCodec: media.CodecMimeType.VIDEO_AVC,
          videoFrameWidth: 1280,
          videoFrameHeight: 720,
          videoFrameRate: 30,
          fileFormat: media.ContainerFormatType.CFT_MPEG_4
        },
        url: `fd://${this.fd}`
      };

      // 创建录制器
      this.avRecorder = await media.createAVRecorder();
      await this.avRecorder.prepare(config);
      await this.avRecorder.start();

      return true;
    } catch (error) {
      console.error('开始录像失败:', error);
      return false;
    }
  }

  // 停止录像
  async stopRecord(): Promise<string> {
    try {
      if (this.avRecorder) {
        await this.avRecorder.stop();
        await this.avRecorder.release();
        this.avRecorder = null;
      }
      
      if (this.fd) {
        fileIo.closeSync(this.fd);
      }
      
      return this.filePath;
    } catch (error) {
      console.error('停止录像失败:', error);
      return '';
    }
  }
}

五、音视频播放

5.1 音频播放

// entry/src/main/ets/common/utils/AudioPlayer.ts
import media from '@kit.MediaKit';
import { BusinessError } from '@ohos.base';

export class AudioPlayer {
  private avPlayer: media.AVPlayer | null = null;
  private isPlaying: boolean = false;

  // 播放音频
  async playAudio(url: string): Promise<boolean> {
    try {
      this.avPlayer = await media.createAVPlayer();
      
      // 监听播放状态
      this.avPlayer.on('stateChange', (state: string) => {
        console.log('播放状态:', state);
        if (state === 'prepared') {
          this.avPlayer?.play();
          this.isPlaying = true;
        }
      });

      // 监听播放完成
      this.avPlayer.on('playbackComplete', () => {
        console.log('播放完成');
        this.isPlaying = false;
      });

      // 设置播放地址
      this.avPlayer.url = url;
      await this.avPlayer.prepare();

      return true;
    } catch (error) {
      console.error('播放音频失败:', error);
      return false;
    }
  }

  // 暂停播放
  pause(): void {
    if (this.avPlayer && this.isPlaying) {
      this.avPlayer.pause();
      this.isPlaying = false;
    }
  }

  // 继续播放
  resume(): void {
    if (this.avPlayer && !this.isPlaying) {
      this.avPlayer.play();
      this.isPlaying = true;
    }
  }

  // 停止播放
  stop(): void {
    if (this.avPlayer) {
      this.avPlayer.stop();
      this.avPlayer.release();
      this.avPlayer = null;
      this.isPlaying = false;
    }
  }

  // 跳转到指定位置
  seekTo(position: number): void {
    if (this.avPlayer) {
      this.avPlayer.seek(position);
    }
  }
}

5.2 视频播放

// entry/src/main/ets/pages/VideoPlayerPage.ets
import media from '@kit.MediaKit';
import { BusinessError } from '@ohos.base';

@Entry
@Component
struct VideoPlayerPage {
  @State isPlaying: boolean = false;
  @State currentTime: number = 0;
  @State duration: number = 0;
  private avPlayer: media.AVPlayer | null = null;

  // 播放视频
  async playVideo(url: string) {
    try {
      this.avPlayer = await media.createAVPlayer();
      
      // 监听播放状态
      this.avPlayer.on('stateChange', (state: string) => {
        if (state === 'prepared') {
          this.duration = this.avPlayer?.duration || 0;
          this.avPlayer?.play();
          this.isPlaying = true;
        }
      });

      // 监听播放进度
      this.avPlayer.on('timeUpdate', (currentTime: number) => {
        this.currentTime = currentTime;
      });

      // 设置播放地址
      this.avPlayer.url = url;
      await this.avPlayer.prepare();
    } catch (error) {
      console.error('播放视频失败:', error);
    }
  }

  // 暂停/继续播放
  togglePlay() {
    if (this.isPlaying) {
      this.avPlayer?.pause();
      this.isPlaying = false;
    } else {
      this.avPlayer?.play();
      this.isPlaying = true;
    }
  }

  // 释放资源
  aboutToDisappear() {
    if (this.avPlayer) {
      this.avPlayer.release();
      this.avPlayer = null;
    }
  }

  build() {
    Column() {
      // 视频播放器
      Video({
        src: this.avPlayer?.url || '',
        controller: this.avPlayer
      })
      .width('100%')
      .height(300)

      // 播放控制
      Row() {
        Button(this.isPlaying ? '暂停' : '播放')
          .onClick(() => this.togglePlay())
        
        Slider({
          value: this.currentTime,
          min: 0,
          max: this.duration,
          step: 1
        })
        .onChange((value: number) => {
          this.avPlayer?.seek(value);
        })
      }
    }
  }
}

六、相册图片选择

6.1 选择单张图片

// entry/src/main/ets/common/utils/PhotoPicker.ts
import photoAccessHelper from '@kit.MediaLibraryKit';
import { BusinessError } from '@ohos.base';

export class PhotoPicker {
  // 选择单张图片
  static async selectSingleImage(): Promise<string | null> {
    try {
      const photoPicker = new photoAccessHelper.PhotoViewPicker();
      const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
      
      photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
      photoSelectOptions.maxSelectNumber = 1;
      
      const result = await photoPicker.select(photoSelectOptions);
      
      if (result.photoUris.length > 0) {
        return result.photoUris[0];
      }
      
      return null;
    } catch (error) {
      console.error('选择图片失败:', error);
      return null;
    }
  }

  // 选择多张图片
  static async selectMultipleImages(maxCount: number = 9): Promise<string[]> {
    try {
      const photoPicker = new photoAccessHelper.PhotoViewPicker();
      const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
      
      photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
      photoSelectOptions.maxSelectNumber = maxCount;
      
      const result = await photoPicker.select(photoSelectOptions);
      
      return result.photoUris;
    } catch (error) {
      console.error('选择图片失败:', error);
      return [];
    }
  }

  // 选择视频
  static async selectVideo(): Promise<string | null> {
    try {
      const photoPicker = new photoAccessHelper.PhotoViewPicker();
      const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
      
      photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.VIDEO_TYPE;
      photoSelectOptions.maxSelectNumber = 1;
      
      const result = await photoPicker.select(photoSelectOptions);
      
      if (result.photoUris.length > 0) {
        return result.photoUris[0];
      }
      
      return null;
    } catch (error) {
      console.error('选择视频失败:', error);
      return null;
    }
  }
}

6.2 图片预览组件

// entry/src/main/ets/components/ImagePreview.ets
import { PhotoPicker } from '../common/utils/PhotoPicker';

@Component
export struct ImagePreview {
  @Prop imageUri: string = '';
  @State showPicker: boolean = false;

  build() {
    Column() {
      if (this.imageUri) {
        Image(this.imageUri)
          .width('100%')
          .height(200)
          .objectFit(ImageFit.Cover)
          .onClick(() => {
            // 点击图片预览大图
          })
      } else {
        Button('选择图片')
          .onClick(async () => {
            const uri = await PhotoPicker.selectSingleImage();
            if (uri) {
              this.imageUri = uri;
            }
          })
      }
    }
  }
}

七、图片处理

7.1 图片压缩

// entry/src/main/ets/common/utils/ImageProcessor.ts
import image from '@kit.ImageKit';
import fileIo from '@kit.CoreFileKit';
import { BusinessError } from '@ohos.base';

export class ImageProcessor {
  // 压缩图片
  static async compressImage(
    imageUri: string,
    maxWidth: number = 1080,
    maxHeight: number = 1080,
    quality: number = 80
  ): Promise<string | null> {
    try {
      // 打开图片文件
      const file = fileIo.openSync(imageUri, fileIo.OpenMode.READ_ONLY);
      
      // 创建图片源
      const imageSource = image.createImageSource(file.fd);
      if (!imageSource) {
        throw new Error('创建图片源失败');
      }
      
      // 获取图片信息
      const imageInfo = await imageSource.getImageInfo();
      const { width, height } = imageInfo.size;
      
      // 计算缩放比例
      const scale = Math.min(maxWidth / width, maxHeight / height);
      const targetWidth = Math.floor(width * scale);
      const targetHeight = Math.floor(height * scale);
      
      // 创建PixelMap
      const pixelMap = await imageSource.createPixelMap({
        desiredSize: { width: targetWidth, height: targetHeight }
      });
      
      // 压缩图片
      const imagePacker = image.createImagePacker();
      const compressedData = await imagePacker.packing(pixelMap, {
        format: 'image/jpeg',
        quality: quality
      });
      
      // 保存压缩后的图片
      const compressedPath = imageUri.replace('.jpg', '_compressed.jpg');
      const compressedFile = fileIo.openSync(compressedPath, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY);
      await fileIo.write(compressedFile.fd, compressedData);
      fileIo.closeSync(compressedFile.fd);
      
      // 释放资源
      pixelMap.release();
      imagePacker.release();
      fileIo.closeSync(file.fd);
      
      return compressedPath;
    } catch (error) {
      console.error('压缩图片失败:', error);
      return null;
    }
  }

  // 裁剪图片
  static async cropImage(
    imageUri: string,
    x: number,
    y: number,
    width: number,
    height: number
  ): Promise<string | null> {
    try {
      const file = fileIo.openSync(imageUri, fileIo.OpenMode.READ_ONLY);
      const imageSource = image.createImageSource(file.fd);
      
      const pixelMap = await imageSource.createPixelMap();
      
      // 裁剪图片
      const croppedPixelMap = await pixelMap.crop({
        x: x,
        y: y,
        width: width,
        height: height
      });
      
      // 保存裁剪后的图片
      const imagePacker = image.createImagePacker();
      const imageData = await imagePacker.packing(croppedPixelMap, {
        format: 'image/jpeg',
        quality: 90
      });
      
      const croppedPath = imageUri.replace('.jpg', '_cropped.jpg');
      const croppedFile = fileIo.openSync(croppedPath, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY);
      await fileIo.write(croppedFile.fd, imageData);
      fileIo.closeSync(croppedFile.fd);
      
      // 释放资源
      croppedPixelMap.release();
      pixelMap.release();
      imagePacker.release();
      fileIo.closeSync(file.fd);
      
      return croppedPath;
    } catch (error) {
      console.error('裁剪图片失败:', error);
      return null;
    }
  }

  // 旋转图片
  static async rotateImage(imageUri: string, angle: number): Promise<string | null> {
    try {
      const file = fileIo.openSync(imageUri, fileIo.OpenMode.READ_ONLY);
      const imageSource = image.createImageSource(file.fd);
      const pixelMap = await imageSource.createPixelMap();
      
      // 旋转图片
      const rotatedPixelMap = await pixelMap.createRotated(angle);
      
      // 保存旋转后的图片
      const imagePacker = image.createImagePacker();
      const imageData = await imagePacker.packing(rotatedPixelMap, {
        format: 'image/jpeg',
        quality: 90
      });
      
      const rotatedPath = imageUri.replace('.jpg', '_rotated.jpg');
      const rotatedFile = fileIo.openSync(rotatedPath, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY);
      await fileIo.write(rotatedFile.fd, imageData);
      fileIo.closeSync(rotatedFile.fd);
      
      // 释放资源
      rotatedPixelMap.release();
      pixelMap.release();
      imagePacker.release();
      fileIo.closeSync(file.fd);
      
      return rotatedPath;
    } catch (error) {
      console.error('旋转图片失败:', error);
      return null;
    }
  }
}

八、实战案例:多媒体应用

8.1 主页面实现

// entry/src/main/ets/pages/MainPage.ets
import { CameraManager } from '../common/utils/CameraManager';
import { AudioRecorder } from '../common/utils/AudioRecorder';
import { VideoRecorder } from '../common/utils/VideoRecorder';
import { PhotoPicker } from '../common/utils/PhotoPicker';
import { ImageProcessor } from '../common/utils/ImageProcessor';

@Entry
@Component
struct MainPage {
  @State currentTab: string = 'camera';
  @State cameraManager: CameraManager;
  @State audioRecorder: AudioRecorder;
  @State videoRecorder: VideoRecorder;
  @State selectedImage: string = '';
  @State isRecordingAudio: boolean = false;
  @State isRecordingVideo: boolean = false;

  aboutToAppear() {
    const context = getContext(this) as common.UIAbilityContext;
    this.cameraManager = new CameraManager(context);
    this.audioRecorder = new AudioRecorder();
    this.videoRecorder = new VideoRecorder();
  }

  // 拍照
  async takePhoto() {
    const pixelMap = await this.cameraManager.takePhoto();
    if (pixelMap) {
      const fileName = 'photo_' + Date.now() + '.jpg';
      const saved = await this.cameraManager.savePhotoToGallery(pixelMap, fileName);
      if (saved) {
        promptAction.showToast({ message: '照片保存成功', duration: 2000 });
      }
    }
  }

  // 开始录音
  async startAudioRecord() {
    const context = getContext(this) as common.UIAbilityContext;
    const started = await this.audioRecorder.startRecord(context);
    if (started) {
      this.isRecordingAudio = true;
    }
  }

  // 停止录音
  async stopAudioRecord() {
    const filePath = await this.audioRecorder.stopRecord();
    this.isRecordingAudio = false;
    if (filePath) {
      promptAction.showToast({ message: '录音已保存', duration: 2000 });
    }
  }

  // 开始录像
  async startVideoRecord() {
    const context = getContext(this) as common.UIAbilityContext;
    const started = await this.videoRecorder.startRecord(context);
    if (started) {
      this.isRecordingVideo = true;
    }
  }

  // 停止录像
  async stopVideoRecord() {
    const filePath = await this.videoRecorder.stopRecord();
    this.isRecordingVideo = false;
    if (filePath) {
      promptAction.showToast({ message: '录像已保存', duration: 2000 });
    }
  }

  // 选择图片
  async selectImage() {
    const imageUri = await PhotoPicker.selectSingleImage();
    if (imageUri) {
      this.selectedImage = imageUri;
    }
  }

  // 压缩图片
  async compressImage() {
    if (!this.selectedImage) {
      promptAction.showToast({ message: '请先选择图片', duration: 2000 });
      return;
    }
    
    const compressedPath = await ImageProcessor.compressImage(this.selectedImage);
    if (compressedPath) {
      promptAction.showToast({ message: '图片压缩成功', duration: 2000 });
      this.selectedImage = compressedPath;
    }
  }

  build() {
    Column() {
      // 标签栏
      Tabs({
        barPosition: BarPosition.Start,
        index: this.getTabIndex(this.currentTab)
      }) {
        // 相机标签页
        TabContent() {
          this.buildCameraTab()
        }.tabBar('相机')
        
        // 录音标签页
        TabContent() {
          this.buildAudioTab()
        }.tabBar('录音')
        
        // 录像标签页
        TabContent() {
          this.buildVideoTab()
        }.tabBar('录像')
        
        // 图片标签页
        TabContent() {
          this.buildImageTab()
        }.tabBar('图片')
      }
    }
  }

  // 相机标签页
  @Builder
  buildCameraTab() {
    Column() {
      Button('拍照')
        .onClick(() => this.takePhoto())
        .margin(20)
    }
  }

  // 录音标签页
  @Builder
  buildAudioTab() {
    Column() {
      Button(this.isRecordingAudio ? '停止录音' : '开始录音')
        .onClick(() => {
          if (this.isRecordingAudio) {
            this.stopAudioRecord();
          } else {
            this.startAudioRecord();
          }
        })
        .margin(20)
    }
  }

  // 录像标签页
  @Builder
  buildVideoTab() {
    Column() {
      Button(this.isRecordingVideo ? '停止录像' : '开始录像')
        .onClick(() => {
          if (this.isRecordingVideo) {
            this.stopVideoRecord();
          } else {
            this.startVideoRecord();
          }
        })
        .margin(20)
    }
  }

  // 图片标签页
  @Builder
  buildImageTab() {
    Column() {
      Button('选择图片')
        .onClick(() => this.selectImage())
        .margin(20)
      
      if (this.selectedImage) {
        Image(this.selectedImage)
          .width(200)
          .height(200)
          .margin(20)
        
        Button('压缩图片')
          .onClick(() => this.compressImage())
          .margin(10)
      }
    }
  }

  // 获取标签索引
  private getTabIndex(tab: string): number {
    const tabs = ['camera', 'audio', 'video', 'image'];
    return tabs.indexOf(tab);
  }
}

九、最佳实践

9.1 权限管理优化

// entry/src/main/ets/common/utils/PermissionManager.ts
import abilityAccessCtrl from '@kit.AbilityKit';
import { BusinessError } from '@ohos.base';

export class PermissionManager {
  // 检查并申请权限
  static async checkAndRequestPermission(
    context: common.UIAbilityContext,
    permission: string
  ): Promise<boolean> {
    try {
      const atManager = abilityAccessCtrl.createAtManager();
      
      // 检查权限状态
      const tokenId = context.applicationInfo.accessTokenId;
      const grantStatus = await atManager.checkAccessToken(tokenId, permission);
      
      if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
        return true;
      }
      
      // 申请权限
      const result = await atManager.requestPermissionsFromUser(context, [permission]);
      return result.authResults[0] === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;
    } catch (error) {
      console.error('权限申请失败:', error);
      return false;
    }
  }

  // 批量申请权限
  static async requestMultiplePermissions(
    context: common.UIAbilityContext,
    permissions: string[]
  ): Promise<boolean> {
    try {
      const atManager = abilityAccessCtrl.createAtManager();
      const result = await atManager.requestPermissionsFromUser(context, permissions);
      
      return result.authResults.every(
        status => status === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED
      );
    } catch (error) {
      console.error('批量权限申请失败:', error);
      return false;
    }
  }
}

9.2 内存管理

// entry/src/main/ets/common/utils/MemoryManager.ts
export class MemoryManager {
  // 检查内存使用情况
  static checkMemoryUsage(): void {
    const memoryInfo = system.getMemoryInfo();
    console.log('内存使用情况:', {
      total: memoryInfo.total,
      available: memoryInfo.available,
      used: memoryInfo.used,
      threshold: memoryInfo.threshold
    });
    
    // 如果内存不足,释放非关键资源
    if (memoryInfo.available < memoryInfo.threshold) {
      this.releaseNonCriticalResources();
    }
  }

  // 释放非关键资源
  private static releaseNonCriticalResources(): void {
    // 释放缓存图片、临时文件等
    console.log('释放非关键资源');
  }
}

9.3 错误处理

// entry/src/main/ets/common/utils/ErrorHandler.ts
import { BusinessError } from '@ohos.base';

export class ErrorHandler {
  // 处理多媒体错误
  static handleMediaError(error: BusinessError): void {
    console.error('多媒体操作失败:', error);
    
    switch (error.code) {
      case 13900001:
        console.error('权限不足,请检查权限设置');
        break;
      case 13900002:
        console.error('参数错误,请检查参数配置');
        break;
      case 13900003:
        console.error('资源不足,请释放资源后重试');
        break;
      case 13900004:
        console.error('操作超时,请检查网络连接');
        break;
      default:
        console.error('未知错误:', error.message);
    }
  }
}

十、总结与行动建议

10.1 核心要点回顾

  • 相机开发:掌握相机初始化、预览、拍照、录像等核心功能
  • 音视频录制:学会使用AVRecorder进行音频和视频录制
  • 音视频播放:掌握AVPlayer的播放控制和状态管理
  • 图片处理:了解图片选择、压缩、裁剪、旋转等操作
  • 权限管理:正确处理多媒体权限申请和用户拒绝场景

10.2 性能优化建议

  1. 内存管理:及时释放PixelMap、AVPlayer等资源,避免内存泄漏
  2. 大文件处理:对大图片和视频进行压缩和缩放,减少内存占用
  3. 权限缓存:缓存权限状态,避免重复申请
  4. 错误重试:对网络错误和权限错误进行重试机制

10.3 下一步行动

  1. 实践练习:基于本篇文章的代码示例,实现一个完整的多媒体应用
  2. 功能扩展:添加滤镜效果、视频剪辑、图片编辑等高级功能
  3. 性能测试:在不同设备上测试多媒体功能的性能表现
  4. 用户体验优化:优化权限申请流程和错误提示,提升用户体验

通过本篇文章的学习,你已经掌握了HarmonyOS多媒体处理的核心能力。在实际开发中,建议参考官方文档和最佳实践,确保应用的稳定性和性能表现。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值