2014年11月23日 星期日

Android Audio 裡的各種設定和其關係 (一)

[文章重點]
  在看 code 時, 時常會遇到 afFrameCount, afLatency, afSampleRate, minBufCount, period_size, period_count ... 等等,  在本文章中會去說明各個意義和它們之間存在著什麼關係. 其實, 網路上已經有很多前輩分享相關資訊, 小弟在此做一個小整理.

[參考]

  1. http://m.blog.csdn.net/blog/njuitjf/9945749 
  2. http://blog.csdn.net/azloong/article/details/17614859
  3. http://www.mediacollege.com/audio/01/sound-waves.html
  4. http://www.divediscover.whoi.edu/expedition12/hottopics/sound.html
  5. http://streaming.wisconsin.edu/creation/st_tech/audio_editing_SF2.html
  6. http://www.ni.com/white-paper/3016/zht/
  7. http://terms.naer.edu.tw/detail/1283354/
  8. https://documentation.apple.com/en/finalcutpro/usermanual/index.html#chapter=52%26section=7%26tasks=true
  9. http://techpubs.sgi.com/library/tpl/cgi-bin/getdoc.cgi?coll=0650&db=bks&srch=&fname=/SGI_Developer/DMSDK_PG/sgi_html/ch08.html
  10. http://alsa.opensrc.org/Frame
[文章目錄]
  1. sound wave
  2. wave length 和頻率 (frequency)
  3. sampling rate
  4. Nyquist sampling theorem
  5. bit depth
  6. channel
  7. frame

[sound wave]


聲音在透過空氣傳播的過程中, 正如上圖所示, 呈現疏密的現象, 我們可以用 sin wave 來呈現此現象, 波峰代表空氣最密集的那個點, 波谷代表空氣最稀疏的那個點.

[wave length 和 頻率]


而一個週期(例如上圖, 波峰到波峰)的長度, 我們稱為波長 ( wave length). 那頻率就是一秒內有多少個週期, 上圖的頻率就是 1k Hz, 因為一個週期要花費 1 ms , 而 1 second = 1000 ms, 所以, 一秒鐘就可以產生 1000 次週期, 換句話說, 頻率就是 1k Hz.


上圖中, 上半部所示的頻率(F1)就小於下半部所示的頻率(F2), 也就是 F1 < F2. 從這圖, 我們也可以發現, 頻率越高, 其波長就越短.

[ sampling rate ]

接下來討論 sampling rate, 為了數位儲存, 我們需要將聲音這樣的類比訊號轉成數位訊號, 所以需要透過採樣(sampling)來達成此目的, 從上圖可以看出, 採樣越多就越能貼近原本的訊號. 也因為採樣越多, 同樣一段音樂, 就需要更多的儲存空間, 換句話說, 針對同樣一段類比訊號, 採樣頻率越高, 轉出的數位檔案就會越大.

在這裡還有一個重點需要注意, 也就是在做 sampling rate 的轉換, 也會影響所需 buffer 的大小, 也就是說, 當在升頻 ( upsampling) 的動作時, 所需的 buffer 也要一起加大, 反之亦然.

[ Nyquist sampling theorem]
說到 sampling rate, 就必須提到 Nyquist sampling theorem: 對一類比信號取樣後,若要能夠完全還原原信號而不引起任何誤差, 則取樣速率必須大於等於輸入信號最大頻率的兩倍. 換句話說, 如果要正確採樣(也就是能夠還原相同頻率的訊號), 針對 1k Hz 頻率的聲音, 採樣頻率就必須要大於等於2k Hz.


如上圖, A 的採樣頻率( Fs )等於該聲音的頻率(f),  也就是 Fs = f , 所以, 這一次採樣到波峰, 那麼下次採樣的位置仍會是波峰, 所以, 當要還原波型時, 就形成一條線.

B 的採樣頻率( Fs )等於該聲音的頻率(f)的兩倍, 也就是 Fs = 2f , 也就是符合 Nyquist sampling theorem 的最低需求, 所以, 當要還原波型時, 就形成一條鋸齒狀, 雖然聲音聽起來有點不一樣, 但至少, 聲音頻率是對的.

C 的採樣頻率( Fs )等於該聲音的頻率(f)的4/3倍, 也就是 Fs = 4/3f , 不符合 Nyquist sampling theorem 的最低需求, 所以, 當要還原波型時, 就形成一條大鋸齒狀, 聲音頻率是錯的.

[ bit depth ]

接下來探討一下 bit depth, 如上圖, 同樣的 sampling rate, 但是, bit depth 不同, 上面是 8 bits, 下面是 16 bits, 我們可以發現, bit depth 越大, 越能貼近原本的訊號, 但是, 其檔案大小也就越大.


從上圖可知, 當 bit depth 越大, 所能表現的動態範圍也就越多.

[ channel ]
   mono 為單聲道, stereo 為雙聲道或又稱立體聲. 同樣地, 針對一段聲音進行錄音( 相同的條件下), 雙聲道的資料量, 是單聲道的兩倍.

[ frame ]
    構成一個聲音的最小單位, 也就是一組 samples, 而這也跟 channel 息息相關.

單聲道 ( mono ) = 1 sample = 1 frame ;
雙聲道 ( stereo) = 2 samples = 1 frame;
5.1聲道(左, 中央,  右, 右後, 左後, 低音) = 6 samples = 1 frame

所以, frame size 的計算公式如下

frame size = bit depth * channels 

以下是節錄至 alsa.opensrc.org 關於 frame 的定義, 供大家參考
frame is a set of samples, one per channel, at a particular instant in time. For stereophonic audio, a frame consists of two samples. For Dolby 5.1 Surround Sound, a frame would consist of six samples (left channel, center channel, right channel, rear right, rear left, and the low frequency channel). For monophonic audio, a frame is equivalent to one sample.




2014年8月24日 星期日

在device tree的架構下, i2c device 與 driver 是如何 match

[文章重點]
了解在 device tree 的架構一下, i2c device 與 i2c driver 是如何 match 進而 call 到 driver 裡的 probe function (本文章是以 Qualcomm MSM8974 liquid board 為例)

[文章目錄]
  1. 在 device tree 架構下的作法
  2. device 如何被註冊
  3. driver 如何與 device 匹配

[在 device tree 架構下的作法]
Linux kernel driver 的架構都是由匯流排 ( bus ), 裝置 ( device ) 和 驅動程式 ( driver ) 所構成, 匯流排將裝置和驅動程式綁定. 當 Linux kernel 在註冊一個裝置的時候, 會尋找與之匹配的驅動程式; 相反的, 當 Linux  kernel 在註冊一個驅動程式的時候, 也會尋找與之匹配的裝置, 而匹配這動作是由匯流排負責完成.  然而 device tree 在做匹配綁定的概念跟之前的作法仍是一致的, 換句話說, 還是利用字串的比對, 只是由原本的 name 屬性改由 device tree 內的 compatible 屬性去做而已.


首先會說明裝置是如何被建立, 是如何被加到匯流排中, 接下來會說明驅動程式在註冊後, 匯流排如何去做這匹配的動作.

[device 如何被註冊]
那裝置是如何被加到匯流排中?  那就是透過 of_platform_populate( ) 這 function.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
 * of_platform_populate() - Populate platform_devices from device tree data
 * @root: parent of the first level to probe or NULL for the root of the tree
 * @matches: match table, NULL to use the default
 * @parent: parent to hook devices from, NULL for toplevel
 *
 * Similar to of_platform_bus_probe(), this function walks the device tree
 * and creates devices from nodes.  It differs in that it follows the modern
 * convention of requiring all device nodes to have a 'compatible' property,
 * and it is suitable for creating devices which are children of the root
 * node (of_platform_bus_probe will only create children of the root which
 * are selected by the @matches argument).
 *
 * New board support should be using this function instead of
 * of_platform_bus_probe().
 *
 * Returns 0 on success, < 0 on failure.
 */
int of_platform_populate(struct device_node *root,
   const struct of_device_id *matches,
   const struct of_dev_auxdata *lookup,
   struct device *parent)
{
 struct device_node *child;
 int rc = 0;

 root = root ? of_node_get(root) : of_find_node_by_path("/");
 if (!root)
  return -EINVAL;

 for_each_child_of_node(root, child) {
  rc = of_platform_bus_create(child, matches, lookup, parent, true);
  if (rc)
   break;
 }

 of_node_put(root);
 return rc;
}

從這 function 的註釋中了解到它會走遍整個 device tree 並為 每一個 device node 去創建 device. 接下透過 call flow來了解先後關係.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
DT_MACHINE_START(MSM8974_DT, "Qualcomm MSM 8974 (Flattened Device Tree)")
 .map_io = msm8974_map_io,
 .init_irq = msm_dt_init_irq,
 .init_machine = msm8974_init,
 .handle_irq = gic_handle_irq,
 .timer = &msm_dt_timer,
 .dt_compat = msm8974_dt_match,
 .reserve = msm_8974_reserve,
 .init_very_early = msm8974_init_very_early,
 .restart = msm_restart,
 .smp = &msm8974_smp_ops,
MACHINE_END

 msm8974_init( ) @ board-8974.c 
 |--> board_dt_populate( ) @ board-dt.c 
     |--> of_platform_populate( )  @ platform.c
        |--> of_plateform_bus_create( ) @ platform.c 
            |--> of_platform_device_create_pdata( ) @ platform.c 
               |--> of_device_alloc( ) @ platform.c
                   /*除了分配struct platform_device的記憶體,
                     還分配了該platform device需要的resource的記憶體*/  
               |--> of_device_add( ) @ device.c /*把這個platform device加入系統中*/  
                   |--> device_add( )  /* 它會call device_create_file( ) 
                                       在 sysfs中建立 attribute file for the device*/


[driver 如何與 device 匹配]
在看完 " device tree 如何新增 device 到系統中" 後, 接下來看 device driver 的註冊過程, 在此, 以 i2c device driver  - audio codec rt5627 為例.

 i2c_add_driver( ) @ i2c.h  
 |-> i2c_register_driver( ) @ i2c-core.c /*將 device_driver中的 bus_type 設成 i2c_bus_type */  
    |--> driver_register( ) @ driver.c  
        |--> bus_add_driver( ) @ bus.c /* 建立 sysfs file node 與 attr*/  
           |--> driver_attach( ) @ dd.c  
              |--> bus_for_each_dev( drv->bus, NULL, drv, __driver_attach) @ bus.c  
                  |--> __driver_attach @ dd.c  
                      |--> driver_match_device(drv, dev) @base.h  
                           |--> i2c_device_match( ) @ i2c-core.c  
                               /***********************************************  
                           如果是device tree, 會透過 of_driver_match_device( ) 去做匹配的動作  
                           如果不是device tree就改用 i2c_match_id( ) 去完成匹配的動作  
                              ***************************************************/  
                  |--> driver_probe_device( ) @ dd.c  
                       /*如果匹配成功, 接下來就要準備 call driver 中的 probe function*/
                       |--> really_probe( ) @ dd.c  
                           |--> i2c_device_probe( ) @ i2c-core.c  
                                |--> rt5627_i2c_probe( ) @ rt5627.c   

從上述的 call flow 得知, device tree 是透過 of_driver_match_device( ) 來做 device 與 driver 匹配的工作. 再從下述的 call flow 得知, 其實最後就是拿 device tree 中的 compatible 屬性跟 driver 中的 of_match_table->compatible 做字串的比較, 如果字串相同, 那就匹配成功囉.

 of_driver_match_device( ) @ of_device.h  
 |--> of_match_device(drv-> of_match_table, dev) @ device.c  
     |--> of_match_node( drv->of_match_table, dev->node) @ base.c  
         |--> of_device_is_compatible(node, drv->of_match_table->compatible) @ base.c 
             |--> of_compat_cmp( ) @ prom.h  
                  /*在 of_compat_cmp( ) 會透過 of_get_property()去取得
                    device tree 中 compatible 對應的值(字串), 
                    再跟 drv->of_match_table->compatible 所指的字串做字串比較*/
                 |--> strncmp( ) /*字串的比較*/  

我們再細看一下 of_match_node( ) 在做什麼事

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
 * of_match_node - Tell if an device_node has a matching of_match structure
 * @matches: array of of device match structures to search in
 * @node:  the of device structure to match against
 *
 * Low level utility function used by device matching.
 */
const struct of_device_id *of_match_node(const struct of_device_id *matches,
      const struct device_node *node)
{
 if (!matches)
  return NULL;

 while (matches->name[0] || matches->type[0] || matches->compatible[0]) {
  int match = 1;
  if (matches->name[0])
   match &= node->name
    && !strcmp(matches->name, node->name);
  if (matches->type[0])
   match &= node->type
    && !strcmp(matches->type, node->type);
  if (matches->compatible[0])
   match &= of_device_is_compatible(node,
      matches->compatible);
  if (match)
   return matches;
  matches++;
 }
 return NULL;
}
EXPORT_SYMBOL(of_match_node);

就 device tree 而言(大部分 driver 的 of_match_table 裡只有填寫 compatible 屬性, 例如: 在下述 rt5627_match_table  這例子裡, 就是只有填寫 compatible 屬性), 它會跑到 of_device_is_compatible( ) 去檢查是否匹配, 下面是就是 of_device_is_compatible( ) 完整的 source code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/** Checks if the given "compat" string matches one of the strings in
 * the device's "compatible" property
 */
int of_device_is_compatible(const struct device_node *device,
  const char *compat)
{
 const char* cp;
 int cplen, l;

 cp = of_get_property(device, "compatible", &cplen);
 if (cp == NULL)
  return 0;
 while (cplen > 0) {
  if (of_compat_cmp(cp, compat, strlen(compat)) == 0)
   return 1;
  l = strlen(cp) + 1;
  cp += l;
  cplen -= l;
 }

 return 0;
}
EXPORT_SYMBOL(of_device_is_compatible);

貼一下關於 of_match_table 是如何放在 driver 中 (以 audio codec rt5627為例)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#ifdef CONFIG_OF
static struct of_device_id rt5627_match_table[] = {
        { .compatible = "realtek,rt5627",},
        { },
};
#else
#define rt5627_match_table NULL
#endif

static struct i2c_driver rt5627_i2c_driver = {
 .driver = {
  .name = "rt5627",
  .owner = THIS_MODULE,
  .of_match_table = of_match_ptr(rt5627_match_table),
 },
 .probe =    rt5627_i2c_probe,
 .remove =   rt5627_i2c_remove,
 .id_table = rt5627_i2c_id,
};

看一下 device tree ( msm8974-liquid.dtsi ) 內, 我們新增一個 i2c device - rt5627 

 &soc {  
    ..  
     i2c@f9923000 {  
           realtek_rt5627@18 {  
                compatible = "realtek,rt5627";  
                reg = <0x18>;  
           };  
      };  
    ...  
 };  

我們將 Realtek rt5627 這顆 audio DAC ( 其 i2c address 是 0x18 ) 掛在 i2c bus 上 ( 其 physical address 為 0xf9923000 的 i2c bus 上).

[結論] 
在使用 device tree 時, 其匹配是利用 device tree 中 的 compatible 屬性, 跟 driver 中的 of_match_tabl e-> compatible 所指的字串做比較, 所以兩個字串要相同, 這樣才會匹配成功.

 [補充]
   針對 rt5627 這 i2c device 是如何被加進系統中做一個補充, 其實在device tree 章節中就有說明 device 是如何被加到系統中 ( board_dt_populate() ->  of_platform_populate( ) -> .... -> device_add( )), 但那只是針對一些父節點而言. 此例中, i2c@f9923000 是父節點, 而 realtek_rt5627@18 是子節點, 那子節點是何時被加到系統中? 在Qualcomm msm8974 平台上, 是在 i2c driver 的 probe function 中去實現, 所以是父節點做完 device <--> driver 的匹配動作之後, 在父節點的 driver 中的 probe function 裡再去找 device tree 中是否還有子節點, 如果有, 就開始一連串 device 註冊的動作.

 qup_i2c_probe( ) @ i2c-qup.c  
 |--> i2c_add_numbered_adapter( ) @ i2c-core.c   
     /* 宣告 i2c adapter 並給予 bus number*/  
 |--> of_i2c_register_device( ) @ of_i2c.c  
     /*走遍 child nodes, 並為 child nodes 註冊 device nodes*/  
     |--> i2c_new_device( ) @ i2c-core.c   
         /*為這 i2c device 依bus number 與 address命名, 例如: 4-0018  
          表示該 device 位於 bus 4, i2c address 是 0018*/  
        |--> device_register( ) @ core.c  
            |--> device_add ( ) @ core.c  

2014年7月10日 星期四

如何在 Android 各 level ( 包含 user space 與 kernel space ) 使用dump call stack的方法

dump call stack


[文章重點]

了解 Android 各 level ( UI, framework 與 HAL) 與 kernel 間, 如何印出 call stack, 方便追 code 與 debug

[文章目錄]
  1. kernel call stack
  2. Android Java layer
  3. Android framework ( written by c++)
  4. Android HAL ( written by c )
  5. Call Stack 沒有出現 function name

kernel call stack

如果想知道call stack,也就是說, 我們想知道是誰call到func_foo(). 此時,我們可以利用 dump_stack(),放在你想dump back trace的地方就OK囉.
 
void func_foo(void){
 
  int a=3;
  ...
  
  dump_stack();

  ...

}

Java layer call stack
在Java檔案, 可以使用下述方法得到dump call stack


public void foo(boolean state, int flags) {
 ...
 Log.d(TAG,"xxxx", new Throwable());
 ...
}


C++ layer call stack

在C/C++ 檔案, Android 已經有寫了frameworks/native/libs/utils/CallStack.cpp 供我們使用


#include <utils/CallStack.h>
...
void foo(void) {
...
   android::CallStack stack;
   stack.update();
   stack.dump("XXX");

...
}


如果你所使用是Android 4.4 之後
請改用


#include <utils/CallStack.h>
...
void foo(void) {
...
   android::CallStack stack;
   stack.update( );
   stack.log("XXX");

...
}

在Android.mk 記得要加


LOCAL_SHARED_LIBRARIES += libutils


在 Android Pie 之後, CallStack 是屬於 libutilscallstack.so, 所以

在 Android.mk 要改成

LOCAL_SHARED_LIBRARIES += libutilscallstack

或是在 Android.db 中

 shared_libs: [
      ...
        "libutilscallstack",

    ],

C layer call stack

由於C去call C++需要做一些宣告, 所以將它獨立出來方便使用(dump_stack.cpp與 dump_stack.h)


dump_stack.h

#ifdef __cplusplus
extern "C" {
#endif

 void dump_stack_android(void);
 
#ifdef __cplusplus
}
#endif


dump_stack.cpp


#include "dump_stack.h"
#include <utils/CallStack.h>

using namespace android;
extern "C"{
 void dump_stack_android(void)
 {
CallStack stack;
stack.update();
stack.dump("XXX");
 }
}


如果你所使用是Android 4.4 之後
請改用


#include "dump_stack.h"
#include <utils/CallStack.h>

using namespace android;
extern "C"{
 void dump_stack_android(void)
 {
CallStack stack;
stack.update();
stack.log("XXX");
 }
}


同樣地, Android.mk也需要修改


LOCAL_SRC_FILES := \
        …... \
        dump_stack.cpp

LOCAL_SHARED_LIBRARIES += libutils

# for Android Pie
LOCAL_SHARED_LIBRARIES += libutilscallstack


接下來在C file中要使用時只要


extern void dump_stack_android();


void function_a()
{
 …
 dump_stack_android();
 …
}


[ Call Stack 沒有出現 function name]
有時我們會發現在C++ 或 C 語言中使用 CallStack , 在 call dump 中並沒有出現 function name


D/XXX (  147): #00  pc 00001b90  /system/lib/hw/audio.primary.mrvl.so (dump_stack_android+19)
D/XXX (  147): #01  pc 00004b56  /system/lib/hw/audio.primary.mrvl.so
D/XXX (  147): #02  pc 0001f828  /system/lib/libaudioflinger.so
D/XXX (  147): #03  pc 00019138  /system/lib/libaudioflinger.so
D/XXX (  147): #04  pc 00023bb6  /system/lib/libaudioflinger.so
D/XXX (  147): #05  pc 0000e9fe  /system/lib/libutils.so (android::Thread::_threadLoop(void*)+213)
D/XXX (  147): #06  pc 0000e530  /system/lib/libutils.so
D/XXX (  147): #07  pc 0000d208  /system/lib/libc.so (__thread_entry+72)
D/XXX (  147): #08  pc 0000d3a4  /system/lib/libc.so (pthread_create+240)


我們追一下 CallStack 是如何被實作
先回顧一下 CallStack 是如何被使用 (以 Android 4.4 為例)
 CallStack stack;  
 stack.update();  
 stack.log();  

先看一下 update( ) function 的定義 ( it is under system/core/include/utils/CallStack.h)
   // Immediately collect the stack traces for the specified thread.  
   void update(int32_t ignoreDepth=1, int32_t maxDepth=MAX_DEPTH, pid_t tid=CURRENT_THREAD);  
所以透過 update( ) function, 我們可以設定想看哪一個 thread 並 dump 出多少層的 call stack, 如果都沒寫, 就是以當前的 thread 去做 call stack dump, update( ) function 會將實際可以 dump 多少的 frame 給抓出來, 其中 frame 的數量記錄在 mCount 變數, 各 frame 的資訊則記錄在 mStack[ ] 裡面, 接下來再透過 log( ) function 把 call stack 裡的 program counter 所記載的記憶體位址去把相對應的 function name 給解析出來.

 log( )  
 |--> print( )  
    |--> get_backtrace_symbols( )  

看一下 get_backtrace_symbols( ) 在做些什麼
void get_backtrace_symbols(const backtrace_frame_t* backtrace, size_t frames,
    backtrace_symbol_t* backtrace_symbols) {

   ... 
for (size_t i = 0; i < frames; i++) {
       ...
           Dl_info info;
           if (dladdr((const void*)frame->absolute_pc, &info) && info.dli_sname) {
            symbol->relative_symbol_addr = (uintptr_t)info.dli_saddr
                    - (uintptr_t)info.dli_fbase;
            symbol->symbol_name = strdup(info.dli_sname);
            symbol->demangled_name =
                       demangle_symbol_name(symbol->symbol_name);
           }
      ...
}
release_my_map_info_list(milist);
}


這是因為它是使用 dladdr() 去讀取該share lib的 dynamic symbol 而獲取 function name

但是如果該 function 是宣告成 static, 該 function name 就不會出現在 dynamic symbol 裡 (你可以使用 arm-linux-androideabi-nm -D xxxx.so | grep the_function_name , 如果沒有出現, 就表示該 funciton name 並不在 dynamic symbol 裡),  遇到這情況就只好使用 add2line 指令去讀 out folder 下的 symbol 了, 各位可以參考我另一篇文章 http://janbarry0914.blogspot.tw/2011/07/android-crash-tombstone.html . 感謝.