語音通訊解決方案總結

語音通訊方案

系統級方案和自建協議html

windows平臺、linux平臺、嵌入式linux平臺、mcu平臺linux

1,在嵌入式Linux上開發的有線通訊語音解決方案ios

 這方案是在嵌入式Linux上開發的,音頻方案基於ALSA,語音通訊相關的都是在user space 作,算是一個上層的解決方案。因爲是有線通訊,網絡環境相對無線通訊而言不是特別惡劣,用的丟包補償措施也不是不少,主要有PLC、RFC2198等。git

2,在Android手機上開發的傳統無線通訊語音解決方案 github

這方案是在Android手機上開發的,是手機上的傳統語音通訊方案(相對於APP語音通訊而言)。Android是基於Linux的,因此也會用到ALSA,可是主要是作控制用,如對codec芯片的配置等。跟音頻數據相關的驅動、編解碼、先後處理等在Audio DSP上開發,網絡側相關的在CP(通訊處理器)上開發,算是一個底層解決方案。該方案的軟件框圖以下: 算法

系統級

聲卡 (Sound Card)也叫音頻卡(港臺稱之爲聲效卡),是計算機多媒體系統中最基本的組成部分,是實現聲波/數字信號相互轉換的一種硬件。聲卡的基本功能是把來自話筒、磁帶、光盤的原始聲音信號加以轉換,輸出到耳機揚聲器擴音機錄音機等聲響設備,或經過音樂設備數字接口(MIDI)發出合成樂器的聲音編程

全部的電腦主板基本都有集成聲卡的,若是有專業要求會再買個獨立聲卡,就像專業玩家同樣買個獨立顯卡,手動狗頭windows

聲卡驅動api

對於音頻處理的技術,主要有以下幾種:緩存

  • 採集麥克風輸入
  • 採集聲卡輸出
  • 將音頻數據送入聲卡進行播放
  • 對多路音頻輸入進行混音處理

Windows平臺內核提供調用聲卡API

1、MME(MultiMedia Extensions)

MME就是winmm.dll提供的接口,也是Windows平臺下第一代API。優勢是使用簡單,通常場景下能夠知足業務需求,缺點是延遲高,某些高級功能沒法實現。

2、XAudio2

也是DirextX的一部分,爲了取代DirectSound。DirextX套件中的音頻組件,大多用於遊戲中,支持硬件加速,因此比MME有更低的延遲。

3、Core Audio API

Vista系統開始引入的新架構,它是以COM的方式提供的接口,用戶模式下處於最底層,上面提到的幾種API最終都將使用它!功能最強,性能最好,可是接口繁雜,使用起來很麻煩。

4、Wasapi 就能夠了(高性能,但更復雜)

而Wave系列的API函數主要是用來實現對麥克風輸入的採集(使用WaveIn系列API函數)和控制聲音的播放(使用後WaveOut系列函數)。

1.使用WaveIn系列API函數實現麥克風輸入採集

涉及的API函數:

  • waveInOpen

    開啓音頻採集設備,成功後會返回設備句柄,後續的API都須要使用該句柄

    調用模塊須要提供一個回調函數(waveInProc),以接收採集的音頻數據

  • waveInClose

    關閉音頻採集模塊

    成功後,由waveInOpen返回的設備句柄將再也不有效 

  • waveInPrepareHeader

    準備音頻採集數據緩存的空間

  • waveInUnprepareHeader

    清空音頻採集的數據緩存

  • waveInAddBuffer

    將準備好的音頻數據緩存提供給音頻採集設備

    在調用該API以前須要先調用waveInPrepareHeader

  • waveInStart

    控制音頻採集設備開始對音頻數據的採集

  • waveInStop

    控制音頻採集設備中止對音頻數據的採集

音頻採集設備採集到音頻數據後,會調用在waveInOpen中設置的回調函數。

其中參數包括一個消息類型,根據其消息類型就能夠進行相應的操做。

如接收到WIM_DATA消息,則說明有新的音頻數據被採集到,這樣就能夠根據須要來對這些音頻數據進行處理。

(示例之後補上)

2.使用Core Audio實現對聲卡輸出的捕捉

涉及的接口有:

  • IMMDeviceEnumerator

  • IMMDevice

  • IAudioClient

  • IAudioCaptureClient

主要過程:

  • 建立多媒體設備枚舉器(IMMDeviceEnumerator)

  • 經過多媒體設備枚舉器獲取聲卡接口(IMMDevice)

  • 經過聲卡接口獲取聲卡客戶端接口(IAudioClient)

  • 經過聲卡客戶端接口(IAudioClient)可獲取聲卡輸出的音頻參數、初始化聲卡、獲取聲卡輸出緩衝區的大小、開啓/中止對聲卡輸出的採集

  • 經過聲卡採集客戶端接口(IAudioCaptureClient)可獲取採集的聲卡輸出數據,並對內部緩衝區進行控制

(示例之後補上)

3.經常使用的混音算法

混音算法就是將多路音頻輸入信號根據某種規則進行運算(多路音頻信號相加後作限幅處理),獲得一路混合後的音頻,並以此做爲輸出的過程。

我目前還作過這一塊,搜索了一下基本有以下幾種混音算法:

  • 將多路音頻輸入信號直接相加取和做爲輸出

  • 將多路音頻輸入信號直接相加取和後,再除以混音通道數,防止溢出

  • 將多路音頻輸入信號直接相加取和後,作Clip操做(將數據限定在最大值和最小值之間),若有溢出就設最大值

  • 將多路音頻輸入信號直接相加取和後,作飽和處理,接近最大值時進行扭曲

  • 將多路音頻輸入信號直接相加取和後,作歸一化處理,所有乘個係數,使幅值歸一化

  • 將多路音頻輸入信號直接相加取和後,使用衰減因子限制幅值

Linux平臺內核提供調用聲卡API

ALSA是目前linux的主流音頻體系架構

是一個有社區維護的開源項目:http://www.alsa-project.org/

包括:

1.內核驅動包 alsa-driver

2.用戶空間庫 alsa-lib

3.附加庫插件包 alsa-libplugins

4.音頻處理工具集 alsa-utils

5.其餘音頻處理小工具包 alsa-tools

6.特殊音頻固件支持包 alsa-firmware

7.alsa-lib的Python綁定包 pyalsa

8.OSS接口兼容包 alsa-oss

9.內核空間中,alsa-soc實際上是對alsa-driver的進一步封裝,他針對嵌入式設備提供了一些列加強的功能。

1.操做說明

安裝

sudo apt install libasound2-dev

流程

  • 打開設備
  • 分配參數內存
  • 填充默認參數
  • 設置參數(詳細的參見 ALSA - PCM接口)
    • 通道數
    • 採樣率(碼率,用來指定時間和文件大小,frames/s)
    • 幀數(每次讀取的數據長度與該參數有關)
    • 數據格式(影響輸出數據、緩存大小)
    • 設備訪問類型(直接讀寫、內存映射,交錯模式、非交錯模式)
  • 讀取、寫入數據

簡單的例子

包含頭文件

#include <alsa/asoundlib.h>

查看設備,根據最後兩個數字肯定設備名稱,一般default就好了

aplay -L

定義相關參數,錄放音都要通過相同的步驟,放一塊兒定義

// 設備名稱,這裏採用默認,還能夠選取"hw:0,0","plughw:0,0"等
const char *device = "default";
// 設備句柄
// 如下均定義兩個,根據前綴區分,c->capture,p->playback,沒有前綴的表示參數相同
snd_pcm_t *chandle; snd_pcm_t *phandle; // 硬件參數 snd_pcm_hw_params_t *cparams; snd_pcm_hw_params_t *pparams; // 數據訪問類型,讀寫方式:內存映射或者讀寫,數據 snd_pcm_access_t access_type = SND_PCM_ACCESS_RW_INTERLEAVED; // 格式, snd_pcm_format_t format = SND_PCM_FORMAT_S16_LE; // 碼率,採樣率,8000Hz,44100Hz unsigned int rate = 44100; // 通道數 unsigned int channels = 2; // 幀數,這裏取32 snd_pcm_uframes_t frames = 32; // 如下爲可選參數 unsigned int bytes_per_frame; // 軟件重採樣 unsigned int soft_resample;

打開設備

snd_pcm_open(&chandle, device, SND_PCM_STREAM_CAPTURE, 0);
snd_pcm_open(&phandle, device, SND_PCM_STREAM_PLAYBACK, 0);

增長一個錯誤判斷

int err;
if ((err = snd_pcm_open(&chandle, device, SND_PCM_STREAM_CAPTURE, 0)) < 0) { std::cout << "Capture device open failed."; } if ((err = snd_pcm_open(&phandle, device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { std::cout << "Playback device open failed."; }

設置參數,這裏就不增長錯誤判斷了,否則顯得有些長了

// 先計算每幀數據的大小
bytes_per_frame = snd_pcm_format_width(format) / 8 * 2;
// 計算須要分配的緩存空間的大小
buffer_size = frames * bytes_per_frame; // 爲參數分配空間 snd_pcm_hw_params_alloca(&params); // 填充參數空間 snd_pcm_hw_params_any(handle, params); // 設置數據訪問方式 snd_pcm_hw_params_set_access(handle, params, access_type); // 設置格式 snd_pcm_hw_params_set_format(handle, params, format); // 設置通道 snd_pcm_hw_params_set_channels(handle, params, channels); // 設置採樣率 snd_pcm_hw_params_set_rate_near(handle, params, &rate, 0); // 可選項,不改不影響 // 設置緩存大小 buffer_size = period_size * 2; snd_pcm_hw_params_set_buffer_size_near(handle, params, &buffer_size); // 設置段大小,period與OSS中的segment相似 period_size = buffer_size / 2; snd_pcm_hw_params_set_period_size_near(handle, params, &period_size, 0)); //設置參數 snd_pcm_hw_params(handle, params);

讀寫數據

// 分配緩存空間,大小上面經過buffer_size計算出了
char *buffer = (char *)malloc(buffer_size);
// 讀寫數據
snd_pcm_readi(chandle, buffer, frames); snd_pcm_writei(phandle, buffer, frames);

循環播放

while(1)
{
    snd_pcm_readi(chandle, buffer, frames);
    snd_pcm_writei(phandle, buffer, frames);
}

捕獲必定時間的音頻數據到文件流

ofstream output("test.pcm", ios::trunc);

int loop_sec; int frames_readed; loop_sec = 10; unsigned long loop_limit; // 計算循環大小 loop_limit = loop_sec * rate; for (size_t i = 0; i < loop_limit; ) { // 這裏還須要判斷一下返回值是否爲負 frames_readed = snd_pcm_readi(chandle, buffer, frames); output.write(buffer, buffer_size); i += frames_readed; }

關閉設備、釋放指針

snd_pcm_close(chandle);
snd_pcm_close(phandle);
free(buffer);

放音過程當中也許會出現"Broken pipe"的錯誤,添加以下須要從新準備設備

err = snd_pcm_writei(handle, input_buffer, frames);
if (err == -EPIPE) { snd_pcm_prepare(handle); continue; // 或者 // return 0; }

完整例子

 1 #ifndef ALSA_AUDIO_H
 2 #define ALSA_AUDIO_H
 3 
 4 #include <QObject>
 5 
 6 #include <alsa/asoundlib.h>
 7 
 8 class ALSA_Audio : public QObject
 9 {
10     Q_OBJECT
11 public:
12     explicit ALSA_Audio(QObject *parent = nullptr);
13 
14 
15     void capture_start();
16     void capture_stop();
17     /**
18      * @brief 讀取音頻數據
19      * @param buffer 音頻數據
20      * @param buffer_size 音頻數據大小
21      * @param frames 讀取的音頻幀數
22      * @return 0 成功,-1 失敗
23      */
24     int audio_read(char **buffer, int *buffer_size, unsigned long *frames);
25 
26     void playback_start();
27     void playback_stop();
28     /**
29      * @brief audio_write 播放音頻
30      * @param buffer 音頻數據
31      * @param frames 播放的音頻幀數
32      * @return 0 成功,-1 失敗
33      */
34     int audio_write(char *buffer);
35 
36 
37 
38 private:
39     bool m_is_capture_start;
40     snd_pcm_t *m_capture_pcm;
41     char *m_capture_buffer;
42     unsigned long m_capture_buffer_size;
43     snd_pcm_uframes_t m_capture_frames;       // 一次讀的幀數
44 
45 
46     bool m_is_playback_start;
47     snd_pcm_t *m_playback_pcm;
48     snd_pcm_uframes_t m_playback_frames;       // 一次寫的幀數
49 
50     /**
51      * @brief ALSA_Audio::set_hw_params
52      * @param pcm
53      * @param hw_params
54      * @param rate 採樣頻率
55      * @param format 格式
56      * @param channels 通道數
57      * @param frames 一次讀寫的幀數
58      * @return
59      */
60     int set_hw_params(snd_pcm_t *pcm, unsigned int rate, snd_pcm_format_t format, unsigned int channels, snd_pcm_uframes_t frames);
61 
62 
63 
64 signals:
65 
66 public slots:
67 };
68 
69 #endif // ALSA_AUDIO_H
alsa_audio.h
  1 #include "alsa_audio.h"
  2 #include "global.h"
  3 
  4 #include <QDebug>
  5 
  6 #include <math.h>
  7 #include <inttypes.h>
  8 
  9 
 10 
 11 ALSA_Audio::ALSA_Audio(QObject *parent) : QObject(parent)
 12 {
 13     m_is_capture_start = false;
 14     m_is_playback_start = false;
 15 }
 16 
 17 
 18 
 19 int ALSA_Audio::set_hw_params(snd_pcm_t *pcm, unsigned int rate, snd_pcm_format_t format, unsigned int channels, snd_pcm_uframes_t frames)
 20 {
 21     snd_pcm_uframes_t period_size;          // 一個處理週期須要的幀數
 22     snd_pcm_uframes_t hw_buffer_size;      // 硬件緩衝區大小
 23     snd_pcm_hw_params_t *hw_params;
 24     int ret;
 25     int dir = 0;
 26 
 27 
 28 
 29     // 初始化硬件參數結構體
 30     snd_pcm_hw_params_malloc(&hw_params);
 31     // 設置默認的硬件參數
 32     snd_pcm_hw_params_any(pcm, hw_params);
 33 
 34     // 如下爲設置所需的硬件參數
 35 
 36     // 設置音頻數據記錄方式
 37     CHECK_RETURN(snd_pcm_hw_params_set_access(pcm, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED));
 38     // 格式。使用16位採樣大小,小端模式(SND_PCM_FORMAT_S16_LE)
 39     CHECK_RETURN(snd_pcm_hw_params_set_format(pcm, hw_params, format));
 40     // 設置音頻通道數
 41     CHECK_RETURN(snd_pcm_hw_params_set_channels(pcm, hw_params, channels));
 42     // 採樣頻率,一次採集爲一幀數據
 43     //CHECK_RETURN(snd_pcm_hw_params_set_rate_near(pcm, hw_params, &rate, &dir));          // 設置相近的值
 44     CHECK_RETURN(snd_pcm_hw_params_set_rate(pcm, hw_params, rate, dir));
 45     // 一個處理週期須要的幀數
 46     period_size = frames * 5;
 47     CHECK_RETURN(snd_pcm_hw_params_set_period_size_near(pcm, hw_params, &period_size, &dir)); // 設置相近的值
 48 //    // 硬件緩衝區大小, 單位:幀(frame)
 49 //    hw_buffer_size = period_size * 16;
 50 //    CHECK_RETURN(snd_pcm_hw_params_set_buffer_size_near(pcm, hw_params, &hw_buffer_size));
 51 
 52     // 將參數寫入pcm驅動
 53     CHECK_RETURN(snd_pcm_hw_params(pcm, hw_params));
 54 
 55     snd_pcm_hw_params_free(hw_params);     // 釋放再也不使用的hw_params空間
 56 
 57     printf("one frames=%ldbytes\n", snd_pcm_frames_to_bytes(pcm, 1));
 58     unsigned int val;
 59     snd_pcm_hw_params_get_channels(hw_params, &val);
 60     printf("channels=%d\n", val);
 61 
 62     if (ret < 0) {
 63         printf("error: unable to set hw parameters: %s\n", snd_strerror(ret));
 64         return -1;
 65     }
 66     return 0;
 67 }
 68 
 69 
 70 void ALSA_Audio::capture_start()
 71 {
 72     m_capture_frames = 160;     // 此處160爲固定值,發送接收均使用此值
 73     unsigned int rate = 8000;                               // 採樣頻率
 74     snd_pcm_format_t format = SND_PCM_FORMAT_S16_LE;        // 使用16位採樣大小,小端模式
 75     unsigned int channels = 1;                              // 通道數
 76     int ret;
 77 
 78     if(m_is_capture_start)
 79     {
 80         printf("error: alsa audio capture is started!\n");
 81         return;
 82     }
 83 
 84     ret = snd_pcm_open(&m_capture_pcm, "plughw:1,0", SND_PCM_STREAM_CAPTURE, 0);       // 使用plughw:0,0
 85     if(ret < 0)
 86     {
 87         printf("snd_pcm_open error: %s\n", snd_strerror(ret));
 88         return;
 89     }
 90 
 91     // 設置硬件參數
 92     if(set_hw_params(m_capture_pcm, rate, format, channels, m_capture_frames) < 0)
 93     {
 94         return;
 95     }
 96 
 97     // 使用buffer保存一次處理獲得的數據
 98     m_capture_buffer_size = m_capture_frames * static_cast<unsigned long>(snd_pcm_format_width(format) / 8 * static_cast<int>(channels));
 99     m_capture_buffer_size *= 5;         // * 5 表示使用5倍的緩存空間
100     printf("snd_pcm_format_width(format):%d\n", snd_pcm_format_width(format));
101     printf("m_capture_buffer_size:%ld\n", m_capture_buffer_size);
102     m_capture_buffer = static_cast<char *>(malloc(sizeof(char) * m_capture_buffer_size));
103     memset(m_capture_buffer, 0, m_capture_buffer_size);
104 
105     // 獲取一次處理所須要的時間,單位us
106     // 1/rate * frames * 10^6 = period_time, 即:採集一幀所需的時間 * 一次處理所需的幀數 * 10^6 = 一次處理所需的時間(單位us)
107     // snd_pcm_hw_params_get_period_time(m_capture_hw_params, &m_period_time, &dir);
108 
109     m_is_capture_start = true;
110 }
111 
112 void ALSA_Audio::capture_stop()
113 {
114     if(m_is_capture_start == false)
115     {
116         printf("error: alsa audio capture is not start!");
117         return;
118     }
119 
120     m_is_capture_start = false;
121 
122     snd_pcm_drain(m_capture_pcm);
123     snd_pcm_close(m_capture_pcm);
124     free(m_capture_buffer);
125 }
126 
127 int ALSA_Audio::audio_read(char **buffer, int *buffer_size, unsigned long *frames)
128 {
129     int ret;
130     if(m_is_capture_start == false)
131     {
132         printf("error: alsa audio capture is stopped!\n");
133         return -1;
134     }
135     memset(m_capture_buffer, 0, m_capture_buffer_size);
136     ret = static_cast<int>(snd_pcm_readi(m_capture_pcm, m_capture_buffer, m_capture_frames));
137     printf("strlen(m_capture_buffer)=%ld\n", strlen(m_capture_buffer));
138     if (ret == -EPIPE)
139     {
140        /* EPIPE means overrun */
141        printf("overrun occurred\n");
142        snd_pcm_prepare(m_capture_pcm);
143     }
144     else if (ret < 0)
145     {
146        printf("error from read: %s\n", snd_strerror(ret));
147     }
148     else if (ret != static_cast<int>(m_capture_frames))
149     {
150        printf("short read, read %d frames\n", ret);
151     }
152 
153     if(m_capture_buffer == nullptr)
154     {
155         printf("error: alsa audio capture_buffer is empty!\n");
156         return -1;
157     }
158     *buffer = m_capture_buffer;
159     *buffer_size = static_cast<int>(m_capture_buffer_size / 5);
160     *frames = m_capture_frames;
161 
162     return 0;
163 }
164 
165 
166 
167 void ALSA_Audio::playback_start()
168 {
169     m_playback_frames = 160;     // 此處160爲固定值,發送接收均使用此值
170     unsigned int rate = 8000;                               // 採樣頻率
171     snd_pcm_format_t format = SND_PCM_FORMAT_S16_LE;        // 使用16位採樣大小,小端模式
172     unsigned int channels = 1;                              // 通道數
173     int ret;
174 
175 
176     if(m_is_playback_start)
177     {
178         printf("error: alsa audio playback is started!\n");
179         return;
180     }
181 
182     ret = snd_pcm_open(&m_playback_pcm, "plughw:1,0", SND_PCM_STREAM_PLAYBACK, 0);      // 使用plughw:0,0
183     if(ret < 0)
184     {
185         printf("snd_pcm_open error: %s\n", snd_strerror(ret));
186         return;
187     }
188 
189     // 設置硬件參數
190     if(set_hw_params(m_playback_pcm, rate, format, channels, m_playback_frames) < 0)
191     {
192         return;
193     }
194 
195 
196     m_is_playback_start = true;
197 
198 }
199 
200 void ALSA_Audio::playback_stop()
201 {
202     if(m_is_playback_start == false)
203     {
204         printf("error: alsa audio playback is not start!");
205         return;
206     }
207 
208     m_is_playback_start = false;
209 
210     snd_pcm_drain(m_playback_pcm);
211     snd_pcm_close(m_playback_pcm);
212 }
213 
214 
215 int ALSA_Audio::audio_write(char *buffer)
216 {
217     long ret;
218     if(m_is_playback_start == false)
219     {
220         printf("error: alsa audio playback is stopped!\n");
221         return -1;
222     }
223     else
224     {
225         ret = snd_pcm_writei(m_playback_pcm, buffer, m_playback_frames);
226         if(ret == -EPIPE)
227         {
228             /* EPIPE means underrun  */
229             printf("underrun occurred\n");
230             snd_pcm_prepare(m_playback_pcm);
231         }
232         else if (ret < 0)
233         {
234            printf("error from write: %s\n", snd_strerror(static_cast<int>(ret)));
235         }
236         else if (ret != static_cast<long>(m_playback_frames))
237         {
238            printf("short write, write %ld frames\n", ret);
239         }
240     }
241     return 0;
242 }
alsa_audio.cpp

2.架構圖

硬件架構:

 軟件架構:

 3.初識alsa設備

注:
controlC0:控制接口,用於控制聲卡,如通道選擇,混音,麥克風輸入增益調節等。
midiC0D0:Raw迷笛接口,用於播放midi音頻。
pcmC0D0c:pcm接口,用於錄音的pcm設備。
pcmC0D0p:用於播放的pcm設備。
pcmC0D1p:
seq:音序器接口。
timer:定時器接口。
即該聲卡下掛載了7個設備。根據聲卡實際能力,驅動實際上能夠掛載更多種類的設備
其中
C0D0表示聲卡0中的設備0。
pcmC0D0c:最後的c表示capture。
pcmC0D0p:最後一個p表示playback。

設備種類 include/sound/core.h:

include/sound/core.h

4.linux內核中音頻驅動代碼分佈

其中:
core:包含 ALSA 驅動的核心層代碼實現。
core/oss:包含模擬舊的OSS架構的PCM和Mixer模塊。
core/seq:音序器相關的代碼。
drivers:存放一些與CPU,bus架構無關的公用代碼。
i2c:ALSA的i2c控制代碼。
pci:PCI總線 聲卡的頂層目錄,其子目錄包含各類PCI聲卡代碼。
isa:ISA總線 聲卡的頂層目錄,其子目錄包含各類ISA聲卡代碼。
soc:ASoC(ALSA System on Chip)層實現代碼,針對嵌入式音頻設備。
soc/codecs:針對ASoC體系的各類音頻編碼器的驅動實現,與平臺無關。

include/sound:ALSA驅動的公共頭文件目錄。

5.驅動分類

OSS音頻設備驅動:
OSS 標準中有兩個最基本的音頻設備: mixer(混音器)和 dsp(數字信號處理器)。

ALSA音頻設備驅動:

雖然 OSS 已經很是成熟,但它畢竟是一個沒有徹底開放源代碼的商業產品,並且目前基本上在 Linux mainline 中失去了更新。而 ALSA (Advanced Linux Sound Architecture)剛好彌補了這一空白,它符合 GPL,是在 Linux 下進行音頻編程時另外一種可供選擇的聲卡驅動體系結構。 ALSA 除了像 OSS 那樣提供了一組內核驅動程序模塊以外,還專門爲簡化應用程序的編寫提供了相應的函數庫,與 OSS 提供的基於 ioctl 的原始編程接口相比, ALSA 函數庫使用起來要更加方便一些。 ALSA 的主要特色以下。支持多種聲卡設備。

模塊化的內核驅動程序。
支持 SMP 和多線程。
提供應用開發函數庫(alsa-lib)以簡化應用程序開發。
支持 OSS API,兼容 OSS 應用程序。

ASoC音頻設備驅動:
ASoC(ALSA System on Chip)是 ALSA 在 SoC 方面的發展和演變,它在本質上仍然屬於
ALSA,可是在 ALSA 架構基礎上對 CPU 相關的代碼和 Codec 相關的代碼進行了分離。其緣由是,採用傳統 ALSA 架構的狀況下,同一型號的 Codec 工做於不一樣的 CPU 時,須要不一樣的驅動,這不符合代碼重用的要求。對於目前嵌入式系統上的聲卡驅動開發,咱們建議讀者儘可能採用 ASoC 框架, ASoC 主要由 3 部分組成。

Codec 驅動。這一部分只關心 Codec 自己,與 CPU 平臺相關的特性不禁此部分操做。

平臺驅動。這一部分只關心 CPU 自己,不關心 Codec。它主要處理兩個問題: DMA 引擎和 SoC 集成的 PCM、 I2S 或 AC ‘97 數字接口控制。

板驅動(也稱爲 machine 驅動)。這一部分將平臺驅動和 Codec 驅動綁定在一塊兒,描述了板一級的硬件特徵。

在以上 3 部分中, 1 和 2 基本均可以仍然是通用的驅動了,也就是說, Codec 驅動認爲本身能夠鏈接任意 CPU,而 CPU 的 I2S、 PCM 或 AC ‘97 接口對應的平臺驅動則認爲本身能夠鏈接任意符合其接口類型的 Codec,只有 3 是不通用的,由特定的電路板上具體的 CPU 和 Codec 肯定,所以它很像一個插座,上面插上了 Codec 和平臺這兩個插頭。在以上三部分之上的是 ASoC 核心層,由內核源代碼中的 sound/soc/soc-core.c 實現,查看其源代碼發現它徹底是一個傳統的 ALSA 驅動。所以,對於基於 ASoC 架構的聲卡驅動而言, alsa-lib以及 ALSA 的一系列 utility 仍然是可用的,如 amixer、 aplay 均無需針對 ASoC 進行任何改動。而ASoC 的用戶編程方法也與 ALSA 徹底一致。內核源代碼的 Documentation/sound/alsa/soc/目錄包含了 ASoC 相關的文檔。

Android平臺內核提供調用聲卡API

目前linux中主流的音頻體系結構是ALSA(Advanced Linux Sound Architecture),ALSA在內核驅動層提供了alsa-driver,在應用層提供了alsa-lib,應用程序只須要調用alsa-lib(libtinyalsa.so)提供的API就能夠完

成對底層硬件的操做。說的這麼好,可是Android中沒有使用標準的ALSA,而是一個ALSA的簡化版叫作tinyalsa。Android中使用tinyalsa控制管理全部模式的音頻通路,咱們也可使用tinyalsa提供的工具進行查看、

調試。

TINYALSA子系統

tinycap.c 實現錄音相關代碼  tinycap

Tinyplay.c 實現放音相關代碼 tinyplay

Pcm.c 與驅動層alsa-driver調用接口,爲audio_hw提供api接口

Tinymix 查看和設置混音器 tinymix

Tinypcminfo.c 查看聲卡信息tinypcminfo

音頻幀(frame)
 
這個概念在應用開發中很是重要,網上不少文章都沒有專門介紹這個概念。

音頻跟視頻很不同,視頻每一幀就是一張圖像,而從上面的正玄波能夠看出,音頻數據是流式的,自己沒有明確的一幀幀的概念,在實際的應用中,爲了音頻算法處理/傳輸的方便,通常約定俗成取2.5ms~60ms爲單位的數據量爲一幀音頻。

這個時間被稱之爲「採樣時間」,其長度沒有特別的標準,它是根據編解碼器和具體應用的需求來決定的,咱們能夠計算一下一幀音頻幀的大小:

假設某音頻信號是採樣率爲8kHz、雙通道、位寬爲16bit,20ms一幀,則一幀音頻數據的大小爲:

int size = 8000 x 2 x 16bit x 0.02s = 5120bit = 640 byte

音頻幀總結

period(週期):硬件中斷間的間隔時間。它表示輸入延時。

聲卡接口中有一個指針來指示聲卡硬件緩存區中當前的讀寫位置。只要接口在運行,這個指針將循環地指向緩存區中的某個位置。

frame size =sizeof(one sample) * nChannels

alsa中配置的緩存(buffer)和週期(size)大小在runtime中是以幀(frames)形式存儲的。

period_bytes =pcm_format_to_bits 用來計算一個幀有多少bits,實際應用的時候常常用到

嵌入式硬件級

電路組成

簡單流程:

MIC採集天然聲轉成模擬電信號,經過運算放大電路放大信號幅度,而後用ADC轉換爲數字信號,(能夠進行音頻的編碼工做,好比編成mp3),(而後進行音頻解碼工做),(經過DAC轉換爲模擬信號)(或者脈衝寬度調製PWM用來對模擬信號的電平進行數字編碼),經過功率放大器放大後輸出給喇叭

看什麼方案了,若是涉及比較複雜的運算,MCU的算力是遠遠不夠的,必須上嵌入式硬件了,這就涉及到系統層面的開發。若是隻是簡單的音頻處理沒事(好比MP3節奏彩燈錄放等等)

其餘方案:

1 利用語言集成芯片如:ISD2560,ISD2560採用多電平直接模擬量存儲技術,能夠很是真實、天然的再現語音、音樂、音調和效果聲,錄音時間爲60s,可重複錄放10萬次。

2 PWM+SPI PWM模擬時鐘時序,SPI傳輸數據,採用PCM編碼方式,而後接放大器+喇叭;

(軟件編寫很簡單,只把wave文件的採樣值往pwm裏面丟就能夠了。固然,pwm信號通常須要加濾波電路才能送往功放、喇叭。通常採用16kbps的採樣率,濾波電路會簡單。)

3 DAC DAC+放大器+喇叭,通常語音芯片都是用這種方式作的,可是應該是專用的DAC語音芯片;

4 IIS+語音解碼芯片

這些總線協議什麼I2C SPI等都是用來接外圍集成電路的

其實所謂的音頻編碼器、解碼器。實際上就是普通的AD或DA後,再由運算芯片進行算法壓縮或解壓來的

編碼方案:

波形編碼的話音質量高,但編碼速率也很高(WAV);

參數編碼的編碼速率很低,產生的合成語音的音質不高(MP3);

混合編碼使用參數編碼技術和波形編碼技術,編碼速率和音質介於它們之間。

方案名詞介紹:

波形編碼PCM

波形編碼是基於對語音信號波形的數字化處理,試圖使處理後重建的語音信號波形與原語音信號波形保持一致。波形編碼的優勢是實現簡單、語音質量較好、適應性強等;缺點是話音信號的壓縮程度不是很高,實現的碼速率比較高。常見的波形壓縮編碼方法有脈衝編碼調製(PCM)

參數編碼MP3

MP3文件實際上是一種通過MP3(即動態影像專家壓縮標準音頻層面)編碼算法壓縮的數據,不能直接送給功放,必須先經過解碼還原出原始音頻數據再進行播放。

PWM原理

脈寬調製(PWM)基本原理:控制方式就是對逆變電路開關器件的通斷進行控制,使輸出端獲得一系列幅值相等的脈衝,用這些脈衝來代替正弦波或所須要的波形。也就是在輸出波形的半個週期中產生多個脈衝,使各脈衝的等值電壓爲正弦波形,所得到的輸出平滑且低次諧波少。按必定的規則對各脈衝的寬度進行調製,便可改變逆變電路輸出電壓的大小,也可改變輸出頻率。 例如,把正弦半波波形分紅N等份,就可把正弦半波當作由N個彼此相連的脈衝所組成的波形。這些脈衝寬度相等,都等於 π/n ,但幅值不等,且脈衝頂部不是水平直線,而是曲線,各脈衝的幅值按正弦規律變化。若是把上述脈衝序列用一樣數量的等幅而不等寬的矩形脈衝序列代替,使矩形脈衝的中點和相應正弦等分的中點重合,且使矩形脈衝和相應正弦部分面積(即衝量)相等,就獲得一組脈衝序列,這就是PWM波形。能夠看出,各脈衝寬度是按正弦規律變化的。根據衝量相等效果相同的原理,PWM波形和正弦半波是等效的。對於正弦的負半周,也能夠用一樣的方法獲得PWM波形。
 
在PWM波形中,各脈衝的幅值是相等的,要改變等效輸出正弦波的幅值時,只要按同一比例係數改變各脈衝的寬度便可,所以在交-直-交變頻器中,PWM逆變電路輸出的脈衝電壓就是直流側電壓的幅值。

 

代碼示例

MCU裸板開發

  1 #include <reg52.h>  
  2 #include <intrins.h>  
  3 #define uchar unsigned char  
  4 #define uint  unsigned int  
  5 //錄音和放音鍵IO口定義:  
  6 sbit   AN=P2^6;//放音鍵控制接口  
  7 sbit    set_key=P2^7;//錄音鍵控制口  
  8 // ISD4004控制口定義:  
  9 sbit SS  =P1^0;     //4004片選  
 10 sbit MOSI=P1^1;     //4004數據輸入  
 11 sbit MISO=P1^2;     //4004數據輸出  
 12 sbit SCLK=P1^3;     //ISD4004時鐘  
 13 sbit INT =P1^4;     //4004中斷  
 14 sbit STOP=P3^4;     //4004復位  
 15 sbit LED1 =P1^6;    //錄音指示燈  
 16 //===============================LCD1602接口定義=====================  
 17 /*-----------------------------------------------------  
 18        |DB0-----P2.0 | DB4-----P2.4 | RW-------P0.1    |  
 19        |DB1-----P2.1 | DB5-----P2.5 | RS-------P0.2    |  
 20        |DB2-----P2.2 | DB6-----P2.6 | E--------P0.0    |  
 21        |DB3-----P2.3 | DB7-----P2.7 | 注意,P0.0到P0.2須要接上拉電阻  
 22     ---------------------------------------------------  
 23 =============================================================*/ 
 24 #define LCM_Data     P0    //LCD1602數據接口  
 25 sbit    LCM_RW     = P2^3;  //讀寫控制輸入端,LCD1602的第五腳  
 26 sbit    LCM_RS     = P2^4;  //寄存器選擇輸入端,LCD1602的第四腳  
 27 sbit    LCM_E      = P2^2;  //使能信號輸入端,LCD1602的第6腳  
 28 //***************函數聲明************************************************  
 29 void    WriteDataLCM(uchar WDLCM);//LCD模塊寫數據  
 30 void    WriteCommandLCM(uchar WCLCM,BuysC); //LCD模塊寫指令  
 31 uchar   ReadStatusLCM(void);//讀LCD模塊的忙標  
 32 void    DisplayOneChar(uchar X,uchar Y,uchar ASCII);//在第X+1行的第Y+1位置顯示一個字符  
 33 void    LCMInit(void);  
 34 void    DelayUs(uint us); //微妙延時程序  
 35 void    DelayMs(uint Ms);//毫秒延時程序  
 36 void    init_t0();//定時器0初始化函數  
 37 void    setkey_treat(void);//錄音鍵處理程序  
 38 void    upkey_treat(void);//播放鍵處理程序  
 39 void    display();//顯示處理程序  
 40 void    isd_setrec(uchar adl,uchar adh);//發送setrec指令  
 41 void    isd_rec();//發送rec指令  
 42 void    isd_stop();//stop指令(中止當前操做)  
 43 void    isd_powerup();//發送上電指令  
 44 void    isd_stopwrdn();//發送掉電指令  
 45 void    isd_send(uchar isdx);//spi串行發送子程序,8位數據  
 46 void    isd_setplay(uchar adl,uchar adh);  
 47 void    isd_play();  
 48 //程序中的一些常量定義  
 49 uint    time_total,st_add,end_add=0;  
 50 uint    adds[25];//25段語音的起始地址暫存  
 51 uint    adde[25];//25段語音的結束地址暫時  
 52 uchar   t0_crycle,count,count_flag,flag2,flag3,flag4;  
 53 uchar   second_count=170,msecond_count=0;  
 54 //second_count爲芯片錄音的起始地址,起始地址原本是A0,也就是160,  
 55 //咱們從170開始錄音吧。  
 56 #define Busy         0x80   //用於檢測LCM狀態字中的Busy標識  
 57  
 58 /*===========================================================================  
 59  主程序  
 60 =============================================================================*/  
 61 void main(void)  
 62 {  
 63    LED1=0;//滅錄音指示燈  
 64    flag3=0;  
 65    flag4=0;  
 66    time_total=340;//錄音地址從170開始,對應的單片機開始計時的時間就是340*0.1秒  
 67    adds[0]=170;  
 68    count=0;  
 69    LCMInit();        //1602初始化  
 70    init_t0();//定時器初始化  
 71    DisplayOneChar( 0,5,'I'); //開機時顯示000  ISD4004-X  
 72    DisplayOneChar( 0,6,'S');  
 73    DisplayOneChar( 0,7,'D');  
 74    DisplayOneChar( 0,8,'4');  
 75    DisplayOneChar( 0,9,'0');  
 76    DisplayOneChar( 0,10,'0');  
 77    DisplayOneChar( 0,11,'4');  
 78    DisplayOneChar( 0,12,'-');  
 79    DisplayOneChar( 0,13,'X');  
 80    while(1)  
 81    {  
 82       display();//顯示處理  
 83       upkey_treat();//放音鍵處理  
 84       setkey_treat();//錄音鍵處理  
 85    }  
 86 }  
 87 //*******************************************  
 88 //錄音鍵處理程序  
 89 //從指定地址開始錄音的程序就是在這段裏面  
 90 void setkey_treat(void)  
 91 {  
 92    set_key=1;//置IO口爲1,準備讀入數據  
 93    DelayUs(1);  
 94    if(set_key==0)  
 95    {  
 96       if(flag3==0)//錄音鍵和放音鍵互鎖,錄音好後,禁止再次錄音。若是要再次錄音,那就要復位單片機,從新開始錄音  
 97       {  
 98         if(count==0)//判斷是否爲上電或復位以來第一次按錄音鍵  
 99         {  
100            st_add=170;  
101         }  
102         else 
103         {  
104           st_add=end_add+3;   
105         }//每段語言間隔3個地址  
106         adds[count]=st_add;//每段語音的起始地址暫時  
107         if(count>=25)//判斷語音段數時候超過25段,由於單片機內存的關係?  
108        //本程序只錄音25段,若是要錄更多的語音,改成不可查詢的便可  
109         {//若是超過25段,則覆蓋以前的語音,重新開始錄音  
110            count=0;  
111            st_add=170;  
112            time_total=340;  
113         }  
114         isd_powerup(); //AN鍵按下,ISD上電並延遲50ms  
115         isd_stopwrdn();  
116         isd_powerup();   
117         LED1=1;//錄音指示燈亮,表示錄音模式  
118         isd_setrec(st_add&0x00ff,st_add>>8); //從指定的地址  
119         if(INT==1)// 斷定芯片有沒有溢出  
120         {         
121             isd_rec(); //發送錄音指令  
122         }  
123         time_total=st_add*2;//計時初始值計算  
124         TR0=1;//開計時器  
125         while(set_key==0);//等待本次錄音結束  
126         TR0=0;//錄音結束後中止計時  
127         isd_stop(); //發送4004中止命令  
128         end_add=time_total/2+2;//計算語音的結束地址  
129         adde[count]=end_add;//本段語音結束地址暫存  
130         LED1=0; //錄音完畢,LED熄滅  
131         count++;//錄音段數自加  
132         count_flag=count;//錄音段數寄存  
133         flag2=1;  
134         flag4=1;//解鎖放音鍵  
135       }  
136   }  
137 }  
138 //=================================================  
139 //放音機處理程序  
140 //從指定地址開始放本段語音就是這段程序  
141 void upkey_treat(void)  
142 {  
143    uchar ovflog;  
144    AN=1;//準備讀入數據  
145    DelayUs(1);  
146    if(AN==0)//判斷放音鍵是否動做  
147    {  
148  //    if(flag4==1)//互鎖錄音鍵  
149  //    {  
150         if(flag2==1)//判斷是否爲錄音好後的第一次放音  
151         {  
152            count=0;//從第0段開始播放  
153         }  
154         isd_powerup(); //AN鍵按下,ISD上電並延遲50ms  
155         isd_stopwrdn();  
156         isd_powerup();   
157         //170 184 196 211  
158    //     st_add=adds[count];//送當前語音的起始地址  
159         st_add=211;//送當前語音的起始地址  
160         isd_setplay(st_add&0x00ff,st_add>>8); //發送setplay指令,從指定地址開始放音  
161         isd_play(); //發送放音指令  
162         DelayUs(20);  
163         while(INT==1); //等待放音完畢的EOM中斷信號  
164         isd_stop(); //放音完畢,發送stop指令  
165         while(AN==0); //   
166         isd_stop();  
167         count++;//語音段數自加  
168         flag2=0;  
169         flag3=1;  
170         if(count>=count_flag)//若是播放到最後一段後還按加鍵,則從第一段從新播放  
171         {  
172              count=0;  
173         }  
174        
175  //     }  
176    }   
177 }  
178 //************************************************?  
179 //發送rec指令  
180 void isd_rec()  
181 {  
182     isd_send(0xb0);  
183     SS=1;  
184 }  
185 //****************************************  
186 //發送setrec指令  
187 void isd_setrec(unsigned char adl,unsigned char adh)  
188 {  
189     DelayMs(1);  
190     isd_send(adl); //發送放音起始地址低位  
191     DelayUs(2);  
192     isd_send(adh); //發送放音起始地址高位  
193     DelayUs(2);  
194     isd_send(0xa0); //發送setplay指令字節  
195     SS=1;  
196 }  
197 //=============================================================================  
198 //**********************************************  
199 //定時器0中斷程序  
200 void timer0() interrupt 1  
201 {  
202     TH0=(65536-50000)/256;  
203     TL0=(65536-50000)%256;  
204     t0_crycle++;  
205     if(t0_crycle==2)// 0.1秒  
206     {  
207       t0_crycle=0;  
208       time_total++;  
209       msecond_count++;  
210       if(msecond_count==10)//1秒  
211       {   
212         msecond_count=0;  
213         second_count++;  
214         if(second_count==60)  
215         {  
216           second_count=0;  
217         }  
218       }  
219       if(time_total==4800)time_total=0;      
220     }  
221 }  
222 //********************************************************************************************  
223 //定時器0初始化函數  
224 void init_t0()  
225 {  
226     TMOD=0x01;//設定定時器工做方式1,定時器定時50毫秒  
227     TH0=(65536-50000)/256;  
228     TL0=(65536-50000)%256;  
229     EA=1;//開總中斷  
230     ET0=1;//容許定時器0中斷  
231     t0_crycle=0;//定時器中斷次數計數單元  
232 }  
233 //******************************************  
234 //顯示處理程序  
235 void display()  
236 {  
237         uchar x;  
238         if(flag3==1||flag4==1)//判斷是否有錄音過或者放音過  
239         {  
240           x=count-1;  
241           if(x==255){x=count_flag-1;}  
242         }  
243         DisplayOneChar( 0,0,x/100+0x30);    //顯示當前語音是第幾段  
244         DisplayOneChar( 0,1,x/10%10+0x30);  
245         DisplayOneChar( 0,2,x%10+0x30);  
246         if(flag3==0)//錄音時顯示本段語音的起始和結束地址  
247         {  
248            DisplayOneChar( 1,0,st_add/1000+0x30);//計算並顯示千位     
249            DisplayOneChar( 1,1,st_add/100%10+0x30);  
250            DisplayOneChar( 1,2,st_add/10%10+0x30);  
251            DisplayOneChar( 1,3,st_add%10+0x30);  
252            DisplayOneChar( 1,4,'-');  
253            DisplayOneChar( 1,5,'-');  
254            DisplayOneChar( 1,6,end_add/1000+0x30);     
255            DisplayOneChar( 1,7,end_add/100%10+0x30);  
256            DisplayOneChar( 1,8,end_add/10%10+0x30);  
257            DisplayOneChar( 1,9,end_add%10+0x30);  
258         }  
259         if(flag4==1)//放音時顯示本段語音的起始和結束地址  
260         {  
261            DisplayOneChar( 1,0,adds[x]/1000+0x30);     
262            DisplayOneChar( 1,1,adds[x]/100%10+0x30);  
263            DisplayOneChar( 1,2,adds[x]/10%10+0x30);  
264            DisplayOneChar( 1,3,adds[x]%10+0x30);  
265            DisplayOneChar( 1,4,'-');  
266            DisplayOneChar( 1,5,'-');  
267            DisplayOneChar( 1,6,adde[x]/1000+0x30);     
268            DisplayOneChar( 1,7,adde[x]/100%10+0x30);  
269            DisplayOneChar( 1,8,adde[x]/10%10+0x30);  
270            DisplayOneChar( 1,9,adde[x]%10+0x30);  
271         }  
272 }  
273 //======================================================================  
274 // LCM初始化  
275 //======================================================================  
276 void LCMInit(void)   
277 {  
278  LCM_Data = 0;  
279  WriteCommandLCM(0x38,0); //三次顯示模式設置,不檢測忙信號  
280  DelayMs(5);  
281  WriteCommandLCM(0x38,0);  
282  DelayMs(5);  
283  WriteCommandLCM(0x38,0);  
284  DelayMs(5);  
285  WriteCommandLCM(0x38,1); //顯示模式設置,開始要求每次檢測忙信號  
286  WriteCommandLCM(0x08,1); //關閉顯示  
287  WriteCommandLCM(0x01,1); //顯示清屏  
288  WriteCommandLCM(0x06,1); // 顯示光標移動設置  
289  WriteCommandLCM(0x0C,1); // 顯示開及光標設置  
290  DelayMs(100);  
291 }  
292 //*=====================================================================  
293 // 寫數據函數: E =高脈衝 RS=1 RW=0  
294 //======================================================================  
295 void WriteDataLCM(uchar WDLCM)  
296 {  
297  ReadStatusLCM(); //檢測忙  
298  LCM_Data = WDLCM;  
299  LCM_RS = 1;  
300  LCM_RW = 0;  
301  LCM_E = 0; //若晶振速度過高能夠在這後加小的延時  
302  LCM_E = 0; //延時  
303  LCM_E = 1;  
304 }  
305 //*====================================================================  
306  // 寫指令函數: E=高脈衝 RS=0 RW=0  
307 //======================================================================  
308 void WriteCommandLCM(unsigned char WCLCM,BuysC) //BuysC爲0時忽略忙檢測  
309 {  
310  if (BuysC) ReadStatusLCM(); //根據須要檢測忙  
311  LCM_Data = WCLCM;  
312  LCM_RS = 0;  
313  LCM_RW = 0;  
314  LCM_E = 0;  
315  LCM_E = 0;  
316  LCM_E = 1;  
317 }  
318 //*====================================================================  
319 //  正常讀寫操做以前必須檢測LCD控制器狀態:E=1 RS=0 RW=1;  
320 //  DB7: 0 LCD控制器空閒,1 LCD控制器忙。  
321  // 讀狀態  
322 //======================================================================  
323 unsigned char ReadStatusLCM(void)  
324 {  
325  LCM_Data = 0xFF;  
326  LCM_RS = 0;  
327  LCM_RW = 1;  
328  LCM_E = 0;  
329  LCM_E = 0;  
330  LCM_E = 1;  
331  while (LCM_Data & Busy); //檢測忙信號    
332  return(LCM_Data);  
333 }  
334 //======================================================================  
335 //功 能:     在1602 指定位置顯示一個字符:第一行位置0~15,第二行16~31  
336 //說 明:     第 X 行,第 y 列  注意:字符串不能長於16個字符  
337 //======================================================================  
338 void DisplayOneChar( unsigned char X, unsigned char Y, unsigned char ASCII)  
339 {  
340  X &= 0x1;  
341  Y &= 0xF; //限制Y不能大於15,X不能大於1  
342  if (X) Y |= 0x40; //當要顯示第二行時地址碼+0x40;  
343  Y |= 0x80; // 算出指令碼  
344  WriteCommandLCM(Y, 0); //這裏不檢測忙信號,發送地址碼  
345  WriteDataLCM(ASCII);  
346 }  
347 //======================================================================  
348 //spi串行發送子程序,8位數據  
349 void isd_send(uchar isdx)  
350 {  
351     uchar isx_counter;  
352     SS=0;//ss=0,打開spi通訊端  
353     SCLK=0;  
354     for(isx_counter=0;isx_counter<8;isx_counter++)//先發低位再發高位,依次發送。  
355     {  
356         if((isdx&0x01)==1)  
357             MOSI=1;  
358         else 
359             MOSI=0;  
360             isdx=isdx>>1;  
361             SCLK=1;  
362             DelayUs(2);  
363             SCLK=0;  
364             DelayUs(2);  
365     }  
366 }  
367 //======================================================================  
368 //stop指令(中止當前操做)  
369 void isd_stop()//  
370 {  
371     DelayUs(10);  
372     isd_send(0x30);  
373     SS=1;  
374     DelayMs(50);  
375 }  
376 //======================================================================  
377 //發送上電指令  
378 void isd_powerup()//  
379 {  
380     DelayUs(10);  
381     SS=0;  
382     isd_send(0x20);  
383     SS=1;  
384     DelayMs(50);  
385 }  
386 //======================================================================  
387 //發送掉電指令  
388 void isd_stopwrdn()//  
389 {  
390     DelayUs(10);  
391     isd_send(0x10);  
392     SS=1;  
393     DelayMs(50);  
394 }  
395  
396 void isd_play()//發送play指令  
397 {  
398     isd_send(0xf0);  
399     SS=1;  
400 }  
401 void isd_setplay(uchar adl,uchar adh)//發送setplay指令  
402 {  
403     DelayMs(1);  
404     isd_send(adl); //發送放音起始地址低位  
405     DelayUs(2);  
406     isd_send(adh); //發送放音起始地址高位  
407     DelayUs(2);  
408     isd_send(0xe0); //發送setplay指令字節  
409     SS=1;  
410 }  
411 void DelayUs(uint us)  
412 {  
413     while(us--);  
414 }   
415 //====================================================================  
416 // 設定延時時間:x*1ms  
417 //====================================================================  
418 void DelayMs(uint Ms)  
419 {  
420   uint i,TempCyc;  
421   for(i=0;i<Ms;i++)  
422   {  
423     TempCyc = 250;  
424     while(TempCyc--);  
425   }  
426 }  
427  
相關文章
相關標籤/搜索