引擎中使用CMdaAudioOutputStream和MMdaAudioOutputStreamCallback完成声音播放功能。它主要有三个类组成:
  CAudioStreamPlayer。它复合CMdaAudioOutputStream,继承CActive,实现MMdaAudioOutputStreamCallback接口。我们需要小心的维持缓冲区的大小以获得低延迟播放。CActive不断的建立新的任务,在RunL函数中估算缓冲区中的剩余数据,向其中追加适当的数据,维持缓冲区的预期大小。
  CSimpleMixer。它实现CAudioGenerator接口。因为CMdaAudioOutputStream是一个单一的流式播放器,所以需要写一个混音器进行波形混合。这里波形混合就是简单的数据相加。混音器有许多的声道(channel)。每个channel记录了其中的CAudio指针和当前播放位置。
  CAudio。包含一个音频缓冲区。对每个声音文件,我们还需要一个类把它载入到内存缓冲区中。
        我不会在此讲解如何实现音频播放,那需要单独的一篇文章。如果你也使用这种方法实现声音播放,我只想在此和大家讨论两个问题。
需要学习声音基础的话,可以参考www.newlc.com/article.php?id_article=113。(可惜我当时学习声音时那篇文章和代码找不到了)

1.5.1. 声音的关闭和开启
        因为整个音频系统是一个拉的结构,音频流从混音器那里拉数据,混音器从音频缓冲区中拉数据。所以,只要把CMdaAudioOutputStream和写数据的CActive对象delete掉,声音播放就全部停止了。在我的实现中,也就是delete CAudioStreamPlayer对象即可。再想要开启声音,只需要重新创建这个对象。
这个实现的好处是程序的其它部分不需要保存声音是否开启这个状态。因为CAudio和CSimpleMixer对象是存在的,CAudio就可以把自己插入到Mixer的channel中,觉得自己好像在播放一样。其实因为CAudioStreamPlayer根本没有从Mixer向外拉数据,声音设备是完全停止的。
        但是在恢复声音播放时有一点需要注意,恢复前需要清空混音器中的声音数据。因为经过了长时间的运行,混音器中的各个channel中已经塞满了各种声音。如果此时突然打开,会传出各种延迟了的杂音。

1.5.2. 特殊错误处理
        MMdaAudioOutputStreamCallback接口中的几个回调函数MaoscOpenComplete、MaoscBufferCopied和MaoscPlayComplete都有一个错误码参数。你不能忽略这个参数。
比如MaoscPlayComplete函数,是在音频停止播放时被调用。停止播放的原因可能是多种多样的。我们都知道要处理KErrUnderflow这个情况,这个错误吗意味着混音器没有及时的供给它音频数据。此时需要重新启动声音流。但是还有一些情况比如KErrDied和KErrInUse很容易被忽略。KErrDied发生在接听电话时,此时声音线程已经死了,那么就需要重建整个音频系统。KErrInUse发生在收到短信时,此时声音设备被抢占,用来播放短信提示音。此时你也需要重建整个声音系统,但是此时不能立刻重建,否则还是一样的结果。你应该等待几秒钟之后才重建它。
上面说的重启声音流和重建声音系统深度不同。重启声音流在稍后的代码中可以看到。其中RunAudioL向音频流写入了第一个声音缓冲区。重建声音系统在我的实现中就是指先delete 再NewL创建CAudioStreamPlayer对象。
这三个错误的处理代码如下:
// Audio stream API callback: Called when playback has finished.
void CAudioStreamPlayer::MaoscPlayComplete(TInt anError) 
{
 if (m_bInDelay)
  return;
 // If we finish due to an underflow, we"ll need to restart playback.
 // Normally KErrUnderlow is raised at stream end, but in our case the API
 // should never see the stream end -- we are continuously feeding it more
 // data! Many underflow errors mean that the latency target is too low.
 if ( anError == KErrUnderflow ) {
  iObserver->MasoMessage(_L("Play Underflow"));
  // The number of samples played gets resetted to zero when we restart
  // playback after underflow
  iBaseSamplesPlayed = iSamplesWritten;

  // Stop and restart
  iStream->Stop();
  Cancel();
#ifdef RATE_16K
  iStream->SetAudioPropertiesL(TMdaAudioDataSettings::ESampleRate16000Hz, 
          TMdaAudioDataSettings::EChannelsMono);
#else
  iStream->SetAudioPropertiesL(TMdaAudioDataSettings::ESampleRate8000Hz, 
          TMdaAudioDataSettings::EChannelsMono);
#endif
  iStream->SetVolume(iStream->MaxVolume() / 4);
  TRAPD(error, RunAudioL());
  if ( error != KErrNone ) {
   User::Panic(KPlay, error);
  }

  return;      
 } 
 else if ( anError == KErrDied )
 {
  m_bInDelay = ETrue;
  m_RebuildDelay = 0; // no delay
 }
 else if ( anError == KErrInUse )
 {
  m_bInDelay = ETrue;
  m_RebuildDelay = 3000; // delay 3 second
 }
 else if ( anError != KErrNone ) {
  // Some other error, panic!
  User::Panic(KPlayComplete, anError);
 }
}
        由外界发现m_RebuildDelay标志,重建CAduioStreamPlayer这个对象。
除了MaoscPlayComplete,我在MaoscBufferCopied中忽略了KErrUnderflow和KErrAbort错误。在MaoscBufferCopied和MaoscOpenComplete也处理了KErrInUse错误。
经过上面的处理,我的程序已经可以安全的应对来电、短信、切换程序等特殊情况了。