Android系统攻城狮头像
关注
Linux Gstreamer深度解析之gst_audio_encoder_set_output_format调用流程与实战(五十三)封面图

Linux Gstreamer深度解析之gst_audio_encoder_set_output_format调用流程与实战(五十三)

简介: CSDN博客专家、《Android系统多媒体进阶实战》作者

博主新书推荐:《Android系统多媒体进阶实战》🚀
Android Audio工程师专栏地址: Audio工程师进阶系列【原创干货持续更新中……】🚀
Android多媒体专栏地址: 多媒体系统工程师系列【原创干货持续更新中……】🚀
专题一 二:AAOS车载系统+AOSP14系统攻城狮入门视频实战课 🚀
专题三:Android14 Binder之HIDL与AIDL通信实战课 🚀
专题四:Android15快速自定义与集成音效实战课 🚀
专题五:Android15音频策略实战课 🚀
专题六:Android15音频性能实战课(无声/杂音/断音/爆音实战案例) 🚀

人生格言: 人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药.

更多原创,欢迎关注:Android系统攻城狮

欢迎关注Android系统攻城狮

🌻1.前言

本篇目的:理解gst_audio_encoder_set_output_format()如何接收编码器子类提供的输出Caps,分析它如何验证并保存输出格式,以及这些Caps何时真正设置到源Pad。

GstAudioEncoder接收的是原始音频,编码器子类输出的是压缩音频。上游输入格式通常由set_format()回调传入,但输出数据属于哪一种编码格式,需要编码器子类主动告诉基类。

gst_audio_encoder_set_output_format()就是连接这两个阶段的接口。

它负责检查输出Caps是否固定、是否属于源Pad模板允许的范围,然后保存到编码器上下文,并标记输出Caps已经发生变化。

它不会在调用时立即完成下游协商,也不会直接发送Caps事件。真正的源PadCaps设置和下游分配查询,会在后续协商流程中完成。

输入GstAudioInfo

编码器子类构造输出GstCaps

gst_audio_encoder_set_output_format

保存输出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引用。因此,调用者在函数返回后仍然需要释放自己持有的引用。

调用失败通常有两类原因:

  1. caps不是固定Caps。
  2. 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。

否

是

否

是

传入输出GstCaps

获取AudioEncoder流锁

Caps是否固定

返回FALSE

是否属于源Pad模板

保存Caps并标记变化

函数成功后保存的是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(),再由默认协商逻辑完成以下工作:

  1. 获取当前输出Caps。
  2. 调用gst_pad_set_caps()设置源PadCaps。
  3. 向下游发起Allocation查询。
  4. 调用decide_allocation()确定输出Buffer分配方式。
  5. 协商成功后清除output_caps_changed。

上游输入Caps

AudioEncoder调用set_format

子类构造输出Caps

gst_audio_encoder_set_output_format

allocate或finish触发协商

设置源PadCaps并查询分配

这说明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设置并验证

测试过程分为三步:

  1. 创建自定义编码器和GstHarness,设置输入音频Caps。
  2. 推送一个320字节的输入Buffer,使编码器进入set_format()和handle_frame()。
  3. 从编码器源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

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--