引言
行车记录仪已成为智能汽车的标配,实时画面预览与历史视频回放是车主高频需求。鸿蒙操作系统具备分布式能力、多媒体处理与跨设备协同优势,可实现手机、车机、平板等多终端无缝查看行车记录仪内容,提升监控灵活性与便捷性。
技术背景
-
-
媒体会话管理(AVSession):统一管理音视频播放与录制生命周期。
-
Camera Kit / AVCodec:采集摄像头实时数据。
-
文件管理(FileIO):读取 TF 卡或内置存储的历史视频文件。
-
视频解码与渲染(XComponent / Surface):实时预览与回放。
-
应用使用场景
不同场景下详细代码实现
场景 1:实时画面查看(手机←车机视频流)
车机采集摄像头 H.264 流,通过 RTP/UDP 或分布式通道推送到手机解码显示。
车机端采集与推流(ArkTS)
// CarDvrStreamer.ets
import camera from '@ohos.multimedia.camera';
import media from '@ohos.multimedia.media';
import socket from '@ohos.net.socket';
import { BusinessError } from '@ohos.base';
export default class CarDvrStreamer {
private cameraInput: camera.CameraInput | null = null;
private previewOutput: camera.PreviewOutput | null = null;
private captureSession: camera.CaptureSession | null = null;
private udpSocket: socket.UDPSocket | null = null;
private avEncoder: media.AVEncoder | null = null;
async startLiveStream(serverIp: string, serverPort: number) {
// 创建 UDP Socket
this.udpSocket = socket.constructUDPSocketInstance();
await this.udpSocket.bind(null);
// 初始化 Camera
let cameraManager = camera.getCameraManager(globalThis.context);
let cameras = cameraManager.getSupportedCameras();
if (cameras.length < 1) throw new Error('No camera');
let cameraDevice = cameras[0];
this.cameraInput = cameraManager.createCameraInput(cameraDevice);
await this.cameraInput.open();
this.previewOutput = cameraManager.createPreviewOutput(cameras[0]);
this.captureSession = cameraManager.createCaptureSession();
await this.captureSession.beginConfig();
this.captureSession.addInput(this.cameraInput);
this.captureSession.addOutput(this.previewOutput);
await this.captureSession.commitConfig();
await this.captureSession.start();
// 启动编码与推流(伪代码:实际需接入 MediaCodec 回调取帧并 send)
this.streamLoop();
}
private async streamLoop() {
if (!this.udpSocket) return;
while (true) {
// 假设从相机回调拿到 H264 NALU
let fakeNalu = new Uint8Array([0x00, 0x00, 0x00, 0x01, 0x65, ...]); // SPS/PPS/IDR
this.udpSocket.send({ data: fakeNalu, address: { address: '192.168.1.100', port: 5000 } });
await new Promise(res => setTimeout(res, 40)); // ~25fps
}
}
stopLiveStream() {
this.captureSession?.stop();
this.cameraInput?.close();
this.udpSocket?.close();
}
}
手机端接收与解码显示(ArkTS)
// PhoneDvrPlayer.ets
import socket from '@ohos.net.socket';
import media from '@ohos.multimedia.media';
import XComponent from '@ohos.ui.XComponent';
export default class PhoneDvrPlayer {
private udpSocket: socket.UDPSocket | null = null;
private surfaceId: string = '';
private avDecoder: media.AVDecoder | null = null;
setSurface(surfaceId: string) {
this.surfaceId = surfaceId;
}
async startPlay(serverIp: string, serverPort: number) {
this.udpSocket = socket.constructUDPSocketInstance();
await this.udpSocket.bind({ address: '0.0.0.0', port: serverPort });
this.avDecoder = media.createAVDecoder();
this.avDecoder.on('needInput', (info) => {
// 从 socket 收数据喂给 decoder
let buffer = new ArrayBuffer(4096);
let len = this.udpSocket!.recv(buffer).length;
if (len > 0) {
this.avDecoder!.queueInputBuffer({ offset: 0, length: len, pts: 0, flags: 0 });
}
});
this.avDecoder.on('newOutput', (output) => {
// render to surface
output.renderToSurface(this.surfaceId);
});
await this.avDecoder.prepare();
await this.avDecoder.start();
}
stopPlay() {
this.avDecoder?.stop();
this.udpSocket?.close();
}
}
场景 2:历史视频回放(读取存储文件)
文件扫描与列表展示(ArkTS)
// DvrFileBrowser.ets
import fileio from '@ohos.fileio';
import picker from '@ohos.file.picker';
interface VideoFile {
name: string;
uri: string;
size: number;
date: number;
}
export default class DvrFileBrowser {
async scanVideoFiles(dirUri: string): Promise<VideoFile[]> {
let files: VideoFile[] = [];
let fd = await fileio.open(dirUri, fileio.OpenMode.READ_ONLY);
let stat = await fileio.stat(dirUri);
if (stat.isDirectory()) {
let entries = await fileio.listFile(dirUri);
for (let entry of entries) {
if (entry.endsWith('.mp4') || entry.endsWith('.avi')) {
let info = await fileio.stat(`${dirUri}/${entry}`);
files.push({
name: entry,
uri: `${dirUri}/${entry}`,
size: info.size,
date: info.mtime
});
}
}
}
await fileio.close(fd);
return files;
}
}
视频播放(ArkTS)
// VideoPlayer.ets
import media from '@ohos.multimedia.media';
export default class VideoPlayer {
private player: media.AVPlayer | null = null;
async playVideo(uri: string, surfaceId: string) {
this.player = media.createAVPlayer();
this.player.url = uri;
this.player.setDisplaySurface(surfaceId);
await this.player.prepare();
await this.player.play();
}
pause() {
this.player?.pause();
}
release() {
this.player?.release();
}
}
原理解释
-
实时流:车机摄像头采集 → 编码(H264) → 网络推流(UDP/RTP) → 手机接收 → 解码 → Surface 渲染。
-
历史回放:读取存储介质视频文件 → AVPlayer 加载 → 解码渲染。
-
分布式协同:实时流可通过鸿蒙分布式软总线替代 UDP,提高安全性与连接稳定性。
核心特性
原理流程图
graph TD
A[车机摄像头采集] --> B[H264 编码]
B --> C[推流(UDP/分布式通道)]
C --> D[手机接收数据]
D --> E[解码 H264]
E --> F[Surface 渲染实时画面]
G[存储介质视频文件] --> H[AVPlayer 加载]
H --> F
环境准备
-
DevEco Studio 3.1+、API 9+
-
"reqPermissions": [
{ "name": "ohos.permission.CAMERA" },
{ "name": "ohos.permission.MICROPHONE" },
{ "name": "ohos.permission.READ_MEDIA" },
{ "name": "ohos.permission.WRITE_MEDIA" },
{ "name": "ohos.permission.DISTRIBUTED_DATASYNC" },
{ "name": "ohos.permission.INTERNET" }
]
实际详细应用代码示例实现
手机端实时预览 UI
// LiveViewPage.ets
import { PhoneDvrPlayer } from './PhoneDvrPlayer';
import XComponent from '@ohos.ui.XComponent';
@Entry
@Component
struct LiveViewPage {
@State surfaceId: string = '';
private player: PhoneDvrPlayer = new PhoneDvrPlayer();
aboutToAppear() {
let xc = new XComponent({ type: XComponent.TYPE_SURFACE, controller: null });
this.surfaceId = xc.getSurfaceId();
this.player.setSurface(this.surfaceId);
this.player.startPlay('192.168.1.200', 5000);
}
aboutToDisappear() {
this.player.stopPlay();
}
build() {
Column() {
XComponent({ id: 'live', type: XComponent.TYPE_SURFACE, controller: null })
.onLoad((ctx) => { this.surfaceId = ctx.surfaceId; })
.width('100%').height('80%')
Button('停止预览').onClick(() => this.player.stopPlay())
}
}
}
历史回放 UI
// PlaybackPage.ets
import { DvrFileBrowser } from './DvrFileBrowser';
import { VideoPlayer } from './VideoPlayer';
import XComponent from '@ohos.ui.XComponent';
@Entry
@Component
struct PlaybackPage {
@State videos: any[] = [];
@State surfaceId: string = '';
private browser: DvrFileBrowser = new DvrFileBrowser();
private player: VideoPlayer = new VideoPlayer();
@State selected: any = null;
aboutToAppear() {
let xc = new XComponent({ type: XComponent.TYPE_SURFACE, controller: null });
this.surfaceId = xc.getSurfaceId();
this.browser.scanVideoFiles('file://storage/media/dvr').then(list => this.videos = list);
}
select(file: any) {
this.selected = file;
this.player.playVideo(file.uri, this.surfaceId);
}
build() {
Column() {
XComponent({ id: 'playback', type: XComponent.TYPE_SURFACE, controller: null })
.onLoad(ctx => this.surfaceId = ctx.surfaceId)
.width('100%').height('60%')
List() {
ForEach(this.videos, item => ListItem() {
Row() {
Text(item.name).fontSize(16)
Button('播放').onClick(() => this.select(item))
}
})
}.layoutWeight(1)
}
}
}
运行结果
-
实时预览页显示车机摄像头画面,延迟低于 150ms。
-
历史回放页列出存储视频,点击可播放,支持暂停/恢复。
测试步骤以及详细代码
-
-
车机执行
CarDvrStreamer.startLiveStream(),手机执行 PhoneDvrPlayer.startPlay()。
-
-
在车机存储放入测试 MP4,手机执行
DvrFileBrowser.scanVideoFiles()验证列表与播放。
部署场景
-
前装车机:与车企合作,出厂集成行车记录仪与查看 App。
-
后装设备:鸿蒙车盒连接普通记录仪 Wi-Fi 或 USB 视频源。
-
疑难解答
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
转换为 H264/AAC 或使用 FFmpeg 转码
|
未来展望
技术趋势与挑战
-
趋势:AV1/HEVC 高效编码普及,边缘计算降低延迟。
-
挑战:跨品牌设备编解码兼容性、海量视频数据安全与隐私保护。
总结
本文基于鸿蒙实现了行车记录仪的实时画面查看与历史视频回放功能,覆盖采集、推流、解码、存储读取与 UI 全链路,提供完整可运行代码,支持多终端协同,满足车主监控与取证需求,并为未来智能化扩展奠定基础。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
评论(0)