📄 acm采样频率转换_混乱思维.htm
字号:
href="http://hi.baidu.com/yrworld/profile">个人档案</A> <SPAN>|</SPAN><A
href="http://hi.baidu.com/yrworld/friend">好友</A> </DIV></DIV>
<DIV class=stage>
<DIV class=stagepad>
<DIV style="WIDTH: 100%">
<TABLE class=modth cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD class=modtl width=7> </TD>
<TD class=modtc noWrap>
<DIV class=modhead><SPAN class=modtit>查看文章</SPAN></DIV></TD>
<TD class=modtc noWrap align=right></TD>
<TD class=modtr width=7> </TD></TR></TBODY></TABLE>
<DIV class=modbox id=m_blog>
<DIV class=tit>ACM采样频率转换</DIV>
<DIV class=date>2008-07-14 11:16</DIV>
<TABLE style="TABLE-LAYOUT: fixed">
<TBODY>
<TR>
<TD>
<DIV class=cnt id=blog_text>ACM采样频率转换
<BR>在音频的处理中,采样频率的转换是经常碰到的问题,比如输入44.1k,要求输出48k,或者相反从48k转换到44.1k。表面上看来,只是增加或减少采样点而已。其实不然。如果只是简单地从时间域上进行采样点的增减,必然导致原有波形的改变,从而声音失真,严重的时候更是不堪入耳。
<BR><CLK></CLK>正确的方法,应该是对输入的数据进行FFT变换到频域,然后再进行转化。这是一个比较繁琐的过程。那么,有没有更简单一点的方法呢?答案是肯定的。微软提供了一套ACM的API函数可以帮<NOBR
style="COLOR: #6600ff; BORDER-BOTTOM: #6600ff 1px dotted; BACKGROUND-COLOR: transparent; TEXT-DECORATION: underline">我们</NOBR>的忙。熟悉DirectShow
Filter的朋友更加知道,在SDK中提供的Filter中就有一个叫ACM Wrapper的,其实它就是微软对ACM
API函数的包装。可以说,ACM Wrapper Filter是ACM API在DirectShow环境中应用形式。美中不足的是,经过ACM
Wrapper
Filter进行采样频率转化后,由于浮点运算的误差,有可能会导致数据的丢失。每次转化的一点点丢失,如果再经过时间上的累加,音频数据会丢得越来越多。由于微软的DirectShow是基于Playback模式的一套架构,时间戳上显示的数据丢失对于人耳根本微不足道。所以仅从播放的角度上来说,这个“问题”是很难被察觉的。如果你要使用经过ACM
Wrapper Filter转化后的数据跟视频流合成,那么,你生成的文件很有可能在半个小时或更长的一段时间后出现音视频的不同步现象。
<BR>解决的办法有两种,一种是自己开发一个In-place-transform的Filter。这个Filter紧跟着接到ACM Wrapper
Filter的后面,对进来的每一个Sample检查时间戳,如果累加的音频丢失“时间”超过一个采样点的时间,则马上补上一个采样点的数据。另外一种解决方法,就是干脆使用ACM
API函数写一个自己的ACM Wrapper Filter。这样,就可以直接在ACM Wrapper内部监视数据的丢失。
<BR>下面我们就来看一下ACM API的使用。请先确认包含了以下头文件:mmreg.h, mmsytem.h,
msacm.h;以及连接了以下库文件:msacm32.lib,
winmm.lib。在进行采样频率转换之前,首先要使用acmStreamOpen函数打开一个转化流,以及对输入输出数据类型的设置。示例代码如下:
<BR>bool CConversionStream::OpenStream(void) <BR>{ <BR>DWORD maxSize = 0;
<BR>MMRESULT mmr = acmMetrics(NULL, ACM_METRIC_MAX_SIZE_FORMAT,
&maxSize); <BR>bool pass = (mmr == MMSYSERR_NOERROR); <BR>if (pass)
<BR>{ <BR>LPWAVEFORMATEX sourceFormat = (LPWAVEFORMATEX) new char
[maxSize]; <BR>LPWAVEFORMATEX destFormat = (LPWAVEFORMATEX) new char
[maxSize]; <BR>memset(sourceFormat, 0, maxSize); <BR>memset(destFormat, 0,
maxSize); <BR>sourceFormat->wFormatTag = WAVE_FORMAT_PCM;
<BR>sourceFormat->nChannels = 2; <BR>sourceFormat->nSamplesPerSec =
44100; <BR>sourceFormat->wBitsPerSample = 16;
<BR>sourceFormat->cbSize = 0; <BR>sourceFormat->nBlockAlign = 4;
<BR>sourceFormat->nAvgBytesPerSec = 44100 * 4;
<BR><BR>destFormat->wFormatTag = WAVE_FORMAT_PCM;
<BR>destFormat->nChannels = 2; <BR>destFormat->nSamplesPerSec =
48000; <BR>destFormat->wBitsPerSample = 16; <BR>destFormat->cbSize =
0; <BR>destFormat->nBlockAlign = 4; <BR>destFormat->nAvgBytesPerSec
= 48000 * 4; <BR><BR>mmr = acmStreamOpen(&mStreamHandler, NULL,
sourceFormat, destFormat, NULL, 0, 0, 0); <BR>pass = (mmr ==
MMSYSERR_NOERROR); <BR>delete[] sourceFormat; <BR>delete[] destFormat;
<BR>} <BR>return pass; <BR>} <BR>/* <BR>实际的数据转化也很简单。首先要建立一个ACM
header,并对其进行设置,如果输入数据的缓冲及数据长度,输出数据的缓冲及缓冲大小。之后务必调用acmStreamPrepareHeader函数对这个ACM
header进行初始化。然后就调用acmStreamConvert进行数据转换。最后不要忘记调用acmStreamUnprepareHeader。
<BR>*/ <BR>bool CConversionStream::DoConverting(unsigned char *
inSourceBuffer, long inSourceLength, <BR>unsigned char * outDestBuffer,
long * ioDestLength) <BR>{ <BR>memset(mAcmheader, 0,
sizeof(ACMSTREAMHEADER)); <BR>DWORD suggestedDestSize = 0;
<BR>acmStreamSize(mStreamHandler, inSourceLength, &suggestedDestSize,
ACM_STREAMSIZEF_SOURCE); <BR>ASSERT(suggestedDestSize <=
*ioDestLength); <BR><BR>// Build ACM header on buffer
<BR>mAcmheader->cbStruct = sizeof(ACMSTREAMHEADER);
<BR>mAcmheader->cbSrcLength = inSourceLength; <BR>mAcmheader->pbSrc
= inSourceBuffer; <BR>mAcmheader->cbDstLength = *ioDestLength;
<BR>mAcmheader->pbDst = outDestBuffer; <BR><BR>// Prepare the buffer
for ACM <BR>MMRESULT mmr = acmStreamPrepareHeader(mStreamHandler,
mAcmheader, 0); <BR>bool pass = (mmr == MMSYSERR_NOERROR); <BR>if (pass)
<BR>{ <BR>mmr = acmStreamConvert(mStreamHandler, mAcmheader,
ACM_STREAMCONVERTF_BLOCKALIGN); <BR>pass = (mmr == MMSYSERR_NOERROR);
<BR>} <BR>*ioDestLength = mAcmheader->cbDstLengthUsed;
<BR>ASSERT(mAcmheader->cbSrcLengthUsed == mAcmheader->cbSrcLength);
<BR>// Unprepare ACM header <BR>acmStreamUnprepareHeader(mStreamHandler,
mAcmheader,0); <BR>return pass; <BR>}
<BR><BR>就这么简单!轻轻松松,实现了音频的采样频率转换。最后,当所有数据都已经转换完毕,不要忘了调用acmStreamClose函数关闭转化流。 </DIV></TD></TR></TBODY></TABLE><BR>
<DIV class=opt><A title=查看该分类中所有文章
href="http://hi.baidu.com/yrworld/blog/category/Directshow">类别:Directshow</A> |
<A title=将此文章添加到百度搜藏 onclick="return addToFavor();"
href="http://cang.baidu.com/do/add" target=_blank>添加到搜藏</A> | 浏览(<SPAN
id=result></SPAN>) | <A
href="http://hi.baidu.com/yrworld/blog/item/da041ad1e12309d6562c8495.html#send">评论</A> (0)
<SCRIPT language=javascript>/*<![CDATA[*/var pre = [true,'设置音频sourcefilterk 中的WAVEFORMATEX结构', '设置音频sourcefilterk 中的WAVE...','/yrworld/blog/item/24c5f5dd099364ea77c6381d.html'];var post = [true,'DirectShow系统初级指南','DirectShow系统初级指南', '/yrworld/blog/item/2ce011e987253639b90e2d7f.html'];if(pre[0] || post[0]){ document.write('<div style="height:5px;line-height:5px;"> </div><div id="in_nav">'); if(pre[0]){ document.write('上一篇:<a href="' + pre[3] + '" title="' + pre[1] + '">' + pre[2] + '</a> '); } if(post[0]){ document.write('下一篇:<a href="' + post[3] + '" title="' + post[1] + '">' + post[2] + '</a>'); } document.write('</div>');}/*]]>*/</SCRIPT>
</DIV>
<DIV class=line></DIV>
<STYLE type=text/css>#in_related_doc A {
TEXT-DECORATION: none
}
</STYLE>
<DIV id=in_related_tmp></DIV>
<SCRIPT language=javascript type=text/javascript>/*<![CDATA[*/function HI_MOD_IN_RELATED_DOC_CALLBACK(arg){ if(arg.length <= 1) return false; var hasMore = arg[0]; var D=function(A,B){A[A.length]=B;} if(arg.length % 2 == 0) D(arg, ["","","",""]); var html = ['<div id="in_related_doc"><div class="tit">相关文章:</div>']; D(html, '<table cellpadding="0" cellspacing="3" border="0">'); for(var i = 1, j = arg.length; i < j; i += 2){ D(html, '<tr>'); D(html, '<td width="15px"><a style="font-size:25px" >•</a></td><td><a href="http://hi.baidu.com/' + arg[i][3] + '/blog/item/' + arg[i][2] + '.html" target="_blank" title="' + arg[i][0] + '">' + arg[i][1] + '</a>'); D(html, new Array(10).join('\u3000')); D(html, '</td>'); if(arg[i + 1][0] != "") D(html, '<td width="15px"><a style="font-size:25px" >•</a></td><td><a href="http://hi.baidu.com/' + arg[i + 1][3] + '/blog/item/' + arg[i + 1][2] + '.html" target="_blank" title="' + arg[i + 1][0] + '">' + arg[i + 1][1] + '</a></td>'); else D(html, '<td> </td><td> </td>'); D(html, '</tr>'); } if(hasMore) D(html, '<tr><td colspan="4"><a target="_blank" href="/sys/search?pageno=1&type=7&sort=1&word=ACM%B2%C9%D1%F9%C6%B5%C2%CA%D7%AA%BB%BB&item=da041ad1e12309d6562c8495">更多>></a></td></tr>'); D(html, '</table></div><div class="line"> </div>'); var div = document.getElementById('in_related_tmp'); if(div){ div.innerHTML = html.join(''); while(div.firstChild){ div.parentNode.insertBefore(div.firstChild, div); } div.parentNode.removeChild(div); } window.setTimeout("tracker_init('in_related_doc')",100);}if(RelatedDocData == -1){ // not supported xhr var script = document.createElement('script'); script.type = 'text/javascript'; script.src = '/sys/search?type=8&word=ACM%B2%C9%D1%F9%C6%B5%C2%CA%D7%AA%BB%BB&item=da041ad1e12309d6562c8495&t=' + new Date().getTime(); document.getElementsByTagName('HEAD')[0].appendChild(script);}else if(RelatedDocData == null){ GetAndEval = true;}else{ eval(RelatedDocData);}/*]]>*/</SCRIPT>
<DIV id=in_reader>
<DIV class=tit>最近读者:</DIV>
<SCRIPT> var g_spAnnony=true;var g_read=[ ["acfly","980f5f41435f666c795f2e05","_AC_fly_"],{}];g_read.length=g_read.length-1;var _rh1="";var _rh2="";function wrreader(){ _rh1 += '<table width="100%" ><tr>'; _rh2+='<tr>'; if(g_spAnnony){ _rh1+='<td align="center" width="10%" ><img border="0" width="55" height="55" src="http://img.baidu.com/hi/img/portraitn.jpg"></td>'; _rh2+='<td> </td>'; if(g_read.length>0){ _rh1+='<td align="left" width="12%">'; }else{ _rh1+='<td align="left" width="100%">'; } _rh1+='<a href="http://passport.baidu.com/?login&tpl=sp&tpl_reg=sp&u='+myref+'" target="_self">登录</a>后,您就出现在这里。</td>'; _rh2+='<td> </td>' } if(g_read.length==0){ if(!g_spAnnony){ _rh1+='<td align=left width="100%">最近还没有登录用户看过这篇文章……</td>'; _rh2+='<td> </td>'; } }else{ for(i=0,len=g_read.length;i<len;i++){ _rh1+='<td align="center" valign="bottom" width="10%" class="user"><a href="/'+g_read[i][0]+'" target="_blank"><img border="0" src="http://himg.baidu.com/sys/portraitn/item/'+g_read[i][1]+'.jpg"></a></td>'; _rh2+='<td align="center" valign="top" class="user"><a href="/'+g_read[i][0]+'" target="_blank">'+g_read[i][2]+'</a></td>'; } } _rh1+='<td width="100%"></td></tr>'; _rh2+='<td></td></tr></table>'; document.write(_rh1+_rh2);}wrreader();</SCRIPT>
</DIV>
<DIV class=line></DIV>
<SCRIPT language=JavaScript>allkey=allkey+"fa45d9eab43e26d3d439c985_da041ad1e12309d6562c8495_";</SCRIPT>
<DIV id=in_comment><A name=comment></A>
<DIV class=tit>网友评论:</DIV>
<SCRIPT>function writecmt(type,id,cmtname,cmturl,portraitId){ var html1=""; if(type==1){ html1="<a href='"+cmturl+"' target='_blank' title='"+cmturl+"'><img border='0' src='http://himg.baidu.com/sys/portraitn/item/"+portraitId+".jpg'><br>"+cmtname+"</a>"; }else{ if(cmtname=="" || cmtname=="匿名网友"){ if(cmturl==""){ html1="<a>匿名网友</a>"; }else{ html1="<a href='"+cmturl+"' target='_blank' title='"+cmturl+"'>"+cmtname+"</a>"; } }else{ if(cmturl==""){ html1="<div class='f14' style='display:inline'>网友:<a>"+cmtname+"</a></div>"; }else{ html1="<div class='f14' style='display:inline'>网友:<a href='"+cmturl+"' target='_blank' title='"+cmturl+"'>"+cmtname+"</a></div>"; } } } document.write(html1);}</SCRIPT>
<DIV id=page></DIV></DIV>
<DIV id=in_send><A name=send></A>
<FORM id=popFormSubmit name=form1 onsubmit="return checkcmtform()"
action=/yrworld/commit method=post><INPUT type=hidden value=8 name=ct> <INPUT
type=hidden value=1 name=cm> <INPUT type=hidden value=da041ad1e12309d6562c8495
name=spBlogID> <INPUT id=spRefURL type=hidden name=spRefURL>
<SCRIPT> document.getElementById("spRefURL").value = window.location.href;</SCRIPT>
<DIV class=tit>发表评论:</DIV>
<TABLE cellSpacing=5 cellPadding=0 width=620 border=0>
<TBODY>
<TR>
<TD class=f14>姓 名:</TD>
<TD><INPUT id=spBlogCmtor style="WIDTH: 220px" onfocus=hidErr(1);
tabIndex=1 maxLength=49 onchange="checkname('spBlogCmtor')"
name=spBlogCmtor>
<SCRIPT>document.write(" <a href='http://passport.baidu.com/?reg&tpl=sp&return_method=get&skip_ok=1&u=http://hi.baidu.com/sys/reg/' target='_blank'>注册</a>");document.write(' | <a href="http://passport.baidu.com/?login&tpl=sp&tpl_reg=sp&u='+myref+'">登录</a>');</SCRIPT>
<DIV id=nmerror style="DISPLAY: none">*姓名最长为50字节</DIV></TD></TR>
<TR id=1_err style="DISPLAY: none">
<TD> </TD>
<TD>
<DIV class=error id=1_err_con></DIV></TD></TR>
<TR>
<TD class=f14>网址或邮箱:</TD>
<TD><INPUT id=spBlogCmtURL style="WIDTH: 360px" onfocus=hidErr(2);
tabIndex=2 maxLength=128 onchange="checkeandu('spBlogCmtURL')"
name=spBlogCmtURL> (选填)</TD>
<SCRIPT>G("spBlogCmtor").value="";G("spBlogCmtURL").value="";</SCRIPT>
</TR>
<TR id=2_err style="DISPLAY: none">
<TD> </TD>
<TD>
<DIV class=error id=2_err_con></DIV></TD></TR>
<TR>
<TD class=f14 vAlign=top>内 容:</TD>
<TD><TEXTAREA id=spBlogCmtText style="WIDTH: 520px; HEIGHT: 155px" onfocus=hidErr(3); tabIndex=3 name=spBlogCmtText></TEXTAREA>
<SCRIPT>G("spBlogCmtor").value=G("spBlogCmtor").defaultValue;G("spBlogCmtText").value="";</SCRIPT>
</TD></TR>
<TR id=3_err style="DISPLAY: none">
<TD> </TD>
<TD>
<DIV class=error id=3_err_con></DIV></TD></TR>
<TR id=vercode>
<TD class=f14 vAlign=top>验证码:</TD>
<TD vAlign=top><INPUT type=hidden
value=3035737061636531393034393330373538303030303030303030303030303031323235363832353036A0200428076174F5DDF9A192D6A01665
name=spVcode> <INPUT id=spVerifyKey onfocus=f_focus() tabIndex=4
maxLength=4 size=6 name=spVerifyKey autocomplete="off"><BR>
<SCRIPT type=text/javascript>/*<![CDATA[*/var imgsrc="http://hiup.baidu.com/cgi-bin/genimg?3035737061636531393034393330373538303030303030303030303030303031323235363832353036A0200428076174F5DDF9A192D6A01665";function f_focus(){ if(G('yanzheng').style.display=="none" ){ G('verifypic').src=imgsrc; G('yanzheng').style.display="block"; }}function newverifypic(){ G("verifypic").src = imgsrc +"&t="+ Math.random(); return false;}/*]]>*/</SCRIPT>
<DIV id=yanzheng style="DISPLAY: none"><IMG id=verifypic height=40
width=120><WBR><A title=看不清左边的字符 onfocus=this.blur();
onclick="return newverifypic();"
href="http://hi.baidu.com/yrworld/blog/item/da041ad1e12309d6562c8495.html#">看不清?</A>
</DIV></TD></TR>
<TR>
<TD class=f14 vAlign=top> </TD>
<TD class=f14 vAlign=top><INPUT id=btn_ok tabIndex=5 type=submit value=发表评论 name=btn_ok></TD></TR></TBODY></TABLE></FORM></DIV><BR></DIV>
<TABLE height=8 cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD class=modbl width=7> </TD>
<TD class=modbc> </TD>
<TD class=modbr width=7> </TD></TR></TBODY></TABLE></DIV></DIV></DIV></DIV>
<SCRIPT language=javascript><!--var hstr="/yrworld/brwstat?key1=1";document.write("<script src='"+hstr+"&key2="+allkey+"'><\/script>");//--></SCRIPT>
<BR>
<CENTER>
<DIV id=ft>©2008 Baidu</DIV></CENTER>
<SCRIPT>if(document.getElementById("m_blog")){ var imgarray = document.getElementById("m_blog").getElementsByTagName('img'); var imgw = document.getElementById("m_blog").offsetWidth; imgw =imgw-40; for(var i=0; i<imgarray.length; i++){ if(imgarray[i].className=="blogimg" && imgarray[i].width>=imgw) imgarray[i].width=imgw; }}// Fix ff bugsvar blog_text = document.getElementById('blog_text');blog_text.innerHTML = blog_text.innerHTML.replace(/href\s*=\s*("|')?(\.\.\/\.\.\/)/gi,"href=$1../$2");</SCRIPT>
</CENTER><IMG style="DISPLAY: none" src=""> </BODY></HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -