简介:CSDN博客专家、《Android系统多媒体进阶实战》作者
博主新书推荐:《Android系统多媒体进阶实战》🚀
Android Audio工程师专栏地址:Audio工程师进阶系列【原创干货持续更新中……】🚀
Android多媒体专栏地址:多媒体系统工程师系列【原创干货持续更新中……】🚀
专题一 二:AAOS车载系统+AOSP14系统攻城狮入门视频实战课🚀
专题三:Android14 Binder之HIDL与AIDL通信实战课🚀
专题四:Android15快速自定义与集成音效实战课🚀
专题五:Android15音频策略实战课🚀
专题六:Android15音频性能实战课(无声/杂音/断音/爆音实战案例)🚀
人生格言:人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药.
🍉🍉🍉文章目录🍉🍉🍉
- 🌻1.前言
- 要点概括
- 🌻2.应用场景与用法
- 函数原型
- 参数说明
- 返回值
- 应用场景
- 🌻3.调用流程剖析
- 🌻3.1核心步骤
- 🌻3.2调用流程图
- 🌻3.3生命周期图
- 🌻4.实战应用案例
- 🌻5.一句话总结
🌻1.前言
本篇目的:
Linux PipeWire深度解析之pw_stream_dequeue_buffer调用流程与实战。
要点概括
核心功能:从PipeWireStream的缓冲队列中取出一个当前周期可处理的pw_buffer。
工作机制:在process回调中,根据Stream方向取得可读或可写Buffer,应用处理完成后再通过pw_stream_queue_buffer归还。
典型用途:音频播放填充PCM数据、音频采集读取PCM数据、视频帧生产、视频帧消费、低延迟媒体处理链路。
pw_stream_dequeue_buffer的本质不是“分配Buffer”,而是“从Stream已经协商好的Buffer池中取出一个可用Buffer”。Buffer的创建、格式协商、内存映射和生命周期管理不由它完成,而是在Stream连接和Buffer协商阶段完成。
它通常工作在process回调中。对于播放流,dequeue得到的是一个可填充的空Buffer;对于采集流,dequeue得到的是一个已经带有媒体数据的Buffer。应用只在dequeue到queue这段时间内短暂持有Buffer。
它和pw_stream_queue_buffer是一对接口。dequeue负责取出,queue负责归还或提交。它和pw_stream_return_buffer也不同,return_buffer更偏向于把取出的Buffer直接退回,不表示已经完成正常的媒体生产或消费。它和pw_stream_get_time_n也不同,后者用于查询Stream时间信息,不参与Buffer流转。
🌻2.应用场景与用法
pw_stream_dequeue_buffer
是PipeWire的Stream API中用于从Stream缓冲队列取出可处理Buffer的接口。
它位于PipeWire客户端媒体数据路径的核心位置。应用创建Stream、连接目标Node、完成格式和Buffer协商之后,PipeWire会在合适的图调度周期触发process回调。应用在process回调中调用pw_stream_dequeue_buffer取得Buffer,然后读取或写入媒体数据,最后调用pw_stream_queue_buffer把Buffer交还给PipeWire继续调度。
pw_stream_dequeue_buffer用于在process回调中取得当前周期可读或可写的Buffer。
函数原型
structpw_buffer*pw_stream_dequeue_buffer(structpw_stream*stream);参数说明
structpw_stream*stream;stream表示已经创建并连接的PipeWireStream对象。
它必须是当前媒体处理链路中的有效Stream。正常情况下,该函数应在该Stream对应的process回调中调用,避免跨Stream错误使用Buffer。Buffer属于触发该回调的Stream,不能把一个Stream取出的Buffer交给另一个Stream使用。
返回值
成功时返回:
structpw_buffer*表示当前周期可处理的Buffer。
返回NULL表示当前没有可用Buffer。它不等价于设备断开,也不一定表示Stream错误。工程上遇到NULL时,通常直接退出本次process回调,等待下一次调度。
返回的pw_buffer内部会关联底层spa_buffer。应用真正读写媒体数据时,通常会访问:
structspa_buffer*buf=b->buffer;structspa_data*data=&buf->datas[0];其中datas保存实际媒体数据区,chunk描述本次有效数据的offset、size、stride等信息。
应用场景
第一类场景是音频播放。
应用创建PW_DIRECTION_OUTPUT方向的Stream,用于向PipeWire图中生产音频数据。process回调触发后,应用通过pw_stream_dequeue_buffer取出空闲Buffer,把PCM数据写入datas,然后设置chunk信息,最后调用pw_stream_queue_buffer提交给PipeWire播放链路。
第二类场景是音频采集。
应用创建PW_DIRECTION_INPUT方向的Stream,用于从PipeWire图中消费音频数据。process回调触发后,应用通过pw_stream_dequeue_buffer取出已经带有采集数据的Buffer,从datas中读取PCM数据,处理完成后调用pw_stream_queue_buffer归还Buffer。
第三类场景是视频处理。
PipeWire不仅处理音频,也处理视频帧。视频源、摄像头采集、屏幕共享、视频播放等场景,也可以通过类似的Buffer模型在process回调中交换帧数据。
第四类场景是低延迟实时处理。
在实时音频、虚拟声卡、回声消除、DSP处理、音视频桥接等场景中,Buffer不能长时间停留在应用侧。dequeue、处理、queue应尽量在一个短路径内完成,减少额外缓存和调度延迟。
🌻3.调用流程剖析
🌻3.1核心步骤
1.应用创建pw_stream对象,并注册process、add_buffer、remove_buffer等事件回调。
2.应用调用pw_stream_connect连接到PipeWire图中的目标对象,并完成媒体格式、参数和Buffer协商。
3.PipeWire完成Buffer准备后,通过add_buffer事件把Buffer登记到Stream内部缓冲池。
4.Stream进入PAUSED或STREAMING状态后,PipeWire图调度开始驱动process回调。
5.应用在process回调中调用pw_stream_dequeue_buffer,从Stream可用队列中取出一个pw_buffer。
6.如果返回NULL,说明当前周期没有可用Buffer,本次process应快速返回。
7.如果是播放/输出流,应用把音频或视频数据写入Buffer,并设置chunk有效数据范围。
8.如果是采集/输入流,应用从Buffer中读取已经到达的音频或视频数据。
9.处理完成后,应用调用pw_stream_queue_buffer把Buffer归还给Stream。
10.PipeWire在后续图调度周期中继续复用该Buffer,直到remove_buffer事件或Stream销毁。
🌻3.2调用流程图
🌻3.3生命周期图
🌻4.实战应用案例
下面以“播放流填充PCM数据”为例,说明pw_stream_dequeue_buffer在真实开发中的用法。
播放流的核心目标是:PipeWire通知应用需要数据时,应用取出一个空Buffer,把PCM帧写进去,然后提交给PipeWire。
structapp_data{structpw_stream*stream;uint32_tframe_size;};staticuint32_tfill_pcm_data(void*dst,uint32_tmax_bytes){/* * 这里通常来自解码器、环形缓冲区、音频算法或业务侧PCM缓存。 * 返回实际写入的字节数。 */return0;}staticvoidon_process(void*userdata){structapp_data*app=userdata;structpw_buffer*b;structspa_buffer*buf;structspa_data*data;uint32_tn_bytes;b=pw_stream_dequeue_buffer(app->stream);if(b==NULL)return;buf=b->buffer;data=&buf->datas[0];if(data->data==NULL||data->chunk==NULL){pw_stream_queue_buffer(app->stream,b);return;}n_bytes=fill_pcm_data(data->data,data->maxsize);data->chunk->offset=0;data->chunk->size=n_bytes;data->chunk->stride=app->frame_size;pw_stream_queue_buffer(app->stream,b);}这个案例中,pw_stream_dequeue_buffer只负责取出Buffer。真正的数据来源可能是播放器解码后的PCM、网络音频流、DSP输出、测试音源或者上游环形缓冲区。
工程上要特别注意三点。
第一,返回NULL时不要误判为设备异常。低延迟图调度中,当前周期没有Buffer是可能出现的正常情况。
第二,处理完必须归还Buffer。如果dequeue之后长期不queue,Stream可用Buffer会越来越少,最终导致卡顿、延迟增大或数据流停止。
第三,process回调要保持轻量。不要在里面做阻塞文件读写、复杂锁等待、长时间内存分配或耗时算法。对于实时音频链路,process回调越短,系统越稳定。
采集流的使用方式类似,只是Buffer方向相反。播放流是“应用写Buffer”,采集流是“应用读Buffer”。
staticvoidon_capture_process(void*userdata){structapp_data*app=userdata;structpw_buffer*b;structspa_buffer*buf;structspa_data*data;constuint8_t*src;uint32_tsize;b=pw_stream_dequeue_buffer(app->stream);if(b==NULL)return;buf=b->buffer;data=&buf->datas[0];if(data->data==NULL||data->chunk==NULL){pw_stream_queue_buffer(app->stream,b);return;}src=SPA_PTROFF(data->data,data->chunk->offset,uint8_t);size=data->chunk->size;/* * 这里通常把采集到的数据送入录音文件、编码器、 * 回声消除算法、语音识别前处理或网络发送模块。 */(void)src;(void)size;pw_stream_queue_buffer(app->stream,b);}播放和采集的代码结构很像,但语义不同:
播放流中,Buffer是待填充的数据载体。
采集流中,Buffer是已经包含有效数据的数据载体。
这也是pw_stream_dequeue_buffer最容易被误解的地方。它不是单纯的“读取函数”,也不是单纯的“写入函数”,而是根据Stream方向交出当前周期可处理的Buffer。
🌻5.一句话总结
pw_stream_dequeue_buffer是PipeWireStream数据路径中的取Buffer接口:在process回调中取出当前周期可用的pw_buffer,播放流用它填充数据,采集流用它消费数据,处理完成后必须用pw_stream_queue_buffer归还。