Qcom平台 ffbm/fct 使用mm-audio-ftm 测试audio流程分析

1:FFBM测试

1.1:测试应用界面

image-20220105103351981

由UI内容可知,audio相关的测试项有4个,分别是 speaker测试,已经2个mic到speaker的回环,以及一个耳机mic的回环;

它在ui中的配置是在:vendor/qcom/proprietary/commonsys/fastmmi/res/config/mmi.xml中

    <!-- default config for UI mode -->
    <modules>
        <module name="SPEAKER"
                lib_name="mmi_audio.so"
                enable="1"
                automation="0"
                display_name="loudspeaker"
                layout="layout_handset.xml"
                parameter="type:play;tc:1;volume:50;duration:4;file:/system/etc/mmi/qualsound.wav;"/>
        <module name="SPEAKER LOOP"
                lib_name="mmi_audio.so"
                enable="1"
                automation="0"
                display_name="audio_loudspeaker_play"
                layout="layout_loudspeaker.xml"
                parameter="type:loopback;tc:227;volume:30;duration:3;file:/mnt/vendor/persist/FTM_AP/speaker_record.wav;tc_play:1;"/>
    <!-- Loopback for headset -->
        <module name="HEADSET LOOP"
                lib_name="mmi_audio.so"
                enable="1"
                automation="0"
                display_name="audio_headset_loopback"
                layout="layout_headset.xml"
                parameter="type:loopback;tc:225;volume:45;duration:3;file:/mnt/vendor/persist/FTM_AP/headset_record.wav;tc_play:1;"/>
    <!-- BACK MIC -->
        <module name="NOISE MIC"
                lib_name="mmi_audio.so"
                enable="1"
                automation="0"
                display_name="audio_noise_mic"
                layout="layout_primary_mic.xml"
                parameter="type:mic;tc:226;volume:45;duration:3;file:/mnt/vendor/persist/FTM_AP/ftm_pcm_record.wav;tc_play:1;"/>

具体FCT相关的内容不做分析,只关注通过UI点击后,给测试命令传递的参数; 即parameter 后的参数,最终使用也是mm-audio-ftm 可执行程序;

1.2:FCT命令mm-audio-ftm

以下是常用的几个测试命令;

#测试扬声器
mm-audio-ftm -c /vendor/etc/ftm_test_config -tc 1 -d 10 -v 60 -file /system/etc/mmi/qualsound.wav
#测试耳机
headset:
left:
mm-audio-ftm -c /vendor/etc/ftm_test_config -tc 3 -d 10 -v 60 -file /system/etc/mmi/qualsound.wav
right:
mm-audio-ftm -c /vendor/etc/ftm_test_config -tc 4 -d 10 -v 60 -file /system/etc/mmi/qualsound.wav

#测试回环
mm-audio-ftm -c /vendor/etc/ftm_test_config -tc 225 -d 10 -v 50

mm-audio-ftm -c /vendor/etc/ftm_test_config -tc 226 -v 20 -d 5

mm-audio-ftm -c /vendor/etc/ftm_test_config -tc 227 -v 20

-c 后跟的参数,是配置文件,指定打开关闭一个通路所执行的配置命令集;

-tc 后跟的参数,是配置文件中标识对应测试项的 id;

-v 后跟的参数,配置测试过程中的音量设置;

-d 后跟的参数,是配置测试延时的时间,以s为单位;

-file 后跟的参数,配置测试过程中使用的音频文件的路径;

1.3:用例配置实例

1.3.1:Speaker

以/vendor/etc/ftm_test_config 中 tc 1 Left Speaker测试用例为例:

tc 1
#Left Speaker
!Playback
Rxdevice:0

enable
RX_CDC_DMA_RX_1 Channels:One
RX_MACRO RX2 MUX:AIF2_PB
RX INT2_1 MIX1 INP0:RX2
AUX_RDAC Switch:1
RX_CDC_DMA_RX_1 Audio Mixer MultiMedia1:1

disable
RX_MACRO RX2 MUX:ZERO
RX INT2_1 MIX1 INP0:ZERO
AUX_RDAC Switch:0
RX_CDC_DMA_RX_1 Audio Mixer MultiMedia1:0

1.3.2:Loop back

以/vendor/etc/ftm_test_config 中 tc 225 Headset loop back测试用例为例:

tc 225
#AMIC2 to HPH_LR AFE loopback
!AfeLoop
Txdevice:31
Rxdevice:30

enable
RX_MACRO RX0 MUX:AIF1_PB
RX_MACRO RX1 MUX:AIF1_PB
RX_CDC_DMA_RX_0 Channels:Two
RX INT0_1 MIX1 INP0:RX0
RX INT1_1 MIX1 INP0:RX1
RX INT0 DEM MUX:CLSH_DSM_OUT
RX INT1 DEM MUX:CLSH_DSM_OUT
RX_COMP1 Switch:1
RX_COMP2 Switch:1
HPHL_RDAC Switch:1
HPHR_RDAC Switch:1
HPHL_COMP Switch:1
HPHR_COMP Switch:1
RX_RX0 Digital Volume:62
RX_RX1 Digital Volume:62

TX DEC0 MUX:SWR_MIC
TX SMIC MUX0:SWR_MIC4
TX_CDC_DMA_TX_3 Channels:One
TX_AIF1_CAP Mixer DEC0:1
ADC2_MIXER Switch:1
ADC2 MUX:INP2
ADC2 Volume:62

RX_CDC_DMA_RX_0_DL_HL Switch:1
RX_CDC_DMA_RX_0 Port Mixer TX_CDC_DMA_TX_3:1

disable

RX_MACRO RX0 MUX:ZERO
RX_MACRO RX1 MUX:ZERO
RX INT0_1 MIX1 INP0:ZERO
RX INT1_1 MIX1 INP0:ZERO
RX INT0 DEM MUX:NORMAL_DSM_OUT
RX INT1 DEM MUX:NORMAL_DSM_OUT
RX_COMP1 Switch:0
RX_COMP2 Switch:0
HPHL_RDAC Switch:0
HPHR_RDAC Switch:0
HPHL_COMP Switch:0
HPHR_COMP Switch:0
RX_RX0 Digital Volume:0
RX_RX1 Digital Volume:0
TX DEC0 MUX:MSM_DMIC
TX SMIC MUX0:ZERO
TX_AIF1_CAP Mixer DEC0:0
ADC2_MIXER Switch:0
ADC2 MUX:ZERO
ADC2 Volume:62

RX_CDC_DMA_RX_0_DL_HL Switch:0
RX_CDC_DMA_RX_0 Port Mixer TX_CDC_DMA_TX_3:0

测试过程是以可执行命令mm-audio-ftm 进入执行的,下面对执行过程的细节进一步分析;

2:mm-audio-ftm分析

文件:

  • code/vendor/qcom/proprietary/mm-audio/ftm_audio_main.c
  • code/vendor/qcom/proprietary/mm-audio/ftm_audio_dispatch.c

2.1:main函数

(以上面测试命令为例, 其他测试情况忽略,仅分析主要流程)

int main(int argc, char *argv[])
{
//.....
  char filename[100], *pfilename = NULL;
  int codec = 0, total_test = INT_MAX, result, test_case;
  int volume = 0, duration = 3, fl = 300, fh = 4000, count = 0;
  int tx_volume = 0, rx_volume = 0;
//.....
#ifdef MSM8960_ALSA
    FILE *fp;
    FILE *fp_config;
    char soundCardInfo[200];
    char soundCardName[200];
    char config_path[200] = {0};
    g_config_test = 0;
    if((fp = fopen("/proc/asound/cards","r")) == NULL) {
      printf("Cannot open /proc/asound/cards file to get sound card info\n");
      return -1;
    } else {     //跳过/proc/asound/cards 声卡name的第一行  获取到 snd card name
        if((fgets(soundCardInfo, sizeof(soundCardInfo), fp) == NULL)) {
            printf("/proc/asound/cards is empty. Abort\n");
            return -1;
        }
        while((fgets(soundCardInfo, sizeof(soundCardInfo), fp) != NULL)) {
            printf("SoundCardInfo %s", soundCardInfo);
            sscanf(soundCardInfo, "%s", soundCardName);
            printf("\nsoundCardName %s\n", soundCardName);
            snprintf(config_path, sizeof(config_path), "%s_%s",
                "/vendor/etc/ftm_test_config", soundCardName);      //指定的这个config文件就用这个测试
 		//......
            } else if ((fp_config = fopen("/vendor/etc/ftm_test_config","r")) != NULL) {
                printf("Use the codec ftm_test_config file\n");
                codec = CODEC_CONFIG_SUPPORT;
                fclose(fp_config);
                fp_config = NULL;
                /* use default config file( ftm_test_config) as last resort. */
                snprintf(config_path, sizeof(config_path), "/vendor/etc/ftm_test_config");
                break;
            } else {
 		//......
        }
        fclose(fp);
        fp = NULL;
    }
 
    if (argc == 1) {
	//......  忽略
    }else if(argc == 3) {
 	//......  忽略
    } else if (argc >= 5) {  	//有5个参数的通过文件指定的,采用的是这种
        count = argc;
        fp = NULL;
        int i = 1;
        test_case = -1;
        /* Support only for codecs with config.txt support */
        if (codec != CODEC_CONFIG_SUPPORT) {
            printf("Command to enter: mm-audio-ftm -tc <tc number>\n");
            return -1;
        }
        while((i + 1) < count) {
           if (!strncmp(argv[i], "-tc", 3)) {
                i++;
                test_case = atoi(argv[i++]);
                printf("\n Test case number %d", test_case);
           } else if (!strncmp(argv[i], "-c", 2)) {              // "-c  指定了 config文件 路径"
                i++;
                printf("\n Conf file name %s", argv[i]);
                if (!(fp = fopen(argv[i++],"rb"))) {
                    printf("\n Failed to open file");
                    return -1;
                }
                g_config_test = 1;
           } else if (!strncmp(argv[i], "-v", 2)) {             // "-v" 配置 音量volume
                i++;
                volume = atoi(argv[i++]);
                if (volume < 0 || volume > 100)                // 范围0 - 100
                    volume = DEFAULT_VOLUME;
                printf("\n Volume level set to %d", volume);
           } else if (!strncmp(argv[i], "-d", 2)) {             // "-d" 配置duration  测试时长
                i++;
                duration = atoi(argv[i++]);
                printf("\n duration set to %d", duration);
           } else if (!strncmp(argv[i], "-fl", 3)) {           // "-fl" 配置频率
                i++;
                fl = atoi(argv[i++]);
                printf("\n  Frequency set to %d", fl);
           } else if (!strncmp(argv[i], "-fh", 3)) {
                i++;
                fh = atoi(argv[i++]);
                printf("\n  Frequency set to %d", fh);
           } else if (!strncmp(argv[i], "-tv", 3)) {           //"-tv" 配置  tx_volume 
                i++;
                tx_volume = atoi(argv[i++]);
                printf("\n  tx volume set to %d", tx_volume);
           } else if (!strncmp(argv[i], "-rv", 3)) {
                i++;
                rx_volume = atoi(argv[i++]);
                printf("\n  rx volume set to %d", rx_volume);  //"-rv"  配置 x_volume
			//......
           } else if (!strncmp(argv[i], "-file", 5)) {       // "-file" 指定音频文件
                uint32 len;
                i++;
                len = strnlen(argv[i], sizeof(filename));
			//......
        }
    }
//......
    result = execute_test_case(test_case, codec, fp, volume,
            fl, fh, duration, pfilename, tx_volume, rx_volume,
            tone_analysis, freq_tolerance);

    g_config_test = 0;
    if (!result) {
        printf("test %d success\n", test_case);
        ret = 0;
    } else {
        printf("test %d failure (%d)\n", test_case, result);
        ret = -1;
    }
    if (fp) {
        fclose(fp);
    }
    return ret;
//......
}

从上分析可知,获取传入的参数配置后,通过execute_test_case 执行具体的测试过程

2.2:execute_test_case 测试用例执行过程

int execute_test_case(int test_case, int codec, FILE *fp, int vol, int fl,
    int fh, int duration, char *file_name, int tx_volume, int rx_volume,
    int tone_analysis, int freq_tolerance)
{
//......
    fprintf(stderr, "size of ftm_tc_devices_tabla = %d\n",
            sizeof(ftm_tc_devices_tabla)/sizeof(ftm_tc_devices_tabla[0]));
    fprintf(stderr, "size of ftm_tc_devices_sitar = %d\n",
            sizeof(ftm_tc_devices_sitar)/sizeof(ftm_tc_devices_sitar[0]));
    fprintf(stderr, "size of ftm_tc_devices_taiko = %d\n",
        sizeof(ftm_tc_devices_taiko)/sizeof(ftm_tc_devices_taiko[0]));
//......
    g_devicecapture = g_deviceplayback = -1;
    if (fp){
        paraminfo = &params;
        pthread_mutex_lock(&params.lock);
        params.duration = duration;
        params.volume = vol;
        params.enable = 1;
        params.fp = fp;
        params.fl = fl;
        params.fh = fh;
        params.test_case = test_case;              //test_case赋值给 params.test_case  后面test_case不是代替test id了
        params.filename = file_name;
        params.type_of_test = -1;                  //params.type_of_test 指测试设备类型
        params.tx_volume = tx_volume;
        params.rx_volume = rx_volume;
        params.tone_analysis = tone_analysis;
        params.freq_tolerance = freq_tolerance;
        printf("\n Call parse function to enable");  //指定 enable为1后 执行parse,  
        parse(&params);								 //实际执行config文件中对应 test case中的 enable ctrl序列设置,详见parse分析
        pthread_mutex_unlock(&params.lock);          
		//......
        test_case = params.type_of_test;             //test_case 变为type_of_test  指测试设备类型,经过parse 函数,
        											 //更加配置文件中每个配置项的"!" 决定PATH 的具体类型
        											 //类型一般定义为:
                                                        /*
                                                        #define PATH_NULL	0
                                                        #define PATH_RX		1
                                                        #define PATH_TX		2
                                                        #define PATH_AFE_LB	3
                                                        #define PATH_ADIE_LB	4
                                                        #define PATH_FM		5
                                                        #define PATH_EXT_LB	6
                                                         */
        ftm_tc_devices[test_case].path_type = params.type_of_test;
        fseek(params.fp, 0 ,SEEK_SET);
    }
    switch (ftm_tc_devices[test_case].path_type)   //在parse过程中 会对该test case的种类做出解析,Playback,Capture,LooP等
    {
    case PATH_RX:        //对应"Playback",详见parse分析
        result = test_ftm_tone_play_comm(test_case, paraminfo);  //对应 实例log中  enable ctl序列和disable ctl序列中间的部分
        break;
    case PATH_TX:        //对应"Capture",详见parse分析
        result = test_ftm_pcm_record_comm(test_case, paraminfo);
        break;
    case PATH_FM:       //对应"FM",详见parse分析
        result = test_ftm_pcm_fm_comm(test_case, paraminfo);
        break;
    case PATH_AFE_LB:    //对应"AfeLoop",详见parse分析
        result = test_ftm_afe_loopback_comm(test_case, paraminfo);
        break;
    case PATH_ADIE_LB:  //对应"CodecLoop",详见parse分析
        result = test_ftm_adie_loopback_comm(test_case, paraminfo);
        break;
    case PATH_EXT_LB:  
        result = test_ftm_ext_loopback_comm(test_case, paraminfo);
        break;
    }

    if (!fp) {
        mxr = mixer_open(SND_CARD_NUM);
        if (!mxr){
            printf("error: failed to open mixer\n");
            return -1;
        }
        ftm_tc_devices[test_case].enable(mxr, 0);
        mixer_close(mxr);
        taiko_i2s = FALSE;
    } else {
        printf("\n Call parse function to disable");
        pthread_mutex_lock(&params.lock);
        params.enable = 0;
        parse(&params);  //指定 enable为0后 执行parse,  实际执行 config文件中对应 test case中的 disable ctrl序列设置,详见parse分析
        pthread_mutex_unlock(&params.lock);
    }
    return result;
}

2.3:parse 解析过程

static int parse(struct test_params *commands)
{
    FILE *fp;
    char *p, array[100], *pmode;
    char tc[10];
    char en[] = "enable";
    char dis[] = "disable";
    int len = 0, found = 0, len2 = 0;
    struct mixer *mxr = NULL;
    struct mixer_ctl *ctl = 0;
    int ret, vol_found = 0;
    char *temp;
    int params, sublen;
	//......
    fp = commands->fp;
    snprintf(tc, sizeof(tc), "tc %d", commands->test_case);
    pmode = (commands->enable)?en:dis;

    /* Find the test case */
    while((p = fgets(array, sizeof(array), fp))) {
        len = strnlen(p,sizeof(array));
        p[len-1] = '\0';
        len--;
        if (!strncmp(p, tc, sizeof(tc))) {      //根据tc 标号 在config文件中查找配置
             /* Differentiate between tc 2 and tc 28 */
             len2 = strnlen(tc, sizeof(tc));
             if (len == len2)
                 found = 1;
             break;
        }
    }
    if (!found) {
        printf("\n Invalid Testcase %s", tc);
        return -1;
    }

    printf("\nFound %s %d seq %s", tc, found, pmode);   //对应上面实例执行中输出的 "Found tc 1 1 seq enable"
    strlcpy(tc, "tc", sizeof(tc));
    /* 下面开始查找 enable or disable 命令序列*/
    found = 0;
    commands->type_of_test = -1;
    while((p = fgets(array, sizeof(array), fp))) {
        len = strnlen(p,sizeof(array));
        p[len-1] = '\0';
        len--;
        /* Comment added print comment */
        if (!strncmp(&p[0], "#", 1)) {                 //读到字符串 以"#" 开始,直接打印, 对应log中第二行的"#Left Speaker"
            printf("\n %s", p);
        } else if (strstr(p, "Rxdevice")) {            //如果是"Rxdevice"
            temp = NULL;
            if ((temp = strstr(p,":"))) {
                sublen = temp - p;
                len = len - sublen - 1;
                temp++;
                temp[len] = '\0';
                if (*temp >= '0' || *temp <= '9')
                    g_deviceplayback = atoi(temp);   // 获取 "Rxdevice" 后的 0 - 9 的编号
                else
                    g_deviceplayback = -1;
            }
        } else if (strstr(p, "Txdevice")) {          //如果是"Txdevice"
            temp = NULL;
            if ((temp = strstr(p,":"))) {
                sublen = temp - p;
                len = len - sublen - 1;
                temp++;
                temp[len] = '\0';
                if (*temp >= '0' || *temp <= '9')
                    g_devicecapture = atoi(temp);   // 获取 "Txdevice" 后的 0 - 9 的编号
                else
                    g_devicecapture = -1;
            }
        } else if (strstr(p, tc) && !strstr(p, ":")) {  // 如果是 test case 本身,直接返回
            break;
        } else if (!strncmp(&p[0], "!", 1)) {           //读到字符串 以"!" 开始, 判断其类型
            if (!strncmp(&p[1], "Playback", len)) { 
                commands->type_of_test = PATH_RX;       //如果是 "Playback";test type 设置为PATH_RX
            } else if (!strncmp(&p[1], "Capture", len)) {
                commands->type_of_test = PATH_TX;       //如果是 "Capture";test type 设置为PATH_TX
            } else if (!strncmp(&p[1], "AfeLoop", len)) {
                commands->type_of_test = PATH_AFE_LB;    //如果是 "AfeLoop";test type 设置为PATH_AFE_LB
            } else if (!strncmp(&p[1], "CodecLoop", len)) {
                commands->type_of_test = PATH_ADIE_LB;   //如果是 "CodecLoop";test type 设置为PATH_ADIE_LB
            } else if (!strncmp(&p[1], "FM", len)) { 
                commands->type_of_test = PATH_FM;        //如果是 "FM";test type 设置为PATH_FM
            } else
                commands->type_of_test = -1;
        } else if (strstr(p, pmode)) {            //在config文件汇总 test case命令配置下  找到了 enable或disable的配置
            found = 1;
            break;
        }
    }
    if (!found || commands->type_of_test == -1) {
        printf("\n Sequence for %s not found", pmode);
        return -1;
    } else {
        printf("\n Sequence for %s found", pmode);      //对应前面实例 log中 "Sequence for enable found" 
    }													//				  "Sequence for disable found"
    pmode = (!commands->enable)?en:dis;
    mxr = mixer_open("/dev/snd/controlC0");            //打开声卡的 Conctrol设备节点
	//......
    while((p = fgets(array, sizeof(array), fp))) {     //获取一行配置
        len = strnlen(p,sizeof(array));
        p[len-1] = '\0';
        len--;
        if ( ((strstr(p, tc) || strstr(p, pmode)) && (!strstr(p, ":"))) ) { //去除非ctrl设置项
        break;
        } else {
            if (len) {
                char ctlname[100];
                char ctlval[100];
                temp = strstr(p,":");
                if (temp) {
                    sublen = temp - p;
                    memcpy(ctlname, p, sublen);
                    ctlname[sublen] = '\0';
                    ctl = get_ctl(mxr, ctlname);  //获取 ':' 之前的内容未 ctlname
                    if (!ctl) {
                        printf("%s Failed to get %s\n", ctlname);
                        break;
                    }
                    sublen = len - sublen;
                    sublen--;
                    temp++;
                    memcpy(ctlval, temp, sublen);  //获取 ':' 之后的内容未 value
                    ctlval[sublen] = '\0';
                    int val = -1;
                    while(sublen > 0) {
                        if (*temp == ' ') {
                            temp++;
                            sublen--;
                        }else if (*temp >= '0' && *temp <= '9') {
                            val = 1;              // value 是 数字, 通过mixer_ctl_set 设置
                            break;
                        } else {
                            val = 0;              // 其他 mixer_ctl_select 设置
                            break;
                        }
                    }
                    if (val < 0) {
                        printf("\n In valid param for val");
                        return -EINVAL;
                    } else if (!val) {
                        printf("\n Set %s %s", ctlname, ctlval);
                        ret = mixer_ctl_select(ctl, ctlval);  //设置ctl的值为 value
                    } else {
                        val =  atoi(temp);
                        if (strstr(ctlname, "Volume")) {
                            val = commands->volume;
                        }
                        printf("\n Set %s %d", ctlname, val); 
                        ret = mixer_ctl_set(ctl, val);    //设置ctl的值为 value
                    }
                }
        }
      }
    }
    mixer_close(mxr);
    return ret;
}

3:测试实例过程分析

3.1:测试Speaker过程分析

3.1.1:测试过程

用mm-audio-ftm 执行测试, Left Speaker测试用例测试过程输出有以下内容:

bengal:/ # mm-audio-ftm -c /vendor/etc/ftm_test_config -tc 1 -d 10 -v 60 -file /system/etc/mmi/qualsound.wav
SoundCardInfo                       bengal-idp-snd-card

soundCardName bengal-idp-snd-card
Use the codec ftm_test_config file

 Conf file name /vendor/etc/ftm_test_config
 Test case number 1
 duration set to 10
 Volume level set to 60
size of ftm_tc_devices_tabla = 45
size of ftm_tc_devices_sitar = 28
size of ftm_tc_devices_taiko = 43
 File name is /system/etc/mmi/qualsound.wav
 Call parse function to enable
Found tc 1 1 seq enable
 #Left Speaker
 Sequence for enable found
 Set RX_CDC_DMA_RX_1 Channels One
 Set RX_MACRO RX2 MUX AIF2_PB
 Set RX INT2_1 MIX1 INP0 RX2
 Set AUX_RDAC Switch 1
 Set RX_CDC_DMA_RX_1 Audio Mixer MultiMedia1 1
 
 ftm_audio_set_path: 8875

 Playback of file /system/etc/mmi/qualsound.wavFTM: audio_ftm_pcm_parse_header:256 - hdr.fmt_sz 16 hdr.bits_per_sample = 16hdr.audio_format = 1
FTM: audio_ftm_pcm_play_attach:362 -
 audio_ftm_pcm_play_attach width 16 channels 2 sample rate 44100ftm_audio_start_file_play: call attach res = 0
audio_ftm_hw_open: flags 0 rate 44100 channels 2 period size 960
 audio_ftm_hw_iocontrol: capturedevice 0 playback 0FTM: aud_ftm_play_pcm:163 -
 Start PlaybackFTM: aud_ftm_play_pcm:168 - EOF file reached
FTM: aud_ftm_play_pcm:186 -
 Done with fileFTM: aud_ftm_play_pcm:194 -
 Close the fileFTM: audio_ftm_hw_iocontrol:1020 -
 PCM Close calledaud_ftm_common_stop: 9019

 Call parse function to disable
Found tc 1 1 seq disable
 #Left Speaker
 Sequence for disable found
 Set RX_MACRO RX2 MUX ZERO
 Set RX INT2_1 MIX1 INP0 ZERO
 Set AUX_RDAC Switch 0
 Set RX_CDC_DMA_RX_1 Audio Mixer MultiMedia1 0test 1 success
bengal:/ #

通过前面内容的分析,可知支持main函数后,携带用户配置参数;
通过parse解析后,在测试speaker的场景下,执行了配置文件中对audio kcontrol序列的enable序列的设置,同时指定了type_of_test = PATH_RX;对照上面的execute_test_case流程分析,在parse后通过 switch (ftm_tc_devices[test_case].path_type) 判断后,紧接着根据类型是PATH_RX,执行的是test_ftm_tone_play_comm; 等待设定的延时时间后,设置 params.enable = 0;通过parse(&params); 再进行一次参数解析,最后执行对audio kcontrol序列的disable序列的设置; 其中speaker播放测试的内容如下:

3.1.2:test_ftm_tone_play_comm

static int test_ftm_tone_play_comm(uint32 path, struct test_params *params)
{
    ftm_audio_pkt_type  cmd;
    ftm_audio_response_type *pkt = NULL;
    int result = 0;
    struct mixer *mxr = 0;
//......

    if (!ftm_audio_set_path(path)) {
        result = 1;
        goto done;
    }
    if (!params) {
        if (!ftm_audio_start_tone(300, 2000)) { //如果没有设置音频格式,播放一个固定频率的声音
            result = 2;
            goto done;
        }
    } else { //有设置音频格式 
        if (params->filename) {   //如果指定文件名,则直接播放这个文件
            printf("\n Playback of file %s", params->filename);
            ftm_audio_start_file_play(params); //播放设置的频率的声音
        } else if (!ftm_audio_start_tone(params->fl, params->fh)) {
            result = 2;
            goto done;
        }
    }
	//......
    if (!params)
        sleep(3);
    else {
        if (params && !params->filename)
            sleep(params->duration);
    }
    if (!params || (params && !params->filename)) {
        printf("\n Stop the tone now");
        if (!ftm_audio_stop_tone()) {
            result = 5;
            goto done;
        }
    } else if (params && params->filename) {
        ftm_audio_stop_file_play();
    }
done:
    return result;
}

3.1.3:测试时序图

3.2:测试Headset Loopback

测试回环的过程与单独的播放是不同的,主要是对对应场景下的设置。

3.2.1:测试过程

用mm-audio-ftm 执行测试,Headset Loopback测试用例测试过程输出有以下内容:

bengal:/ # mm-audio-ftm -c /vendor/etc/ftm_test_config -tc 225 -d 10 -v 50
SoundCardInfo                       bengal-idp-snd-card

soundCardName bengal-idp-snd-card
Use the codec ftm_test_config file

 Conf file name /vendor/etc/ftm_test_config
 Test case number 225
 duration set to 10
size of ftm_tc_devices_tabla = 45
size of ftm_tc_devices_sitar = 28
size of ftm_tc_devices_taiko = 43
 Volume level set to 50
 Call parse function to enable
Found tc 225 1 seq enable
 #AMIC2 to HPH_LR AFE loopback
 Sequence for enable found
 Set RX_MACRO RX0 MUX AIF1_PB
 Set RX_MACRO RX1 MUX AIF1_PB
 Set RX_CDC_DMA_RX_0 Channels Two
 Set RX INT0_1 MIX1 INP0 RX0
 Set RX INT1_1 MIX1 INP0 RX1
 Set RX INT0 DEM MUX CLSH_DSM_OUT
 Set RX INT1 DEM MUX CLSH_DSM_OUT
 Set RX_COMP1 Switch 1
 Set RX_COMP2 Switch 1
 Set HPHL_RDAC Switch 1
 Set HPHR_RDAC Switch 1
 Set HPHL_COMP Switch 1
 Set HPHR_COMP Switch 1
 Set RX_RX0 Digital Volume 62
 Set RX_RX1 Digital Volume 62
 Set TX DEC0 MUX SWR_MIC
 Set TX SMIC MUX0 SWR_MIC4
 Set TX_CDC_DMA_TX_3 Channels One
 Set TX_AIF1_CAP Mixer DEC0 1
 Set ADC2_MIXER Switch 1
 Set ADC2 MUX INP2
 Set ADC2 Volume 10
 Set RX_CDC_DMA_RX_0_DL_HL Switch 1
 Set RX_CDC_DMA_RX_0 Port Mixer TX_CDC_DMA_TX_3 1
 Set Path to device: 3
ftm_audio_set_path: 8875

 AFE loopback to status: 1

 ftm_audio_start_dsp_loopback: cdevice 31 pdevice 30
 audio_ftm_hw_iocontrol: capturedevice 31 playback 30audio_ftm_hw_loopback_en(1.1)
play_loopback
rec_loopback

 AFE loopback to status: 0
FTM: audio_ftm_hw_iocontrol:1023 -
 PCM Close not calledaudio_ftm_hw_loopback_en(0.1)
aud_ftm_common_stop: 9019

 Call parse function to disable
Found tc 225 1 seq disable
 #AMIC2 to HPH_LR AFE loopback
 Sequence for disable found
 Set RX_MACRO RX0 MUX ZERO
 Set RX_MACRO RX1 MUX ZERO
 Set RX INT0_1 MIX1 INP0 ZERO
 Set RX INT1_1 MIX1 INP0 ZERO
 Set RX INT0 DEM MUX NORMAL_DSM_OUT
 Set RX INT1 DEM MUX NORMAL_DSM_OUT
 Set RX_COMP1 Switch 0
 Set RX_COMP2 Switch 0
 Set HPHL_RDAC Switch 0
 Set HPHR_RDAC Switch 0
 Set HPHL_COMP Switch 0
 Set HPHR_COMP Switch 0
 Set RX_RX0 Digital Volume 0
 Set RX_RX1 Digital Volume 0
 Set TX DEC0 MUX MSM_DMIC
 Set TX SMIC MUX0 ZERO
 Set TX_AIF1_CAP Mixer DEC0 0
 Set ADC2_MIXER Switch 0
 Set ADC2 MUX ZERO
 Set ADC2 Volume 12
 Set RX_CDC_DMA_RX_0_DL_HL Switch 0
 Set RX_CDC_DMA_RX_0 Port Mixer TX_CDC_DMA_TX_3 0test 225 success
bengal:/ #

通过前面内容的分析,可知支持main函数后,携带用户配置参数;
通过parse解析后,在测试speaker的场景下,执行了配置文件中对audio kcontrol序列的enable序列的设置,同时指定了type_of_test = PATH_AFE_LB;对照上面的execute_test_case流程分析,在parse后通过 switch (ftm_tc_devices[test_case].path_type) 判断后,紧接着根据类型是PATH_RX,执行的是test_ftm_afe_loopback_comm; 等待设定的延时时间后,设置 params.enable = 0;通过parse(&params); 再进行一次参数解析,最后执行对audio kcontrol序列的disable序列的设置; 其中headset loopback测试的内容如下:

3.2.2:test_ftm_afe_loopback_comm

test_ftm_afe_loopback_comm(test_case, paraminfo);

int test_ftm_afe_loopback_comm(uint32 path,  struct test_params *params)
{
    ftm_audio_pkt_type  cmd;
    struct mixer *mxr = 0;
    int result = 0;

    cmd.composite_header.ftm_hdr.cmd_id=FTM_AUDIO_SET_PATH;  //设置cmd Id, 为FTM_AUDIO_SET_PATH;
    cmd.audio_params.device=path;							 //test case
    ftm_audio_dispatch((PACKED void *)&cmd, FALSE);          //发送cmd   FALSE是指非DIAG

    cmd.composite_header.ftm_hdr.cmd_id=FTM_AUDIO_DSP_LOOPBACK;  //设置cmd Id, 为FTM_AUDIO_DSP_LOOPBACK;
    cmd.audio_params.on_off = ON;                                //设置ON
    ftm_audio_dispatch((PACKED void *)&cmd, FALSE);              //发送cmd FALSE是指非DIAG

    if (!params) {
//......
    }
//......
    else
        sleep(params->duration);                               //有设置执行时间(睡眠等待)sleep(params->duration); 
        													   //没有设置,按main函数中的初始化值duration是3   约3s;

    cmd.audio_params.on_off = OFF;                             //设置OFF
    ftm_audio_dispatch((PACKED void *)&cmd, FALSE);            //送cmd  FALSE是指非DIAG
    sleep(1);
done:
    return result;
}

其中一些参数的结构定义:

test_params

struct test_params {
  FILE *fp;
  int test_case;
  int enable;
  int volume;
  int fl;
  int fh;
  int duration;
  char *filename;
  int type_of_test;
  int tx_volume;
  int rx_volume;
  int tone_analysis;
  int freq_tolerance;
  pthread_mutex_t lock;
};
struct test_params params;

ftm_audio_pkt_type

ftm_audio_pkt_type:

  • ftm_audio_composite_cmd_header_type
    • ftm_audio_diagpkt_subsys_header_type
    • ftm_audio_cmd_header_type
  • ftm_audio_params

ftm_audio_pkt_type

typedef struct
{
  ftm_audio_composite_cmd_header_type composite_header;
  ftm_audio_params              audio_params;
}__attribute__((packed)) ftm_audio_pkt_type;

ftm_audio_composite_cmd_header_type

typedef struct
{
  ftm_audio_diagpkt_subsys_header_type  diag_hdr;
  ftm_audio_cmd_header_type             ftm_hdr;
}__attribute__((packed)) ftm_audio_composite_cmd_header_type;

ftm_audio_diagpkt_subsys_header_type

typedef struct
{
  diagpkt_cmd_code_type         cmd_code;
  diagpkt_subsys_id_type        subsys_id;
  diagpkt_subsys_cmd_code_type  subsys_cmd_code;
}__attribute__((packed)) ftm_audio_diagpkt_subsys_header_type;

ftm_audio_cmd_header_type

typedef struct
{
  uint16 cmd_id;            /* command id (required) */
  uint16 cmd_data_len;      /* request pkt data length, excluding the diag and ftm headers
                             (optional, set to 0 if not used)
                          */
  uint16 cmd_rsp_pkt_size;  /* rsp pkt size, size of response pkt if different then req pkt
                             (optional, set to 0 if not used)
                          */
}__attribute__((packed)) ftm_audio_cmd_header_type;

ftm_audio_params

typedef union
{
  boolean                 on_off;
  uint16                  device;
  uint8                   volume;
  uint16                  num_pcm_buffers;
  sint15                  mic_gain_offset;
  ftm_audio_tone_type     tone_params;
  ftm_audio_pcm_req_type  pcm_req;
}__attribute__((packed)) ftm_audio_params;

能够设置的type 枚举:

typedef enum
{
  FTM_AUDIO_SET_PATH,
  FTM_AUDIO_SET_VOLUME,
  FTM_AUDIO_DSP_LOOPBACK,
  FTM_AUDIO_PCM_LOOPBACK,
  FTM_AUDIO_TONES_PLAY,
  FTM_AUDIO_TONES_STOP,
  FTM_AUDIO_NS_CONTROL,
  FTM_AUDIO_PCM_ENABLE,
  FTM_AUDIO_GET_PCM_SAMPLES,
  FTM_AUDIO_PCM_DISABLE,
  FTM_MIC_GAIN_OFFSET,
  FTM_AUDIO_MP3_PLAY,
  FTM_AUDIO_EXT_LOOPBACK
} ftm_audio_subcmd_type;

输命令的是ftm_audio_dispatch

3.2.3:ftm_audio_dispatch

ftm_audio_dispatch

主要的FTM 音频命令处理程序。

这个函数可以被认为是 FTM 音频功能的 main()。
它从 FTM 主调度程序(PC FTM 工具 ->
DIAG -> PC USB -> Phone USB -> DIAG -> FTM -> 此处)并参与各种
音频系统驱动程序执行对应于 FTM 音频的操作命令发送。

@param cmd_ptr FTM 音频 DIAG 数据包结构。
@return DIAG 响应数据包。

PACKED void * ftm_audio_dispatch( PACKED void * request, DALBOOL is_diag)
{
    ftm_audio_pkt_type * cmd_ptr = (ftm_audio_pkt_type *)request;
    char  result;
    ftm_audio_response_type * new_pkt=NULL;
    AUDIO_FTM_OUTPUT_PATH_ENUM_T x;
    result=FTM_AUDIO_COMMAND_SUCCESS;

    switch( cmd_ptr->composite_header.ftm_hdr.cmd_id )
    {
    case FTM_AUDIO_SET_PATH:
        printf("\n Set Path to device: %d\n", cmd_ptr->audio_params.device);   //对应 回环测试中"Set Path to device: 3"
            																   //test type  PATH设置PATH_AFE_LB即3
		//......
        if( ftm_audio_set_path(cmd_ptr->audio_params.device) != TRUE)
		//......
        break;
    case FTM_AUDIO_SET_VOLUME:
		//......
        break;
    case FTM_AUDIO_PCM_LOOPBACK:
		//......
        break;
    case FTM_AUDIO_TONES_PLAY:
		//......
        break;
    case FTM_AUDIO_TONES_STOP:
		//......
        break;
    case FTM_AUDIO_DSP_LOOPBACK:

        printf("\n AFE loopback to status: %d\n",cmd_ptr->audio_params.on_off);

        if (g_curr_device <= 0) {
            printf("\n In correct device");
            break;
        }

        if(( cmd_ptr->audio_params.on_off == ON ) && (!ftm_audio_checkif_active_session())
            && (ftm_tc_devices[g_curr_device].path_type == PATH_AFE_LB))
        {
            ftm_audio_start_dsp_loopback();      //设置回环通路打开
        }
        else if(( cmd_ptr->audio_params.on_off == OFF ) && (ftm_audio_checkif_active_session()))
        {
            ftm_audio_stop_dsp_loopback();      //设置回环通路关闭
        }
        break;
	//......
    default:
        printf("%s: unknown command (%d)\n",__func__,cmd_ptr->composite_header.ftm_hdr.cmd_id);
        result=FTM_AUDIO_COMMAND_FAILURE;
        break;
    }

    if (!is_diag)
        return NULL;
		//......
}

ftm_audio_set_path

static DALBOOL ftm_audio_set_path(
  word test_case
)
{
//......
    printf("%s: %d\n", __func__, __LINE__);  //对应log:ftm_audio_set_path: 8875
        mxr = mixer_open(SND_CARD_NUM);
//......
    if (!g_config_test) {
        if (ftm_tc_devices[test_case].path_type == PATH_RX) {
            if (g_curr_rx_device >= 0)
                ftm_tc_devices[g_curr_rx_device].enable(mxr, 0); //执行enable回调
        }
//......

    if (g_config_test || !ftm_tc_devices[test_case].enable(mxr, 1)) {
        switch(ftm_tc_devices[test_case].path_type)
        {
        case PATH_RX:
            g_curr_rx_device = test_case;
            break;
        case PATH_TX:
            g_curr_tx_device = test_case;
            break;
        case PATH_AFE_LB:
            g_curr_afe_lb_device = test_case;
            break;
		//......
        }
    }	//......
    }
    g_curr_device = test_case;
	//......

}

ftm_audio_start_dsp_loopback

static DALBOOL ftm_audio_start_dsp_loopback()
{
    AUDIO_FTM_STS_T res;
    AUD_FTM_DISPATCH_PARAM_T ftm_param;
    memset(&ftm_param,0,sizeof(ftm_param));

    ftm_param.afe_lp_param.path.inpath=g_current_path.iPath;
    ftm_param.afe_lp_param.path.outpath=g_current_path.oPath;
    ftm_param.afe_lp_param.gain=(uint16)g_volume_level;
    ftm_param.afe_lp_param.channel=g_current_path.channel;
    ftm_param.afe_lp_param.capturedevice=g_devicecapture;
    ftm_param.afe_lp_param.playbackdevice=g_deviceplayback;
    printf("\n %s: cdevice %d pdevice %d", __func__,
               g_devicecapture, g_deviceplayback);
    res=Audio_FTM_Attach(				    //调用Audio_FTM_Attach
        AUDIO_FTM_METHOD_AFE_LOOPBACK,      //指定为AUDIO_FTM_METHOD_AFE_LOOPBACK
        &ftm_param,
        &g_active_ftm_driver_attach_hdl);
	//......
    return aud_ftm_common_start();         //开始  
}

ftm_audio_stop_dsp_loopback

static DALBOOL ftm_audio_stop_dsp_loopback()
{
    return aud_ftm_common_stop();
}

Audio_FTM_Attach

AUDIO_FTM_STS_T
Audio_FTM_Attach
(
    AUDIO_FTM_METHOD_ENUM_T nMethod,
    void *pParam,
    AUDIO_FTM_DRIVER_HANDLE_T * pDriverHdl
)
{
  uint8 i;
  AUDIO_FTM_STS_T ret;

  nCurrent_ftm_method=nMethod;

  if(g_pFncPtrTbl != NULL)
    return AUDIO_FTM_ERROR;

  for(i=0; i<AUDIO_FTM_METHOD_MAX;i++)
  {
    if(i<ARRAYLEN(g_IntfaceTable))
      if(nCurrent_ftm_method == g_IntfaceTable[i].method_id)
      break;
  }

	if(i >= AUDIO_FTM_METHOD_MAX)
		return AUDIO_FTM_ERR_INVALID_PARAM;

  g_pFncPtrTbl=&g_IntfaceTable[i].intf;
  ret=g_pFncPtrTbl->Attach(pParam,pDriverHdl);
  return ret;
}

aud_ftm_common_start

static DALBOOL aud_ftm_common_start()
{
    AUDIO_FTM_STS_T res;

    res=Audio_FTM_Open(
        g_active_ftm_driver_attach_hdl,
        AUD_FTM_ACCESS_MODE_CTRL,
        AUD_FTM_SHAREMODE_EXCLU,
        &g_active_ftm_driver_open_hdl);

    if((res != AUDIO_FTM_SUCCESS) || (g_active_ftm_driver_open_hdl == NULL)) {
        g_active_ftm_driver_open_hdl=NULL;
        printf("%s: open failed\n",__func__);
        return FALSE;
    }

    res=Audio_FTM_IOControl(
        g_active_ftm_driver_open_hdl,
        IOCTL_AUDIO_FTM_START,
        NULL);

    if(res != AUDIO_FTM_SUCCESS)
    {
        printf("%s: ioctl failed\n",__func__);
        return FALSE;
    }

    AddRef(&g_total_ftm_instances);
    return TRUE;
}

aud_ftm_common_stop

static DALBOOL aud_ftm_common_stop()
{
    AUDIO_FTM_STS_T res;

    res=Audio_FTM_IOControl(
        g_active_ftm_driver_open_hdl,
        IOCTL_AUDIO_FTM_STOP,
        NULL);

    if(res != AUDIO_FTM_SUCCESS)
    {
        printf("%s: stop failed\n",__func__);
        return FALSE;
    }

    printf("%s: %d\n",__func__,__LINE__);
    res=Audio_FTM_Close(g_active_ftm_driver_open_hdl);

    if(res != AUDIO_FTM_SUCCESS)
    {
        return FALSE;
    }

    g_active_ftm_driver_open_hdl = NULL;

    res=Audio_FTM_Detach(g_active_ftm_driver_attach_hdl);

    if(res != AUDIO_FTM_SUCCESS)
    {
        printf("%s: detach failed\n",__func__);
        return FALSE;
    }

    g_active_ftm_driver_attach_hdl = NULL;
    Release(&g_total_ftm_instances);
    return TRUE;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 `vendor/qcom/proprietary/chi-cdk/` 目录下,如果你已经获取到源码并编译完成,可以使用以下步骤获取yuv420图像: 1. 找到要获取yuv420图像的摄像头的camera ID。你可以在 `vendor/qcom/proprietary/chi-cdk/Android.mk` 文件中找到这些摄像头的 ID。 2. 配置 `CameraDevice`。在 `vendor/qcom/proprietary/chi-cdk/frameworks/camx/src/core/camxdeviceapi/camxhwcontext.cpp` 文件中,你可以找到 `SetCameraDevice()` 函数,可以在该函数中配置 `CameraDevice`。例如,以下代码可以配置一个摄像头为前置摄像头(ID为0): ``` CAMX_LOG_INFO(CamxLogGroupHAL, "Open camera device for ID %d", cameraId); m_hCameraDevice = CameraDevice::Create(cameraId, "qcom.camera.front", this); ``` 3. 打开 `CameraDevice`。在 `vendor/qcom/proprietary/chi-cdk/frameworks/camx/src/core/camxdeviceapi/camxhwcontext.cpp` 文件中,你可以找到 `Initialize()` 函数,在该函数中可以打开 `CameraDevice`。例如: ``` m_hCameraDevice->Open(); ``` 4. 创建 `CameraBufferManager`。在 `vendor/qcom/proprietary/chi-cdk/frameworks/camx/src/core/camxhwcontext.cpp` 文件中,你可以找到 `Initialize()` 函数,在该函数中创建 `CameraBufferManager`。例如: ``` m_pBufferManager = BufferManager::Create("qcom.camera.front", m_hCameraDevice); ``` 5. 获取 `CameraBufferManager`。在 `vendor/qcom/proprietary/chi-cdk/frameworks/camx/src/core/camxhwcontext.cpp` 文件中,你可以找到 `GetBufferManager()` 函数,可以通过该函数获取到 `CameraBufferManager` 的指针。例如: ``` CameraBufferManager* pBufferManager = GetBufferManager("qcom.camera.front"); ``` 6. 获取 `Stream`。在 `vendor/qcom/proprietary/chi-cdk/frameworks/camx/src/core/camxhwcontext.cpp` 文件中,你可以找到 `GetStream()` 函数,可以通过该函数获取到 `Stream` 的指针。例如: ``` Stream* pStream = GetStream("qcom.camera.front", streamId); ``` 7. 获取 `Buffer`。在 `vendor/qcom/proprietary/chi-cdk/frameworks/camx/src/core/camxhwcontext.cpp` 文件中,你可以找到 `GetBuffer()` 函数,可以通过该函数获取到 `Buffer` 的指针。例如: ``` Buffer* pBuffer = pBufferManager->GetBuffer(pStream->GetFormat(), pStream->GetBufferProperties()); ``` 8. 获取 `ImageFormat`。在 `vendor/qcom/proprietary/chi-cdk/frameworks/camx/src/core/camxhwcontext.cpp` 文件中,你可以找到 `GetImageFormat()` 函数,可以通过该函数获取到 `ImageFormat` 的指针。例如: ``` const ImageFormat* pImageFormat = ImageFormatUtils::GetImageFormatFromPixelFormat(pStream->GetFormat()); ``` 9. 获取 `ImageBuffer`。在 `vendor/qcom/proprietary/chi-cdk/frameworks/camx/src/core/camxhwcontext.cpp` 文件中,你可以找到 `GetImageBuffer()` 函数,可以通过该函数获取到 `ImageBuffer` 的指针。例如: ``` ImageBuffer* pImageBuffer = ImageBufferUtils::GetImageBuffer(pBuffer->GetBuffer(), pImageFormat, pBuffer->GetPlaneStride(), pBuffer->GetPlaneOffset()); ``` 10. 获取 `Plane`。在 `vendor/qcom/proprietary/chi-cdk/frameworks/camx/src/core/camxhwcontext.cpp` 文件中,你可以找到 `GetPlane()` 函数,可以通过该函数获取到 `Plane` 的指针。例如: ``` Plane* pPlane = pImageBuffer->GetPlane(pImageFormat->componentOrder[0]); ``` 11. 获取 `Plane` 的数据。在 `vendor/qcom/proprietary/chi-cdk/frameworks/camx/src/core/camxhwcontext.cpp` 文件中,你可以找到 `GetBuffer()` 函数,可以通过该函数获取到 `Plane` 的数据。例如: ``` UINT8* pPlaneData = static_cast<UINT8*>(pPlane->GetBuffer()); ``` 通过以上步骤,你就可以获取到 yuv420 图像数据了。注意,以上代码只是提供了获取 yuv420 图像数据的一个示例,实际上在使用时还需要根据具体的需求进行适当的修改和调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值