Topology: Add development example of demux and EQ band-split pipeline

This patch adds topologies sof-apl-nocodec-demux-eq-2ch4ch.tplg and
sof-apl-nocodec-demux-eq-4ch4ch.tplg. Playback of 2ch creates 4ch
output in format L_lo, L_hi, R_lo, and R_hi. An example band-split
at 2 kHz is configured for EQ processing. The low band contains
an additional 80 Hz high-pass.

The pipeline was tested in UP2 device. The nocodec topology enables
an useful SPP loopback mode. The capture PCM is connected to DAI
loopback so this pipeline can be tested with simultaneous 2ch aplay
and 4ch arecord.

Signed-off-by: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
This commit is contained in:
Seppo Ingalsuo 2021-08-25 15:24:04 +03:00 committed by Liam Girdwood
parent 0fefcc8bdf
commit 23c7e4739c
5 changed files with 467 additions and 1 deletions

View File

@ -26,7 +26,8 @@ set(TPLGS
"sof-imx8-compr-mp3-wm8960\;sof-imx8-compr-mp3-wm8960"
"sof-imx8mp-compr-aac-wm8960\;sof-imx8mp-compr-aac-wm8960"
"sof-imx8mp-compr-mp3-wm8960\;sof-imx8mp-compr-mp3-wm8960"
"sof-apl-nocodec-demux-eq-4ch4ch\;sof-apl-nocodec-demux-eq-4ch4ch"
"sof-apl-nocodec-demux-eq-2ch4ch\;sof-apl-nocodec-demux-eq-2ch4ch"
)

View File

@ -0,0 +1,92 @@
#
# Topology for generic Apollolake board with no codec and digital mic array.
#
# APL Host GW DMAC support max 6 playback and max 6 capture channels so some
# pipelines/PCMs/DAIs are commented out to keep within HW bounds. If these
# are needed then they can be used provided other PCMs/pipelines/SSPs are
# commented out in their place.
# Include topology builder
include(`utils.m4')
include(`dai.m4')
include(`ssp.m4')
include(`pipeline.m4')
# Include TLV library
include(`common/tlv.m4')
# Include Token library
include(`sof/tokens.m4')
# Include Apollolake DSP configuration
include(`platform/intel/bxt.m4')
include(`platform/intel/dmic.m4')
#
# Define the pipelines
#
# PCM0P ---> demux ---> eq_iir ---> SSP0
# PCM0C <-------------------------- SSP0
dnl PIPELINE_PCM_ADD(pipeline,
dnl pipe id, pcm, max channels, format,
dnl period, priority, core,
dnl pcm_min_rate, pcm_max_rate, pipeline_rate,
dnl time_domain, sched_comp)
# Low Latency playback pipeline 1 on PCM 0 using max 2 channels of s32le.
# Set 1000us deadline on core 0 with priority 0
PIPELINE_PCM_ADD(sof/pipe-demux-eq-iir-playback.m4,
1, 0, 2, s32le,
1000, 0, 0,
48000, 48000, 48000)
# Volume switch capture pipeline 2 on PCM 0 using max 4 channels of s32le.
# 1000us deadline on core 0 with priority 0
PIPELINE_PCM_ADD(sof/pipe-passthrough-capture.m4,
2, 0, 4, s32le,
1000, 0, 0,
48000, 48000, 48000)
#
# DAIs configuration
#
dnl DAI_ADD(pipeline,
dnl pipe id, dai type, dai_index, dai_be,
dnl buffer, periods, format,
dnl deadline, priority, core, time_domain)
# playback DAI is SSP0 using 2 periods
# Buffers use s32le format, 1000us deadline on core 0 with priority 0
DAI_ADD(sof/pipe-dai-playback.m4,
1, SSP, 0, NoCodec-0,
PIPELINE_SOURCE_1, 4, s32le,
1000, 0, 0, SCHEDULE_TIME_DOMAIN_TIMER)
# capture DAI is SSP0 using 2 periods
# Buffers use s32le format, 1000us deadline on core 0 with priority 0
DAI_ADD(sof/pipe-dai-capture.m4,
2, SSP, 0, NoCodec-0,
PIPELINE_SINK_2, 4, s32le,
1000, 0, 0, SCHEDULE_TIME_DOMAIN_TIMER)
dnl PCM_DUPLEX_ADD(name, pcm_id, playback, capture)
PCM_DUPLEX_ADD(Port0, 0, PIPELINE_PCM_1, PIPELINE_PCM_2)
#
# BE configurations - overrides config in ACPI if present
#
dnl DAI_CONFIG(type, dai_index, link_id, name, ssp_config/dmic_config)
DAI_CONFIG(SSP, 0, 0, NoCodec-0,
dnl SSP_CONFIG(format, mclk, bclk, fsync, tdm, ssp_config_data)
SSP_CONFIG(I2S, SSP_CLOCK(mclk, 24576000, codec_mclk_in),
SSP_CLOCK(bclk, 6144000, codec_slave),
SSP_CLOCK(fsync, 48000, codec_slave),
SSP_TDM(4, 32, 15, 15),
dnl SSP_CONFIG_DATA(type, dai_index, valid bits, mclk_id, quirks)
SSP_CONFIG_DATA(SSP, 0, 32, 0, SSP_QUIRK_LBM)))

View File

@ -0,0 +1,93 @@
#
# Topology for generic Apollolake board with no codec and digital mic array.
#
# APL Host GW DMAC support max 6 playback and max 6 capture channels so some
# pipelines/PCMs/DAIs are commented out to keep within HW bounds. If these
# are needed then they can be used provided other PCMs/pipelines/SSPs are
# commented out in their place.
# Include topology builder
include(`utils.m4')
include(`dai.m4')
include(`ssp.m4')
include(`pipeline.m4')
# Include TLV library
include(`common/tlv.m4')
# Include Token library
include(`sof/tokens.m4')
# Include Apollolake DSP configuration
include(`platform/intel/bxt.m4')
include(`platform/intel/dmic.m4')
#
# Define the pipelines
#
# PCM0P ---> demux ---> eq_iir ---> SSP0
# PCM0C <-------------------------- SSP0
dnl PIPELINE_PCM_ADD(pipeline,
dnl pipe id, pcm, max channels, format,
dnl period, priority, core,
dnl pcm_min_rate, pcm_max_rate, pipeline_rate,
dnl time_domain, sched_comp)
# Low Latency playback pipeline 1 on PCM 0 using max 4 channels of s32le.
# Set 1000us deadline on core 0 with priority 0
PIPELINE_PCM_ADD(sof/pipe-demux-eq-iir-playback.m4,
1, 0, 4, s32le,
1000, 0, 0,
48000, 48000, 48000)
# Volume switch capture pipeline 2 on PCM 0 using max 4 channels of s32le.
# 1000us deadline on core 0 with priority 0
PIPELINE_PCM_ADD(sof/pipe-passthrough-capture.m4,
2, 0, 4, s32le,
1000, 0, 0,
48000, 48000, 48000)
#
# DAIs configuration
#
dnl DAI_ADD(pipeline,
dnl pipe id, dai type, dai_index, dai_be,
dnl buffer, periods, format,
dnl deadline, priority, core, time_domain)
# playback DAI is SSP0 using 2 periods
# Buffers use s32le format, 1000us deadline on core 0 with priority 0
DAI_ADD(sof/pipe-dai-playback.m4,
1, SSP, 0, NoCodec-0,
PIPELINE_SOURCE_1, 4, s32le,
1000, 0, 0, SCHEDULE_TIME_DOMAIN_TIMER)
# capture DAI is SSP0 using 2 periods
# Buffers use s32le format, 1000us deadline on core 0 with priority 0
DAI_ADD(sof/pipe-dai-capture.m4,
2, SSP, 0, NoCodec-0,
PIPELINE_SINK_2, 4, s32le,
1000, 0, 0, SCHEDULE_TIME_DOMAIN_TIMER)
dnl PCM_DUPLEX_ADD(name, pcm_id, playback, capture)
PCM_DUPLEX_ADD(Port0, 0, PIPELINE_PCM_1, PIPELINE_PCM_2)
#
# BE configurations - overrides config in ACPI if present
#
dnl DAI_CONFIG(type, dai_index, link_id, name, ssp_config/dmic_config)
DAI_CONFIG(SSP, 0, 0, NoCodec-0,
dnl SSP_CONFIG(format, mclk, bclk, fsync, tdm, ssp_config_data)
SSP_CONFIG(I2S, SSP_CLOCK(mclk, 24576000, codec_mclk_in),
SSP_CLOCK(bclk, 6144000, codec_slave),
SSP_CLOCK(fsync, 48000, codec_slave),
SSP_TDM(4, 32, 15, 15),
dnl SSP_CONFIG_DATA(type, dai_index, valid bits, mclk_id, quirks)
SSP_CONFIG_DATA(SSP, 0, 32, 0, SSP_QUIRK_LBM)))

View File

@ -0,0 +1,140 @@
# Demux EQ band split pipeline
#
# host PCM_P --B0--> demux(M) --B1--> eq_iir --B2--> sink DAI0
#
# Include topology builder
include(`utils.m4')
include(`buffer.m4')
include(`pcm.m4')
include(`muxdemux.m4')
include(`bytecontrol.m4')
include(`eq_iir.m4')
dnl Configure demux
dnl name, pipeline_id, routing_matrix_rows
dnl Diagonal 1's in routing matrix mean that every input channel is
dnl copied to corresponding output channels in all output streams.
dnl I.e. row index is the input channel, 1 means it is copied to
dnl corresponding output channel (column index), 0 means it is discarded.
dnl There's a separate matrix for all outputs.
define(matrix1, `ROUTE_MATRIX(1,
`BITS_TO_BYTE(1, 0, 0 ,0 ,0 ,0 ,0 ,0)',
`BITS_TO_BYTE(1, 0, 0 ,0 ,0 ,0 ,0 ,0)',
`BITS_TO_BYTE(0, 1, 0 ,0 ,0 ,0 ,0 ,0)',
`BITS_TO_BYTE(0, 1, 0 ,0 ,0 ,0 ,0 ,0)',
`BITS_TO_BYTE(0, 0, 0 ,0 ,0 ,0 ,0 ,0)',
`BITS_TO_BYTE(0, 0, 0 ,0 ,0 ,0 ,0 ,0)',
`BITS_TO_BYTE(0, 0, 0 ,0 ,0 ,0 ,0 ,0)',
`BITS_TO_BYTE(0, 0, 0 ,0 ,0 ,0 ,0 ,0)')')
dnl name, num_streams, route_matrix list
MUXDEMUX_CONFIG(demux_priv_1, 1, LIST(` ', `matrix1'))
# demux Bytes control with max value of 255
C_CONTROLBYTES(concat(`DEMUX', PIPELINE_ID), PIPELINE_ID,
CONTROLBYTES_OPS(bytes, 258 binds the mixer control to bytes get/put handlers, 258, 258),
CONTROLBYTES_EXTOPS(258 binds the mixer control to bytes get/put handlers, 258, 258),
, , ,
CONTROLBYTES_MAX(, 304),
, concat(`demux_priv_', PIPELINE_ID))
# EQ IIR Bytes control
define(DEF_EQIIR_COEF, concat(`eqiir_coef_', PIPELINE_ID))
define(DEF_EQIIR_PRIV, concat(`eqiir_priv_', PIPELINE_ID))
# Bandsplit, created with example_iir_bandsplit.m 25-Aug-2021
CONTROLBYTES_PRIV(DEF_EQIIR_PRIV,
` bytes "0x53,0x4f,0x46,0x00,0x00,0x00,0x00,0x00,'
` 0xb0,0x00,0x00,0x00,0x00,0x30,0x01,0x03,'
` 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,'
` 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,'
` 0xb0,0x00,0x00,0x00,0x04,0x00,0x00,0x00,'
` 0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,'
` 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,'
` 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,'
` 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,'
` 0x01,0x00,0x00,0x00,0x02,0x00,0x00,0x00,'
` 0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,'
` 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,'
` 0x00,0x00,0x00,0x00,0x50,0xda,0xf0,0xc0,'
` 0x1e,0x5d,0x0d,0x7f,0x3c,0xdf,0xd6,0x1f,'
` 0x89,0x41,0x52,0xc0,0x3c,0xdf,0xd6,0x1f,'
` 0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00,'
` 0x2d,0x3a,0xcd,0xd3,0xc0,0xf5,0x82,0x68,'
` 0xb9,0x41,0x76,0x00,0x72,0x83,0xec,0x00,'
` 0xb9,0x41,0x76,0x00,0xff,0xff,0xff,0xff,'
` 0x99,0x7f,0x00,0x00,0x01,0x00,0x00,0x00,'
` 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,'
` 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,'
` 0x00,0x00,0x00,0x00,0x2d,0x3a,0xcd,0xd3,'
` 0xc0,0xf5,0x82,0x68,0x01,0xe1,0xa6,0x1a,'
` 0xff,0x3d,0xb2,0xca,0x01,0xe1,0xa6,0x1a,'
` 0x00,0x00,0x00,0x00,0xb2,0x7f,0x00,0x00"'
)
C_CONTROLBYTES(DEF_EQIIR_COEF, PIPELINE_ID,
CONTROLBYTES_OPS(bytes, 258 binds the control to bytes get/put handlers, 258, 258),
CONTROLBYTES_EXTOPS(258 binds the control to bytes get/put handlers, 258, 258),
, , ,
CONTROLBYTES_MAX(, 1024),
,
DEF_EQIIR_PRIV)
#
# Components and Buffers
#
# Host "Speaker Playback" PCM
# with 2 sink and 0 source periods
W_PCM_PLAYBACK(PCM_ID, Speaker Playback, 2, 0, SCHEDULE_CORE)
# "EQ IIR" has x sink period and 2 source periods
W_EQ_IIR(0, PIPELINE_FORMAT, 2, 2, SCHEDULE_CORE,
LIST(` ', "DEF_EQIIR_COEF"))
# Mux 0 has 2 sink and source periods.
W_MUXDEMUX(0, 1, PIPELINE_FORMAT, 2, 2, SCHEDULE_CORE,
LIST(` ', concat(`DEMUX', PIPELINE_ID)))
# Low Latency Buffers
W_BUFFER(0, COMP_BUFFER_SIZE(2,
COMP_SAMPLE_SIZE(PIPELINE_FORMAT), PIPELINE_CHANNELS, COMP_PERIOD_FRAMES(PCM_MAX_RATE, SCHEDULE_PERIOD)),
PLATFORM_HOST_MEM_CAP)
W_BUFFER(1, COMP_BUFFER_SIZE(2,
COMP_SAMPLE_SIZE(PIPELINE_FORMAT), PIPELINE_CHANNELS, COMP_PERIOD_FRAMES(PCM_MAX_RATE, SCHEDULE_PERIOD)),
PLATFORM_COMP_MEM_CAP)
W_BUFFER(2, COMP_BUFFER_SIZE(DAI_PERIODS,
COMP_SAMPLE_SIZE(PIPELINE_FORMAT), 4, COMP_PERIOD_FRAMES(PCM_MAX_RATE, SCHEDULE_PERIOD)),
PLATFORM_COMP_MEM_CAP)
#
# Pipeline Graph
#
# host PCM_P --B0--> Demux(M) --B1--> eq_iir --B2--> sink DAI0
P_GRAPH(pipe-demux-eq-playback, PIPELINE_ID,
LIST(` ',
`dapm(N_BUFFER(0), N_PCMP(PCM_ID))',
`dapm(N_MUXDEMUX(0), N_BUFFER(0))',
`dapm(N_BUFFER(1), N_MUXDEMUX(0))',
`dapm(N_EQ_IIR(0), N_BUFFER(1))',
`dapm(N_BUFFER(2), N_EQ_IIR(0))'))
#
# Pipeline Source and Sinks
#
indir(`define', concat(`PIPELINE_SOURCE_', PIPELINE_ID), N_BUFFER(2))
indir(`define', concat(`PIPELINE_PCM_', PIPELINE_ID), Speaker Playback PCM_ID)
#
# PCM Configuration
#
# PCM capabilities supported by FW
PCM_CAPABILITIES(Speaker Playback PCM_ID, CAPABILITY_FORMAT_NAME(PIPELINE_FORMAT), 48000, 48000, 2, PIPELINE_CHANNELS, 2, 16, 192, 16384, 65536, 65536)
undefine(`matrix1')
undefine(`DEF_EQIIR_COEF')
undefine(`DEF_EQIIR_PRIV')

View File

@ -0,0 +1,140 @@
%% Design effect EQs and bundle them to parameter block
% SPDX-License-Identifier: BSD-3-Clause
%
% Copyright (c) 2016-2020, Intel Corporation. All rights reserved.
%
% Author: Seppo Ingalsuo <seppo.ingalsuo@linux.intel.com>
function example_iir_bandsplit()
%% Common definitions
fs = 48e3;
tpath = '../../topology/topology1/m4';
cpath = '../../ctl';
priv = 'DEF_EQIIR_PRIV';
%% --------------------------------------------------
%% Example: Band-split 2ch to 4ch low and high bands
%% --------------------------------------------------
blob_fn = fullfile(cpath, 'eq_iir_bandsplit.bin');
alsa_fn = fullfile(cpath, 'eq_iir_bandsplit.txt');
tplg_fn = fullfile(tpath, 'eq_iir_bandsplit.m4');
comment = 'Bandsplit, created with example_iir_bandsplit.m';
%% Design IIR loudness equalizer
eq_lo = lo_band_iir(fs);
eq_hi = hi_band_iir(fs);
%% Quantize and pack filter coefficients plus shifts etc.
bq_lo = eq_iir_blob_quant(eq_lo.p_z, eq_lo.p_p, eq_lo.p_k);
bq_hi = eq_iir_blob_quant(eq_hi.p_z, eq_hi.p_p, eq_hi.p_k);
%% Build blob
channels_in_config = 4; % Setup max 4 channels EQ
assign_response = [0 1 0 1]; % Order: lo, hi, lo, hi
num_responses = 2; % Two responses: lo, hi
bm = eq_iir_blob_merge(channels_in_config, ...
num_responses, ...
assign_response, ...
[bq_lo bq_hi]);
%% Pack and write file
eq_pack_export(bm, blob_fn, alsa_fn, tplg_fn, priv, comment)
%% ------------------------------------
%% Done.
%% ------------------------------------
end
%% -------------------
%% EQ design functions
%% -------------------
function eq = lo_band_iir(fs)
%% Get defaults for equalizer design
eq = eq_defaults();
eq.fs = fs;
eq.enable_iir = 1;
eq.norm_type = 'peak';
eq.norm_offs_db = 0;
%% Manually setup low-shelf and high shelf parametric equalizers
%
% Parametric EQs are PEQ_HP1, PEQ_HP2, PEQ_LP1, PEQ_LP2, PEQ_LS1,
% PEQ_LS2, PEQ_HS1, PEQ_HS2 = 8, PEQ_PN2, PEQ_LP4, and PEQ_HP4.
%
% Parametric EQs take as second argument the cutoff frequency in Hz
% and as second argument a dB value (or NaN when not applicable) . The
% Third argument is a Q-value (or NaN when not applicable).
% Low-pass at 2 kHz, add a high-pass at 80 Hz for a small woofer
eq.peq = [ ...
eq.PEQ_HP2 80 NaN NaN ; ...
eq.PEQ_LP2 2000 NaN NaN ; ...
];
%% Design EQ
eq = eq_compute(eq);
%% Plot
eq_plot(eq);
end
function eq = hi_band_iir(fs)
%% Get defaults for equalizer design
eq = eq_defaults();
eq.fs = fs;
eq.enable_iir = 1;
eq.norm_type = 'peak';
eq.norm_offs_db = 0;
%% Manually setup low-shelf and high shelf parametric equalizers
%
% Parametric EQs are PEQ_HP1, PEQ_HP2, PEQ_LP1, PEQ_LP2, PEQ_LS1,
% PEQ_LS2, PEQ_HS1, PEQ_HS2 = 8, PEQ_PN2, PEQ_LP4, and PEQ_HP4.
%
% Parametric EQs take as second argument the cutoff frequency in Hz
% and as second argument a dB value (or NaN when not applicable) . The
% Third argument is a Q-value (or NaN when not applicable).
% High-pass at 2 kHz for a tweeter
eq.peq = [ ...
eq.PEQ_HP2 2000 NaN NaN ; ...
];
%% Design EQ
eq = eq_compute(eq);
%% Plot
eq_plot(eq);
end
% Pack and write file common function for all exports
function eq_pack_export(bm, bin_fn, ascii_fn, tplg_fn, priv, note)
bp = eq_iir_blob_pack(bm);
if ~isempty(bin_fn)
eq_blob_write(bin_fn, bp);
end
if ~isempty(ascii_fn)
eq_alsactl_write(ascii_fn, bp);
end
if ~isempty(tplg_fn)
eq_tplg_write(tplg_fn, bp, priv, note);
end
end