☰
Hi3536音频单声道改双声道:驱动与编码链路实战
2026/10/19 9:32:34 网站建设 项目流程

做嵌入式音视频设备的开发者,大概率都碰过Hi3536这颗芯片。它在NVR、视频采集盒这类产品里出镜率很高,常见的音频需求就是录音和放音。我这次拿到手的项目更直接:产品只有一路麦克风输入,扬声器也是单声道,但客户要求编码出的视频音频轨必须能被播放器识别为双声道,这就引出了标题里说的“Hi3536音频单声道改双声道”。这需求听起来不复杂,真正做起来却要跨硬件通路、内核驱动、应用编码三层,踩过一圈坑之后,我想把完整的排查过程和改动方案记录下来,给同样在这个芯片平台上做音频适配的朋友一个参考。

1. 先弄清楚:单声道音频到底“单”在哪一层

1.1 硬件单声道与软件单声道的区别

动手之前,必须先分清所谓“单声道”到底是在哪个环节形成的。有的产品是硬件上只有一路MIC输入,音频编解码芯片只做单路模拟信号采样,这样采集到的PCM数据天然就是单声道。有的产品硬件其实支持两路输入,但驱动代码里只使能了其中一路通道。还有一种是硬件立体声,驱动和配置也都是立体声,但上层应用创建编码通道时,填写音频编码参数结构体时把声道数写成了1,最终封装出来的视频文件音轨就是单声道。

这三种情况的改法完全不同。硬件单声道最麻烦,因为模拟前端只有一路输入,你没法简单靠配置就真的变成两路独立信号,只能在软件端做数据处理,把一份数据复制成左右两份;驱动只开了单通道的情况相对好办,找到音频输入模块的通道选择寄存器或SDK配置,把另一路使能即可;纯应用层参数错误则最简单,改一个变量就能恢复。绝大多数批量生产的板卡到送测阶段,硬件早就定型了,你能动的只有驱动和应用层,目标也变成了“让系统声卡上报为双声道,播放和录制出来的内容左右声道都有有效信号”。

这里有个特别容易混淆的概念:用户说“我要双声道”,有时候只是希望耳机或喇叭两边都有声音,有时候则是要求录像文件在后期编辑软件里能看到两个分开的波形轨道。这两种诉求的落地路径完全不同。前者只要在输出侧做数据复制就行,后者必须让采集、编码、封装整条链路都以双声道去解析和处理。我这次接到的需求属于后者,客户明确要检查音轨的立体声属性,所以从一开始就得按完整链路来改。

1.2 为什么上层复制了数据,播放出来还是单声道

很多开发者第一反应是“底层不改,我在应用层把单声道PCM复制一份变成双声道不就行了吗”。这个思路在PC上玩过音频处理的人都很容易产生,但在Hi3536平台上会遇到一个非常迷惑的问题:上层数据明明已经是左声道一份、右声道一份,播放出来还是单声道,甚至用播放器打开录像文件,音轨依旧显示1声道。

原因在于Hi3536的音频链路并不是简单把应用写的数据直接送到物理输出。音频输入和输出由芯片内部专门的音频模块管理,数据要通过DMA搬运、I2S或数字音频接口传输到外置codec。驱动初始化I2S时,会根据配置的声道数来决定帧格式,比如LRCK信号的解析方式、每个采样周期搬几个字节。如果驱动里还是单声道配置,上层送进来的双声道数据在DMA搬运时会被底层按单声道帧格式拆解。举个例子:一个左声道采样点是0x0101,右声道采样点是0x0202,在单声道配置的底层眼中,这会被当成两个独立的单声道采样点来处理,数据错位,最终听感上依然是一路信号。

更隐蔽的是编码器环节。Hi3536支持硬件音频编码,创建编码通道时,有一个音频编码参数的配置项明确写着声道数。如果这个参数保持默认的1,硬件编码器会按单声道的采样节奏去读取输入PCM缓存,哪怕前面驱动已经正确处理成了双声道交织数据,编码器也只取其中一半来用,最终码流里的声道信息自然还是1。所以“单声道改双声道”严格来说是一串连锁改动,不是某一个点的调整。

1.3 把目标拆解成三个可验证的小结论

我在项目一开始就给自己定了一个可验证的验收标准,把“改双声道”拆成三个必须同时满足的小目标。第一,采集端能获得双声道的PCM数据,换句话说,应用层从音频输入接口读到的帧缓冲区里,左右声道的内容都在,而且格式正确。第二,输出端能按双声道的帧格式把数据送出去,也就是I2S的LRCK翻转正常、DMA的传输字节数与周期匹配、codec侧的左右声道寄存器都配置正确。第三,音视频封装层的声道信息必须为2,编码器创建通道时的参数、封装模块写入视频文件音轨头的信息都要同步更新。

这三个目标每完成一个,我就用对应的工具做一次验证,避免所有问题攒到最后一起排查。实际做下来这个习惯帮了大忙,因为链路里每一层的改动都可能牵动下一层。比如仅仅把AI通道的声道数改成2,如果codec硬件只接了单路输入,那右声道采到的一直是接近静音的无效值,这种“假立体声”在波形图上能看出来,但靠耳朵听很可能忽略。先把目标拆细、定好验收方法,再动手改代码,后面会省掉大量来回试错的时间。

2. 改双声道的整体思路与方案取舍

2.1 四种可行方案:硬件、驱动、应用、伪装

我在评估这个改造时,整理了四条技术路线,按彻底程度从高到低排开。第一条是硬件真改,在设备上增加第二路MIC输入或线性输入,从模拟前端就实现真正的立体声采集。这是最理想的情况,但需要改原理图和PCB,量产板卡基本不考虑。第二条是驱动层扩展,保持硬件单输入不变,在音频驱动内部把采集到的单声道数据复制成双声道,同时把I2S和DMA配置改成双声道模式。这条方案的普适性最好,所有跑这个驱动的应用都会自动获得“立体声”能力。第三条是应用层处理,业务代码里自己复制PCM数据,再以双声道格式送入编码器或播放器。这条方案改动局部,适合驱动难以改动或时间紧张的场景。第四条是封装伪装,只修改视频文件容器里的声道信息字段,让播放器误认为这是双声道。

第四条基本属于歪门邪道,因为播放器在解码时发现实际数据长度和声道数对不上,轻则静音,重则产生持续爆音。但对于纯演示环境,偶尔有人这么干,我只能说慎用。对我这次的情况,硬件改不了,应用层方案虽然快,但项目里有多个业务进程都在采集和播放音频,每个进程都要处理一遍,漏掉一个就出问题,所以最后选了驱动层方案作为主线,应用层逻辑反而不用动。

2.2 为什么优先做驱动层改动

从维护角度讲,驱动层的改动有一个天然优势:用户态程序看到的音频接口参数是统一的,改完后所有调用方都能透明获得双声道能力,不用逐个进程适配。而且Hi3536的SDK把音频模块的接口封得比较完整,改动通常集中在几个关键配置结构体里,不涉及大量算法逻辑。相比改应用代码,驱动层改动引入的不确定因素反而更可控。

但驱动层方案也有明显代价。第一,修改后影响范围是全系统的所有音频通路,如果板卡同时把同一路I2S引到HDMI或模拟输出,声道数变化可能会导致另一端设备解码异常。第二,需要重新编译内核模块并烧录,每验证一次都要走一遍完整的集成流程。第三,DMA相关的buffer大小和周期必须重新计算,一旦算错,跑一段时间后会出现周期性爆音,这类问题最让人头疼。权衡之下,我还是坚持驱动层方案,因为后续产品线可能复用这套驱动,一次投入长期受益。

2.3 常见方案的横向对比

为了更直观地说明选型依据,我把这几条路线整理成了对比表,方便遇到类似需求的朋友直接参考。

改造层级实现方式改动量风险点适用场景
硬件增加第二路MIC、调整模拟前端大高,需要重新打样新品定义阶段
驱动I2S/DMA/codec配置改为双声道,内部做单声道数据扩展中中,影响全系统音频通路多数量产板卡升级
应用应用内复制PCM并设置编码器声道数小低,但每个进程都要处理时间紧、驱动不好动
封装伪装只修改容器中的声道信息极小高,播放器可能解码异常临时演示,不建议

表格里的驱动方案风险写的是“中”,原因是声道数变化后,同一路数字音频接口输出的数据格式整体变了。比如某块板子的I2S音频同时供给本地喇叭和HDMI,HDMI对端设备如果只支持单声道PCM,收到立体声流后可能直接不输出声音。这种问题在前期评估时很难发现,只有在验证阶段覆盖各输出接口的实际场景才能暴露。我自己就在这部分吃过亏,后面会专门展开讲。

3. 实操过程:从驱动到编码器的完整链路修改

3.1 准备环境与确认当前音频通路

动手前先把环境和验证工具备齐。Hi3536的SDK一般自带内核源码、音频库和示例程序,交叉编译工具链要保证能正常编译出内核模块。板卡跑起来之后,我习惯先做三件事:一是通过调试串口进入系统,查看声卡设备注册情况;二是用SDK自带的音频采集示例把一段PCM数据保存出来;三是确认应用层拿到的PCM格式参数,重点是声道数。这三个信息是后面所有改动的基准。

这里有一个很实用的小技巧:保存PCM数据时,不要只存几十个字节,至少采集一秒钟的数据,也就是在48kHz采样率、16bit位深下,单声道大约96KB左右的数据量。拿到数据后放到PC上,用音频分析工具打开看波形。单声道数据的波形只有一条轨道,而双声道交织数据能看到两条独立的轨道。如果两条轨道内容完全一样,说明数据本身就是单声道复制出来的;如果只有一条轨道有波形、另一条是直线,说明驱动配置成了双声道但硬件只有单路有效信号。这一步能给后续方案提供很直接的判断依据。

3.2 AI通道的声道配置与数据扩展逻辑

确认现状之后,我开始改AI侧。在Hi3536的SDK音频输入初始化接口中,会传入一个音频采样参数结构体,里面包含采样率、位深和声道数。先把声道数从1改成2,再编译运行,采集一段PCM数据看结果。正如前面说的,因为硬件只有一路模拟输入,双声道配置下右声道会采到接近静音的数据。这时需要在AI数据处理里加一个扩展逻辑,把左声道数据和右声道数据做混合后复制到两个声道上。

这个扩展逻辑的示意是这样的,实际项目中你可以根据自己的位深和交织方式调整:

/* 单声道扩展为双声道:基于交织16bit PCM */ void mono_to_dual(short *pcm_in, short *pcm_out, int frame_len) { for (int i = 0; i < frame_len / 2; i++) { short sample = (pcm_in[i * 2] + pcm_in[i * 2 + 1]) / 2; pcm_out[i * 2] = sample; pcm_out[i * 2 + 1] = sample; } }

注意,这个例子默认输入已经是左右声道交织排列,且左声道有数据、右声道接近静音。如果驱动或DMA配置的是非交织模式,也就是一段数据全是左声道、紧接着一段全是右声道,那处理方式就完全不同了,需要按半帧为单位进行复制。判断交织和非交织的方法很简单:采集一段已知只有左声道有信号的数据,如果每两个采样点里第一个值接近有效幅度、第二个值接近0,说明是交织;如果连续几十个采样点都是有效值、然后连续几十个采样点都是近0值,说明是非交织。这个判断错误会导致后面全部白做,必须在一开始就确认清楚。

3.3 AO侧的I2S与codec配置

AI改完之后,接着改AO。这里核心是I2S总线格式。I2S协议中,左右声道的数据通过LRCK信号的电平进行区分,两个声道共用一条数据线和一条时钟线。驱动初始化I2S时,会设定数据格式是单声道还是双声道。单声道模式下,驱动会忽略其中一个LRCK电平周期的数据,只取另一个周期;双声道模式下,左右声道的采样点都会被打包进DMA传输。

改AO的方式通常有两种。一种是在驱动初始化时把声道数配置成2,让I2S驱动按双声道帧格式生成LRCK。另一种是保持I2S单声道配置,在DMA搬运或送给codec之前做数据重新打包。第一种方式更干净,但前提是你要能找到驱动里对应的格式配置结构体。我实际操作时会在驱动日志里加上一段初始化参数打印,把声道数、位深、采样率、buffer大小全部打出来,确认改动已经生效。

有一点容易被忽略:codec芯片本身也有左右声道的控制寄存器。声道数改成2之后,要同步确认codec处在立体声模式,而不是强制单声道输出模式。有的codec提供通道选择寄存器,可以通过I2C或SPI总线配置,如果这个寄存器值不对,I2S链路输出的立体声数据到了codec那一步依然会被合成一路。调试时可以在codec驱动加载完成后,通过驱动导出的debug文件查看各寄存器的值,确认无误后再继续。

3.4 编码器通道与封装信息同步修改

AI和AO都改完,只能保证设备本机录音和播放是双声道,视频文件里的音轨属性未必跟着变。问题出在硬件编码器和封装模块。Hi3536平台的音视频编码走的是硬件编码器,创建音频编码通道时,传入的编码参数结构体里有声道数字段,如果不改成2,编码器就会把输入的双声道PCM按单声道节奏重新解析,采样点错乱,码流里记录的音轨属性也依然是1声道。

这里要特别留意SDK示例代码里可能存在硬编码。我在项目中遇到的实际情况是,驱动和编码器参数都已经改成2了,但生成的MP4文件用播放器打开还是显示单声道。最后定位发现,是示例工程里封装模块有一个单独定义的声道宏,写死为1,打包音轨头时用了这个宏。这种问题纯靠排查链路很难发现,因为每个环节都认为是别人那边的问题。建议在验证时直接用播放器看最终文件的音频属性,不要只看中间某个模块的日志,否则极容易被误导。

3.5 编译、烧录与三阶段验证

代码改完之后,编译内核音频驱动模块和SDK应用,按平台标准方式烧录。我习惯分三步验证。第一步是设备本地播放测试,准备一个左声道和右声道内容不同的立体声文件,左声道是1kHz正弦波,右声道是440Hz正弦波,播放时仔细听两个喇叭或耳机的声音。如果左右两侧听到的声音不同,说明AO链路正确;如果两边听起来完全一样,说明右声道数据在底层被丢弃或混合了。

第二步是录音测试,用板上麦克风录音,生成PCM或WAV文件,到PC上打开看波形轨道数量。如果能看到两条独立轨道,且两条轨道内容一致或者接近一致,说明AI链路的双声道数据处理生效。第三步是完整录像流程,把音频编码、视频编码、音视频封装全部走一遍,生成最终交付的媒体文件,再用播放器和音频分析工具核对声道属性。这三步全部通过,才算改造完成。

4. 踩坑实录与问题排查技巧

4.1 常见问题速查表

整个改造过程中,我陆续踩过不少坑,也帮同事排查过类似问题。下面这张表整理了最常见的现象、直接原因和排查方向,可以直接当作速查手册使用。

现象直接原因排查方向
系统声卡列表上报仍然是1声道驱动或设备树配置未生效检查模块是否重新编译加载、配置是否被覆盖
播放时只有一侧有声音AO或I2S仍未按双声道配置检查LRCK波形、用左右不同内容测试文件
录音文件在PC上显示单声道AI通道与编码器参数不一致同步检查AI参数和编码器声数字段
声音明显失真且有规律爆音DMA的period/buffer大小未按新字节数重算计算双声道下每秒字节数,重设DMA参数
视频文件音轨显示为2声道但播放无声封装层写入的声道信息和实际数据不匹配重点排查封装模块中硬编码的声道宏
接HDMI对端设备无声I2S改双声道后HDMI音频包结构随之变化评估HDMI音频通路的格式兼容性

这里最让我印象深刻的是DMA周期大小导致爆音的问题。声道数从1变2,数据传输量翻倍,如果驱动里buffer_size和period_size还按单声道字节数计算,一个中断周期内搬不完当前数据,下一个周期的数据提前到来,缓冲区就被覆盖,结果就是持续不断的爆音。这种问题不会在启动阶段报错,往往要播放一段时间才能听出来,定位起来相当隐蔽。

4.2 几个亲测有效的调试方法

调试音频最有效的手段就是把PCM数据抓出来拿PC分析。设备端采集一段数据导出到主机,用十六进制查看工具直接看采样点排列。16bit单声道数据,一段连续的采样点都是有效值且每个采样点后紧跟着的下一个采样点也是同样值,说明数据是重复的;16bit交织双声道数据,两个采样点为一组,第一组里的两个数分别对应左右声道。通过统计前几十个采样点的规律,基本就能判断数据格式对不对。

如果平台走的是标准ALSA框架,用ALSA工具查看声卡参数非常直观,可以一次性拿到声道数、位深、采样率等信息。如果是SDK私有音频接口,就在驱动代码里增加打印,把初始化时收到的声道数和DMA配置都打出来。把驱动调试开关打开,在AI和AO初始化时刻分别打印一次声道数、位深、采样率、buffer大小,所有参数一目了然,问题定位速度能提高不少。

4.3 性能开销与长期稳定性的一点心得

“单声道改双声道”听起来是数据量翻倍,很多人担心CPU扛不住。实际测试下来,Hi3536的音频处理吞吐量应付立体声绰绰有余,48kHz采样率、16bit位深、双声道每秒钟的数据量大概是188KB左右,这个量级对芯片来说完全不是压力。真正要留意的反而是内存带宽,特别是有多路视频编码同时跑的设备,多录一路立体声可能会和视频编码器抢DMA带宽。所以改造之后不要只测试音频功能,要跑一遍整机满载的场景,确认不影响视频流畅度。

还有一个很容易被忽略的问题:代码中所有与声道数相关的逻辑,不只是一个初始化参数,还有循环里的帧长度计算、DMA中断处理里的剩余数据量判断、编码器输入缓存的大小估算、上层缓冲区的申请,全部要跟着改成2。我见过一种典型的低级错误:初始化参数改成了双声道,但DMA中断服务函数还在按单声道帧长度计算剩余数据量,导致数据越攒越多、缓冲区溢出。这类问题要在老化和多路并发测试里才会暴露,所以建议改完之后做至少12小时的连续播放录音测试,中途反复切换音视频通道,稳定性过关后再合入正式版本。

考虑到后续产品线可能还会遇到类似需求,我的体会是不要在驱动里把双声道写得太死,最好把声道数做成一个运行时可配置的参数,通过设备树或者模块加载参数传入。这样单声道设备和双声道设备可以共用一套驱动,只是参数配置不同,能省下大量重复劳动。我这次先按固定双声道实现,随后又花时间改成可配置,之后其他项目再碰到机器变体时,基本只调参数不动代码,复用效果非常明显。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询