当前位置: 首页 > news >正文

【OpenHarmony多媒体开发大总结】从播放到转码全流程+实测踩坑便签,一文打通AVPlayer/SoundPool/录屏/缩略图/元数据提取

1. 总览图

场景系统模块核心类数据类型典型延迟官方入口
长视频/音频播放AVPlayer+Video+XComponentAVPlayer+Video+XComponent本地/网络流100 ms+media-kit-intro
短音效(按键/游戏)SoundPoolSoundPool内存缓存<20 ms同上
摄像头 + 麦克风录制AVRecorderAVRecorder本地文件实时同上
屏幕 + 系统声录制AVScreenCaptureAVScreenCapture本地/mp4实时同上
缩略图/封面AVImageGeneratorAVImageGenerator位图1 帧同上
元数据提取AVMetadataExtractorAVMetadataExtractorJSON<100 ms同上
视频转码/剪辑AVTranscoderAVTranscoder任意→任意1×~10× 实时同上

2. 决策表

需求推荐模块不推荐原因
播放 2 h 电影AVPlayerSoundPool支持 seek、缓冲、字幕
连续点击音效SoundPoolAVPlayer低延迟、内存复用
录网课(人+屏)AVRecorder + AVScreenCapture 双路纯录屏需要画中画
截取视频封面AVImageGeneratorAVPlayer毫秒级、任意时间点
仅提取码率/时长AVMetadataExtractorAVPlayer不解码,极速
4K→720P 压缩AVTranscoderAVPlayer硬编硬解,省电

3. 播放:AVPlayer+Video+XComponent

Feature:

  • 协议:file/http/https/HLS/DASH
  • 格式:mp4/mkv/ts/mp3/aac/flac
  • 硬解:H264/H265/VP9/AV1(芯片决定)
  • 字幕:srt/ass/ttml 内嵌/外挂
  • 倍速:0.5×~4× 不变调
  • 投屏:标准 DLNA/Miracast(系统级)

代码模板(ArkTS):

#Video组件开发示例
// xxx.ets
@Entry
@Component
struct VideoCreateComponent {// $rawfile('video1.mp4')、$r('app.media.poster1')需要分别替换为开发者所需的视频、图片资源文件@State videoSrc: Resource = $rawfile('video1.mp4');@State previewUri: Resource = $r('app.media.poster1');@State curRate: PlaybackSpeed = PlaybackSpeed.Speed_Forward_1_00_X;@State isAutoPlay: boolean = false;@State showControls: boolean = true;@State isShortcutKeyEnabled: boolean = false;@State showFirstFrame: boolean = false;controller: VideoController = new VideoController();build() {Column() {Video({src: this.videoSrc,previewUri: this.previewUri, // 设置预览图currentProgressRate: this.curRate, // 设置播放速度controller: this.controller,posterOptions: { showFirstFrame: this.showFirstFrame } // 关闭首帧送显}).width('100%').height(600).autoPlay(this.isAutoPlay).controls(this.showControls).enableShortcutKey(this.isShortcutKeyEnabled).onStart(() => {console.info('onStart');}).onPause(() => {console.info('onPause');}).onFinish(() => {console.info('onFinish');}).onError(() => {console.info('onError');}).onStop(() => {console.info('onStop');}).onPrepared((e?: DurationObject) => {if (e != undefined) {console.info('onPrepared is ' + e.duration);}}).onSeeking((e?: TimeObject) => {if (e != undefined) {console.info('onSeeking is ' + e.time);}}).onSeeked((e?: TimeObject) => {if (e != undefined) {console.info('onSeeked is ' + e.time);}}).onUpdate((e?: TimeObject) => {if (e != undefined) {console.info('onUpdate is ' + e.time);}}).onFullscreenChange((e?: FullscreenObject) => {if (e != undefined) {console.info('onFullscreenChange is ' + e.fullscreen);}})Row() {// $rawfile('video2.mp4')、$r('app.media.poster2')需要分别替换为开发者所需的视频、图片资源文件Button('src').onClick(() => {this.videoSrc = $rawfile('video2.mp4'); // 切换视频源。}).margin(5)Button('previewUri').onClick(() => {this.previewUri = $r('app.media.poster2'); // 切换视频预览海报。}).margin(5)Button('controls').onClick(() => {this.showControls = !this.showControls; // 切换是否显示视频控制栏}).margin(5)}Row() {Button('start').onClick(() => {this.controller.start(); // 开始播放}).margin(2)Button('pause').onClick(() => {this.controller.pause(); // 暂停播放}).margin(2)Button('stop').onClick(() => {this.controller.stop(); // 结束播放}).margin(2)Button('reset').onClick(() => {this.controller.reset(); // 重置AVPlayer}).margin(2)Button('setTime').onClick(() => {this.controller.setCurrentTime(10, SeekMode.Accurate); // 精准跳转到视频的10s位置}).margin(2)}Row() {Button('rate 0.75').onClick(() => {this.curRate = PlaybackSpeed.Speed_Forward_0_75_X; // 0.75倍速播放}).margin(5)Button('rate 1').onClick(() => {this.curRate = PlaybackSpeed.Speed_Forward_1_00_X; // 原倍速播放}).margin(5)Button('rate 2').onClick(() => {this.curRate = PlaybackSpeed.Speed_Forward_2_00_X; // 2倍速播放}).margin(5)}}}
}interface DurationObject {duration: number;
}interface TimeObject {time: number;
}interface FullscreenObject {fullscreen: boolean;
}
#AVPlayer组件开发示例
import { media } from '@kit.MediaKit';let player: media.AVPlayer | null = null;
let surfaceId: string = 'player_surface'; // XComponent 的 surfaceIdasync function initPlayer() {try {player = await media.createAVPlayer();// 设置媒体资源player.url = 'https://example.com/video.mp4';// 绑定 XComponent 的 SurfaceIDplayer.surfaceId = surfaceId;// 设置播放参数player.setPlaybackRate(1.0);// 添加状态监听player.on('stateChange', (state) => {console.log(`Player state: ${state}`);if (state === 'prepared') {player.play();} else if (state === 'error') {console.error('Player error:', player.error);}});// 添加缓冲监听player.on('bufferingUpdate', (buffering) => {console.log(`Buffering: ${buffering.percent}%`);});// 添加完成监听player.on('completion', () => {console.log('Playback completed');player.seek(0); // 重置播放位置});// 准备播放await player.prepare();} catch (error) {console.error('Player initialization failed:', error);}
}// 播放控制
function play() {if (player) {player.play();}
}function pause() {if (player) {player.pause();}
}function seek(timeMs: number) {if (player) {player.seek(timeMs);}
}

性能锦囊:

  • 首帧 < 300 ms(实测 8650 开发板 1080P)
  • 循环播放 player.loop = true 无 seek 回退
  • 网络流 player.setBufferingParams({ low: 2, high: 5 }) 单位秒
  • 字幕加载:player.addTextTrack('srt', 'https://example.com/subs.srt')
  • 投屏:player.startCast('device_id')(需要设备支持)

高级用法:

// 自定义播放器
const customPlayer = await media.createAVPlayer();
customPlayer.url = 'https://example.com/video.mp4';
customPlayer.surfaceId = surfaceId;
customPlayer.setPlaybackRate(1.5); // 1.5倍速
customPlayer.setVolume(0.8); // 80% 音量// 设置字幕
const textTrack = await customPlayer.addTextTrack('srt', 'https://example.com/subs.srt');
textTrack.enabled = true;// 处理字幕变化
textTrack.on('textChanged', (text) => {console.log('Current subtitle:', text);
});// 播放
await customPlayer.prepare();
customPlayer.play();

4. 短音效:SoundPool

适用:

  • 按键、子弹、道具掉落等 <7 s 音效
  • 同时播放 ≤32 路
  • 内存预加载,CPU 零拷贝

代码:

import { media } from '@kit.MediaKit';let soundPool: media.SoundPool | null = null;async function initSoundPool() {try {// 创建 SoundPool 实例,最大32个音效同时播放soundPool = await media.createSoundPool(32, media.AudioStream.STREAM_MUSIC, 0);// 加载音效资源const clickSoundId = await soundPool.load('/raw/click.mp3', 0);const shootSoundId = await soundPool.load('/raw/shoot.mp3', 0);// 播放音效const clickSound = () => {soundPool?.play(clickSoundId, 1.0, 1.0, 0, 0, 1.0);};const shootSound = () => {soundPool?.play(shootSoundId, 1.0, 1.0, 0, 0, 1.0);};return { clickSound, shootSound };} catch (error) {console.error('SoundPool initialization failed:', error);}
}// 使用示例
async function playSounds() {const { clickSound, shootSound } = await initSoundPool();// 按键点击clickSound();// 游戏射击shootSound();// 同时播放多个音效for (let i = 0; i < 5; i++) {clickSound();await new Promise(resolve => setTimeout(resolve, 100));}
}// 释放资源
function releaseSoundPool() {if (soundPool) {soundPool.release();soundPool = null;}
}

实测:

  • 延迟 8~15 ms(RK3568)
  • 包体 <1 MB 可全部 preload
  • 100+ 音效加载时间 < 200 ms

高级用法:

// 带优先级的音效播放
const playWithPriority = (soundId: number, priority: number) => {if (soundPool) {soundPool.play(soundId, 1.0, 1.0, priority, 0, 1.0);}
};// 循环播放
const loopSound = (soundId: number) => {if (soundPool) {soundPool.play(soundId, 1.0, 1.0, 0, -1, 1.0); // -1 表示无限循环}
};// 播放后回调
const playWithCallback = (soundId: number, callback: () => void) => {if (soundPool) {soundPool.play(soundId, 1.0, 1.0, 0, 0, 1.0);soundPool.on('playbackComplete', () => {callback();});}
};

5. 录制:AVRecorder

能力:

  • 视频:H264/H265,最高 4K@60fps
  • 音频:AAC/FLAC,48 kHz,16/24-bit
  • 来源:摄像头 + 麦克风(可分别开关)
  • 片段:支持分段录制(maxDuration/maxSize)

模板:

import { media } from '@kit.MediaKit';
import { file } from '@ohos.file.fs';async function initRecorder() {try {const recorder = await media.createAVRecorder();// 配置录制参数const config: media.AVRecorderConfig = {audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC,videoSourceType: media.VideoSourceType.VIDEO_SOURCE_TYPE_SURFACE,profile: {fileFormat: media.ContainerFormatType.CFT_MPEG_4,audioBitrate: 128000,audioChannels: 2,audioSampleRate: 48000,videoBitrate: 8000000,videoCodec: media.CodecMimeType.VIDEO_AVC,videoFrameWidth: 1920,videoFrameHeight: 1080,videoFrameRate: 30},url: 'fd://' + (await file.open('/data/storage/el2/base/haps/entry/files/recording.mp4', file.OpenMode.CREATE))};// 准备录制await recorder.prepare(config);// 开始录制await recorder.start();// 返回录制器实例,用于控制录制return {recorder,stop: async () => {await recorder.stop();await recorder.release();}};} catch (error) {console.error('Recorder initialization failed:', error);throw error;}
}// 使用示例
async function startRecording() {const { recorder, stop } = await initRecorder();// 模拟录制10秒await new Promise(resolve => setTimeout(resolve, 10000));// 停止录制await stop();console.log('Recording completed');
}

技巧:

  • 暂停继续 recorder.pause()/resume(),文件自动拼接
  • 前后台切换框架自动 stop,无需自己监听生命周期
  • 多路录制:同时录制视频和音频

高级用法:

// 多路录制(视频+音频)
async function initMultiRecorder() {const recorder = await media.createAVRecorder();const config: media.AVRecorderConfig = {audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC,videoSourceType: media.VideoSourceType.VIDEO_SOURCE_TYPE_SURFACE,profile: {fileFormat: media.ContainerFormatType.CFT_MPEG_4,audioBitrate: 128000,audioChannels: 2,audioSampleRate: 48000,videoBitrate: 8000000,videoCodec: media.CodecMimeType.VIDEO_AVC,videoFrameWidth: 1920,videoFrameHeight: 1080,videoFrameRate: 30},url: 'fd://' + (await file.open('/data/storage/el2/base/haps/entry/files/multi_recording.mp4', file.OpenMode.CREATE))};await recorder.prepare(config);await recorder.start();return recorder;
}// 分段录制
async function startSegmentedRecording() {const recorder = await media.createAVRecorder();const config: media.AVRecorderConfig = {audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC,videoSourceType: media.VideoSourceType.VIDEO_SOURCE_TYPE_SURFACE,profile: {fileFormat: media.ContainerFormatType.CFT_MPEG_4,audioBitrate: 128000,audioChannels: 2,audioSampleRate: 48000,videoBitrate: 8000000,videoCodec: media.CodecMimeType.VIDEO_AVC,videoFrameWidth: 1920,videoFrameHeight: 1080,videoFrameRate: 30},url: 'fd://' + (await file.open('/data/storage/el2/base/haps/entry/files/segment_1.mp4', file.OpenMode.CREATE)),maxDuration: 60000, // 60秒maxSize: 200000000 // 200MB};await recorder.prepare(config);await recorder.start();// 60秒后停止await new Promise(resolve => setTimeout(resolve, 60000));await recorder.stop();console.log('First segment recorded');// 开始第二段const secondConfig = {...config,url: 'fd://' + (await file.open('/data/storage/el2/base/haps/entry/files/segment_2.mp4', file.OpenMode.CREATE))};await recorder.prepare(secondConfig);await recorder.start();// 再录60秒await new Promise(resolve => setTimeout(resolve, 60000));await recorder.stop();console.log('Second segment recorded');
}

6. 录屏:AVScreenCapture

特色:

  • 同时录 系统声 + 麦克风(可选)
  • 支持 子屏幕/窗口(多屏场景)
  • 录屏过程中可 动态开关麦克风
  • 输出直接 mp4,与 AVRecorder 同一套 profile

最小可运行:

import { media } from '@kit.MediaKit';async function startScreenRecording() {try {const capture = await media.createAVScreenCapture();// 初始化屏幕捕获,1920x1080,30fps,使用麦克风await capture.init(1920, 1080, 30, media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC);// 开始录制到文件await capture.start('/data/storage/el2/base/haps/entry/files/screen_recording.mp4');console.log('Screen recording started');// 模拟录制10秒await new Promise(resolve => setTimeout(resolve, 10000));// 停止录制await capture.stop();console.log('Screen recording stopped');} catch (error) {console.error('Screen capture failed:', error);}
}

权限(必须):

"requestPermissions": [{"name": "ohos.permission.CAPTURE_SCREEN"},{"name": "ohos.permission.MICROPHONE"}
]

高级用法:

// 录制特定窗口
async function startWindowRecording(windowId: string) {const capture = await media.createAVScreenCapture();// 初始化屏幕捕获,1920x1080,30fps,使用系统内录await capture.init(1920, 1080, 30, media.AudioSourceType.AUDIO_SOURCE_TYPE_ALL_PLAYBACK);// 开始录制特定窗口await capture.start('/data/storage/el2/base/haps/entry/files/window_recording.mp4', windowId);console.log(`Recording window: ${windowId}`);// 10秒后停止await new Promise(resolve => setTimeout(resolve, 10000));await capture.stop();
}// 动态开关麦克风
async function toggleMicrophone(capture: media.AVScreenCapture, enable: boolean) {if (enable) {await capture.setAudioSource(media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC);} else {await capture.setAudioSource(media.AudioSourceType.AUDIO_SOURCE_TYPE_NONE);}console.log(`Microphone ${enable ? 'enabled' : 'disabled'}`);
}// 使用示例
async function fullScreenRecording() {const capture = await media.createAVScreenCapture();await capture.init(1920, 1080, 30, media.AudioSourceType.AUDIO_SOURCE_TYPE_ALL_PLAYBACK);await capture.start('/data/storage/el2/base/haps/entry/files/full_screen_recording.mp4');// 5秒后开启麦克风await new Promise(resolve => setTimeout(resolve, 5000));await toggleMicrophone(capture, true);// 10秒后停止await new Promise(resolve => setTimeout(resolve, 5000));await capture.stop();
}

7. 缩略图:AVImageGenerator

import { media } from '@kit.MediaKit';async function generateThumbnail(videoPath: string, timeMs: number = 1000) {try {const gen = await media.createAVImageGenerator();// 生成指定时间点的缩略图const image = await gen.generateImage(videoPath, { timeUs: timeMs * 1000 });// 获取像素映射const pixelMap = await image.pixelMap;return pixelMap;} catch (error) {console.error('Failed to generate thumbnail:', error);throw error;}
}// 使用示例
async function showThumbnail() {const pixelMap = await generateThumbnail('/data/storage/el2/base/haps/entry/files/video.mp4', 5000);// 将像素映射显示在 Image 组件上this.pixmap = pixelMap;console.log('Thumbnail generated successfully');
}

高级用法:

// 生成多帧缩略图
async function generateMultipleThumbnails(videoPath: string, times: number[]) {const gen = await media.createAVImageGenerator();const thumbnails = [];for (const time of times) {const image = await gen.generateImage(videoPath, { timeUs: time * 1000 });const pixelMap = await image.pixelMap;thumbnails.push({ time, pixelMap });}return thumbnails;
}// 生成滑动预览
async function generateSlidingPreview(videoPath: string, intervalMs: number = 1000) {const gen = await media.createAVImageGenerator();const duration = await getVideoDuration(videoPath);const previews = [];for (let time = 0; time < duration; time += intervalMs) {const image = await gen.generateImage(videoPath, { timeUs: time * 1000 });const pixelMap = await image.pixelMap;previews.push({ time, pixelMap });}return previews;
}// 辅助函数:获取视频时长
async function getVideoDuration(videoPath: string): Promise<number> {const extractor = await media.createAVMetadataExtractor();const metadata = await extractor.extract(videoPath);return metadata.durationUs / 1000000; // 转换为秒
}

8. 元数据:AVMetadataExtractor

import { media } from '@kit.MediaKit';async function extractMetadata(mediaPath: string) {try {const extractor = await media.createAVMetadataExtractor();const metadata = await extractor.extract(mediaPath);// 输出元数据console.log('Metadata:', metadata);// 提取特定信息const duration = metadata.durationUs / 1000000; // 转换为秒const width = metadata.width;const height = metadata.height;const videoBitrate = metadata.videoBitrate;const audioSampleRate = metadata.audioSampleRate;console.log(`Video: ${width}x${height}, Duration: ${duration}s, Bitrate: ${videoBitrate}bps`);console.log(`Audio: ${audioSampleRate}Hz`);return metadata;} catch (error) {console.error('Metadata extraction failed:', error);throw error;}
}// 使用示例
async function showMetadata() {const metadata = await extractMetadata('/data/storage/el2/base/haps/entry/files/video.mp4');// 从元数据中获取专辑封面const albumArt = await metadata.albumArt;if (albumArt) {console.log('Album art found');// 处理专辑封面}
}

高级用法:

// 网络文件边下边抽
async function extractNetworkMetadata(url: string) {try {const extractor = await media.createAVMetadataExtractor();// 设置范围请求,仅下载元数据部分const range = 'bytes=0-1024'; // 仅下载前1KBawait extractor.setRange(range);// 提取网络文件元数据const metadata = await extractor.extract(url);return metadata;} catch (error) {console.error('Network metadata extraction failed:', error);throw error;}
}// 从元数据中提取特定字段
async function extractSpecificMetadata(mediaPath: string, field: string) {const metadata = await extractMetadata(mediaPath);return metadata[field];
}// 使用示例
async function showSpecificMetadata() {const duration = await extractSpecificMetadata('/data/storage/el2/base/haps/entry/files/video.mp4', 'durationUs');console.log(`Video duration: ${duration} us`);const artist = await extractSpecificMetadata('/data/storage/el2/base/haps/entry/files/audio.mp3', 'artist');console.log(`Artist: ${artist}`);
}

9. 转码:AVTranscoder

场景:

  • 4K→720P 省流量
  • ts→mp4 统一封装
  • 加水印、裁剪、拼接

代码骨架:

import { media } from '@kit.MediaKit';async function transcodeVideo(inputPath: string, outputPath: string) {try {const trans = await media.createAVTranscoder();// 设置输入await trans.setInput(inputPath);// 设置输出await trans.setOutput(outputPath, {videoCodec: media.CodecMimeType.VIDEO_HEVC,videoBitrate: 2_000_000,videoFrameWidth: 1280,videoFrameHeight: 720,videoFrameRate: 30});// 添加进度监听trans.on('progress', (progress) => {console.log(`Transcoding progress: ${progress.percent}%`);});// 添加完成监听trans.on('completion', () => {console.log('Transcoding completed');});// 开始转码await trans.start();console.log('Transcoding started');} catch (error) {console.error('Transcoding failed:', error);throw error;}
}// 使用示例
async function startTranscoding() {await transcodeVideo('/data/storage/el2/base/haps/entry/files/input.mp4','/data/storage/el2/base/haps/entry/files/output.mp4');
}

高级用法:

// 4K→720P 转码,带水印
async function transcodeWithWatermark(inputPath: string, outputPath: string) {const trans = await media.createAVTranscoder();await trans.setInput(inputPath);await trans.setOutput(outputPath, {videoCodec: media.CodecMimeType.VIDEO_HEVC,videoBitrate: 2_000_000,videoFrameWidth: 1280,videoFrameHeight: 720,videoFrameRate: 30,waterMark: {position: media.WaterMarkPosition.BOTTOM_RIGHT,image: '/raw/watermark.png',opacity: 0.5,size: 0.2}});trans.on('progress', (progress) => {console.log(`Transcoding progress: ${progress.percent}%`);});await trans.start();
}// 多路转码(视频+音频)
async function transcodeMultiStream(inputPath: string, outputPath: string) {const trans = await media.createAVTranscoder();await trans.setInput(inputPath);await trans.setOutput(outputPath, {videoCodec: media.CodecMimeType.VIDEO_HEVC,videoBitrate: 2_000_000,videoFrameWidth: 1280,videoFrameHeight: 720,videoFrameRate: 30,audioCodec: media.CodecMimeType.AUDIO_AAC,audioBitrate: 128000,audioChannels: 2,audioSampleRate: 48000});await trans.start();
}// 裁剪转码
async function transcodeWithCropping(inputPath: string, outputPath: string) {const trans = await media.createAVTranscoder();await trans.setInput(inputPath);await trans.setOutput(outputPath, {videoCodec: media.CodecMimeType.VIDEO_HEVC,videoBitrate: 2_000_000,videoFrameWidth: 1280,videoFrameHeight: 720,videoFrameRate: 30,crop: {x: 100,y: 100,width: 1000,height: 600}});await trans.start();
}

10. 性能

建议说明
播放网络流先 HEAD确认 206 支持,否则首次缓冲 2 s+
SoundPool 资源 <7 s超过限制自动走文件回退,延迟飙到 100 ms
录屏固定 30 fps高于 30 fps 部分芯片会降分辨率
AVRecorder 分段maxDuration=60000 避免单个文件 >2 GB 触发 FAT32 异常
缩略图用 0 秒部分文件 0 秒关键帧缺失,会回退到 1 秒
转码先 extractor确认原视频帧率、旋转角,防止输出比例异常
AVPlayer 低延迟使用 setPlaybackRate(1.0)setBufferingParams 优化
AVRecorder 低延迟使用 videoFrameRatevideoBitrate 优化
音频录制质量使用 audioSampleRateaudioChannels 优化
多设备适配使用 getSystemInfo 检测设备能力
文件路径使用 file 模块获取应用沙箱路径
内存管理录制和播放完成后及时 release 资源
异常处理所有媒体操作都应有 try/catch
后台转码申请 ohos.permission.ANY_BACKGROUND_TASK 权限

详细说明:

  1. 网络流预检查

    import { http } from '@ohos.net.http';async function checkNetworkStream(url: string) {const request = http.createRequest(url);request.method = 'HEAD';return new Promise((resolve, reject) => {request.send((err, response) => {if (err) {reject(err);} else {resolve(response.statusCode === 200);}});});
    }
    
  2. AVRecorder 分段录制

    const config: media.AVRecorderConfig = {// ...其他配置maxDuration: 60000, // 60秒maxSize: 200000000 // 200MB
    };
    
  3. AVTranscoder 旋转处理

    const trans = await media.createAVTranscoder();
    await trans.setInput(inputPath);// 获取原始视频旋转信息
    const metadata = await media.createAVMetadataExtractor().extract(inputPath);
    const rotation = metadata.rotation || 0;// 设置旋转
    await trans.setOutput(outputPath, {// ...其他配置rotation: rotation // 旋转角度

11. 官方文档

  • 媒体 Kit 总览
  • AVPlayer 指南
  • AVRecorder 指南
  • SoundPool 指南
  • AVScreenCapture 指南
  • AVImageGenerator 指南
  • AVMetadataExtractor 指南
  • AVTranscoder 指南
http://www.dtcms.com/a/483121.html

相关文章:

  • 02117 信息组织【第六章】
  • 【Linux内核】Linux内核裁剪完全指南:从理论到实战的系统优化
  • 旅游公司网站难做吗外贸英文网站模板
  • 尚品本色木门网站是哪个公司做的猎头做单网站
  • Linux的动态库和静态库
  • 参透测试(1):普通权限弱口令/弱加密方式/未授权访问
  • 嘉兴专业网站排名推广网站建设的后期服务要包括什么软件
  • 统计订单总数并列出排名
  • **标题:发散创新:探索Deno框架下的应用开发之旅**摘要:本文将深入探讨Den
  • 网站icp备案新规药品网站 icp
  • Linux对象管理机制
  • 网站建设实训考试做电商的步骤
  • 微信小程序uni.request 返回值存在精度丢失问题
  • 做外贸上哪些网站鹤壁集团网站建设
  • 惠城网站建设有哪些网站建设欧美
  • 注册 区块链节点
  • 硅谷甄选(续2)首页
  • 茂名建设公司网站wordpress写书typecho主题
  • 上海网站建设软件下载唐山的做网站的企业
  • 图解网络(科普版)
  • TensorFlow Implementation of Content-Based Filtering|基于内容过滤的TensorFlow实现
  • 【Pr】Adobe Premiere Pro 2025 学习笔记-01工作流实操
  • 手机端网站模板下载开发者助手app
  • 怎样做网站代理拼多多怎么开店
  • php按步骤做网站苏州企业网站建设服务中心
  • 月报 Vol.04:新增 async test 与 async fn main 语法,新增 lexmatch 表达式
  • 04--CSS基础(3)
  • C语言--函数
  • `String`、`StringBuilder` 和 `StringBuffer`区别卓望一面面试题
  • 【11408学习记录】考研英语阅读长难句得分密码:5层拆解2016真题复杂句!