在symbian中,除了使用插件架构(MMF)将文件格式匹配适当的编解码器,还可以在处理原始音频数据时,明确选择需要的编解码器。
 
Symbian中依靠实用类来完成基本的音频任务的,用观察器来获取播放或录音事件。
Clients using any of the CMdaAudioRecorderUtility, CMdaAudioConvertUtility orCMdaAudioPlayerUtility classes must have an active scheduler running in their thread because the implementations use active objects and callbacks.
 
 
一. CMdaAudioToneUtility
用来播放音频声音。音频声音可以是正xuan波,双音多频(DTMF),电话按键音等。
使用方法如下:
 
CMdaAudioToneUtility * iAudioToneUtility = CMdaAudioToneUtility::NewL(MyObserver, NULL);
...
iAudioToneUtility->PrepareToPlayTone(2000, /*aDuration*/3000000); /* 这里 2000代表将播放的音调的频率,3000000表示将播放3000000微秒(3秒钟)。*/
PrepareToPlayTone方法是异步的,当准备完成后,框架将调用MMdaAudioToneObserver::MatoPrepareComplete(TInt aError)方法。在这之间我们可以调用iAudioToneUtility->CancelPrepare()方法,这样观察器就无法接受到MatoPrepareComplete通知了。
 
我们在MatoPrepareComplete(TInt aError)方法中进行如下处理:
void CMyObserver::MatoPrepareComplete(TInt aError)
{
    if(aError != KErrNone)
        {
        iAudioToneUtility->CancelPlay();
        }
    else{
        iAudioToneUtility->SetVolume(iAudioToneUtility->MaxVolume()/3);
        iAudioToneUtility->SetBalanceL(KMMFBalanceMaxLeft); // 设置平衡。默认是KMMFBalanceCenter
        iAudioToneUtility->SetRepeats(2, 1000000); 
/* 设置多重复的次数,注意是多重复的次数,而不是重复的次数,所以这里就代表在播放完第一次之后还要继续重复两次,并且这三次重复播放之间的间隔是 1000000微秒 */
        iAudioToneUtility->SetVolumeRamp(500000); 
/* Defines the period over which the volume level is to rise smoothly from nothing to the normal volume level.*/
        iAudioToneUtility->Play(); /* 开始播放。这是个异步函数,当播放完成时会调用MMdaAudioToneObserver::MatoPlayComplete(TInt aError)方法。 */
        
/* iAudioToneUtility->CancelPlay(); // 我们也可以取消播放,这样的话观察器也接受不了MatoPlayComplete通知了 */    
        }
}
 
void CMyObserver::MatoPlayComplete(TInt aError)
{
    if(aError != KErrNone)
        {
        iAudioToneUtility->CancelPlay();
        }
    else // 循环整个播放流程
        {
        //delete iAudioToneUtility;
        //iAudioToneUtility = NULL;

        iAudioToneUtility->PrepareToPlayTone(tone, /*aDuration*/1000000); 
        tone += 1000;
        }
}
 
二. CMdaAudioPlayerUtility
播放音频数据,例如.wav和.midi
 
CMdaAudioPlayerUtility * iAudioPlayer = CMdaAudioPlayerUtility::NewL(MyObserver);
...
iAudioPlayer->OpenFileL(_L("c:\\data\\Ring.wav")); /* 载入音频数据。此方法为异步方法,载入完成会调用MyObserver.MapcInitComplete(aError, aDuration)方法 */
// 若数据已经放入描述符缓冲区则调用iAudioPlayer->OpenDesL(const TDesC8 &);
/* 经试验,直到S60 3rd FP1版本,CMdaAudioPlayerUtility::OpenUrlL()方法也不被支持,原文如下: 
 
CMdaAudioPlayerUtility::OpenUrlL() is not supported in the S60 platform. This method does not leave, but KErrNotSupported (-5) is returned in MMdaAudioPlayerCallback::MapcInitComplete().
Solution: 
http:// URLs are not supported. However, from S60 2nd Edition FP2 onwards OpenUrlL() from CVideoPlayerUtility can be used for opening rtsp:// URLs with RealAudio (audio-only) streams.
*/
 
/* 音频数据载入完成。注意有些方法需要在载入音频数据完成后调用才有意义,比如 iAudioPlayer->Duration() 等等 */
void CMyObserver::MapcInitComplete(TInt aError, const TTimeIntervalMicroSeconds &aDuration) // the aDuration equal to 
{
    if (aError == KErrNone)
    {
        // iAudioPlayer->Duration()可以得到歌曲的总时间,值和aDuration相等
        iAudioPlayer->SetVolume(iAudioPlayer->MaxVolume() / 3);
        iAudioPlayer->Play(); /* 播放音频数据。此方法为异步方法,载入完成会调用MMdaAudioPlayerCallback::MapcPlayComplete(TInt aError)方法。注意有些方法需要在正在播放音频数据时调用才有意义,比如 iAudioPlayer->GetPosition(TTimeIntervalMicroSeconds &)和iAudioPlayer->Pause() 等等 */
    }
    else
    {
        //iAudioPlayer->CancelRegisterAudioResourceNotification();
        iAudioPlayer->Stop();
    }
}
 
// 播放完成
void CMyObserver::MapcPlayComplete(TInt aError)
{
    if (aError == KErrNone)
        {
        iAudioPlayer->Close(); 
/* Closes the current audio clip, so can allow another clip to be opened). */
        iAudioPlayer->OpenFileL(_L("c:\\data\\Fur-Elise.mid")); /* 打开另一个音频文件并重复播放流程 */
        }
    else
        {
        iAudioPlayer->Stop();
        }
}
 
三. CMdaAudioOutputStream
音频流播放类允许程序播放音频时,不必拥有全部音频的数据。音频数据以递增方式存取和缓冲,并尽量保证平滑和连续播放。
 
这个类的工作原理就是将PCM数据传递给底层音乐设备并通知其开始播放。
 
如果我们的程序中使用了第三方解码库,也需要使用这个类来将解码后的音频数据传递给底层音乐设备。
 
流播放器支持的唯一一种音频格式是16位脉冲编码调制(pulse code modulation,PCM)。通常PCM数据包含一个44字节的头部(包含音频数据的有效载荷信息)。由于流播放器只接受16位PCM,因此它并不需要这个头部信息。
 
CMdaAudioOutputStream::SetAudioPropertiesL(TInt aSampleRate, TInt aChannels)方法可以设置取样率和通道数。
而音量等附加信息可以通过传递TMdaAudioDataSettings(TMdaAudioDataSettings是TMdaPackage类的子类)对象给Open()方法来控制。
 
CMdaAudioOutputStream * iOutputStream = CMdaAudioOutputStream::NewL(MyObserver);
...
// iOutputStream->SetAudioPropertiesL(aSampleRate, aChannels); /* 控制采样率和声道数。不是必须的。 */
...
TMdaAudioDataSettings        iStreamSettings;
... // 通过对TMdaAudioDataSettings设置来指定一些音频信息。不是必须的。
iOutputStream->Open(&iStreamSettings); /* 打开TMdaAudioDataSettings设置信息。此方法是异步的,打开完成之后会调用MMdaAudioOutputStreamCallback::MaoscOpenComplete(TInt aError)通知方法。 */
 
// 打开完成
void CMyObserver::MaoscOpenComplete(TInt aError)
{
    if (aError == KErrNone)
    {
        /* PCMStreamBuffer中存放的全是PCM音频数据。注意,若缓冲区myPCMStreamBuf尺寸太小,可能会造成音频听起来断断续续,这是因为音频设备消耗缓冲的速度快于了新缓冲被复制的速度了 */
        TRAPD(err, iOutputStream->WriteL(myPCMStreamBuf)); /* 将PCM数据写入到底层设备的缓冲区中,低层设备会在接受完PCM数据后自动开始播放。此方法为异步方法,当传入的PCM数据全部写入到底层设备的缓冲区中后,会调用MMdaAudioOutputStreamCallback::MaoscBufferCopied(TInt aError, const TDesC8 &aBuffer);通知方法。只有在PCM数据全部写入到底层设备的缓冲区中之后对myPCMStreamBuf的修改是安全的。 */
    }
    else
    {
        iOutputStream->Stop();
    }
}
 
// 传入的所有PCM数据已经写入到底层设备的缓冲区中
void CMyObserver::MaoscBufferCopied(TInt aError, const TDesC8 &aBuffer)
{
    if(aError != KErrNone)
        {
        iOutputStream->Stop();
        }
    else if (aError == KErrNone && iCount < 5)
    {
        iCount++; // 计数器,播放5次,初始化是0
        iOutputStream->SetVolume(iOutputStream->MaxVolume() / 3);
        TRAPD(err, iOutputStream->WriteL(myPCMStreamBuf)); /* 再次写入,又会导致回调本通知方法。当第5次被写入的pcm数据被播放完成后,会调用MMdaAudioOutputStreamCallback::MaoscPlayComplete(TInt aErr)通知方法来通知播放结束 */
    }
}
 
// 播放结束了
void CMyObserver::MaoscPlayComplete(TInt aError)
{
    if(aError != KErrNone)
    {
        iOutputStream->Stop();
    }
}
 
四. CMdaAudioRecorderUtility
就如同这个类的字面意思一样,这个类可以提供录音功能,但是它也提供了播放功能。如果我们只需要播放,那么使用之前介绍的CMdaAudioPlayerUtility类要节约资源一点。
 
录音可以发生于文件,描述符。(虽然提供了OpenUrlL()方法,���是和CMdaAudioPlayerUtility一样,OpenUrlL()方法是不被支持的)
 
录音基本分为3个步骤:
1.打开数据存储的句柄  2.设定参数  3.录音
 
其中 打开数据存储的句柄 和 录音 都是异步操作的,MMdaObjectStateChangeObserver::MoscoStateChangeEvent(CBase *aObject, TInt aPreviousState, TInt aCurrentState, TInt aErrorCode)方法通知应用程序每一个操作的事件。
 
CMdaAudioRecorderUtility * iSound = CMdaAudioRecorderUtility::NewL(aMyObserver);
...
// recording message settings
TMdaPcmWavCodec iMessageCodec;
iMessageCodec.iBits = TMdaPcmWavCodec::E16BitPcm;
...
TMdaAudioDataSettings    iMessageSettings;
iMessageSettings.iCaps = TMdaAudioDataSettings::ESampleRateFixed | TMdaAudioDataSettings::ESampleRate8000Hz | TMdaAudioDataSettings::EChannelsMono;
iMessageSettings.iSampleRate = 8000;
iMessageSettings.iChannels = 1;
...
TMdaFileClipLocation    iMessageLocation;
iMessageLocation.iName = _L("e:\\data\\yourmes.wav");
...
TMdaWavClipFormat        iMessageFormat;
...
iSound->OpenL(&iMessageLocation, &iMessageFormat, &iMessageCodec, &iMessageSettings); /* 这是个异步方法,当打开资源的操作完成后会调用MMdaObjectStateChangeObserver::MoscoStateChangeEvent(CBase *aObject, TInt aPreviousState, TInt aCurrentState, TInt aErrorCode) */
/*
there are several ways to record to a file by the CMdaAudioRecorderUtility:
(1). use OpenFileL()
    if we use this method, then the file must all ready exist, and just creating a new file using RFile.CreateL() or RFile.ReplaceL() will not be accepted by the recorder utility therefore, this would put a limit on how many messages could be kept
(2). use OpenL()
     if we use this method, then the file name passed to it, will lead it to construct a new file with this filename or it will overwrite an existing file with that name however, in order to use this function, we need to pass in several objects:
    (1) file location.
    (2) file format to be recorded, this is populated by the function.
    (3) recording codec to use.
    (4) audio settings, such as sample rate and number of channels.
*/
 
 
void CAnsPhoneEngine::MoscoStateChangeEvent(CBase* aObject, TInt aPreviousState, TInt aCurrentState, TInt aErrorCode)
{
    // if the recording has died, this means that the recording should be finished;
    // should only happen when recording on the telephony line
    if(iState == ERecord && !iIsLocal && aErrorCode == KErrDied)
    {
        Stop();
        return;
    }
    User::LeaveIfError(aErrorCode);
    if(aCurrentState != CMdaAudioClipUtility::EOpen)
        return;
    switch(iState)
    {
        case ERecordInit:
            // 设置为电话线路上的记录并设置为最大增益
            if(iIsLocal) /* if aIsLocal = ETrue, then record to the user's message; use the telephony line. */
            {
                iSound->SetAudioDeviceMode(CMdaAudioRecorderUtility::ELocal); /* CMdaAudioRecorderUtility::TDeviceMode是音频设备模式,指定录音和播放所使用的设备 */

                iSound->SetGain(iSound->MaxGain());
            }
            else /* if aIsLocal = EFalse, then record to aFileName; use the device microphone. */
            {
                iSound->SetAudioDeviceMode(CMdaAudioRecorderUtility::ETelephonyNonMixed);
                TInt maxGain = iSound->MaxGain();
                iSound->SetGain(maxGain / 2);
            }
            // Delete current audio sample from beginning of file
            iSound->SetPosition(TTimeIntervalMicroSeconds(0));
            iSound->CropL();
            // start recording
            iSound->RecordL(); /× 当录音结束之后仍然会调用此通知方法。使用iSound->Close()方法停止录音和播放(Stops the current operation (playback/recording/conversion).) */
            iState = ERecord;
            break;
        case ERecord:
            break;
        case EPlayInit:
        {
            // Play through the device speaker and set to max volume
            if(iIsLocal)
            {
                iSound->SetAudioDeviceMode(CMdaAudioRecorderUtility::ELocal);
            }
            else
            {
                iSound->SetAudioDeviceMode(CMdaAudioRecorderUtility::ETelephonyOrLocal);
                // iPhone.DeviceSpeakerOff();
            }
            iSound->SetVolume(iSound->MaxVolume());
            // Set the playback position to the start of the file
            iSound->SetPosition(TTimeIntervalMicroSeconds(0));
            iSound->PlayL(); // 当播放结束之后仍然会调用此通知方法。
            iState = EPlay;
            // we start the timer
            // add a half a second onto the play time to make sure we've finished playing
            TInt64 playTime = iSound->Duration().Int64() + KHalfSecond;
            if (iTimer)
            {
                delete iTimer;
                iTimer = NULL;
            }
            iTimer = CAnsPhoneTimer::NewL(*this, playTime.GetTInt());
            break;
        }
        default:
            break;
    }
}
 
五. CMdaAudioInputStream
用法和CMdaAudioOutputStream大同小异,只不过CMdaAudioOutputStream负责将PCM数据传入底层设备的buffer进行播放,而CMdaAudioInputStream负责在录制音频的时候将底层设备的buffer中的PCM数据获取而已。
 
 
六. CMdaAudioConvertUtility
CMdaAudioConvertUtility provides features to convert audio clips between different formats. For the purposes of this description, the conversion process has been broken down into the following sections.
 
CMdaAudioConvertUtility not supported on S60:
  platform:
S60 2nd Edition (all Feature Packs)
S60 3rd Edition (all Feature Packs)
S60 5th Edition
  Description
CMdaAudioConvertUtility, which is part of Symbian’s Media Client Audio API, is not supported since S60 1st Edition (v1.2). When used on newer platforms, all variants of CMdaAudioConvertUtility::OpenL() will return KErrNotSupported error code.
  Solution
Audio convert utility is no longer supported in S60. Streaming applications no longer need to convert audio to PCM format before passing it to the stream. Instead, it is possible to read or write compressed audio data directly from/to the stream, provided that a supported data type (fourCC code) is used when initialising the stream object.
For example, when using CMdaAudioInputStream to read audio data directly in AMR-NB format:
void MyStreamClass::MaiscOpenComplete(TInt aError)
   {
   iInputStream->SetDataTypeL(KMMFFourCCCodeAMR);
   ...
   }
  Conversion between different audio encoding types is supported only via the CMMFCodec API. Conversion can be done between encoding types if a suitable decoder/encoder combination is supported by the device. See the Device multimedia feature tables available at Forum Nokia for more information about codecs in S60 devices.