我們前面分享了兩篇文章, 從頭腦風暴,純思想實驗出發,引出了PDM以及sigma-deleta的原理。如果讀者有心,仔細去回味,思考,可能會舉一反三,發現實踐中很多場景可以應用類似的思想,其實這是一類問題,一通則百通。
如果沒有,那么看完本文, 希望能有。看完你可能會拍案而起,或者激動的拍大腿,這也是本人希望分享技術文章的初衷,希望能讓讀者讀到欲罷不能甚至有激動興奮的感覺。本文重點還是一如既往的希望授人以漁,而不是單純的輸出一個技術點。
https://mp.weixin.qq.com/s/9Vhe1rUCI8ZGBGGy3todcw PDM系列文章之二:一文搞懂PDM編碼基本原理1bit sigma-deleta ADC,
https://mp.weixin.qq.com/s/uuKljd3zPjj-gkrjlvWEbg?PDM系列文章之一- 前菜篇,假如來發明我們自己的PDM
這個問題是一個讀者私下問我的一個問題,即我們在音視頻處理中”生產者”和”消費者”可能速率不匹配,比如視頻采集幀率很快,但是視頻編解碼可能達不到采集的幀率需要降幀率處理。對于降幀比是整數倍的很好解決,就是指定幀數處理一幀就行,比如輸入10FPS需要降幀率到5FPS處理,那么就是每兩幀丟掉一幀即可。那么如果不是整數倍數的降幀率呢,比如輸出10FPS要按照3FPS處理,算法如何實現?
說實話我之前也沒有寫過該算法,但是看到這個問題,我一秒鐘就想到了我們之前討論過的PDM的原理。我們還是按照之前文章提出的思路,先抽象,再分析。這個問題題干很簡單,簡單描述就是對幀率為xFPS的輸入,如何挑選丟棄一些幀得到更小的幀率yFPS,當然要保證均勻性。我們進一步抽象,按照之前設計自己的PDM的思路進行,先對輸入對象抽象,輸入對象就是幀流,我們可以每一幀用1表示,那么輸入就是11111的流,而輸出是對11111流的挑選是否需要進行處理,如果需要對輸入進行處理就輸出1,不需要處理就輸出0。那么結果就是10的序列編碼輸出。那么問題就抽象為了如何對輸入流11xxxx,通過挑選使得其輸出流10xxxxx,滿足1均勻分布,且1的比例(密度)正好是指定的倍數。原來1的密度認為是100%,那么降低幀率倍數為x,那么輸出的1的密度就是原來的1/x.
我們再來看抽象后的模型不就是我們的PDM編碼嗎? 用1的密度代表幀率,之前引出PDM時是用1的密度代表電壓,所以不管是代表電壓還是代表幀率還是代表什么妖魔鬼怪,都是一樣的問題,對于抽象或者數學上來說就是一個數字。所以再次說明我們之前提出的,解決問題的第一步是抽象這一重要觀點。
如果讀者看過之前兩篇文章,且確實是用心去思考理解過的話,到此為此,應該就要拍大腿,激動的說哦原來如此了,所以實際本文至此就可以結束了。但是我們還是善始善終,還是把整個實現過程分享一下。
為了方便編碼,我們還是回憶下sigma-deleta的系統框圖, 把我們的問題抽象后,映射到模型上,圖像化,才能方便實現。
典型的1bit sigam-delta系統框圖如下,由一個差分模塊(計算本次偏差),積分模塊(計算累計誤差),和一個比較模塊-DAC輸出模塊(負反饋)組成。如下所示,DAC輸出實際是一個開關控制輸出,要么輸出VREF參考電壓,要么輸出0,參考電壓VREF大于Xi輸入電壓,開關的控制由比較器決定,比較器的輸出由誤差的累計決定,也就是說有正向誤差則控制DAC輸出VREF,此時Xi

不說什么高深的概念,四個字就可以說明其核心思想,多退少補! 多了往少的方向調整,少了往多的方向調整。
移植到我們降低幀率的問題來看,DAC的輸出即參考電壓VREF對應的是輸入幀率,需要對其進行不斷的開關開關(即10xxx序列),最終使得其等效為輸出幀率Xi(即待采樣電壓)。
積分器的輸出就是實時的誤差,DAC的開關序列即幀的挑選序列。
好了有了以上的模型說明,編碼其實很簡單了
幾行就搞定,只需要簡單的加減法即可,非常高效
/**?* \struct sigma_delta_st?* sigma_delta結構體*/typedef struct{? ? int input; ? ? ?/**< 輸入值(參考值) ? ?*/? ? int output; ? ? /**< 目標值 ? ? ? ? ? */? ? int integrator; /**< 積分器輸出 ? ? ? ?*/? ? int dac; ? ? ? ?/**< 比較器輸出, 0表示DAC無需輸出參考值,1表示DAC需要輸出參考值 */} sigma_delta_st;/**?* \fn sigma_delta_init?* 初始化?* \param dev 用戶提供的實例, \ref sigma_delta_st?* \param input 輸入值(參考值)?* \param output 目標值*/static void sigma_delta_init(sigma_delta_st* dev, int input, int output){? ? if(dev == (sigma_delta_st*)0)? ? {? ? ? ? return;? ? }? ? dev->dac = 0; ? ? /**< 初始時DAC無輸出,初始積分器偏差為0-output */? ? dev->integrator = 0-output;? ? dev->input = input;? ? dev->output = output;}/**?* \fn sigma_delta_update?* 計算下一步1bit DAC是否輸出, 1輸出, 0不輸出?* 反復調用本函數反復輸出10序列,10序列即input的開關序列,其平均效果等效于目標output的值.?* \param dev 用戶提供的實例, \ref sigma_delta_st?* \return 返回1或者0,即DAC的輸出開關,1表示輸出,0表示不輸出*/static int sigma_delta_update(sigma_delta_st* dev){? ? if(dev == (sigma_delta_st*)0)? ? {? ? ? ? return 0;? ? }? ? /* (偏差)積分值,經過比較器(和0比較),確認偏差的正負方向,然后控制DAC輸出反向調整積分值,形成負反饋? ? ?* 目標是讓積分器的輸出趨于0,即偏差趨于0,即差分輸入正負輸出等效,即output和input的開關序列等效? ? ?*/? ? if(dev->integrator > 0)? ? {? ? ? ? /* 如果積分值大于0, 則DAC使能輸出,即輸出input? ? ? ? ? ?差分輸入dev->output-dev->input小于0,使得積分值減小,反向調整積分值? ? ? ? ?*/? ? ? ? dev->dac = 1;? ? ? ? dev->integrator += dev->output-dev->input; ?? ? }? ? else? ? {? ? ? ? /* 如果積分值小于等于0, 則DAC停止輸出,即輸出0? ? ? ? ? ?差分輸入dev->output-0大于0,使得積分值增大,反向調整積分值? ? ? ? ?*/? ? ? ? dev->dac = 0;? ? ? ? dev->integrator += (dev->output-0);? ? }? ? return dev->dac;}
測試代碼如下
/**?* \fn sigma_delta_test?* 測試函數?* \param[in] input 輸入值?* \param[in] input 輸出值?* \param[in] pnum ?更新次數*/static void sigma_delta_test(int input, int output, int pnum){? ? sigma_delta_st dev;? ? sigma_delta_init(&dev, input, output);????printf("\r\n");? ? while(pnum--)? ? {? ? ? ? int res = sigma_delta_update(&dev);????????printf("%d?%d?%d\r\n",res,dev.input,dev.integrator);? ? ? ? os_delay(10);? ? }}
我們實現命令行方便測試
void sdtestfunc(uint8_t* param){? int input;? int output;? int pnum;? if(3 == sscanf((const char*)param, "%*s %d %d %d", &input, &output, &pnum))? {? ? sigma_delta_test(input,output,pnum);? }}
測試不同的降采樣率比例,打印挑選序列
10:8降采樣率,第一列為抽取序列1表示輸出0表示丟棄,第三列表示當前偏差,可以看到任意5個中有4個1,正好是10:8
sh>
sdtest 10 8 20
0 10 0
0 10 8
1 10 6
1 10 4
1 10 2
1 10 0
0 10 8
1 10 6
1 10 4
1 10 2
1 10 0
0 10 8
1 10 6
1 10 4
1 10 2
1 10 0
10:3降采樣率,第一列為抽取序列1表示輸出0表示丟棄,第三列表示當前偏差,可以看到任意10個中有3個1,正好是10:3
sh>
sdtest 10 3 20
0 10 0
0 10 3
1 10 -4
0 10 -1
0 10 2
1 10 -5
0 10 -2
0 10 1
1 10 -6
0 10 -3
0 10 0
0 10 3
1 10 -4
0 10 -1
0 10 2
1 10 -5
0 10 -2
0 10 1
1 10 -6
0 10 -3
可以看到滿足均勻輸出,且抽取比例符合需求,當然可能存在偶爾一個窗口內有偏差的情況,因為輸出只能是要么輸出input要么輸出0,顆粒度不是無限小,所以從長期時間平均才能更高精度等效,時間越長精度越高,這也是sigma-deleta ADC精度高但是需要采樣時間長的原因,即時間換精度。
我們還是保持刨根問題和發散思維的精神,上述說的是降采樣率,那么問題來了,如何升采樣率呢,即對低采樣率進行插值得到高采樣率呢。我們要有逆向思考的思維,升采樣率還是一回事,我們就假設知道升采樣率后的幀率,然后對其降采樣率,那么輸出1則表示是使用輸入幀,輸出0則表示該幀需要插值。
好的,看到上面是不是還意猶未盡,我們繼續思考是不是還可以想到類似的系統?
理想運放?
負反饋系統?
DC-DC?
PID?
等等是不是都是類似的,實際上你在實踐中會發現更多類似的系統。
其實我們從哲學的角度來考慮這個問題會更有深刻的認知,中國傳統文化其實是提倡中庸之道的,你會發現古人的智慧,其實負反饋就是中庸之道,負反饋系統是真實世界最最常見的系統,為什么呢,因為其中庸之道,多了少點,少了多點,趨于中庸穩定。而正反饋系統,則會一直朝著一個方向發散最后失控,就好比宇宙來源于大爆炸,最終肯定還是會坍縮,重復下一次大爆炸, 反復不息....,做人,工作,生活又何嘗不是如此呢。
所以以上全文技術點用一句三歲小孩都懂的話來總結,就是多了就少點,少了就多點,所以技術沒有什么高深的,原理都很簡單,而用一句話讓不懂技術的人搞懂,也是評估你是不是真的懂的最好的標準,如果你需要用一大堆高深的術語公式去說明一個技術問題,那么說明你肯定沒有真的搞懂。而我們學習一個技術也要思考其背后的思想,甚至從哲學層面去思考,而不是僅僅停留在技術表面。