解决VB和inner audio同时存在,并播放由电脑合成的PCM。

hi35xx提供AIAO用于和片内、片外的audio codec对接,完成音频数据的输入输出功能。其中AIAO对应于一对AIP、AOP音频通道。如果采用inner audio方式需要用到I2S接口。
该接口特点为。
-支持主模式立体声 16/24bit 数据的发送和接收。
-支持 8kHz~192kHz 采样率。
-接收(AIP)和发送(AOP)相互独立,可以单独使能或关闭。
-接收(AIP)和发送(AOP)均采用 DMA 操作,通过软件开辟的循环缓冲区存取数据,循环缓冲区大小和水线可调。
-AIP 支持主模式 I2S 对接内部 Audio Codec 、AOP 支持主模式 I2S 对接内部 Audio Codec 。
功能原理。
-AIP 通过 I2 S接口接收对接内部集成的 Audio Codec 进行 AD(Analog-to-Digital)转换后的音频数据,存入为 AIP 开辟的循环缓冲区,然后由 CPU取走并存储,从而完成录音功能。
-AOP 从循环缓冲区中读取音频数据,然后按照设定的采样率,把音频数据通过 I2 S 接口传送给对接内部集成的 Audio Codec 进行 DAC(Digital-to-Analog)转换后进行声音播放。海思文档说:“为了兼容历史版本,使用海思语音编解码库进行 G711、G726、ADPCM 格式的编码,编码后的码流需在每帧码流数据的净荷数据之前填充有 4 个字节的帧头”
添加海思语音帧头参考代码:
int HisiVoiceAddHisiHeader(short *inputdata, short *Hisivoicedata, int
PersampleLen,int inputsamplelen)
{
    int len = 0, outlen = 0;
    short HisiHeader[2];
    short *copyHisidata, *copyinputdata;
    int copysamplelen = 0;
    HisiHeader[0] = (short)(0x001<<8) & (0x0300);
    HisiHeader[1] = PersampleLen & 0x00ff;
    copysamplelen = inputsamplelen;
    copyHisidata = Hisivoicedata;
    copyinputdata = inputdata;
    while(copysamplelen >= PersampleLen)
    {
        memcpy(copyHisidata, HisiHeader, 2 * sizeof(short));
        outlen += 2;
        copyHisidata += 2;
        memcpy(copyHisidata, copyinputdata, PersampleLen * sizeof(short));
        copyinputdata += PersampleLen;
        copyHisidata += PersampleLen;
        copysamplelen -= PersampleLen;
        outlen += PersampleLen;
    }
    return outlen;
}

不过我们使用的是PCM就不考虑4字节头了。

使用预先合成的PCM导入板端,然后进行PCM的播放。就需要用到AOP通道。我们先看看海思SDK给我们提供了的资源。

mmp/ko目录下。不开源的驱动
这里写图片描述
mmp/sample/audio/目录下。开源的测试用例。
这里写图片描述
以及配置inner auio的管脚复用
writel(0xd, IO_ADDRESS(0x201200E0)); //MISC_CTRL56 AIP/AOP复用、输出时钟选择配置
插入驱动的方法:
1.加载的驱动:
acodec.ko:管理音频音量
hi3518e_aenc.ko: 编码
hi3518e_adec.ko: 解码
hi3518e_ai.ko: 录制
hi3518e_ao.ko:播放
hi3518e_aio.ko:循环播放?

  1. 在开发板加载音频驱动时 在/dev下没有相关的设备节点(即使用medv -s 也不行)
    所以我们需要进入这个目录 /sys/devices 找到相应模块目录 cat uevent
    从而查看得到主从设备号 这样就可以手动mknod了
    acodec.ko: 218 61
    hi3518e_aenc.ko: 218 13
    hi3518e_adec.ko: 218 14
    hi3518e_ai.ko: 218 5
    hi3518e_ao.ko: 218 6
    hi3518e_aio.ko:
上面是总结hisi SDK给出的资源,由于只需要播放,那么只需要插入acodec.ko、 hi3518e_adec.ko、 hi3518e_ao.ko、 hi3518e_aio.ko。
下面详细介绍如何使用SDK资源做出inner audio播放PCM。

由于初始化inner audio设备需要重新init VB..而audio恰恰不需要VB,但是在init audio之前不做VB init,audio起来时会报声音设备没初始化、通道没初始化、设备没找到等等奇葩错误。所以我们需要下面那些代码。

VB_CONF_S stVbConf;
memset(&stVbConf, 0, sizeof(VB_CONF_S));
s32Ret = COMM_SYS_Init(&stVbConf);
if (HI_SUCCESS != s32Ret)
{
    printf("%s: system init failed with %d!\n", __FUNCTION__, s32Ret);
    return HI_FAILURE;
}

注意,一旦VB清空视频子系统将不能起来。。。后面会介绍两者兼容的解决方案。

现在我先介绍audio单独使用的情况。

先上关键代码。

//初始化音频播放模块
int DRV_AUDIO_Init(PAYLOAD_TYPE_E gs_enPayloadType)
{
    HI_S32 s32Ret = HI_SUCCESS;
    AIO_ATTR_S stAioAttr;

    signal(SIGINT, AUDIO_HandleSig);
    signal(SIGTERM, AUDIO_HandleSig);
    stAioAttr.enSamplerate   = AUDIO_SAMPLE_RATE_16000; //16kHZ
    stAioAttr.enBitwidth     = AUDIO_BIT_WIDTH_16;      // 16位采样位数 
    stAioAttr.enWorkmode     = AIO_MODE_I2S_MASTER;     //主模式 
    stAioAttr.enSoundmode    = AUDIO_SOUND_MODE_MONO;   //单声道 
    stAioAttr.u32EXFlag      = 0;
    stAioAttr.u32FrmNum      = 30;                      // 缓冲区的大小 目前为30帧大小
    stAioAttr.u32PtNumPerFrm = AUDIO_PTNUMPERFRM;       //设置从音频编码通道获取每包音频数据大小为AUDIO_PTNUMPERFRM+4 byte (AUDIO_PTNUMPERFRM+4,4字节是帧头)
    stAioAttr.u32ChnCnt      = 1;                       //通道编号
    stAioAttr.u32ClkSel      = 1;
#ifdef TEST_VERSION
    VB_CONF_S stVbConf;
    memset(&stVbConf, 0, sizeof(VB_CONF_S));
    s32Ret = COMM_SYS_Init(&stVbConf);
    if (HI_SUCCESS != s32Ret)
    {
        printf("%s: system init failed with %d!\n", __FUNCTION__, s32Ret);
        return HI_FAILURE;
    }
#endif 
    s32Ret = COMM_AUDIO_CfgAcodec(&stAioAttr);
    if (HI_SUCCESS != s32Ret)
    {
        DBG(s32Ret);
        goto ADECAO_ERR3;
    }

    s32Ret = COMM_AUDIO_StartAdec(AdChn, gs_enPayloadType);
    if (s32Ret != HI_SUCCESS)
    {
        DBG(s32Ret);
        goto ADECAO_ERR3;
    }

    s32AoChnCnt = stAioAttr.u32ChnCnt;
    s32Ret = COMM_AUDIO_StartAo(AoDev, s32AoChnCnt, &stAioAttr);
    if (s32Ret != HI_SUCCESS)
    {
        DBG(s32Ret);
        goto ADECAO_ERR2;
    }

    s32Ret = COMM_AUDIO_AoBindAdec(AoDev, AoChn, AdChn);
    if (s32Ret != HI_SUCCESS)
    {
        DBG(s32Ret);
        goto ADECAO_ERR1;
    }
    printf("bind adec:%d to ao(%d,%d) ok \n", AdChn, AoDev, AoChn);
    audio_dev = (struct audio_d *)malloc(sizeof(struct audio_d));
    audio_dev->status = 0;
    audio_dev->pfd = NULL;
    audio_dev->execut = 0;
    pthread_mutex_init(&m, NULL);

    s32Ret = COMM_AUDIO_CreatTrdFileAdec(AdChn);
    if (s32Ret != HI_SUCCESS)
    {
        DBG(s32Ret);
        goto ADECAO_ERR1;
    }
    s32Acodec_Fd = open(ACODEC_FILE,O_RDWR);

    return HI_SUCCESS;

ADECAO_ERR1:
    s32Ret |= COMM_AUDIO_StopAo(AoDev, s32AoChnCnt);
    if (s32Ret != HI_SUCCESS)
    {
        DBG(s32Ret);
    }
ADECAO_ERR2:
    s32Ret |= COMM_AUDIO_StopAdec(AdChn);
    if (s32Ret != HI_SUCCESS)
    {
        DBG(s32Ret);
    }

ADECAO_ERR3:
    return s32Ret;
}

看到TEST_VERSION没?如果定义了这个宏,这代码为测试模式(单独使用audio)。
自定义的声音抽象设备,目的为了支持声音的重入。
audio_dev = (struct audio_d *)malloc(sizeof(struct audio_d));
audio_dev->status = 0;
audio_dev->pfd = NULL;
audio_dev->execut = 0;
接着,COMM_AUDIO_CreatTrdFileAdec会创建线程COMM_AUDIO_AdecProc,这个线程干的活是将PCM读入到AOP

fread(stAudioStream.pStream, 1, u32Len, audio_dev->pfd);
....
....
HI_MPI_ADEC_SendStream(s32AdecChn, &stAudioStream, HI_TRUE);

好了,hisi inner audio 测试代码的主干就是这样。。。当然还有初始化设备、启动设备、bind通道、设置音量大小,这些基本操作都在hisi SDk 给出,而且很多博客牛人都有说。。。我就不重复了。

下面我将来一些干货,即如何在实现播放LDPCM的基础下编写接口代码,实现声音的重入。即A声音没播放完,B声音要切断A声音的播放状态。并实现B声音的播放。当然,同理C声音来了也要切断B声音。

贴代码:

//声音设备结构体
struct audio_d
{
    //当前声音设备 0:初始化态             1:播放态    -1:被迫停止.     2:正常停止
    int status;
    //执行权限  0:无权限       1:有权限
    int execut;
    FILE*  pfd;
};

//播放音频文件,支持代码重入.
int gui_play_audio(const char * file, int volume)
{
    HI_S32 s32Ret=HI_SUCCESS;
    FILE*        pfd = NULL;

    set_volume(volume);
    if(audio_dev->status == 1)
        stop_audio();

    if(audio_dev->status !=0){
    //等待声音设备正常停止后才能开启新的播放.
        while(audio_dev->status !=2 );
    }

    pfd = AUDIO_OpenAdecFile(file);
    if (!pfd)
    {
        DBG(HI_FAILURE);
        goto ADECAO_ERR0;
    }   
    audio_dev->pfd = pfd;   
    audio_dev->execut = 1;

    return s32Ret;

ADECAO_ERR0:
    return HI_FAILURE;
}

void stop_audio()
{
    pthread_mutex_lock(&m);
    audio_dev->status = -1;
    audio_dev->execut = 0;
    pthread_mutex_unlock(&m);
    printf("stop audio\n");
}


 /******************************************************************************
* function : get stream from file, and send it  to Adec
******************************************************************************/
void *COMM_AUDIO_AdecProc(void *parg)
{
    HI_S32 s32Ret;
    AUDIO_STREAM_S stAudioStream;
    HI_U32 u32Len = 50;
    HI_U32 u32ReadLen;
    HI_S32 s32AdecChn;
    HI_U8 *pu8AudioStream = NULL;
    SAMPLE_ADEC_S *pstAdecCtl = (SAMPLE_ADEC_S *)parg;
    s32AdecChn = pstAdecCtl->AdChn;

    pu8AudioStream = (HI_U8*)malloc(sizeof(HI_U8)*MAX_AUDIO_STREAM_LEN);
    if (NULL == pu8AudioStream)
    {
        printf("%s: malloc failed!\n", __FUNCTION__);
        return NULL;
    }   
    stAudioStream.pStream = pu8AudioStream;

    while (HI_TRUE == pstAdecCtl->bStart)
    {
        while(audio_dev->execut == 0 || audio_dev->pfd== NULL)  //等待切换到执行态
            usleep(10000);

        pthread_mutex_lock(&m);
        audio_dev->status = 1;
        pthread_mutex_unlock(&m);
        while(1)
        {
            if(audio_dev->status == -1)
                break;
            u32ReadLen = fread(stAudioStream.pStream, 1, u32Len, audio_dev->pfd);
            if(u32ReadLen !=u32Len)
            {
                if(ferror(audio_dev->pfd)){
                    perror("fread failed");
                    break;
                }
                else        //读完就退出
                    break;
            }
            stAudioStream.u32Len = u32ReadLen;
            s32Ret = HI_MPI_ADEC_SendStream(s32AdecChn, &stAudioStream, HI_TRUE);
            if(HI_SUCCESS != s32Ret){
                printf("%s: HI_MPI_ADEC_SendStream(%d) failed with %#x!\n",\
                    __FUNCTION__, s32AdecChn, s32Ret);
                break;
            }
        }   
        exit_func();
    }

    free(pu8AudioStream);
    pu8AudioStream = NULL;
    pstAdecCtl->bStart = HI_FALSE;
    return NULL;
}

void exit_func()
{
    pthread_mutex_lock(&m);
    audio_dev->status = 2;
    audio_dev->execut = 0;
    pthread_mutex_unlock(&m);
    fclose(audio_dev->pfd);
    audio_dev->pfd = NULL;
    printf("audio exit \n");
}

代码逻辑是:上来就设置音量、如果是播放态就stop线程的播放,等到完全stop后开始播放。if(audio_dev->status !=0)做这个判断是避开初始状态。然后就AUDIO_OpenAdecFile,设执行权。线程就开始播放了。
当然了,由于audio_dev->status audio_dev->execut 是临界资源需要上锁。
有个小推荐:将gui_play_audio编成.a、.so就可以使上层下层解耦。

#create by jinfa

SRC = $(wildcard src/*.c audio.c) #*/
OBJ = $(SRC:%.c=%.o)

APP_NAME=audio_app

CC=$(CROSS_COMPILE)gcc
AR=$(CROSS_COMPILE)ar

CFLAGS += -I../../mpp/include
CFLAGS += -I./include  -Wall
AUDIO_LIBA = ../../mpp/lib/libVoiceEngine.a
MPI_LIBS += ../../mpp/lib/libmpi.a
MPI_LIBS += ../../mpp/lib/libive.a

.PHONY : clean all
all: libhi_audio.a

libhi_audio.a: $(OBJ)
#   @echo -e "\e[0;32;1mCompile $(APP_NAME)...\e[0;36;1m\e[0m"
#   $(CC)  -ldl  -lpthread -lm $^ -o $@   $(CFLAGS) $(MPI_LIBS) $(AUDIO_LIBA) 
    $(AR) -r libhi_audio.a $^ $(AUDIO_LIBA) $(MPI_LIBS)
    @cp libhi_audio.a ../../mpp/lib/


$(OBJ):%.o:%.c
    $(CC) $< -o $@ $(CFLAGS) -c


clean:
    @rm -f libhi_audio.a audio.o $(OBJ) ../../mpp/lib/libhi_audio.a

distclean: clean

测试代码:

int main(int argc, char const *argv[])
{
    int volume=0;
    DRV_AUDIO_Init(PT_LPCM);

    while(1)
    {
        printf("please input volume\n");
        scanf("%d", &volume);
        gui_play_audio("/mnt/flash/scanner", volume);
        getchar();
    }
    return 0;
}

使用AudioConverter这个工具将WAV转PCM、或者用音频编辑大师工具生成PCM。导入板端就可以实现音频播放了。

最后:也是最关键的,如何实现VB和inner audio同时存在。
/** init hisi sdk **/
    /* mpp system init */
    s32Ret = DRV_VIC_MppInit();
    if (S_OK != s32Ret)
    {
        TRACE3("vic mpp init failed with err=%d!\n", s32Ret);
        return S_FAIL;
    }
    s32Ret = DRV_AUDIO_Init(PT_LPCM);
    if (S_OK != s32Ret)
    {
        TRACE3("audio ao adec init failed with err=%d!\n", s32Ret);
        DRV_AUDIO_UnInt();
        return S_FAIL;
    }

在DRV_VIC_MppInit后跟DRV_AUDIO_Init就好了,注意TEST_VERSION这个宏不能打开。

猜你喜欢

转载自blog.csdn.net/huang_165/article/details/80401959
今日推荐