当前位置: 首页 > 编程日记 > 正文

ALSA声卡驱动中的DAPM详解之四:在驱动程序中初始化并注册widget和route

前几篇文章我们从dapm的数据结构入手,了解了代表音频控件的widget,代表连接路径的route以及用于连接两个widget的path。之前都是一些概念的讲解以及对数据结构中各个字段的说明,从本章开始,我们要从代码入手,分析dapm的详细工作原理:

  • 如何注册widget
  • 如何连接两个widget
  • 一个widget的状态裱画如何传递到整个音频路径中
/*****************************************************************************************************/
声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢!
/*****************************************************************************************************/

dapm context


在讨论widget的注册之前,我们先了解另一个概念:dapm context,直译过来的意思是dapm上下文,这个好像不好理解,其实我们可以这么理解:dapm把整个音频系统,按照功能和偏置电压级别,划分为若干个电源域,每个域包含各自的widget,每个域中的所有widget通常都处于同一个偏置电压级别上,而一个电源域就是一个dapm context,通常会有以下几种dapm context:
  • 属于codec中的widget位于一个dapm context中
  • 属于platform的widget位于一个dapm context中
  • 属于整个声卡的widget位于一个dapm context中
对于音频系统的硬件来说,通常要提供合适的偏置电压才能正常地工作,有了dapm context这种组织方式,我们可以方便地对同一组widget进行统一的偏置电压管理,ASoc用snd_soc_dapm_context结构来表示一个dapm context:
[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. struct snd_soc_dapm_context {  
  2. enum snd_soc_bias_level bias_level;  
  3. enum snd_soc_bias_level suspend_bias_level;  
  4. struct delayed_work delayed_work;  
  5. unsigned int idle_bias_off:1; /* Use BIAS_OFF instead of STANDBY */  
  6. struct snd_soc_dapm_update *update;  
  7. void (*seq_notifier)(struct snd_soc_dapm_context *,  
  8. enum snd_soc_dapm_type, int);  
  9. struct device *dev; /* from parent - for debug */  
  10. struct snd_soc_codec *codec; /* parent codec */  
  11. struct snd_soc_platform *platform; /* parent platform */  
  12. struct snd_soc_card *card; /* parent card */  
  13. /* used during DAPM updates */  
  14. enum snd_soc_bias_level target_bias_level;  
  15. struct list_head list;  
  16. int (*stream_event)(struct snd_soc_dapm_context *dapm, int event);  
  17. #ifdef CONFIG_DEBUG_FS  
  18. struct dentry *debugfs_dapm;  
  19. #endif  
  20. };
snd_soc_bias_level的取值范围是以下几种:
  • SND_SOC_BIAS_OFF
  • SND_SOC_BIAS_STANDBY
  • SND_SOC_BIAS_PREPARE
  • SND_SOC_BIAS_ON
snd_soc_dapm_context被内嵌到代表codec、platform、card、dai的结构体中:
[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. struct snd_soc_codec {  
  2. ......
  3. /* dapm */  
  4. struct snd_soc_dapm_context dapm;  
  5. ......
  6. };
  7. struct snd_soc_platform {  
  8. ......
  9. /* dapm */  
  10. struct snd_soc_dapm_context dapm;  
  11. ......
  12. };
  13. struct snd_soc_card {  
  14. ......
  15. /* dapm */  
  16. struct snd_soc_dapm_context dapm;  
  17. ......
  18. };
  19. :
  20. struct snd_soc_dai {  
  21. ......
  22. /* dapm */  
  23. struct snd_soc_dapm_widget *playback_widget;  
  24. struct snd_soc_dapm_widget *capture_widget;  
  25. struct snd_soc_dapm_context dapm;  
  26. ......
  27. };
代表widget结构snd_soc_dapm_widget中,有一个snd_soc_dapm_context结构指针,指向所属的codec、platform、card、或dai的dapm结构。同时,所有的dapm结构,通过它的list字段,链接到代表声卡的snd_soc_card结构的dapm_list链表头字段。

创建和注册widget


我们已经知道,一个widget用snd_soc_dapm_widget结构体来描述,通常,我们会根据音频硬件的组成,分别在声卡的codec驱动、platform驱动和machine驱动中定义一组widget,这些widget用数组进行组织,我们一般会使用dapm框架提供的大量的辅助宏来定义这些widget数组,辅助宏的说明请参考前一偏文章:ALSA声卡驱动中的DAPM详解之三:如何定义各种widget。

codec驱动中注册    我们知道,我们会通过ASoc提供的api函数snd_soc_register_codec来注册一个codec驱动,该函数的第二个参数是一个snd_soc_codec_driver结构指针,这个snd_soc_codec_driver结构需要我们在codec驱动中显式地进行定义,其中有几个与dapm框架有关的字段:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. struct snd_soc_codec_driver {  
  2. ......
  3. /* Default control and setup, added after probe() is run */  
  4. const struct snd_kcontrol_new *controls;  
  5. int num_controls;  
  6. const struct snd_soc_dapm_widget *dapm_widgets;  
  7. int num_dapm_widgets;  
  8. const struct snd_soc_dapm_route *dapm_routes;  
  9. int num_dapm_routes;  
  10. ......
  11. }

我们只要把我们定义好的snd_soc_dapm_widget结构数组的地址和widget的数量赋值到dapm_widgets和num_dapm_widgets字段即可,这样,经过snd_soc_register_codec注册codec后,在machine驱动匹配上该codec时,系统会判断这两个字段是否被赋值,如果有,它会调佣dapm框架提供的api来创建和注册widget,注意这里我说还要创建这个词,你可能比较奇怪,既然代表widget的snd_soc_dapm_widget结构数组已经在codec驱动中定义好了,为什么还要在创建?事实上,我们在codec驱动中定义的widget数组只是作为一个模板,dapm框架会根据该模板重新申请内存并初始化各个widget。我们看看实际的例子可能是这样的:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. static const struct snd_soc_dapm_widget wm8993_dapm_widgets[] = {  
  2. ......
  3. SND_SOC_DAPM_SUPPLY("VMID", SND_SOC_NOPM, 0, 0, NULL, 0),  
  4. SND_SOC_DAPM_AIF_IN("AIFINL", "Playback", 0, SND_SOC_NOPM, 0, 0),  
  5. SND_SOC_DAPM_AIF_IN("AIFINR", "Playback", 1, SND_SOC_NOPM, 0, 0),  
  6. ......
  7. };
  8. static struct snd_soc_codec_driver soc_codec_dev_wm8993 = {  
  9. .probe =        codec_xxx_probe,
  10. ......
  11. .dapm_widgets =       &wm8993_dapm_widgets[0],
  12. .num_dapm_widgets =      ARRAY_SIZE(wm8993_dapm_widgets),
  13. ......
  14. };
  15. static int codec_wm8993_i2c_probe(struct i2c_client *i2c,  
  16. const struct i2c_device_id *id)  
  17. {
  18. ......
  19. ret = snd_soc_register_codec(&i2c->dev,
  20. &soc_codec_dev_wm8993, &wm8993_dai, 1);
  21. ......
  22. }

上面这种注册方法有个缺点,有时候我们为了代码的清晰,可能会根据功能把不同的widget定义成多个数组,但是snd_soc_codec_driver中只有一个dapm_widgets字段,无法设定多个widget数组,这时候,我们需要主动在codec的probe回调中调用dapm框架提供的api来创建这些widget:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. static int wm8993_probe(struct snd_soc_codec *codec)  
  2. {
  3. ......
  4. snd_soc_dapm_new_controls(dapm, wm8993_dapm_widgets,
  5. ARRAY_SIZE(wm8993_dapm_widgets));
  6. ......
  7. }

实际上,对于第一种方法,snd_soc_register_codec内部其实也是调用snd_soc_dapm_new_controls来完成的。后面会有关于这个函数的详细分析。

platform驱动中注册    和codec驱动一样,我们会通过ASoc提供的api函数snd_soc_register_platform来注册一个platform驱动,该函数的第二个参数是一个snd_soc_platform_driver结构指针,snd_soc_platform_driver结构中同样也包含了与dapm相关的字段:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. struct snd_soc_platform_driver {  
  2. ......
  3. /* Default control and setup, added after probe() is run */  
  4. const struct snd_kcontrol_new *controls;  
  5. int num_controls;  
  6. const struct snd_soc_dapm_widget *dapm_widgets;  
  7. int num_dapm_widgets;  
  8. const struct snd_soc_dapm_route *dapm_routes;  
  9. int num_dapm_routes;  
  10. ......
  11. }

要注册platform级别的widget,和codec驱动一样,只要把定义好的widget数组赋值给dapm_widgets和num_dapm_widgets字段即可,snd_soc_register_platform函数注册paltform后,当machine驱动匹配上该platform时,系统会自动完成创建和注册的工作。同理,我们也可以在platform驱动的probe回调函数中主动使用snd_soc_dapm_new_controls来完成widget的创建工作。具体的代码和codec驱动是类似的,这里就不贴了。

machine驱动中注册    有些widget可能不是位于codec中,例如一个独立的耳机放大器,或者是喇叭功放等,这种widget通常需要在machine驱动中注册,通常他们的dapm context也从属于声卡(snd_soc_card)域。做法依然和codec驱动类似,通过代表声卡的snd_soc_card结构中的几个dapm字段完成:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. struct snd_soc_card {  
  2. ......
  3. /* 
  4.          * Card-specific routes and widgets. 
  5.          */  
  6. const struct snd_soc_dapm_widget *dapm_widgets;  
  7. int num_dapm_widgets;  
  8. const struct snd_soc_dapm_route *dapm_routes;  
  9. int num_dapm_routes;  
  10. bool fully_routed;  
  11. ......
  12. }

只要把定义好的widget数组和数量赋值给dapm_widgets指针和num_dapm_widgets即可,注册声卡使用的api:snd_soc_register_card(),也会通过snd_soc_dapm_new_controls来完成widget的创建工作。

注册音频路径


系统中注册的各种widget需要互相连接在一起才能协调工作,连接关系通过snd_soc_dapm_route结构来定义,关于如何用snd_soc_dapm_route结构来定义路径信息,请参考:ALSA声卡驱动中的DAPM详解之三:如何定义各种widget中的"建立widget和route"一节的内容。通常,所有的路径信息会用一个snd_soc_dapm_route结构数组来定义。和widget一样,路径信息也分别存在与codec驱动,machine驱动和platform驱动中,我们一样有两种方式来注册音频路径信息:
  • 通过snd_soc_codec_driver/snd_soc_platform_driver/snd_soc_card结构中的dapm_routes和num_dapm_routes字段;
  • 在codec、platform的的probe回调中主动注册音频路径,machine驱动中则通过snd_soc_dai_link结构的init回调函数来注册音频路径;
两种方法最终都是通过调用snd_soc_dapm_add_routes函数来完成音频路径的注册工作的。以下的代码片段是omap的pandora板子的machine驱动,使用第二种方法注册路径信息:
[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. static const struct snd_soc_dapm_widget omap3pandora_in_dapm_widgets[] = {  
  2. SND_SOC_DAPM_MIC("Mic (internal)", NULL),  
  3. SND_SOC_DAPM_MIC("Mic (external)", NULL),  
  4. SND_SOC_DAPM_LINE("Line In", NULL),  
  5. };
  6. static const struct snd_soc_dapm_route omap3pandora_out_map[] = {  
  7. {"PCM DAC", NULL, "APLL Enable"},  
  8. {"Headphone Amplifier", NULL, "PCM DAC"},  
  9. {"Line Out", NULL, "PCM DAC"},  
  10. {"Headphone Jack", NULL, "Headphone Amplifier"},  
  11. };
  12. static const struct snd_soc_dapm_route omap3pandora_in_map[] = {  
  13. {"AUXL", NULL, "Line In"},  
  14. {"AUXR", NULL, "Line In"},  
  15. {"MAINMIC", NULL, "Mic (internal)"},  
  16. {"Mic (internal)", NULL, "Mic Bias 1"},  
  17. {"SUBMIC", NULL, "Mic (external)"},  
  18. {"Mic (external)", NULL, "Mic Bias 2"},  
  19. };
  20. static int omap3pandora_out_init(struct snd_soc_pcm_runtime *rtd)  
  21. {
  22. struct snd_soc_codec *codec = rtd->codec;  
  23. struct snd_soc_dapm_context *dapm = &codec->dapm;  
  24. int ret;  
  25. /* All TWL4030 output pins are floating */  
  26. snd_soc_dapm_nc_pin(dapm, "EARPIECE");  
  27. ......
  28. //注册kcontrol控件  
  29. ret = snd_soc_dapm_new_controls(dapm, omap3pandora_out_dapm_widgets,
  30. ARRAY_SIZE(omap3pandora_out_dapm_widgets));
  31. if (ret < 0)  
  32. return ret;  
  33. //注册machine的音频路径  
  34. return snd_soc_dapm_add_routes(dapm, omap3pandora_out_map,  
  35. ARRAY_SIZE(omap3pandora_out_map));
  36. }
  37. static int omap3pandora_in_init(struct snd_soc_pcm_runtime *rtd)  
  38. {
  39. struct snd_soc_codec *codec = rtd->codec;  
  40. struct snd_soc_dapm_context *dapm = &codec->dapm;  
  41. int ret;  
  42. /* Not comnnected */  
  43. snd_soc_dapm_nc_pin(dapm, "HSMIC");  
  44. ......
  45. //注册kcontrol控件  
  46. ret = snd_soc_dapm_new_controls(dapm, omap3pandora_in_dapm_widgets,
  47. ARRAY_SIZE(omap3pandora_in_dapm_widgets));
  48. if (ret < 0)  
  49. return ret;  
  50. //注册machine音频路径  
  51. return snd_soc_dapm_add_routes(dapm, omap3pandora_in_map,  
  52. ARRAY_SIZE(omap3pandora_in_map));
  53. }
  54. /* Digital audio interface glue - connects codec <--> CPU */  
  55. static struct snd_soc_dai_link omap3pandora_dai[] = {  
  56. {
  57. .name = "PCM1773",  
  58. ......
  59. .init = omap3pandora_out_init,
  60. }, {
  61. .name = "TWL4030",  
  62. .stream_name = "Line/Mic In",  
  63. ......
  64. .init = omap3pandora_in_init,
  65. }
  66. };


dai widget


上面几节的内容介绍了codec、platform以及machine级别的widget和route的注册方法,在dapm框架中,还有另外一种widget,它代表了一个dai(数字音频接口),关于dai的描述,请参考:Linux ALSA声卡驱动之七:ASoC架构中的Codec。dai按所在的位置,又分为cpu dai和codec dai,在硬件上,通常一个cpu dai会连接一个codec dai,而在machine驱动中,我们要在snd_soc_card结构中指定一个叫做snd_soc_dai_link的结构,该结构定义了声卡使用哪一个cpu dai和codec dai进行连接。在Asoc中,一个dai用snd_soc_dai结构来表述,其中有几个字段和dapm框架有关:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. struct snd_soc_dai {  
  2. ......
  3. struct snd_soc_dapm_widget *playback_widget;  
  4. struct snd_soc_dapm_widget *capture_widget;  
  5. struct snd_soc_dapm_context dapm;  
  6. ......
  7. }
dai由codec驱动和平台代码中的iis或pcm接口驱动注册,machine驱动负责找到snd_soc_dai_link中指定的一对cpu/codec dai,并把它们进行绑定。不管是cpu dai还是codec dai,通常会同时传输播放和录音的音频流的能力,所以我们可以看到,snd_soc_dai中有两个widget指针,分别代表播放流和录音流。这两个dai widget是何时创建的呢?下面我们逐一进行分析。

codec dai widget

首先,codec驱动在注册codec时,会传入该codec所支持的dai个数和记录dai信息的snd_soc_dai_driver结构指针:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. static struct snd_soc_dai_driver wm8993_dai = {  
  2. .name = "wm8993-hifi",  
  3. .playback = {
  4. .stream_name = "Playback",  
  5. .channels_min = 1,
  6. .channels_max = 2,
  7. .rates = WM8993_RATES,
  8. .formats = WM8993_FORMATS,
  9. .sig_bits = 24,
  10. },
  11. .capture = {
  12. .stream_name = "Capture",  
  13. .channels_min = 1,
  14. .channels_max = 2,
  15. .rates = WM8993_RATES,
  16. .formats = WM8993_FORMATS,
  17. .sig_bits = 24,
  18. },
  19. .ops = &wm8993_ops,
  20. .symmetric_rates = 1,
  21. };
  22. static int wm8993_i2c_probe(struct i2c_client *i2c,  
  23. const struct i2c_device_id *id)  
  24. {
  25. ......
  26. ret = snd_soc_register_codec(&i2c->dev,
  27. &soc_codec_dev_wm8993, &wm8993_dai, 1);
  28. ......
  29. }
这回使得ASoc把codec的dai注册到系统中,并把这些dai都挂在全局链表变量dai_list中,然后,在codec被machine驱动匹配后,soc_probe_codec函数会被调用,他会通过全局链表变量dai_list查找所有属于该codec的dai,调用snd_soc_dapm_new_dai_widgets函数来生成该dai的播放流widget和录音流widget:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. static int soc_probe_codec(struct snd_soc_card *card,  
  2. struct snd_soc_codec *codec)  
  3. {
  4. ......
  5. /* Create DAPM widgets for each DAI stream */  
  6. list_for_each_entry(dai, &dai_list, list) {
  7. if (dai->dev != codec->dev)  
  8. continue;  
  9. snd_soc_dapm_new_dai_widgets(&codec->dapm, dai);
  10. }
  11. ......
  12. }
我们看看snd_soc_dapm_new_dai_widgets的代码:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. int snd_soc_dapm_new_dai_widgets(struct snd_soc_dapm_context *dapm,  
  2. struct snd_soc_dai *dai)  
  3. {
  4. struct snd_soc_dapm_widget template;  
  5. struct snd_soc_dapm_widget *w;  
  6. WARN_ON(dapm->dev != dai->dev);
  7. memset(&template, 0, sizeof(template));  
  8. template.reg = SND_SOC_NOPM;  
  9. // 创建播放 dai widget                  
  10. if (dai->driver->playback.stream_name) {  
  11. template.id = snd_soc_dapm_dai_in;  
  12. template.name = dai->driver->playback.stream_name;  
  13. template.sname = dai->driver->playback.stream_name;  
  14. w = snd_soc_dapm_new_control(dapm, &template);  
  15. w->priv = dai;
  16. dai->playback_widget = w;
  17. }
  18. // 创建录音 dai widget  
  19. if (dai->driver->capture.stream_name) {  
  20. template.id = snd_soc_dapm_dai_out;  
  21. template.name = dai->driver->capture.stream_name;  
  22. template.sname = dai->driver->capture.stream_name;  
  23. w = snd_soc_dapm_new_control(dapm, &template);  
  24. w->priv = dai;
  25. dai->capture_widget = w;
  26. }
  27. return 0;  
  28. }
分别为Playback和Capture创建了一个widget,widget的priv字段指向了该dai,这样通过widget就可以找到相应的dai,并且widget的名字就是snd_soc_dai_driver结构的stream_name。

cpu dai widget    
这里顺便说一个小意外,昨天晚上手贱,执行了一下git pull,版本升级到了3.12 rc7,结果发现ASoc的代码有所变化,于是稍稍纠结了一下,用新的代码继续还是恢复之前的3.10 rc5?经过查看了一些变化后,发现还是新的版本改进得更合理,现在决定,后面的内容都是基于3.12 rc7了。如果大家发现后面贴的代码和之前贴的有差异的地方,自己比较一下这两个版本的代码吧!
回到cpu dai,以前的内核版本由驱动通过snd_soc_register_dais注册,新的版本中,这个函数变为了soc-core的内部函数,驱动改为使用snd_soc_register_component注册,snd_soc_register_component函数再通过调用snd_soc_register_dai/snd_soc_register_dais来完成实际的注册工作。和codec dai widget一样,cpu dai widget也发生在machine驱动匹配上相应的platform驱动之后,soc_probe_platform会被调用,在soc_probe_platform函数中,通过比较dai->dev和platform->dev,挑选出属于该platform的dai,然后通过snd_soc_dapm_new_dai_widgets为cpu dai创建相应的widget:
[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. static int soc_probe_platform(struct snd_soc_card *card,  
  2. struct snd_soc_platform *platform)  
  3. {
  4. int ret = 0;  
  5. const struct snd_soc_platform_driver *driver = platform->driver;  
  6. struct snd_soc_dai *dai;  
  7. ......
  8. if (driver->dapm_widgets)  
  9. snd_soc_dapm_new_controls(&platform->dapm,
  10. driver->dapm_widgets, driver->num_dapm_widgets);
  11. /* Create DAPM widgets for each DAI stream */  
  12. list_for_each_entry(dai, &dai_list, list) {
  13. if (dai->dev != platform->dev)  
  14. continue;  
  15. snd_soc_dapm_new_dai_widgets(&platform->dapm, dai);
  16. }
  17. platform->dapm.idle_bias_off = 1;
  18. ......
  19. if (driver->controls)  
  20. snd_soc_add_platform_controls(platform, driver->controls,
  21. driver->num_controls);
  22. if (driver->dapm_routes)  
  23. snd_soc_dapm_add_routes(&platform->dapm, driver->dapm_routes,
  24. driver->num_dapm_routes);
  25. ......
  26. return 0;  
  27. }
从上面的代码我们也可以看出,在上面的”创建和注册widget“一节提到的第一种方法,即通过给snd_soc_platform_driver结构的dapm_widgets和num_dapm_widgets字段赋值,ASoc会自动为我们创建所需的widget,真正执行创建工作就在上面所列的soc_probe_platform函数中完成的,普通的kcontrol和音频路径也是一样的原理。反推回来,codec的widget也是一样的,在soc_probe_codec中会做同样的事情,只是我上面贴出来soc_probe_codec的代码里没有贴出来,有兴趣的读者自己查看一下它的代码即可。
花了这么多篇幅来讲解dai widget,好像现在看来它还没有什么用处。嗯,不要着急,实际上dai widget是一条完整dapm音频路径的重要元素,没有她,我们无法完成dapm的动态电源管理工作,因为它是音频流和其他widget的纽带,细节我们要留到下一篇文章中来阐述了。

端点widget


一条完整的dapm音频路径,必然有起点和终点,我们把位于这些起点和终点的widget称之为端点widget。以下这些类型的widget可以成为端点widget:

codec的输入输出引脚

  • snd_soc_dapm_output
  • snd_soc_dapm_input
外接的音频设备
  • snd_soc_dapm_hp
  • snd_soc_dapm_spk
  • snd_soc_dapm_line
音频流(stream domain)
  • snd_soc_dapm_adc
  • snd_soc_dapm_dac
  • snd_soc_dapm_aif_out
  • snd_soc_dapm_aif_in
  • snd_soc_dapm_dai_out
  • snd_soc_dapm_dai_in
电源、时钟和其它
  • snd_soc_dapm_supply
  • snd_soc_dapm_regulator_supply
  • snd_soc_dapm_clock_supply
  • snd_soc_dapm_kcontrol

当声卡上的其中一个widget的状态发生改变时,从这个widget开始,dapm框架会向前和向后遍历路径上的所有widget,判断每个widget的状态是否需要跟着变更,到达这些端点widget就会认为它是一条完整音频路径的开始和结束,从而结束一次扫描动作。至于代码的分析,先让我歇一会......,我会在后面的文章中讨论。

相关文章:

双线性插值实现

#include <opencv2/imgproc/imgproc.hpp> #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <iostream> // 实现双线性插值图像缩放 cv::Mat BilinearInterpolation(cv::Mat srcImage) {CV_Assert(srcI…

【C++】C++ 强制转换运算符

C 运算符 强制转换运算符是一种特殊的运算符&#xff0c;它把一种数据类型转换为另一种数据类型。强制转换运算符是一元运算符&#xff0c;它的优先级与其他一元运算符相同。 大多数的 C 编译器都支持大部分通用的强制转换运算符&#xff1a; (type) expression 其中&…

WPS 2019 更新版(8392)发布,搭配优麒麟 19.04 运行更奇妙!

WPS 2019 支持全新的外观界面、目录更新、方框打勾、智能填充、内置浏览器、窗口拆组、个人中心等功能。特别是全新的新建页面&#xff0c;让你可以整合最近打开的文档、本地模版、公文模版、在线模板等。 随着优麒麟即将发布的新版 19.04 的到来&#xff0c;金山办公软件也带来…

图像金字塔操作,上采样、下采样、缩放

#include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" #include <iostream> // 图像金子塔采样操作 void Pyramid(cv::Mat srcImage) {// 根据图像源尺寸判断是否需要缩放if(srcImage.rows > 400 && srcImage…

【C++】【OpenCv】图片加噪声处理,计时,及键盘事件响应捕捉

图像噪声添加 cv::Mat addGuassianNoise(cv::Mat& src, double a, double b) {cv::Mat temp src.clone();cv::Mat dst(src.size(), src.type()); ​// Construct a matrix containing Gaussian noisecv::Mat noise(temp.size(), temp.type());cv::RNG rng(time(NULL));//…

透视学理论(十四)

2019独角兽企业重金招聘Python工程师标准>>> 已知左右距点分别位于透视画面两侧&#xff0c;因此我们可以左距点来得出透视画面的纵深长度。 距点&#xff0c;指的是与透视画面成45的水平线的消失点。我们把画面边缘按1米的宽度&#xff08;A点&#xff09;&#xf…

适用于Mac上的SQL Server

适用于Mac上的SQL Server&#xff1f; 众所周知&#xff0c;很多人都在电脑上安装了SQL Server软件&#xff0c;普通用户直接去官网下载安装即可&#xff0c;Mac用户则该如何在Mac电脑上安装SQL Server呢&#xff1f;想要一款适用于Mac上的SQL Server&#xff1f;点击进入&…

【MATLAB】————拷贝指定文件路径下的有序文件(选择后),可处理固定规律的文件图片数据或者文件

总体上来说这种数据有2中处理思路。第一种如下所示&#xff0c;从一组数据中挑选出符合要求的数据&#xff1b; 第二中就是数据中不需要的数据删除&#xff0c;选择处理不需要的数据&#xff0c;留下的补集就是需要的数库。一般情况下需要看问题是否明确&#xff0c;需求明确的…

mouseover与mouseenter,mouseout与mouseleave的区别

mouseover与mouseenter 不论鼠标指针穿过被选元素或其子元素&#xff0c;都会触发 mouseover 事件。只有在鼠标指针穿过被选元素时&#xff0c;才会触发 mouseenter 事件。 mouseout与mouseleave不论鼠标指针离开被选元素还是任何子元素&#xff0c;都会触发 mouseout 事件。只…

图像掩码操作的两种实现

#include <opencv2/imgproc/imgproc.hpp> #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <iostream> #include <stdio.h> using namespace cv; using namespace std; // 基于像素邻域掩码操作…

控制反转 IOC

2019独角兽企业重金招聘Python工程师标准>>> 控制反转&#xff08;Inversion of Control&#xff0c;缩写为IoC&#xff09;面向对象设计原则&#xff0c;降低代码耦合度 依赖注入&#xff08;Dependency Injection&#xff0c;简称DI&#xff09; 依赖查找&#xf…

【C++】explicit关键字

explicit的优点是可以避免不合时宜的类型变换&#xff0c;缺点无。所以google约定所有单参数的构造函数都必须是显式的** explicit关键字只需用于类内的单参数构造函数前面。由于无参数的构造函数和多参数的构造函数总是显式调用&#xff0c;这种情况在构造函数前加explicit无…

mongodb3 分片集群平滑迁移

分片集群平滑迁移实验(成功)过程概述&#xff1a;为每个分片添加多个从节点&#xff0c;然后自动同步。同步完后&#xff0c;切换主节点到新服务器节点。导出原来的config 数据库&#xff0c;并导入到新服务器的config数据库停掉整个集群&#xff0c;可以使用kill 命令停掉新服…

图像添加椒盐噪声

#include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <cstdlib> // 图像添加椒盐噪声 cv::Mat addSaltNoise(const cv::Mat srcImage, int n) {cv::Mat resultIamge srcImage.clone() ;for(int k0; k<n; k){// 随机取值行…

我用python10年后,我发现学python必看这三本书!

非常喜欢python 我非常喜欢python&#xff0c;在前面5年里&#xff0c;它一直是我热衷使用并不断研究的语言&#xff0c;迄今为止&#xff0c;python都非常友好并且易于学习&#xff01; 它几乎可以做任何事&#xff0c;从简单的脚本创建、web&#xff0c;到数据可视化以及AI人…

【OpenCV】内核的形状函数使用记录

opencv getStructuringElement函数 为形态操作返回指定大小和形状的结构化元素。 该函数构造并返回结构化元素&#xff0c;这些元素可以进一步传递给侵蚀、扩张或morphologyEx。但是您也可以自己构造一个任意的二进制掩码&#xff0c;并将它用作结构化元素。 getStructuringE…

boxFilter 滤波器实现

cv::Ptr<cv::FilterEngine> cv::createBoxFilter( int srcType, int dstType, Size ksize,Point anchor, bool normalize, int borderType ) {// 基础参数设置 图像深度int sdepth CV_MAT_DEPTH(srcType);int cn CV_MAT_CN(srcType), sumType CV_64F;if( sdepth < …

git代理设置

2019独角兽企业重金招聘Python工程师标准>>> git config --global http.proxy http://127.0.0.1:1080 git config --global https.proxy https://127.0.0.1:1080 git config --global http.sslVerify false删除 git config --global --unset http.proxy git config …

获得PMP证书的这一年

很荣幸&#xff0c;通过2018年12月的PMP考试&#xff0c;这不仅是一张证书的收获&#xff0c;更体现了我的成长&#xff0c;明确了以后的道路。在考证的过程中&#xff0c;我收获了很多&#xff0c;不仅是工作技能方面&#xff0c;还包括思想的升华。  首先&#xff0c;重拾了…

【OpenCV】图片操作小结:RAW图转image以及image连续保存

opencv将RAW图转image uint32_t ReadRawImage(cv::Mat& image,const std::string& path,int width,int height,int cv_image_type) {cv::Mat input_image(height, width, cv_image_type);std::ifstream in(path, std::ios::in | std::ios::binary);if (!in) {std::cou…

Windows server2008服务器设置多用户登录

添加用户 右击我的电脑-->管理-->本地用户和组-->新用户 启用远程服务并添加远程用户 启用 右键我的电脑--->属性--->远程设置--->勾上允许远程连接到此电脑 添加用户 点击选择用户--->添加--->高级--->立即查找 防火墙允许远程连接设置 控制面板(c…

Linux6版本系统搭建Open***远程访问

前言&#xff1a;open***是一个***工具,用于创建虚拟专用网络(Virtual Private Network)加密通道的免费开源软件,提供证书验证功能,也支持用户名密码认证登录方式,当然也支持两者合一,为服务器登录和连接提供更加安全的方式,可以在不同网络访问场所之间搭建类似于局域网的专用网…

【Smart_Point】unique_ptr与shared_ptr使用实例

shared_ptr使用实例 文章目录shared_ptr使用实例unique_ptr使用实例cv::fitLine中斜率为正无穷的情况&#xff0c;需要特殊考虑std::string path "D:\\code\\test_base_opencv\\example\\depth_98.raw";std::string save_path "D:\\code\\test_base_opencv\\e…

关于kNN、kMeans、Apriori算法小结

趁着准备即将到来的笔试&#xff0c;也为了回顾一下这一星期来所学的三个机器学习算法&#xff0c;觉得还是重新理一下思路&#xff0c;好理解一下这几个算法。 复制代码 kNN算法 即k-近邻算法&#xff0c;属监督学习。 概述 优点&#xff1a;精度高&#xff0c;对异常值不敏感…

[PHP] Phalcon操作示范

这篇内容将对下列操作进行示范&#xff1a; Insert、Select、Update、Calculation、Transaction、models advanced、dev-tools、cookies [ Insert ] &#xff08;1&#xff09; // 模型内操作&#xff0c;data是[字段>值]的一维数组。$bool $this->save($data);return $…

【C++】lambda 表达式

1.lambda 表达式 1.1 lambda 特点 lambda表示一个可调用单元&#xff0c;可视为内联函数 范式 : 具有一个返回类型&#xff0c;一个参数列表&#xff0c;一个函数体 [captrue list](parameters list)->return type {function body} captrue list 捕获列表是一个lambda所…

8位图像的双边滤波器实现

static void bilateralFilter_8u( const Mat& src, Mat& dst, int d,double sigma_color, double sigma_space,int borderType ) {// 获取原始图像信息int cn src.channels();int i, j, maxk, radius;Size size src.size();CV_Assert( (src.type() CV_8UC1 || src.t…

读取Cert格式证书的密钥

不想存储Cert证书内容&#xff0c;只想存储证书密钥&#xff0c;可通过以下2種方式实现 一、通過java读取证书的密钥出来&#xff1a; 1 package com.zat.ucop.service.util;2 3 import sun.misc.BASE64Encoder;4 5 import java.io.FileInputStream;6 import java.security.Pu…

图像导向滤波操作

#include <iostream> #include "opencv2/core/core.hpp" #include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" using namespace std; using namespace cv; // 导向滤波器 cv::Mat guid…

【C++】bind参数绑定 P354(通用的函数适配器)

1. 什么时候该使用bing &#xff1f;什么时候该使用lambda&#xff1f; 当只有少数地方调用时候使用lambda,当需要多次调用lambda时&#xff0c;需要定义一个函数&#xff0c;而不是多次编译相同的lambda表达式。 调用bind的一般形式为&#xff1a; auto newCallable bind(cal…