简介: CSDN博客专家、《Android系统多媒体进阶实战》作者
博主新书推荐:《Android系统多媒体进阶实战》🚀
Android Audio工程师专栏地址: Audio工程师进阶系列【原创干货持续更新中……】🚀
Android多媒体专栏地址: 多媒体系统工程师系列【原创干货持续更新中……】🚀
专题一 二:AAOS车载系统+AOSP14系统攻城狮入门视频实战课 🚀
专题三:Android14 Binder之HIDL与AIDL通信实战课 🚀
专题四:Android15快速自定义与集成音效实战课 🚀
专题五:Android15音频策略实战课 🚀
专题六:Android15音频性能实战课(无声/杂音/断音/爆音实战案例) 🚀
人生格言: 人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药.

🍉🍉🍉文章目录🍉🍉🍉
🌻1.前言
本篇目的:理解
gst_audio_encoder_set_output_format()如何接收编码器子类提供的输出Caps,分析它如何验证并保存输出格式,以及这些Caps何时真正设置到源Pad。
GstAudioEncoder接收的是原始音频,编码器子类输出的是压缩音频。上游输入格式通常由set_format()回调传入,但输出数据属于哪一种编码格式,需要编码器子类主动告诉基类。
gst_audio_encoder_set_output_format()就是连接这两个阶段的接口。
它负责检查输出Caps是否固定、是否属于源Pad模板允许的范围,然后保存到编码器上下文,并标记输出Caps已经发生变化。
它不会在调用时立即完成下游协商,也不会直接发送Caps事件。真正的源PadCaps设置和下游分配查询,会在后续协商流程中完成。
🌻2.应用场景和用法
2.1编码器输出Caps配置场景
编码器子类通常在set_format()中调用gst_audio_encoder_set_output_format()。
常见场景如下:
| 使用场景 | 输出Caps内容 | 解决的问题 |
|---|---|---|
| 固定编码格式 | audio/x-alaw、audio/x-gsm等 | 声明编码器输出类型 |
| 根据输入参数构造Caps | 把输入采样率、声道数写入输出Caps | 保证编码参数与输入一致 |
| 输出动态编码格式 | 根据编码器配置生成audio/mpeg或audio/x-adpcm | 向下游描述实际压缩格式 |
| 重新配置编码器 | 再次传入新的固定Caps | 触发后续源Pad重新协商 |
| 检查非法输出格式 | 传入非固定Caps或模板不允许的Caps | 阻止错误格式进入输出流程 |
编码器源Pad模板定义了允许输出的Caps范围。gst_audio_encoder_set_output_format()不会接受任意Caps,而是要求传入的Caps必须是固定Caps,并且必须属于源Pad模板Caps的子集。
例如,源Pad模板声明支持:
audio/x-test-encoded, rate=(int)8000, channels=(int)1
那么编码器只能设置与该模板兼容的固定Caps。如果传入audio/video或带有未确定范围的Caps,函数会返回FALSE。
2.2函数原型与返回语义
函数原型如下:
gboolean
gst_audio_encoder_set_output_format (GstAudioEncoder *enc,
GstCaps *caps);
| 参数 | 含义 |
|---|---|
enc | 已经创建的GstAudioEncoder对象 |
caps | 编码器要输出的固定Caps,函数不接管调用者所有权 |
| 返回值 | Caps被接受返回TRUE,校验失败返回FALSE |
最小调用方式如下:
GstCaps *caps;
gboolean ret;
caps = gst_caps_new_simple (
"audio/x-test-encoded",
"rate", G_TYPE_INT, 8000,
"channels", G_TYPE_INT, 1,
NULL);
ret = gst_audio_encoder_set_output_format (encoder, caps);
/* 参数是transfer none,调用者仍然释放自己的Caps引用 */
gst_caps_unref (caps);
if (!ret)
return FALSE;
调用成功后,基类会把Caps保存到内部上下文,并将output_caps_changed设置为TRUE。
这里的Caps通过gst_caps_replace()保存。基类会为新Caps增加引用,同时释放旧Caps引用。因此,调用者在函数返回后仍然需要释放自己持有的引用。
调用失败通常有两类原因:
caps不是固定Caps。caps不属于源Pad模板Caps的子集。
即使函数返回TRUE,也只代表基类接受了这组输出Caps,不代表下游协商已经完成。
几个相邻接口的职责不同:
| 接口 | 主要职责 |
|---|---|
set_format() | 接收上游输入音频格式,并让子类完成编码器配置 |
gst_audio_encoder_set_output_format() | 保存编码器输出Caps |
gst_audio_encoder_negotiate() | 使用当前输出Caps与下游协商 |
gst_audio_encoder_allocate_output_buffer() | 按当前输出格式准备编码输出Buffer |
gst_audio_encoder_finish_frame() | 提交编码后的Buffer并推动输出流程 |
🌻3.调用流程剖析
3.1校验Caps并保存输出状态
gst_audio_encoder_set_output_format()的核心实现集中在Caps校验和上下文更新。
gboolean
gst_audio_encoder_set_output_format (GstAudioEncoder *enc,
GstCaps *caps)
{
gboolean res = TRUE;
GstCaps *templ_caps;
GST_AUDIO_ENCODER_STREAM_LOCK (enc);
if (!gst_caps_is_fixed (caps))
goto refuse_caps;
templ_caps =
gst_pad_get_pad_template_caps (enc->srcpad);
if (!gst_caps_is_subset (caps, templ_caps)) {
gst_caps_unref (templ_caps);
goto refuse_caps;
}
gst_caps_unref (templ_caps);
gst_caps_replace (&enc->priv->ctx.caps, caps);
enc->priv->ctx.output_caps_changed = TRUE;
done:
GST_AUDIO_ENCODER_STREAM_UNLOCK (enc);
return res;
refuse_caps:
res = FALSE;
goto done;
}
调用过程可以拆成四个动作。
第一步,获取GstAudioEncoder的流锁,保护编码器上下文。
第二步,通过gst_caps_is_fixed()检查Caps是否已经完全确定。包含范围、列表或未确定字段的Caps不能直接作为最终输出格式。
第三步,读取源Pad模板Caps,并使用gst_caps_is_subset()检查调用者提供的Caps是否在模板允许范围内。
第四步,使用gst_caps_replace()更新ctx.caps,并把output_caps_changed设置为TRUE。
函数成功后保存的是Caps引用,而不是立即修改源Pad当前Caps。
因此,这个函数的直接结果是:
调用前:ctx.caps可能为空或指向旧格式
执行后:ctx.caps指向新格式,output_caps_changed为TRUE
3.2从输入格式回调进入输出协商
完整调用链通常从上游发送输入Caps开始。
GstAudioEncoder收到输入Caps后,会把它转换成GstAudioInfo,再调用编码器子类的set_format()。
子类根据输入采样率、声道数和自身编码器配置构造输出Caps,然后调用gst_audio_encoder_set_output_format()。
该函数只保存Caps并设置变化标志。后续当子类调用gst_audio_encoder_allocate_output_buffer()或gst_audio_encoder_finish_frame()时,基类会检查output_caps_changed。
如果该标志为TRUE,基类会进入gst_audio_encoder_negotiate_unlocked(),再由默认协商逻辑完成以下工作:
- 获取当前输出Caps。
- 调用
gst_pad_set_caps()设置源PadCaps。 - 向下游发起Allocation查询。
- 调用
decide_allocation()确定输出Buffer分配方式。 - 协商成功后清除
output_caps_changed。
这说明gst_audio_encoder_set_output_format()和gst_audio_encoder_negotiate()之间存在明确边界:
- 前者负责保存并验证输出Caps。
- 后者负责把已保存的Caps应用到源Pad,并与下游完成协商。
如果源Pad模板不允许目标Caps,前者直接返回FALSE。如果下游不接受已经保存的Caps,后续协商仍然可能失败。
🌻4.实战案例
4.1准备最小音频编码器
本案例定义一个测试用的GstAudioEncoder子类。
它接收8000Hz、单声道、S16LE原始音频,并输出:
audio/x-test-encoded, rate=(int)8000, channels=(int)1
set_format()负责构造输出Caps并调用gst_audio_encoder_set_output_format()。
handle_frame()使用gst_audio_encoder_allocate_output_buffer()申请4字节输出Buffer,再调用gst_audio_encoder_finish_frame()提交一个编码帧。
调用allocate_output_buffer()时,基类会发现output_caps_changed为TRUE,从而触发源PadCaps协商。
4.2执行输出Caps设置并验证
测试过程分为三步:
- 创建自定义编码器和
GstHarness,设置输入音频Caps。 - 推送一个320字节的输入Buffer,使编码器进入
set_format()和handle_frame()。 - 从编码器源Pad读取当前Caps,确认输出Caps已经被设置为测试编码格式。
验证重点包括:
gst_audio_encoder_set_output_format()返回TRUE。gst_audio_encoder_allocate_output_buffer()能够成功分配输出Buffer。- 编码器源Pad当前Caps与源Pad模板一致。
- 输出Buffer能够通过
gst_audio_encoder_finish_frame()发送到Harness。
4.3完整代码
#include <gst/gst.h>
#include <gst/audio/audio.h>
#include <gst/check/gstharness.h>
typedef struct _CapsEncoder
{
GstAudioEncoder parent;
} CapsEncoder;
typedef struct _CapsEncoderClass
{
GstAudioEncoderClass parent_class;
} CapsEncoderClass;
G_DEFINE_TYPE (CapsEncoder, caps_encoder, GST_TYPE_AUDIO_ENCODER);
/* 编码器输入为8000Hz、单声道、S16LE原始音频 */
static GstStaticPadTemplate encoder_sink_template =
GST_STATIC_PAD_TEMPLATE (
"sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (
"audio/x-raw, "
"format=(string)S16LE, "
"layout=(string)interleaved, "
"rate=(int)8000, "
"channels=(int)1"));
/* 编码器输出为固定的测试压缩格式 */
static GstStaticPadTemplate encoder_src_template =
GST_STATIC_PAD_TEMPLATE (
"src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (
"audio/x-test-encoded, "
"rate=(int)8000, "
"channels=(int)1"));
/* 根据输入GstAudioInfo构造并登记输出Caps */
static gboolean
caps_encoder_set_format (GstAudioEncoder * encoder,
GstAudioInfo * info)
{
GstCaps *caps;
gboolean ret;
caps = gst_caps_new_simple (
"audio/x-test-encoded",
"rate", G_TYPE_INT, GST_AUDIO_INFO_RATE (info),
"channels", G_TYPE_INT,
GST_AUDIO_INFO_CHANNELS (info),
NULL);
/* 这个调用只保存Caps并标记待协商状态 */
ret = gst_audio_encoder_set_output_format (
encoder,
caps);
/* set_output_format不会接管调用者的Caps引用 */
gst_caps_unref (caps);
if (!ret)
return FALSE;
/* 告诉基类每个编码帧需要160个输入采样 */
gst_audio_encoder_set_frame_samples_min (
encoder,
160);
gst_audio_encoder_set_frame_samples_max (
encoder,
160);
gst_audio_encoder_set_frame_max (
encoder,
1);
return TRUE;
}
/* 申请编码输出Buffer并提交一帧编码结果 */
static GstFlowReturn
caps_encoder_handle_frame (GstAudioEncoder * encoder,
GstBuffer * input)
{
GstBuffer *output;
GstMapInfo map;
if (input == NULL)
return gst_audio_encoder_finish_frame (
encoder,
NULL,
-1);
/* allocate_output_buffer会在必要时触发输出Caps协商 */
output = gst_audio_encoder_allocate_output_buffer (
encoder,
4);
if (output == NULL)
return GST_FLOW_ERROR;
if (!gst_buffer_map (
output,
&map,
GST_MAP_WRITE)) {
gst_buffer_unref (output);
return GST_FLOW_ERROR;
}
/* 写入4字节测试编码数据 */
map.data[0] = 0x11;
map.data[1] = 0x22;
map.data[2] = 0x33;
map.data[3] = 0x44;
gst_buffer_unmap (output, &map);
/* 提交一个包含160个输入采样的编码帧 */
return gst_audio_encoder_finish_frame (
encoder,
output,
160);
}
static void
caps_encoder_class_init (CapsEncoderClass * klass)
{
GstElementClass *element_class =
GST_ELEMENT_CLASS (klass);
GstAudioEncoderClass *encoder_class =
GST_AUDIO_ENCODER_CLASS (klass);
gst_element_class_add_static_pad_template (
element_class,
&encoder_sink_template);
gst_element_class_add_static_pad_template (
element_class,
&encoder_src_template);
gst_element_class_set_metadata (
element_class,
"Caps Encoder",
"Encoder/Audio",
"Output format test encoder",
"GStreamer");
encoder_class->set_format =
caps_encoder_set_format;
encoder_class->handle_frame =
caps_encoder_handle_frame;
}
static void
caps_encoder_init (CapsEncoder * encoder)
{
}
/* Harness输入端使用原始音频Caps */
static GstStaticPadTemplate harness_src_template =
GST_STATIC_PAD_TEMPLATE (
"harness-src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("audio/x-raw"));
/* Harness输出端接收编码器Caps */
static GstStaticPadTemplate harness_sink_template =
GST_STATIC_PAD_TEMPLATE (
"harness-sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("audio/x-test-encoded"));
int
main (int argc, char *argv[])
{
GstElement *encoder_element;
GstHarness *harness;
GstBuffer *input;
GstBuffer *output;
GstCaps *src_caps;
gchar *caps_text;
gboolean ok = TRUE;
gst_init (&argc, &argv);
/* 创建编码器实例并交给Harness管理 */
encoder_element =
g_object_new (caps_encoder_get_type (), NULL);
harness = gst_harness_new_full (
encoder_element,
&harness_src_template,
"sink",
&harness_sink_template,
"src");
gst_object_unref (encoder_element);
/* 设置输入端的原始音频Caps */
gst_harness_set_src_caps (
harness,
gst_caps_new_simple (
"audio/x-raw",
"format", G_TYPE_STRING, "S16LE",
"layout", G_TYPE_STRING, "interleaved",
"rate", G_TYPE_INT, 8000,
"channels", G_TYPE_INT, 1,
NULL));
/* 准备160个单声道S16采样,共320字节 */
input = gst_buffer_new_allocate (
NULL,
320,
NULL);
if (gst_harness_push (harness, input) != GST_FLOW_OK)
ok = FALSE;
/* 取出编码器输出Buffer,确认finish_frame已经提交数据 */
output = gst_harness_pull (harness);
if (output == NULL)
ok = FALSE;
else {
if (gst_buffer_get_size (output) != 4)
ok = FALSE;
gst_buffer_unref (output);
}
/* 读取源Pad当前Caps,确认协商使用了子类登记的输出格式 */
src_caps = gst_pad_get_current_caps (
GST_AUDIO_ENCODER_SRC_PAD (
GST_AUDIO_ENCODER (harness->element)));
if (src_caps == NULL) {
ok = FALSE;
} else {
caps_text = gst_caps_to_string (src_caps);
g_print ("source caps: %s\n", caps_text);
g_free (caps_text);
gst_caps_unref (src_caps);
}
gst_harness_teardown (harness);
return ok ? 0 : 1;
}
🌻5.总结
gst_audio_encoder_set_output_format()就是为GstAudioEncoder校验并保存编码器输出Caps,标记输出格式变化,再由后续协商流程将其应用到源Pad。
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/u010164190/article/details/166688587




