【Advanced Windows Phone Programming】在windows phone 8中錄制MP3和AAC及Amr音頻


PS:博主開了微博了~詳情猛戳:http://weibo.com/SandCu

wp系統中默認的給出的是WAV格式的音頻,當然無論是用來存儲還是與網絡交互都顯得過大了,不過在p7中只能是用c#進行處理,所以通常會將其保存為Amr格式比如QQ又或者是使用Speex進行編碼然后打包成OGG,

鑒於Wp8中開放了Native Code,所以我們可以考慮性價比更高的格式如AAC或者MP3格式。

1.使用AAC格式

wp8在winPRT中新加入了AudioVideoCaptureDevice類,通過這個類我們可以錄制音頻或者視頻,但是因為是在prt中而不是。net類庫,所以在使用的時候可能會有些限制。

首先我們來看看錄音的使用方法。

首先在構造函數中來初始化AudioVideoCaptureDevice類的一些設置,當然初始化的時機需要根據不同的應用場景來區分,這里暫時寫在頁面的構造函數里

  private AudioVideoCaptureDevice AVCaptureDevice;
  private IRandomAccessStream Iras;
 
private string path; public MainPage() {   InitializeComponent();    init(); } private async void init() { AVCaptureDevice= await AudioVideoCaptureDevice.OpenForAudioOnlyAsync(); AVCaptureDevice.AudioEncodingFormat = CameraCaptureAudioFormat.Aac; }
CameraCaptureAudioFormat是一個枚舉值,有Aac,Amr,None,Pcm四種格式,當然不可能有Mp3,畢竟是收費格式~
下面我們來看錄音函數:
我們需要使用AudioVideoCaptureDevice的的IAsyncAction StartRecordingToStreamAsync(IRandomAccessStream stream)方法,畢竟是PRT的類,所以使用
IRandomAccessStream來做參數也無可厚非,WP8中各種流之間的轉換是一個比較讓人反胃的東西,不過習慣了就好了,下面我們來看一下錄音的Record函數:
 private async void Record(string fileName="sandcu")
        {
                StorageFile storageFile=null;
                StorageFolder localFolder = ApplicationData.Current.LocalFolder;
                IsolatedStorageFile isStore = IsolatedStorageFile.GetUserStoreForApplication();
                if (isStore.FileExists(localFolder.Path + "\\"+fileName+".aac"))
                {
                    storageFile = await localFolder.GetFileAsync(fileName+".aac");
                }
                storageFile = storageFile ?? await localFolder.CreateFileAsync(fileName+".aac", CreationCollisionOption.ReplaceExisting);
                path = storageFile.Path;
                if (storageFile != null)
                {
                    Iras= await storageFile.OpenAsync(FileAccessMode.ReadWrite);
                    await AVCaptureDevice.StartRecordingToStreamAsync(Iras);
                }
        }
 
        

由於StorageFile 的OpenAsync方法可以很方便的返回一個IRandomAccessStream流所以我們暫且在根目錄下創建這樣一個臨時文件存儲流,這樣錄音之后轉存的Aac文件流會直接寫入文件中。

緊接着是停止錄音的方法StopRecord。很簡單~停止,然后釋放流。

 private async void StopRecord()
        {
            await AVCaptureDevice.StopRecordingAsync();
            Iras.AsStream().Dispose();
        }

大功告成~

為了測試我們是否成功的錄音了,可以播放一下看看:

private void Play()
{ 
    new MediaPlayerLauncher()
            {
                Media = new Uri(path, UriKind.Relative),
            }.Show();
}

 如果程序結束后不需要AudioVideoCaptureDevice了 記得Dispose掉;

 2.使用AMR格式

把init中的AVCaptureDevice.AudioEncodingFormat = CameraCaptureAudioFormat.Aac;  改為 AVCaptureDevice.AudioEncodingFormat = CameraCaptureAudioFormat.Amr;就好......

3.使用MP3格式

這個會比較麻煩,目前使用最廣的MP3編碼器當然是Lame~詳見 http://lame.sourceforge.net/download.php,打不開的自覺翻牆,不過在wp8上使用lame略微有點麻煩,而且比較容易出錯。

在wp8中使用native庫有兩種方式,一是使用dll二是使用lib,但是我們不能直接在c#中使用庫,所以需要用C++/CX包一層,至於如何包這一層網上有很多方法,詳情請google一下,在這里我們使用Lib的靜態方式來調用Lame。

首先下載lame的源代碼,然后找到他的vc_solution文件夾,這里我們需要的是vc9_lame.sln這個解決方案,而最終對我們有用的是libmp3lame-static和libmpghip-static這兩個工程,其他可以先remove掉,然后直接編譯就好,這里需要注意的是如果你使用模擬器調試,那么實際上需要的是win32的lib,而如果你使用真機調試,那么需要的則是Arm的lib,這也就是問什么同樣的工程在模擬器下可用而在真機時則崩潰。

編譯,然后我們拿到libmpghip-static.lib和libmp3lame-static.lib兩個庫,然后將他們引入我們自己的wp8 project中。記得Include進來lame.h

在不同的Platform下都要設置,比如Arm的就要引用Arm的文件夾,win32的則用win32的lib

接着我們需要建立一個windows phone 組件來包裝lib,起名就叫audio好了,下面就是audio工程設置的大概屬性

設定完lib所在的路徑后則記得在鏈接中加入庫的引用,同樣也要區分平台

准備工作做完后建立兩個filter一個是include一個是source,當然不建立也沒有任何問題

然后我們建立兩個類,一個是包裝類叫做LameWrapper,另一個是CompressedMp3Content用作返回值,這兩個類都是C++/CX的類,並且要被C#使用,所以注意這兩個類必須是Public ref class並且是sealed的並且不能有public的析構函數,煩吧~~~

我們來看LameWrapper.h的定義

public ref class LameWrapper sealed
    {
    public:
        LameWrapper();
        IAsyncOperation<CompressedMp3Content^>^ EncodePcm2Mp3(IBuffer^ inPcm, int sampleRate, int channels);
    };

然后是用作返回值的CompressedMp3Content類的定義

public ref class CompressedMp3Content sealed
    {
    public:
        CompressedMp3Content(void);
        property Platform::Array<unsigned char>^ Mp3Data;
    };

接下來是壓縮MP3的重頭戲~LameWrapper.CPP

首先Include “Lame.h”

然后來看一下EncodePcm2Mp3方法:

Windows::Foundation::IAsyncOperation<CompressedMp3Content^>^ LameWrapper::EncodePcm2Mp3(IBuffer^ inPcm, int sampleRate, int channels)
{
    lame_global_flags* lame = lame_init();
    lame_set_in_samplerate(lame, sampleRate);
    lame_set_num_channels(lame, channels);
    lame_set_quality(lame, 5);
    lame_init_params(lame);
    IUnknown* pUnk = reinterpret_cast<IUnknown*>(inPcm);
    IBufferByteAccess* pAccess = NULL;
    byte* bytes = NULL;
    HRESULT hr = pUnk->QueryInterface(__uuidof(IBufferByteAccess), (void**)&pAccess);
    if (SUCCEEDED(hr))
    {
        hr = pAccess->Buffer(&bytes);
        if (SUCCEEDED(hr))
        {
            return Concurrency::create_async([=]()->CompressedMp3Content^
            {
                CompressedMp3Content^ result = ref new CompressedMp3Content();
                int pcmLength = inPcm->Length;

                ///TODO:此處直接獲取了pcmLength的一半,在pcmLength為奇數的時候會丟掉最后一個字節~不過無所謂了......
                std::vector<short> inBuffer = std::vector<short>(pcmLength / 2);

                for (std::vector<short>::size_type i=0; i<inBuffer.size(); i++)
                {
                    inBuffer[i] = (((short)bytes[i*2+1]) << 8) + bytes[i*2];
                }

                std::vector<byte> outBuffer(inPcm->Length);
                int size = lame_encode_buffer(lame, inBuffer.data(), inBuffer.data(), inBuffer.size(), outBuffer.data(), 0);
                if (size > 0)
                {
                    result->Mp3Data = ref new Platform::Array<unsigned char>(size);

                    for(int i=0; i<size; i++)
                    {
                        (result->Mp3Data)->get(i) = outBuffer[i];
                    }
                }

                lame_close(lame);
                pAccess->Release();
                return result;
            });
        }
        else
        {
            lame_close(lame);
            pAccess->Release();
            throw ref new Platform::Exception(hr, L"Couldn't get bytes from the buffer");
        }
    }
    else
    {
        lame_close(lame);
        throw ref new Platform::Exception(hr, L"Couldn't access the buffer");
    }
}

先設置所需參數,通常我們錄下來只有一個聲道了,所以channels給個1好了,當然左右聲道都給同一個PCM buffer就變成雙聲道了也可以,lame_set_quality時有0~9個階段的質量可選,取中就好,設的太高壓縮會很慢,接下來開始process傳進來的buffer,為了方便我們給進來的是byte數組,而lame要的是short數組,所以我們要先將byte*轉換為short*轉換完成之后直接調用lame_encode_buffer來進行轉換吧,轉換完成后會返回實際的字節數,然后我們將轉好的byte*放入CompressedMp3Content類中以供c#進行進一步處理,然后記得release all~~~~~

回到c#這一側,當然要引用我們的audio工程,在C#這一側我們先要拿到pcm流,不知道為什么使用之前提到的AudioVideoCaptureDevice錄制PCM會崩潰,所以我們還是使用傳統的xna下的Microphone來錄制pcm流,

錄制完成后就很簡單了new出一個LameWrapper后直接調用EncodePCM2Mp3就好了~

LameWrapper lame = new LameWrapper();
CompressedMp3Content item = await lame.EncodePcm2Mp3(buffer, MicrophoneWrapper.Instance.SampleRate, 1);

 附件1:錄制AAC的http://files.cnblogs.com/bader/RecordAAC.rar

附件2:錄制MP3的,稍后再傳......

大功告成~呼~

 

PS:博主開了微博了~詳情猛戳:http://weibo.com/SandCu

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM