OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 9127|回复: 12

MP3软件解码

[复制链接]

4

主题

18

帖子

0

精华

初级会员

Rank: 2

积分
120
金钱
120
注册时间
2012-8-8
在线时间
18 小时
发表于 2016-1-6 13:17:34 | 显示全部楼层 |阅读模式
1金钱
我在论坛里面找了个heilx软件解码的程序,数据是通过串口接收的,
程序查找同步字都成功了,解码函数也执行通过就是没有看到输出PCM数据,不知道PCM数据到底是在那里输出的

大家帮忙看看在那个位置输出PCM

/**
  * @brief  mp3_player 进行mp3文件解码、播放
  * @param  filename:要播放的文件路径
  * @retval none
  */
//static void mp3_player(const char *filename)
void mp3_player(void)
{
        int err,/* i,*/ outputSamps;//, current_sample_rate = 0;       

        int                                read_offset = 0;                                /* 读偏移指针                                */
        int                                bytes_left = 0;                                        /* 剩余字节数                                */       
        unsigned long        Frames = 0;                                            /* mP3帧计数                                */
        unsigned char        *read_ptr = buffer;                            /* 缓冲区指针                                */
        HMP3Decoder                Mp3Decoder;                                                /* mp3解码器指针                */
       

        //打开成功
        //初始化MP3解码器
        Mp3Decoder = MP3InitDecoder();       
       
        //获取输入数据流,调用helix库解码,输出PCM数据,约20ms完成一次循环
        //开始进入播放状态,期间中断会修改touch_even状态
        while(1)        //循环1, 如果touch_even不是切歌状态则继续呆在循环体里
        {
                //有时出现解码错误,错误后继续在本循环体内,继续播放
               
               
                //
                read_ptr  = buffer;
                bytes_left = 0;
               
                //
                Communication();
               
                if(UART1_Index3 < 1126)
                        break;
                read_ptr     = buffer;                                                                                //指向mp3输入流
                bytes_left   = UART1_Index3;                                                                //实际读到的输入流大小大小
                UART1_Index3 = 0;
                bufflag      = 0;
        //        UART1_Index3 = 0;
               
                //按帧处理       
                while(1)                       
                {

                        read_offset = MP3FindSyncWord(read_ptr, bytes_left);        //寻找帧同步,返回第一个同步字的位置
                        if(read_offset < 0)                                                                                //没有找到同步字
                        {
                                break;                                                                                                //跳出循环2,回到循环1       
                        }
                       
                        read_ptr   += read_offset;                                                                //偏移至同步字的位置
                        bytes_left -= read_offset;                                                                //同步字之后的数据大小       
               
                        //
                        err = MP3Decode(Mp3Decoder, &read_ptr, &bytes_left, outBuf[bufflag], 0);                                                //开始解码 参数:mp3解码结构体、输入流指针、输入流大小、输出流指针、数据格式
                        Frames++;                       
                       
                        if (err != ERR_MP3_NONE)                                                                                                //错误处理
                        {
                                switch (err)
                                {
                                        case ERR_MP3_INDATA_UNDERFLOW:
                                        //        printf("ERR_MP3_INDATA_UNDERFLOW\r\n");
                                                read_ptr = buffer;
                                                //fres = f_read(&file, read_ptr, sizeof(buffer), &rw_num);
                                                bytes_left = rw_num;
                                                break;
                       
                                        case ERR_MP3_MAINDATA_UNDERFLOW:
                                                /* do nothing - next call to decode will provide more mainData */
                                        //        printf("ERR_MP3_MAINDATA_UNDERFLOW\r\n");
                                                break;
                       
                                        default:
                                        //        printf("UNKNOWN ERROR:%d\r\n", err);
                       
                                                // 跳过此帧
                                                if (bytes_left > 0)
                                                {
                                                        bytes_left --;
                                                        read_ptr ++;
                                                }       
                                                break;
                                }
                        }
                        else                //解码无错误,准备把数据输出到PCM
                        {
                                MP3GetLastFrameInfo(Mp3Decoder, &Mp3FrameInfo);                //获取解码信息                               

                                /* 输出到DAC */
                                outputSamps = Mp3FrameInfo.outputSamps;                                                        //PCM数据个数
                               
                                if (outputSamps > 0)
                                {
                                        //
                                        if(1 == Mp3FrameInfo.nChans)
                                                Convert_Mono(  &outBuf[bufflag][0]);
                                        else
                                                Convert_Stereo(&outBuf[bufflag][0]);
                                //        if (Mp3FrameInfo.nChans == 1)        //单声道
                                //        {
                                                //单声道数据需要复制一份到另一个声道
                                //                for (i = outputSamps - 1; i >= 0; i--)
                                        //        {
                                        //                outBuf[bufflag][i * 2] = outBuf[bufflag];
                                        //                outBuf[bufflag][i * 2 + 1] = outBuf[bufflag];
                                        //        }
                                        //        outputSamps *= 2;
                                //        }
                                //        else
                                //        {

                                //        }
                               
                                        //非单声道数据可直接由DMA传输到IIS交给DAC
                                        /* 等待DMA播放完,这段时间我们可以干其他的事,扫描事件进行处理 */
                                //        while((DMA1_Channel5->CCR&DMA_CCR1_EN) && !(DMA1->ISR&DMA1_IT_TC5))
                                //        {

                                //                static uint8_t i=0;
                                        //        if(i==0)
                                        //        {
                                                //  PCM1770_VolumeSet(voice);
                                        //          i++;
                                        //        }

//                                                        even_process();                                                       
                                        //        }
                                               
                                                /*DMA传输完毕*/
                                        // DMA_ClearFlag(DMA1_FLAG_TC5 | DMA1_FLAG_TE5);
                                        // DMA_I2S_Configuration((uint32_t)outBuf[bufflag], outputSamps);
                                        //        bufflag = 1 -bufflag;                                                                                                                                                        //切换buffer

                                }
                        }
                       
               
        //        if(file.fptr==file.fsize)                 //如果指针指向了文件尾,表示数据全部读完
        //        {
                //        printf("END\r\n");
                //        if(play_index<file_num-1)                //自动开始下一首歌曲
                //        {
                        //        play_index++;
                        //        player_state = S_SWITCH;        //进入切歌状态,跳出
                        //}
                        //else
                        //{
                        //        play_index = 0;
                        //        player_state = S_SWITCH;
                        //}
                                       
                //        break;                                                                                 //跳出这首歌的播放状态        while break;
        //        }
         
        }
       
}

//        f_close(&file);                                                        //结束播放本歌曲,关闭文件
//        MP3FreeDecoder(Mp3Decoder);
//        I2S_Stop();
       
}




程序已经执行到了红色区域了,输出PCM个数是2304个PCM,但是我查看outbuf缓存区全部00不知道是怎么回事

正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

3

主题

2178

帖子

2

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3323
金钱
3323
注册时间
2013-7-19
在线时间
195 小时
发表于 2016-1-6 14:12:50 | 显示全部楼层
回复

使用道具 举报

4

主题

18

帖子

0

精华

初级会员

Rank: 2

积分
120
金钱
120
注册时间
2012-8-8
在线时间
18 小时
 楼主| 发表于 2016-1-6 14:30:12 | 显示全部楼层
ricefat 发表于 2016-1-6 14:12
http://www.openedv.com/thread-21204-1-1.html希望能给你帮助

谢谢的的回答,我看了你的程序,但是不知道你的PCM数据是不是放到,buffer1和,buffer2缓存区里面的,是不是红色区域的代码,PCM数据就已经出错到,buffer缓存区里面的
/**
  * @brief  Start wave player
  * @param  None
  * @retval None
  */
void WavePlayerStart(void)
{
  u32 br;
        FRESULT res;
        uint16_t i;
       
        char path[] = "0:/";

  //buffer_switch = 1;
       
        hMP3Decoder=MP3InitDecoder();//初始化MP3解码器

  /* Get the read out protection status */
  if (f_opendir(&dir, path)!= FR_OK)
  {
    while(1)
    {
      STM_EVAL_LEDToggle(LED5);
      Delay(10);
    }   
  }
  else
  {
    if (WaveRecStatus == 1)
    {
      WaveFileName = REC_WAVE_NAME;
    }
    else
    {
      WaveFileName = "0:audio.mp3";//WAVE_NAME; 名字改下
    }
    /* Open the wave file to be played */
    if (f_open(&fileR, WaveFileName , FA_READ) != FR_OK)
    {
      STM_EVAL_LEDOn(LED5);
      Command_index = 1;
    }
    else
    {  


      //复位计数器
                bytesLeft=0;
                readPtr=readBuf;                                       
                res=f_read(&fileR,readBuf,READBUF_SIZE,&br);
                bytesLeft += br;                                       
                //if(res!=FR_OK||br==0) break;
                XferCplt=1;
                buffer_switch=0;
                init=0;
                WavePlayerInit((u32)44100);
                while(1)
                {
                        offset=MP3FindSyncWord(readPtr, bytesLeft);//寻找下一帧头        assume EOF if no sync found
                        if(offset<0)break;
                        readPtr+=offset; //data start point
                        bytesLeft-=offset; //in buffer
                        if(bytesLeft<READBUF_SIZE)
                        {
                                memmove(readBuf,readPtr,bytesLeft);
                                res=f_read(&fileR,readBuf+bytesLeft,READBUF_SIZE-bytesLeft,&br);
                                if((res)||(br==0)) break;                                               
                                if(br<READBUF_SIZE-bytesLeft)
                                memset(readBuf+bytesLeft+br,0,READBUF_SIZE-bytesLeft-br);
                                bytesLeft=READBUF_SIZE;
                                readPtr=readBuf;               
                        }
                        MP3GetLastFrameInfo(hMP3Decoder, &mp3FrameInfo);
从这里开始是不是PCM已经转换完成了,准备送往DA转换

//                        if(init==0)//根据MP3帧信息初始化音频接口  //
//                        {
//                                WavePlayerInit((u32)mp3FrameInfo.samprate);
//                                init=1;
//                        }                                                       




                        while(XferCplt==0);
                        XferCplt=0;                                                       
                        if(buffer_switch == 0)
                        {
                               
                                Audio_MAL_Play((u32)buffer2,BUFF_SIZE*2);       


                                //Decbuf=buffer1;
                                MP3Decode(hMP3Decoder,&readPtr,&bytesLeft,buffer1,0);
                                //Convert_Stereo(buffer1);
                                buffer_switch = 1;
                        }
                        else
                        {   
                                Audio_MAL_Play((u32)buffer1,BUFF_SIZE*2);
                                //Decbuf=buffer2;
                                MP3Decode(hMP3Decoder,&readPtr,&bytesLeft,buffer2,0);
                                //Convert_Stereo(buffer2);
                                buffer_switch = 0;
                        }
                }
                       
    }   
  }
}
回复

使用道具 举报

3

主题

2178

帖子

2

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3323
金钱
3323
注册时间
2013-7-19
在线时间
195 小时
发表于 2016-1-6 14:36:53 | 显示全部楼层
爱好单片机 发表于 2016-1-6 14:30
谢谢的的回答,我看了你的程序,但是不知道你的PCM数据是不是放到,buffer1和,buffer2缓存区里面的,是不 ...

解码是这句,前面只是读取mp3文件用的,
MP3Decode(hMP3Decoder,&readPtr,&bytesLeft,buffer1,0);
MP3Decode(hMP3Decoder,&readPtr,&bytesLeft,buffer2,0);
解码完成的PCM数据放在buffer1,2中,Audio_MAL_play实际是开启buffer到外部DAC的DMA通道
回复

使用道具 举报

4

主题

18

帖子

0

精华

初级会员

Rank: 2

积分
120
金钱
120
注册时间
2012-8-8
在线时间
18 小时
 楼主| 发表于 2016-1-6 14:49:10 | 显示全部楼层
ricefat 发表于 2016-1-6 14:36
解码是这句,前面只是读取mp3文件用的,
MP3Decode(hMP3Decoder,&readPtr,&bytesLeft,buffer1,0);
MP3D ...

奥,我在看看,不知道为我执行过解码函数MP3Decode,buffer缓存区没有数据全部是00
回复

使用道具 举报

4

主题

18

帖子

0

精华

初级会员

Rank: 2

积分
120
金钱
120
注册时间
2012-8-8
在线时间
18 小时
 楼主| 发表于 2016-1-6 18:41:43 | 显示全部楼层
ricefat 发表于 2016-1-6 14:36
解码是这句,前面只是读取mp3文件用的,
MP3Decode(hMP3Decoder,&readPtr,&bytesLeft,buffer1,0);
MP3D ...


问题还是没有找到,解码进入这个函数,我单步调试没有发现向buffer缓存区写PCM数据的,只有出错清空buffer缓存区,你能帮我看看这个函数那个位置是写如buffer缓存区的代码吗,
我把我程序执行的位置用红色标识出来。
/**********************************************************
* Function:    MP3Decode
*
* Description: decode one frame of MP3 data
*
* Inputs:      valid MP3 decoder instance pointer (HMP3Decoder)
*              double pointer to buffer of MP3 data (containing headers + mainData)
*              number of valid bytes remaining in inbuf
*              pointer to outbuf, big enough to hold one frame of decoded PCM samples
*              flag indicating whether MP3 data is normal MPEG format (useSize = 0)
*                or reformatted as "self-contained" frames (useSize = 1)
*
* Outputs:     PCM data in outbuf, interleaved LRLRLR... if stereo
*                number of output samples = nGrans * nGranSamps * nChans
*              updated inbuf pointer, updated bytesLeft
*
* Return:      error code, defined in mp3dec.h (0 means no error, < 0 means error)
*
* Notes:       switching useSize on and off between frames in the same stream
*                is not supported (bit reservoir is not maintained if useSize on)
**********************************************************/
int MP3Decode(HMP3Decoder hMP3Decoder, unsigned char **inbuf, int *bytesLeft, short *outbuf, int useSize)
{
        int offset, bitOffset, mainBits, gr, ch, fhBytes, siBytes, freeFrameBytes;
        int prevBitOffset, sfBlockBits, huffBlockBits;
        unsigned char *mainPtr;
        MP3DecInfo *mp3DecInfo = (MP3DecInfo *)hMP3Decoder;

        if (!mp3DecInfo)
                return ERR_MP3_NULL_POINTER;

        /* unpack frame header */ // 解析帧包头
        fhBytes = UnpackFrameHeader(mp3DecInfo, *inbuf);
        if (fhBytes < 0)       
                return ERR_MP3_INVALID_FRAMEHEADER;                /* don't clear outbuf since we don't know size (failed to parse header) */ //不知道帧头无法解析 //
        *inbuf += fhBytes;
       
        /* unpack side info */
        siBytes = UnpackSideInfo(mp3DecInfo, *inbuf);
        if (siBytes < 0)
        {
                MP3ClearBadFrame(mp3DecInfo, outbuf);
                return ERR_MP3_INVALID_SIDEINFO;
        }
        *inbuf += siBytes;
        *bytesLeft -= (fhBytes + siBytes);
       
        /* if free mode, need to calculate bitrate and nSlots manually, based on frame size */
        if (mp3DecInfo->bitrate == 0 || mp3DecInfo->freeBitrateFlag)
        {
                if (!mp3DecInfo->freeBitrateFlag)
                {
                        /* first time through, need to scan for next sync word and figure out frame size */
                        mp3DecInfo->freeBitrateFlag = 1;
                        mp3DecInfo->freeBitrateSlots = MP3FindFreeSync(*inbuf, *inbuf - fhBytes - siBytes, *bytesLeft);
                        if (mp3DecInfo->freeBitrateSlots < 0)
                        {
                                MP3ClearBadFrame(mp3DecInfo, outbuf);
                                return ERR_MP3_FREE_BITRATE_SYNC;
                        }
                        freeFrameBytes = mp3DecInfo->freeBitrateSlots + fhBytes + siBytes;
                        mp3DecInfo->bitrate = (freeFrameBytes * mp3DecInfo->samprate * 8) / (mp3DecInfo->nGrans * mp3DecInfo->nGranSamps);
                }
                mp3DecInfo->nSlots = mp3DecInfo->freeBitrateSlots + CheckPadBit(mp3DecInfo);        /* add pad byte, if required */
        }

        /* useSize != 0 means we're getting reformatted (RTP) packets (see RFC 3119)
         *  - calling function assembles "self-contained" MP3 frames by shifting any main_data
         *      from the bit reservoir (in previous frames) to AFTER the sync word and side info
         *  - calling function should set mainDataBegin to 0, and tell us exactly how large this
         *      frame is (in bytesLeft)
         */
        if (useSize)
        {
                mp3DecInfo->nSlots = *bytesLeft;
                if (mp3DecInfo->mainDataBegin != 0 || mp3DecInfo->nSlots <= 0)
                {
                        /* error - non self-contained frame, or missing frame (size <= 0), could do loss concealment here */
                        MP3ClearBadFrame(mp3DecInfo, outbuf);
                        return ERR_MP3_INVALID_FRAMEHEADER;
                }

                /* can operate in-place on reformatted frames */
                mp3DecInfo->mainDataBytes = mp3DecInfo->nSlots;
                mainPtr = *inbuf;
                *inbuf += mp3DecInfo->nSlots;
                *bytesLeft -= (mp3DecInfo->nSlots);
        }
        else
        {
                /* out of data - assume last or truncated frame */
                if (mp3DecInfo->nSlots > *bytesLeft)
                {
                        MP3ClearBadFrame(mp3DecInfo, outbuf);
                        return ERR_MP3_INDATA_UNDERFLOW;       
                }
                /* fill main data buffer with enough new data for this frame */
                if (mp3DecInfo->mainDataBytes >= mp3DecInfo->mainDataBegin)
                {
                        /* adequate "old" main data available (i.e. bit reservoir) */
                        memmove(mp3DecInfo->mainBuf, mp3DecInfo->mainBuf + mp3DecInfo->mainDataBytes - mp3DecInfo->mainDataBegin, mp3DecInfo->mainDataBegin);
                        memcpy(mp3DecInfo->mainBuf + mp3DecInfo->mainDataBegin, *inbuf, mp3DecInfo->nSlots);


                        mp3DecInfo->mainDataBytes = mp3DecInfo->mainDataBegin + mp3DecInfo->nSlots;
                        *inbuf += mp3DecInfo->nSlots;
                        *bytesLeft -= (mp3DecInfo->nSlots);
                        mainPtr = mp3DecInfo->mainBuf;
                }
                else
                {
                        /* not enough data in bit reservoir from previous frames (perhaps starting in middle of file) */
                        memcpy(mp3DecInfo->mainBuf + mp3DecInfo->mainDataBytes, *inbuf, mp3DecInfo->nSlots);
                        mp3DecInfo->mainDataBytes += mp3DecInfo->nSlots;
                        *inbuf += mp3DecInfo->nSlots;
                        *bytesLeft -= (mp3DecInfo->nSlots);
                        MP3ClearBadFrame(mp3DecInfo, outbuf);
                        return ERR_MP3_MAINDATA_UNDERFLOW;
                }
        }
        bitOffset = 0;
        mainBits = mp3DecInfo->mainDataBytes * 8;

        /* decode one complete frame */
        for (gr = 0; gr < mp3DecInfo->nGrans; gr++)
        {
                for (ch = 0; ch < mp3DecInfo->nChans; ch++)
                {
                        /* unpack scale factors and compute size of scale factor block */
                        prevBitOffset = bitOffset;
                        offset = UnpackScaleFactors(mp3DecInfo, mainPtr, &bitOffset, mainBits, gr, ch);

                        sfBlockBits = 8*offset - prevBitOffset + bitOffset;
                        huffBlockBits = mp3DecInfo->part23Length[gr][ch] - sfBlockBits;
                        mainPtr += offset;
                        mainBits -= sfBlockBits;

                        if (offset < 0 || mainBits < huffBlockBits)
                        {
                                MP3ClearBadFrame(mp3DecInfo, outbuf);
                                return ERR_MP3_INVALID_SCALEFACT;
                        }

                        /* decode Huffman code words */
                        prevBitOffset = bitOffset;
                        offset = DecodeHuffman(mp3DecInfo, mainPtr, &bitOffset, huffBlockBits, gr, ch);
                        if (offset < 0)
                        {
                                MP3ClearBadFrame(mp3DecInfo, outbuf);
                                return ERR_MP3_INVALID_HUFFCODES;
                        }

                        mainPtr += offset;
                        mainBits -= (8*offset - prevBitOffset + bitOffset);
                }
                /* dequantize coefficients, decode stereo, reorder short blocks */
                if (Dequantize(mp3DecInfo, gr) < 0)
                {
                        MP3ClearBadFrame(mp3DecInfo, outbuf);
                        return ERR_MP3_INVALID_DEQUANTIZE;                       
                }

                /* alias reduction, inverse MDCT, overlap-add, frequency inversion */
                for (ch = 0; ch < mp3DecInfo->nChans; ch++)
                        if (IMDCT(mp3DecInfo, gr, ch) < 0)
                        {
                                MP3ClearBadFrame(mp3DecInfo, outbuf);
                                return ERR_MP3_INVALID_IMDCT;                       
                        }

                /* subband transform - if stereo, interleaves pcm LRLRLR */
                if (Subband(mp3DecInfo, outbuf + gr*mp3DecInfo->nGranSamps*mp3DecInfo->nChans) < 0)
                {
                        MP3ClearBadFrame(mp3DecInfo, outbuf);
                        return ERR_MP3_INVALID_SUBBAND;                       
                }
        }
        return ERR_MP3_NONE;
}
我仿真一直跟踪没有看到一个地方是输出PCM数据的就是你的buffer缓存区,不知道为啥,是不是那里出错没有执行到,如果出错就会跳出这个函数的,我是整个函数顺利执行完成的



回复

使用道具 举报

3

主题

2178

帖子

2

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3323
金钱
3323
注册时间
2013-7-19
在线时间
195 小时
发表于 2016-1-6 19:27:41 | 显示全部楼层
爱好单片机 发表于 2016-1-6 18:41
问题还是没有找到,解码进入这个函数,我单步调试没有发现向buffer缓存区写PCM数据的,只有出错清空buf ...

如果清空缓冲区 说明mp3帧错误,你看看decode这个函数返回的什么值
回复

使用道具 举报

4

主题

18

帖子

0

精华

初级会员

Rank: 2

积分
120
金钱
120
注册时间
2012-8-8
在线时间
18 小时
 楼主| 发表于 2016-1-7 08:47:23 | 显示全部楼层
ricefat 发表于 2016-1-6 19:27
如果清空缓冲区 说明mp3帧错误,你看看decode这个函数返回的什么值

返回是0正确的
回复

使用道具 举报

5

主题

42

帖子

0

精华

初级会员

Rank: 2

积分
147
金钱
147
注册时间
2017-11-8
在线时间
33 小时
发表于 2018-10-26 09:59:58 | 显示全部楼层
楼主解决了吗?我遇到和你一模一样的问题,输入指针的数据都有,输出buff全都是0没有数据,解码函数返回值是0也是正常的。
回复

使用道具 举报

0

主题

2

帖子

0

精华

新手入门

积分
11
金钱
11
注册时间
2019-12-23
在线时间
2 小时
发表于 2019-12-23 16:11:50 | 显示全部楼层
我也出现这个问题,不知道你们都怎么解决的?
回复

使用道具 举报

1

主题

4

帖子

0

精华

新手上路

积分
45
金钱
45
注册时间
2019-5-15
在线时间
13 小时
发表于 2020-2-9 17:04:51 | 显示全部楼层
遇到同样的问题了,整了两天头大,各位后面是怎么解决的?
回复

使用道具 举报

0

主题

10

帖子

0

精华

初级会员

Rank: 2

积分
132
金钱
132
注册时间
2019-8-31
在线时间
44 小时
发表于 2020-5-29 15:42:57 | 显示全部楼层
我也是这个问题 ,有人解决了吗
回复

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165540
金钱
165540
注册时间
2010-12-1
在线时间
2117 小时
发表于 2020-5-30 01:19:19 | 显示全部楼层
参考我们提供的代码
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-6-28 22:34

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表