音频采集与播放

更新时间:2023-08-09 09:25:02下载pdf

本文介绍 TKL(TuyaOS Kernel Layer)和 TAL(TuyaOS Abstraction Layer)层音频采集与播放 API 的调用流程。

  • 音频采集:简称 AI(Audio Input),是指通过麦克采集声音,并编码成音频数据。通常 1 秒采集 25 帧。

  • 音频播放:简称 AO(Audio Output),是指使用喇叭播放音频数据。包括提示音,对讲数据等。

实现方案

音频采集

音频采集数据为裸数据 PCM。若需要 G711u/a 等格式编码,您需要在应用层处理。

音频采集与播放

音频播放

音频播放数据在上层解码为裸数据后,再调用播放接口。AO 播放前后,请注意开关扩音器的功放引脚。

音频采集与播放

数据结构

初始化参数结构体

typedef struct {
  UINT_T          enable;             // 1,enable,0,disable
  UINT_T          card;               // audio card num
  TKL_AI_CHN_E       ai_chn;          // audio input channel
  TKL_AUDIO_SAMPLE_E    sample;       // sample
  TKL_AUDIO_DATABITS_E   datebits;    // datebit
  TKL_AUDIO_CHANNEL_E   channel;      // channel num
  TKL_MEDIA_CODEC_TYPE_E codectype; // codec type
  INT32_T         is_softcodec;       // 1, soft encode,0, hardware encode
  UINT_T          fps;                // frame per second,suggest 25
  INT32_T         mic_volume;         // mic volume,[0,100]
  INT32_T         spk_volume;         // spk volume,[0,100]
  INT32_T         spk_volume_offset; // spk volume offset, for adapting different speakers,The default value is 0,[0,100]
  INT32_T         spk_gpio;           // spk amplifier pin number, <0, no amplifier
  INT32_T         spk_gpio_polarity; // pin polarity, 0 high enable, 1 low enable
  void          * padta;
}TKL_AUDIO_CONFIG_T;                  // audio config
#define TAL_AUDIO_CONFIG_T           TKL_AUDIO_CONFIG_T

音频数据结构体

typedef struct{
    TKL_MEDIA_FRAME_TYPE_E   type;                       // frame type
    CHAR_T                  *pbuf;                       // buffer
    UINT_T                   buf_size;                   // buffer size
    UINT_T                   used_size;                  // used buffer
    UINT64_T                 pts;                        // sdk pts
    UINT64_T                 timestamp;                  // system utc time,unit: ms
    TKL_MEDIA_CODEC_TYPE_E   codectype;                  // codec type
    TKL_AUDIO_SAMPLE_E       sample;                     // sample
    TKL_AUDIO_DATABITS_E     datebits;                   // date bit
    TKL_AUDIO_CHANNEL_E      channel;                    // channel num
    UINT_T                   seq;                        // frame sequence number
}TKL_AUDIO_FRAME_INFO_T;                                 // audio frame
#define TAL_AUDIO_FRAME_INFO_T       TKL_AUDIO_FRAME_INFO_T

控制命令

typedef enum {
    TAL_AI_CMD_VOL, // AI volume
} TAL_AI_CMD_E;

typedef enum {
    TAL_AO_CMD_VOL, // AO volume, int, val[0, 100]
} TAL_AO_CMD_E;

音频采集

音频采集初始化

IPC 设备,暂时只支持 1 路 AI。音频初始化前,注意先初始化视频。

/**
 * @brief AI init
 *
 * @param[in] pconfig: audio config
 * @param[in] count: count of pconfig
 *
 * @return OPRT_OK on success. Others on error, please refer to tkl_error_code.h
 */
OPERATE_RET tal_ai_init(TAL_AUDIO_CONFIG_T *pconfig, INT32_T count);

开启采集

/**
 * @brief AI start
 *
 * @param[in] card: card number
 * @param[in] chn: channel number
 *
 * @return OPRT_OK on success. Others on error, please refer to tkl_error_code.h
 */
OPERATE_RET tal_ai_start(INT32_T card, INT32_T chn);

停止采集

/**
 * @brief AI stop
 *
 * @param[in] card: card number
 * @param[in] chn: channel number
 *
 * @return OPRT_OK on success. Others on error, please refer to tkl_error_code.h
 */
OPERATE_RET tal_ai_stop(INT32_T card, INT32_T chn);

获取音频帧

/**
 * @brief AI get frame
 *
 * @param[in] card: card number
 * @param[in] chn: channel number
 * @param[out] pframe: audio frame, pframe->pbuf allocated by upper layer application
 *
 * @return OPRT_OK on success. Others on error, please refer to tkl_error_code.h
 */
OPERATE_RET tal_ai_get_frame(INT32_T card, INT32_T chn, TAL_AUDIO_FRAME_INFO_T *pframe);

pframe,需要给变量 pbuf 分配内存。内存大小建议 640 字节。

采集设置

音量设置一般在初始化化时配置,启动后通常不修改 AI 的音量。

/**
 * @brief AI set
 *
 * @param[in] card: card number
 * @param[in] chn: channel number
 * @param[in] cmd
 * @param[in] parg
 *
 * @return OPRT_OK on success. Others on error, please refer to tkl_error_code.h
 */
OPERATE_RET tal_ai_set(INT32_T card, INT32_T chn, TAL_AI_CMD_E cmd, VOID *parg);

采集去初始化

/**
 * @brief AO uninit
 *
 * @return OPRT_OK on success. Others on error, please refer to tkl_error_code.h
 */
OPERATE_RET tal_ai_uninit(VOID);

音频播放

音频播放初始化

  • IPC 设备,暂时只支持 1 路 AO。

  • AO 和 AI 初始化,部分平台有顺序要求。通常先初始化 AI,再初始 AO。

  • VOID **handle 仅供中台设备使用。IPC 不使用。

/**
 * @brief AO init
 *
 * @param[in] pconfig: audio config
 * @param[in] count: config count
 *
 * @return OPRT_OK on success. Others on error, please refer to tkl_error_code.h
 */
OPERATE_RET tal_ao_init(TAL_AUDIO_CONFIG_T *pconfig, INT32_T count, VOID **handle);

开启播放

/**
 * @brief AO start
 *
 * @param[in] card: card number
 * @param[in] chn: channel number
 * @param[out] handle: handle of start
 *
 * @return OPRT_OK on success. Others on error, please refer to tkl_error_code.h
 */
OPERATE_RET tal_ao_start(INT32_T card, INT32_T chn, VOID *handle);

停止播放

OPERATE_RET tal_ao_stop(INT32_T card, INT32_T chn, VOID *handle);

播放音频帧

播放的音频数据要求为裸数据。

OPERATE_RET tal_ao_put_frame(INT32_T card, INT32_T chn, VOID *handle, TAL_AUDIO_FRAME_INFO_T *pframe);

播放设置

AO 音量设置,在 AO 初始后即可。

/**
 * @brief AO set
 *
 * @param[in] card: card number
 * @param[in] chn: channel number
 * @param[in] handle: the return of start
 * @param[in] cmd
 * @param[in] parg
 *
 * @return OPRT_OK on success. Others on error, please refer to tkl_error_code.h
 */
OPERATE_RET tal_ao_set(INT32_T card, INT32_T chn, VOID *handle, TAL_AO_CMD_E cmd, VOID *parg);

播放去初始化

/**
 * @brief AO uninit
 *
 * @return OPRT_OK on success. Others on error, please refer to tkl_error_code.h
 */
OPERATE_RET tal_ao_uninit(VOID *handle);

使用示例

// 参数配置
    .audio[0].enable = 1,
    .audio[0].ai_chn = 0,
    .audio[0].sample = 8000,
    .audio[0].datebits = 16,
    .audio[0].channel = 0,
    .audio[0].codectype = 101,
    .audio[0].fps = 25,
    .audio[0].mic_volume = 80,
    .audio[0].spk_volume = 80,
    .audio[0].spk_gpio = 15,
    .audio[0].spk_gpio_polarity = 0,

// 初始化:
    ret = tal_ai_init(pinfo->audio, 1);
    if (0 != ret) {
        TYERROR("tal_ai_init failed,%d\n", ret);
        return -1;
    }

    ret = tal_ao_init(pinfo->audio, 1, NULL);
    if (0 != ret) {
        TYERROR("tal_ao_init failed,%d\n", ret);
        return -1;
    }

     ret = ty_sys_gpio_init(&spk_gpio);
     if (0 != ret) {
         TYERROR("tycam_gpio_init failed, %d\n", ret);
          return -1;
     }

// 音频采集:
    ret = tal_ai_start(0, 0);
    if (0 != ret) {
        TYERROR("ty_dev_ai_start failed,%d\n", ret);
        return -1;
    }

    TAL_AUDIO_FRAME_INFO_T frame = {0};
    frame.pbuf = (char *)malloc(640);
    frame.buf_size = 640;
    ret = tal_ai_get_frame(0, 0, frame);
    if (0 != ret) {
        // TYERROR("get frame failed type:%d\n", type);
        return -1;
    }
     ret = tuya_g711_encode(TUYA_G711_MU_LAW, (unsigned short *)buf, used_size, tmpBuf, &outLen);
    ...

// 音频播放:
    ret = tal_ao_start(0, 0, handle);
    ...
    ret = tal_gpio_write(spk_gpio, TAL_GPIO_LEVEL_HIGH);
    ...
    ret = tuya_g711_decode(g711Type, (unsigned short *)pbuf, used_size, buf, &outLen);
    ...
    ret = tal_ao_put_frame(0, 0, NULL, pFrame);
    ...
    ret = tal_gpio_write(spk_gpio, TAL_GPIO_LEVEL_HIGH);
    ...
    ret = tal_ao_stop(spk_gpio, TAL_GPIO_LEVEL_HIGH);
    ...

// 播放音量调节:
    ret = tal_ao_set(0, 0, NULL, TAL_AO_CMD_VOL, &spk_volume);

注意事项

音频初始化前先初始化视频

通常,支持视频采集的设备,需要先初始化视频,再初始化 AI。原因是,芯片平台原生 SDK 需要分配一次 MMZ(Media Memory Zone)内存。MMZ 内存是在视频初始化时分配的。

常见问题

音频帧采集推荐的分配长度是多少?

建议 640 字节。由于需要对接三方设备,编码的流通常为 G711 或 8k PCM。PCM 1s 的音频 8000*16/8=16000,按照 25fps,一帧长度 16000/25=640 字节。g711 对 PCM,压缩比 2:1,需要长度 320 字节。