2026 M-Design设计竞赛 - 基于 i.MX93 的智能家居语音中控终端设计
该项目使用了i.MX93,实现了音频实时通信的设计,它的主要功能为:SIP对讲。
标签
嵌入式系统
imx93
SIP对讲
bigzhu
更新2026-06-09
21

注:以上视频中文字为可商用,没有版权问题

基于 i.MX93 的智能家居语音中控终端设计

一、项目介绍与创意说明

随着智能家居的快速发展,家庭中的设备逐渐从“单点控制”向“集中控制”和“智能交互”演进。其中,语音作为最自然的人机交互方式之一,正在成为智能家居系统的重要入口。然而,目前市场上的智能家居中控设备多依赖云端语音服务,存在隐私风险、延迟较高以及网络依赖性强等问题。

本项目以“智能家居中控”为设计方向,基于 NXP i.MX93 处理器平台,结合高性能音频编解码芯片 AK4619,设计并实现了一套低延迟、本地化处理能力强的智能语音通信终端。该终端具备实时音频通信能力,可作为家庭中的语音交互节点,实现家庭成员之间的远程对讲、紧急呼叫以及语音交互入口功能。

项目的核心创意在于:

  • 将“实时音频通信”能力引入智能家居中控系统
  • 构建一个低延迟、本地优先的语音交互节点
  • 结合嵌入式 Linux + baresip 音频框架,实现灵活的音频管理
  • 提供可扩展接口,未来可接入语音识别、AI分析等功能

该系统不仅可以作为家庭语音中控终端,还可以扩展为家庭安全系统中的语音监测节点,具有较强的应用前景。




二、硬件系统介绍

硬件设计方案,以NXP iMX93 FRDM开发板为核心,设计了音频扩展板。

i.MX93 FRDM开发板通过I2S总线与AK4619音频编解码芯片进行数字音频数据交互,AK4619完成模拟音频与数字音频之间的转换。输入部分由两路麦克风构成,用于采集现场语音信号。输出部分由两片HT6872功放芯片组成双声道立体声放大电路,驱动扬声器输出。另外通过HDMI接口实现系统运行状态和通话界面的图形化显示。硬件架构图如下:

1. 主控平台:i.MX93 FRDM

i.MX93 是 NXP 推出的一款面向边缘计算与低功耗应用的处理器,具备以下特点:

  • ARM Cortex-A55 内核
  • 支持 Linux 系统(Yocto)
  • 丰富的外设接口(I2S、MIPI、USB等)
  • 适合音视频及边缘智能应用

该平台为整个系统提供计算能力和系统调度能力。

2. 音频子系统:AK4619

扩展板上集成了 AK4619 高性能音频编解码芯片,主要功能包括:

  • 多通道音频输入输出
  • 高信噪比音频采集
  • 支持 I2S 接口与 i.MX93 连接

外围设计包括:

  • 麦克风输入电路(用于语音采集)
  • 功放电路(驱动外接扬声器)

该部分是实现实时语音通信的核心硬件基础。

3. 显示子系统:HDMI 显示屏

系统通过 HDMI 接口扩展连接一块 1280*720 分辨率的 LCD 显示屏,主要用于:

  • 显示来电/去电信息
  • 展示系统状态

4. 自研扩展板设计

使用 Cadence 设计工具完成扩展板设计,主要功能模块包括:

  • 音频编解码电路(AK4619)

  • 麦克风与功放接口(LMV321A)


使用Allegro绘制的PCB

TOP视图

BOTTOM视图

焊接好的扩展板

三、系统方案框图与设计思路

1. 系统总体架构

系统整体架构如下:

2. 设计思路

系统设计遵循以下原则:

(1)模块化设计

  • 音频处理、通信、UI、控制逻辑相互解耦
  • 使用 baresip 模块化机制统一管理音频流

(2)低延迟优先

  • 使用 Baresip 实现 SIP 通信
  • 启用 WebRTC AEC 实现回声消除
  • 优化音频 buffer 参数

(3)本地优先处理

  • 音频采集与处理在本地完成
  • 降低对云服务依赖

(4)可扩展性

  • 预留 AI 语音识别接口
  • 可扩展为智能家居控制中心

四、软件流程与关键实现

1. 软件架构

系统软件主要由以下几个部分组成:

  • Baresip:负责 SIP 通信
  • ALSA:音频路由与管理
  • LVGL UI:显示界面
  • Python 主程序:业务控制逻辑

2. 软件流程

整体流程如下:

自定义模块lvlg_ui

基于baresip定制的模块lvlg_ui截图

3. 关键技术点

(1)回声消除(AEC)

通过 baresip 集成 WebRTC AEC,实现:

  • 消除扬声器回放声音被麦克风拾取的问题
  • 提高通话清晰度

baresip相关配置如下:

audio_player            alsa,default
audio_source           alsa,default
audio_alert             alsa,default
# webrtc aec module
module                 webrtc_aec.so

(2)UI模块扩展

在 Baresip 中扩展 LVGL UI 模块,实现:

  • 来电界面显示


  • 呼叫状态显示
  • 基本交互功能

baresip相关配置如下

video_display           lvgl_ui
module_app             lvgl_ui.so
# HDMI card0
lvgl_ui_device         /dev/dri/card0


(3)Python 控制逻辑

Python 程序负责:

  • 启动/管理各服务
  • 按键检测
  • 控制呼叫流程
  • 与 UI 交互

主控制程序main.py如下

import queue
import subprocess
import time
import os
import json

from app_db_config import AppDbConfig
from key_monitor import KeyMonitor
from baresip_client import BaresipClient
from mq import MsgQueue
from shell_executor import ShellExecutor
from sys_settings import SysSettings
from udp_cmd import UdpCommandServer
from app_utils import AppUtils
from multicast_sender import client as multicast_client


# Global event queue for inter-thread communication
event_queue = queue.Queue()


class MainApp:
   def __init__(self):
       self.baresip = None
       self.key_monitor = None
       self.user_cmd = None
       self.call_in_progress = False
       self.incoming_call_waiting = False
       self.ss = SysSettings()
       self.executor = ShellExecutor()

   def reload_config(self):
       """
      Reload the application configuration from the database.
      """
       c = AppDbConfig()
       c.init_db_config()

       # Update system settings based on new config
       self.ss.set_mic_level(c.micfil_volume)
       self.ss.set_ak4619_level(c.ak4619_volume)
       # self.ss.pw_update_volume(c.pw_source_volume, c.pw_sink_volume)

       return "OK"

   def pw_reload_links(self):
       self.ss.setup_pipewire_links()

   def play_wav_file(self, wav_path):
       if not os.path.exists(wav_path):
           print(f"File {wav_path} does not exist.")
           return False
       # alsa_output.platform-sound.stereo-fallback
       self.executor.start(f"aplay {wav_path}")
       return True

   def call_test(self):
       # multicast test: find PCUV device IP
       byteList = bytearray(16)
       byteList[0] = 0xFF
       byteList[7] = 0x01
       byteList[15] = 0xFE
       multicast_client.send(byteList)
       replies = multicast_client.find_device_ip_by_type("FRDM")
       print("replies", replies)
       if len(replies) > 0:
           frdm_ip = replies[0]
           print(f"Found FRDM device at {frdm_ip}, making SIP call...")
           client = f"sip:passenger_pei@{frdm_ip}"
           self.call_in_progress = True
           self.baresip.make_call(client, with_video=True)
       else:
           print("No FRDM device found.")

   def handle_app_cmd(self, cmd: str, data: dict):
       # {'cmd': 'sip-start', 'data': {'client': 'sip:passenger_pei@192.168.123.77'}}
       print(f"app-cmd: {data}")
       try:
           c = data['cmd']
           d = data['data']
           if c == "play":
               wavFile = d.get('file', '1.wav')
               if self.executor.get_pending_count() < 1:
                   # target = "alsa_output.platform-sound.stereo-fallback"
                   target = '"Echo Cancellation Sink"'
                   wav = f"./assets/{wavFile}"
                   if not os.path.exists(wav):
                       return json.dumps({"resp": c, "message": "fail", "info": f"{wavFile} not exist"})
                   # looping audio playback
                   # self.executor.start(f"while true; do pw-cat --playback --target={target} {wav}; done")
                   self.executor.start(f"while true; do aplay {wav}; done")
           elif c == "stop":
               self.executor.stop()
           elif c == "sip-start":
               client = d['client']
               with_video = d.get('video', False)
               self.baresip.make_call(client, with_video=with_video)
           elif c == "sip-stop":
               self.baresip.hangup_call()
           elif c == "sip-mute":
               self.baresip.call_mute()
           elif c == "key-press":
               # simulate key press event
               key = 'KEY_1'
               if d:
                   key = d
               msg = {'event': key, 'state': 'click'}
               event_queue.put(msg)
           else:
               print(f"unhandle app-cmd {cmd} -> {data}")
               return json.dumps({"resp": c, "message": "fail", "info": f"unhandle {cmd}"})

           return json.dumps({"resp": c, "message": "OK"})
       except Exception as e:
           print(f"handle_app_cmd err: {e}")

       return json.dumps({"message": "fail"})

   def start(self):
       try:
           AppUtils.sync_db_if_newer('./assets/app_data.db', '/mnt/param/app_data.db')
           AppUtils.sync_device_db('./assets/device.db', '/mnt/param/device.db')
           try:
               c = AppDbConfig('/mnt/param/app_data.db')
           except Exception as e:
               AppUtils.sync_db_if_newer('./assets/app_data.db', '/mnt/param/app_data.db', force_copy=True)
               c = AppDbConfig('/mnt/param/app_data.db')

           print(f"DB VERSION: {c.version}")

           # system configuration settings
           self.reload_config()
           # self.pw_reload_links()

           self.play_wav_file("./assets/booting.wav")

           # check network online
           print(f"[MAIN] Checking network on interface {c.netinterface}...")
           while True:
               ip = AppUtils.get_ip_address(c.netinterface)
               if ip and not ip.startswith("169.254"):
                   print(f"[MAIN] Network is online: {ip}")
                   break
               time.sleep(1)

           self.user_cmd = UdpCommandServer()
           self.user_cmd.start()

           self.mq = MsgQueue()
           self.mq.start()

           self.baresip = BaresipClient(event_queue)
           self.baresip.start()

           self.key_monitor = KeyMonitor(c.keypath, event_queue)
           self.key_monitor.start()

           # add json command for database
           self.user_cmd.register_json_command("update-sip", c.command)
           self.user_cmd.register_json_command("get-config", c.command)
           self.user_cmd.register_json_command("reload-config", lambda obj, addr: self.reload_config())
           self.mq.register_command("reload-config", lambda cmd, data: self.reload_config())
           self.mq.register_command("app-cmd", self.handle_app_cmd)

           print("[MAIN] FRDM Running ...")

           while True:
               e = event_queue.get()
               event = e.get('event', None)

               if event == 'BTN_1':            # BTN_1
                   print("[MAIN] BTN_1 pressed")
                   self.call_in_progress = False
                   self.incoming_call_waiting = False
                   self.baresip.hangup_call()
               elif event == 'BTN_2':          # BTN_2
                   print("[MAIN] BTN_2 pressed")
                   if self.incoming_call_waiting:
                       print("[MAIN] Answer incoming SIP call")
                       self.incoming_call_waiting = False
                       self.call_in_progress = True
                       self.baresip.answer_call(with_video=True)
                   elif self.call_in_progress:
                       print("[MAIN] Hangup current SIP call")
                       self.baresip.hangup_call()
                   else:
                       self.call_test()
               elif event == 'sipcall':        # MQTT new sip call
                   user = e.get('user', None)
                   if user:
                       print(f"[MAIN] SIP call to {user}")
                       self.baresip.make_call(user, with_video=True)
               elif event == 'sip':            # SIP MESSAGE
                   evtype = e.get('evtype', None)
                   if evtype == 'CALL_CLOSED':
                       self.call_in_progress = False
                       self.incoming_call_waiting = False
                   elif evtype == 'CALL_ESTABLISHED':
                       self.call_in_progress = True
                   elif evtype == 'CALL_INCOMING':
                       self.incoming_call_waiting = True
                       print("[MAIN] Incoming SIP call, press BTN_2 to answer")
               else:
                   print(f"[MAIN] Unknown event: {e}")

       except KeyboardInterrupt:
           self.mq.stop()
           self.baresip.stop()
           self.key_monitor.stop()
           self.user_cmd.stop()
           self.mq.join()
           self.key_monitor.join()
           self.user_cmd.join()


if __name__ == "__main__":
   app = MainApp()
   app.start()


五、功能展示说明

当前系统已实现以下功能:

1. 实时语音通话

  • 基于 SIP 协议实现端到端通话
  • 支持全双工语音通信

2. 回声消除

  • 启用 WebRTC AEC
  • 在扬声器外放场景下仍可正常通话

3. 来电显示界面

  • LCD 屏幕显示来电信息
  • UI 基于 LVGL 实现

默认状态页面(时间显示为2025年是因为设备没有接入互联网,没有同步时间)

呼叫中

接听中,底部会显示已通话时间

设备整体拍照

呼叫中

已接通

4. 按键控制

  • 支持按键触发呼叫
  • 支持接听操作

5. 系统集成运行

  • 所有模块通过 Python 统一调度
  • 系统可稳定运行

设备正常运行log截图

六、设计中遇到的难题与解决方法

1. 回声问题严重

问题:
扬声器声音被麦克风拾取,导致严重回声。

解决:

  • 集成 WebRTC AEC
  • 优化音频路径绑定

2. UI 与通信模块耦合问题

问题:
Baresip 原生无图形界面。

解决:

  • 扩展 LVGL UI 模块
  • 解耦 UI 与通信逻辑

七、心得体会与建议

通过本次比赛项目的设计与实现,收获颇多:

1. 技术层面

  • 深入理解了嵌入式 Linux 音频架构
  • 掌握了 SIP 通信与实时音频处理
  • 提升了硬件设计与软件协同能力

2. 工程经验

  • 模块化设计极其重要
  • 调试复杂系统需要分层定位问题
  • 音频系统调优需要大量实验验证

八、总结

本项目完成了智能家居语音中控终端的核心功能设计,实现了从硬件设计、系统集成到应用开发的完整流程。虽然目前仅实现了部分功能,但已具备完整系统雏形,并具有良好的扩展能力。

未来可进一步扩展:

  • 接入语音识别(ASR)
  • 实现语音控制家电
  • 加入AI异常检测
  • 构建完整智能家居系统
附件下载
ak4619-board.zip
Cadence PCB源文件
baresip-main.zip
baresip和自定义模块lvgl_ui源代码
frdm-app-main.zip
python状态管理程序
团队介绍
个人电子爱好者
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号