当前位置: 互联网>综合
本页文章导读:
▪Speex Acoustic Echo Cancellation (AEC) 回声消除模块的使用 背景:回声与啸叫的产生 http://blog.csdn.net/u011202336/article/details/9238397
参考资料: http://www.speex.org/docs/manual
从代码分析,下边是Speex test demo
#include <stdio.h>
#include <stdl.........
▪Acoustic Echo Cancellation (AEC) 回音消除技术探索
回声产生的原因: 本地产生的音频信息通过网络传输到远端, 远端音频信号通过反射再由远端麦克采集到远端系统,再通过IP网络传输本地,本地播放后,在由本地麦.........
▪笔试面试常考数据结构-单链表常用操作编程实现 单链表是笔试以及面试手写代码中常考的数据结构之一。下面实现了单链表的常见操作:创建单链表、删除节点、打印单链表(包括正向打印以及逆向打印)、反转单链表、找出单链表的倒数.........
[1]Speex Acoustic Echo Cancellation (AEC) 回声消除模块的使用
来源: 互联网 发布时间: 2013-10-26
背景:回声与啸叫的产生 http://blog.csdn.net/u011202336/article/details/9238397
参考资料: http://www.speex.org/docs/manual
从代码分析,下边是Speex test demo
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include "speex/speex_echo.h" #include "speex/speex_preprocess.h" #define NN 128 #define TAIL 1024 int main(int argc, char **argv) { FILE *echo_fd, *ref_fd, *e_fd; short echo_buf[NN], ref_buf[NN], e_buf[NN]; SpeexEchoState *st; SpeexPreprocessState *den; int sampleRate = 8000; if (argc != 4) { fprintf(stderr, "testecho mic_signal.sw speaker_signal.sw output.sw\n"); exit(1); } echo_fd = fopen(argv[2], "rb"); ref_fd = fopen(argv[1], "rb"); e_fd = fopen(argv[3], "wb"); // Step1: 初始化结构 st = speex_echo_state_init(NN, TAIL); den = speex_preprocess_state_init(NN, sampleRate); //Step2: 设置相关参数 speex_echo_ctl(st, SPEEX_ECHO_SET_SAMPLING_RATE, &sampleRate); speex_preprocess_ctl(den, SPEEX_PREPROCESS_SET_ECHO_STATE, st); while (!feof(ref_fd) && !feof(echo_fd)) { fread(ref_buf, sizeof(short), NN, ref_fd); fread(echo_buf, sizeof(short), NN, echo_fd); //Step3: 调用Api回声消除,ref_buf是麦克采集到的数据 // echo_buf:是从speaker处获取到的数据 // e_buf: 是回声消除后的数据 speex_echo_cancellation(st, ref_buf, echo_buf, e_buf); speex_preprocess_run(den, e_buf); fwrite(e_buf, sizeof(short), NN, e_fd); } //Step4: 销毁结构 释放资源 speex_echo_state_destroy(st); speex_preprocess_state_destroy(den); fclose(e_fd); fclose(echo_fd); fclose(ref_fd); return 0; }
Speex 源码中附带的这个例子,只适合于串行的链式媒体流,当媒体播放、媒体采集、媒体网络数据接口分属在不同现成时,就会存在同步问题,异步线程会导致信号延迟加大,回声消除收敛效果不好。其中Speex 回声消除必须按照建议的流程:
write_to_soundcard(echo_frame, frame_size); //播放音频数据,并从声卡获得播放的数据echo_frame. read_from_soundcard(input_frame, frame_size); //在数据播放后,从声卡麦克获取采集到的数据input_frame. speex_echo_cancellation(echo_state, input_frame, echo_frame, output_frame); //调用Api消除噪声,输入input_frame,echo_frame,输出out_frame
在典型的VOIP类型应用中:
echo_frame: 从RTP接收的数据包解码后,送入声卡播放,获取的数据。
input_frame: 本地麦克采集到的数据
output_frame: 回声消除后的数据,送入encodec,并构造rtp数据包,传输到远端。
典型的应用模式:
Thread A: 接收audio rtp -> decodec -----> sound card
|__> echo_frame queue
Thread B: 获取麦克数据input_frame --> speex_echo_cancellation( speex_state, input_frame, echo_frame,out_frame ) -> rtp packet -> network
也可以将rtp packet 与network 传输放到另外一个线程。
作者:u011202336 发表于2013-7-3 23:51:22 原文链接
阅读:21 评论:0 查看评论
[2]Acoustic Echo Cancellation (AEC) 回音消除技术探索
来源: 互联网 发布时间: 2013-10-26
回声产生的原因: 本地产生的音频信息通过网络传输到远端, 远端音频信号通过反射再由远端麦克采集到远端系统,再通过IP网络传输本地,本地播放后,在由本地麦克采集到,这就构成了类似闭环正反馈,当延时较小时,信号不断加强,就会导致啸叫现象产生,当延时较大,就会产生回音。
回音消除的基本原理是用等幅等频率但反相的信号与采集的信号相叠加,当通过网络或者物理反射获得音频信号与等频率反相的信号相叠加,就可以出去回声。问题的关键就是获取这个反相的信号,回声信号的产生主要由: 1.本地环境的反射 2.通过网络在远端播放后,音频反射后由远端麦克采集到并传输到本地扬声器外放后,再由本地麦克采集到,从而形成闭环正反馈。
本地物理反射: 声学信号在本地物理多路径反射,从信号上讲类似一个衰减延时的滤波器;
网络反射: 网络传输到远端,远端播放后,再进过远端物理反射后,再进入远端麦克后通过网络传输到本地扬声器,在进入本地麦克,在正反馈,也是一个衰减延时网络。
回声的产生由这两种因素产生,两种效果叠加后,就是一个衰减延时滤波器,我们需要利用本地播放音频信号与远端回传信号,来设计滤波器。回音消除的关键就是这个自适应滤波器的设计。
作者:u011202336 发表于2013-7-3 23:11:30 原文链接
阅读:16 评论:0 查看评论
[3]笔试面试常考数据结构-单链表常用操作编程实现
来源: 互联网 发布时间: 2013-10-26
单链表是笔试以及面试手写代码中常考的数据结构之一。下面实现了单链表的常见操作:创建单链表、删除节点、打印单链表(包括正向打印以及逆向打印)、反转单链表、找出单链表的倒数第K个节点、合并两个有序单链表等操作。
代码(C++):
//笔试面试单链表常用操作编程实现 #include <iostream> #include <stack> #include <cstdlib> using namespace std; //单链表节点数据结构定义 typedef struct link_node_s{ int m_val; struct link_node_s *next; }link_node_t,*link_list_t; //函数:创建单链表(头插法) link_list_t create_linklist(int *a,int n); //函数:打印单链表(从头到尾) void print_linklist(link_list_t head); //函数:打印单链表(从尾到头) void print_linklist_reverse(link_list_t head); //函数:新建链表节点 link_list_t creart_linknode(int val); //函数:删除链表中的某一个节点(前提条件:该节点一定存在) //性能要求:在O(1)时间复杂度内实现 void delete_node_exist(link_list_t *head,link_list_t node_deleted); //函数:删除链表中数据值等于给定值的节点 void delete_node(link_list_t *head,int val); //函数:获得链表中的倒数第K个节点 link_list_t get_kth_node(link_list_t head,int k); //函数:反转链表 link_list_t reverse_linklist(link_list_t head); //函数:合并两个已排序的链表(递归方法实现) link_list_t merge_linklist_recursive(link_list_t head1,link_list_t head2); int main(){ const int num1 = 8; const int num2 = 10; int *a = new int[num1]; int *b = new int[num2]; int *a_sorted = new int[num1]; int *b_sorted = new int[num2]; srand(1); for(int i = 0;i < num1;++i){ *(a + i) = rand() % 100; *(a_sorted + i) = 50 - i * 2 + 8; } for(int i = 0;i < num2;++i){ *(b + i) = rand() % 200; *(b_sorted + i) = 50 - i * 4 + 1; } cout << "**********创建链表测试**********" << endl; link_list_t list1 = create_linklist(a,num1); link_list_t list2 = create_linklist(b,num2); link_list_t list_sorted1 = create_linklist(a_sorted,num1); link_list_t list_sorted2 = create_linklist(b_sorted,num2); cout << "**********输出链表测试(正向输出)**********" << endl; cout << "链表1:" << endl; print_linklist(list1); cout << "链表1(已序):" << endl; print_linklist(list_sorted1); cout << "链表2(已序):" << endl; print_linklist(list_sorted2); cout << "**********输出链表测试(逆向输出)**********" << endl; print_linklist_reverse(list1); cout << "**********获取链表的倒数第K个节点测试**********" << endl; int k = 3; link_list_t kth_node = get_kth_node(list1,k); if(NULL == kth_node) cout << "链表中倒数第" << k << "个节点不存在" << endl; else cout << "链表中倒数第" << k <<"个节点是: " <<kth_node->m_val << endl; k = 8; kth_node = get_kth_node(list1,k); if(NULL == kth_node) cout << "链表中倒数第" << k << "个节点不存在" << endl; else cout << "链表中倒数第" << k <<"个节点是: " <<kth_node->m_val << endl; k = 11; kth_node = get_kth_node(list1,k); if(NULL == kth_node) cout << "链表中倒数第" << k << "个节点不存在" << endl; else cout << "链表中倒数第" << k <<"个节点是: " <<kth_node->m_val << endl; cout << "**********删除链表中一定存在的节点测试(输入参数是要删除的节点指针)**********" << endl; link_list_t node_deleted = list1; while(node_deleted->m_val != *(a + 4)) node_deleted = node_deleted->next; cout << "删除节点" << *(a + 4) << "之后的单链表:" << endl; delete_node_exist(&list1,node_deleted); print_linklist(list1); node_deleted = list1; while(node_deleted->m_val != *(a + 6)) node_deleted = node_deleted->next; cout << "删除节点" << *(a + 6) << "之后的单链表:" << endl; delete_node_exist(&list1,node_deleted); print_linklist(list1); cout << "**********删除链表中值等于给定值的节点测试(不一定存在,输入参数是int型值)**********" << endl; const int val_deleted = 22; delete_node(&list1,val_deleted); cout << "删除值等于" << val_deleted << "之后的链表:" << endl; print_linklist(list1); cout << "**********合并链表测试**********" << endl; link_list_t merge_list_head = merge_linklist_recursive(list_sorted1,list_sorted2); print_linklist(merge_list_head); cout << "**********逆转链表测试**********" << endl; link_list_t head_reverse = reverse_linklist(merge_list_head); cout << "逆转之后的链表:" << endl; cout << "头节点:" << head_reverse->m_val << endl; print_linklist(head_reverse); return 0; } //函数:创建单链表(头插法) link_list_t create_linklist(int *a,int n){ link_list_t head = NULL; if(NULL == a || 0 == n) return NULL; for(int i = 0;i < n;++i){ link_list_t new_node = creart_linknode(*(a + i)); if(NULL == head){ head = new_node; } else{ new_node->next = head; head = new_node; } } return head; } //函数:新建链表节点 link_list_t creart_linknode(int val){ link_list_t node = new link_node_t; node->m_val = val; node->next = NULL; return node; } //函数:打印单链表 void print_linklist(link_list_t head){ link_list_t node = head; cout << "正向输出单链表" << endl; while(node != NULL){ cout << node->m_val << " "; node = node->next; } cout << endl; return; } //函数:打印单链表(从尾到头) void print_linklist_reverse(link_list_t head){ stack<int> node_stack; link_list_t node = head; while(node != NULL){ node_stack.push(node->m_val); node = node->next; } cout << "逆向输出单链表" << endl; while(!node_stack.empty()){ cout << node_stack.top() << " "; node_stack.pop(); } cout << endl; return; } //函数:删除链表中的某一个节点(前提条件:该节点一定存在) //性能要求:在O(1)时间复杂度内实现 void delete_node_exist(link_list_t *head,link_list_t node_deleted){ //算法思想: //通过拷贝要删除节点的后继节点的内容覆盖要删除节点的内容,然后删除要删除节点的后继节点即可 //要考虑的特殊情况是:要删除的节点是链表尾部节点,仍然需要遍历链表 if(NULL == head || NULL == node_deleted) return; //要删除的节点不是尾节点 if(node_deleted->next != NULL){ link_list_t next_node = node_deleted->next; node_deleted->m_val = next_node->m_val; node_deleted->next = next_node->next; delete next_node; next_node = NULL; } //链表中只有一个节点 else if(*head == node_deleted){ delete node_deleted; node_deleted = NULL; *head = NULL; } //要删除的节点是尾节点 else{ link_list_t node = *head; while(node->next != node_deleted) node = node->next; node->next = node_deleted->next; delete node_deleted; node_deleted = NULL; } return; } //函数:获得链表中的倒数第K个节点 link_list_t get_kth_node(link_list_t head,int k){ //性能:只需遍历链表一遍即可 //算法思想:设置两个指针,一个指向链表头部,一个指向第k个节点,然后两个指针同时向后移动,当第二个指针指向链表的尾节点时,第一个指针指向的节点便是倒数第K个节点 //注意代码的鲁棒性,防止程序的崩溃 if(NULL == head || k <= 0) return NULL; //设置两个指针 link_list_t p1 = head,p2 = head; int i = 0; //第二个指针向前走k-1步 while(i < k - 1 && p2->next != NULL){ p2 = p2->next; ++i; } //注意链表中总节点数小于K的情况 if(i != k - 1 && NULL == p2->next) return NULL; //两个指针同时向后前进 while(p2->next != NULL){ p1 = p1->next; p2 = p2->next; } return p1; } //函数:反转链表 //返回值:反转之后的链表头节点 link_list_t reverse_linklist(link_list_t head){ //链表为空或者只有一个节点 if(NULL == head || NULL == head->next) return head; link_list_t prev_node = NULL,next_node,cur_node = head,head_reverse; while(cur_node != NULL){ next_node = cur_node->next; if(NULL == next_node) head_reverse = cur_node;//原链表尾节点即逆转后链表的头节点 cur_node->next = prev_node; prev_node = cur_node; cur_node = next_node; } return head_reverse; } //函数:删除链表中数据值等于给定值的节点 void delete_node(link_list_t *head,int val){ if(NULL == head){ cout << "Delete node failed :The node to be delete not exist!" << endl; return; } if(val == (*head)->m_val){ link_list_t node = *head; *head = (*head)->next; delete node; return;
最新技术文章: