• Thứ Năm, 15/10/2009 09:15 (GMT+7)

    Lập trình phát âm thanh ở 2 card khác nhau

    Câu hỏi :
    Máy có 2 card âm thanh, tôi muốn viết chương trình VB6 chơi nhạc mp3, mp4... phát bài số 1 ở card thứ nhất, bài số 2 ở card thứ 2. Xin hướng dẫn.

    Trả lời :
    Cách tổng quát và dễ dàng nhất để chơi nhiều file multimedia thuộc nhiều định dạng khác nhau ra những card âm thanh khác nhau theo yêu cầu là lập trình dùng các đối tượng COM thuộc thành phần DirectShow trong bộ DirectX của Microsoft. Ý tưởng cơ bản của DirectShow trong việc play/record 1 nguồn multimedia là xây dựng 1 đồ thị (graph) gồm nhiều phần tử độc lập, kết nối chúng theo thứ tự thích hợp, mỗi phần tử xử lý 1 công việc trong nhiều công việc. Thí dụ 1 graph đơn giản nhất gồm 2 phần tử: phần tử đầu sẽ đọc file multimedia cần chơi, phân tích, giải mã thông tin nguồn ra dạng thô, còn phần tử thứ 2 sẽ nhận thông tin dạng thô rồi phát nó ra sound card. Như vậy để chơi 2 file mp3 và mp4 (hay 1 định dạng khác), bạn chỉ cần tạo 2 graph khác nhau, mỗi graph có 2 phần tử, phần tử thứ 2 trong mỗi graph chính là sound card nào đó mà bạn muốn. Để lập trình DirectShow và dễ dàng điều khiển chi tiết các bước chức năng, bạn nên dùng ngôn ngữ VC++ thay vì VB6.0. Để bạn có thể thấy rõ chi tiết lập trình dùng DirectShow để chơi 2 file âm thanh khác nhau ra 2 sound card khác nhau, chúng tôi đã lập trình 1 ứng dụng nhỏ viết bằng VC++, qui trình gồm các bước thao tác sau:

     1. Nếu bạn dùng Windows XP hay cũ hơn, một trong nhiều cách để có được thư viện lập trình DirectShow là download gói "Windows Server 2003 SP1 SDK Release" và cài vào máy.
     2. Chạy VC++, chọn menu File.New để hiển thị cửa sổ New. Chọn button Projects, chọn loại project MFC AppWizard(Exe), chọn vị trí chứa project (Location), nhập tên Project (thí dụ PlayMP3), chọn button OK. Khi cửa sổ "MFC AppWizard - Step 1" hiển thị, chọn option "Dialog Based" rồi button Finish để tạo Project chứa 1 Form giao diện đơn giản.
     3. Khi Form thiết kế hiển thị, bạn hãy xây dựng Form chỉ chứa 1 button đơn giản. Nhấn đúp chuột vào button duy nhất vừa tạo ra trong Form để tạo hàm xử lý sự kiện click chuột trên button này rồi viết code cho thân hàm như sau:
     void CPlayMP3Dlg::OnButton1() {
     //khai báo các biến cần dùng
     IGraphBuilder *pGraph1, *pGraph2;
     IMediaControl *pMediaControl1, *pMediaControl2;
     IMediaEvent *pEvent1, *pEvent2;
     IBaseFilter *pBaseReader1, *pBaseReader2;
     IBaseFilter *pAudioDst1, *pAudioDst2;
     HRESULT hr;
     //khởi động hệ thống quản lý COM
     CoInitialize(NULL);
     //tạo 1 graph builder để chơi file 1
     CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&pGraph1);
     pGraph1->QueryInterface(IID_IMediaControl, (void **)&pMediaControl1);
     pGraph1->QueryInterface(IID_IMediaEvent, (void **)&pEvent1);
     hr = pGraph1->AddSourceFilter(L"C:\\File1.mp3", L"", &pBaseReader1);
     hr = pGraph1->AddFilter(pBaseReader1, L"AudioSrc");
     //tìm sounddevice có tên "CyberLink Audio Renderer" (tương ứng với soundcard 1)
     if (FindSoundDevice("CyberLink Audio Renderer", &pAudioDst1)) {
     hr = pGraph1->AddFilter(pAudioDst1, L"AudioDst");
     IPin *pOutPin1, *pInpPin1;
     GetPin (pBaseReader1,PINDIR_OUTPUT, &pOutPin1);
     GetPin (pAudioDst1,PINDIR_INPUT, &pInpPin1);
     hr = pGraph1->Connect(pOutPin1, pInpPin1);
     //kích hoạt chạy các thành phần của graph.
     pMediaControl1->Run();
     }
     //tạo 1 graph builder khác để chơi file 2
     CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&pGraph2);
     pGraph2->QueryInterface(IID_IMediaControl, (void **)&pMediaControl2);
     pGraph2->QueryInterface(IID_IMediaEvent, (void **)&pEvent2);
     hr = pGraph2->AddSourceFilter(L"C:\\File2.mp3", L"", &pBaseReader2);
     hr = pGraph2->AddFilter(pBaseReader2, L"AudioSrc");
     //tìm sounddevice có tên "Realtek AC97 Audio" (tương ứng với soundcard 2)
     if (FindSoundDevice("Realtek AC97 Audio", &pAudioDst2)) {
     hr = pGraph2->AddFilter(pAudioDst2, L"AudioDst");
     IPin *pOutPin2, *pInpPin2;
     GetPin (pBaseReader2,PINDIR_OUTPUT, &pOutPin2);
     GetPin (pAudioDst2,PINDIR_INPUT, &pInpPin2);
     hr = pGraph2->Connect(pOutPin2, pInpPin2);
     //kích hoạt chạy các thành phần của graph.
     pMediaControl2->Run();
     }
     //đợi từng graph chạy xong
     long evCode;
     pEvent1->WaitForCompletion(INFINITE, &evCode);
     pEvent2->WaitForCompletion(INFINITE, &evCode);
     
     //dọn dẹp các đối tượng đã dùng
     pMediaControl1->Release();
     pEvent1->Release();
     pGraph1->Release();
     pMediaControl2->Release();
     pEvent2->Release();
     pGraph2->Release();
     CoUninitialize();
     }
     4. Viết thêm 2 hàm dịch vụ FindSoundDevice và GetPin vào trước hàm OnButton1 như sau:
     //hàm tìm sounddevice với tên xác định
     int FindSoundDevice (char* sname, IBaseFilter **p) {
     ICreateDevEnum *pSysDevEnum = NULL;
     IBaseFilter *pd;
     HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void **)&pSysDevEnum);
     //tìm đối tượng thống kê các sounddevice
     IEnumMoniker *pEnumCat = NULL;
     hr = pSysDevEnum->CreateClassEnumerator(CLSID_AudioRendererCategory, &pEnumCat, 0);
     if (hr == S_OK) { // nếu có
     IMoniker *pMoniker;
     ULONG cFetched;
     int lPos = 0;
     //duyệt tìm từng sounddevice
     while(pEnumCat->Next(1, &pMoniker, &cFetched) == S_OK) {
     IPropertyBag *pPropBag;
     pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pPropBag);
     //xác định tên của sounddevice
     VARIANT varName;
     VariantInit(&varName);
     hr = pPropBag->Read(L"FriendlyName", &varName, 0);
     if (SUCCEEDED(hr)) {
     //so sánh với tên cần tìm
     BSTR m_bstr = varName.bstrVal;
     char sdstr[256];
     WideCharToMultiByte(CP_UTF8, 0, m_bstr, -1, sdstr, 256, NULL, NULL);
     if (strcmp(sname,sdstr)==0) {
     pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void **)&pd);
     *p = pd;
     VariantClear(&varName);
     pMoniker->Release();
     pPropBag->Release();
     pEnumCat->Release();
     pSysDevEnum->Release();
     return 1;
     }
     }
     VariantClear(&varName);
     pPropBag->Release();
     }
     pEnumCat->Release();
     }
     pSysDevEnum->Release();
     return 0;
     }
     
     //hàm xác định ngõ giao tiếp (pin) của 1 thành phần trong graph
     int GetPin(IBaseFilter *pBF,PIN_DIRECTION eDir,IPin** ppPin) {
     HRESULT hr = S_OK;
     IEnumPins *spPins = NULL;
     hr = pBF->EnumPins(&spPins);
     if(SUCCEEDED(hr)) {
     ULONG ulFetched = 0;
     IPin *spPin = NULL;
     while(spPins->Next(1,&spPin,&ulFetched) == S_OK) {
     PIN_DIRECTION eTmpDir;
     hr = spPin->QueryDirection(&eTmpDir);
     if(SUCCEEDED(hr)&&(eTmpDir==eDir)) {
     *ppPin = spPin;
     (*ppPin)->AddRef();
     break;
     }
     spPin = NULL;
     }
     }
     return hr;
     }

     5. Viết thêm các hàng lệnh để sử dụng thư viện DirectShow vào đầu file PlayMP3Dlg.cpp như sau:
     #include <dshow.h>
     #pragma comment(lib,"Strmiids.lib")

     6. Chọn menu Tools.Options.Directories để xem và cấu hình các thư mục thông tin được dùng cho môi trường VC6.0. Chọn mục "Include Files" rồi thêm đường dẫn chứa các file *.h của DirectShow vào danh sách (mặc định là c:\Program Files\Windows Platform SDK\Include).

     7. Chọn menu Build.Execute PlayMP3.exe để dịch và chạy chương trình vừa viết. Khi cửa sổ ứng dụng hiển thị, nhấn chuột vào button để chơi 2 file MP3 ra 2 sound card khác nhau theo yêu cầu.
     Lưu ý rằng chương trình trên đã chơi nhạc ra 2 sound card khác nhau, mỗi sound card được nhận dạng thông qua tên sounddevice có tên lần lượt là "CyberLink Audio Renderer" và "Realtek AC97 Audio". Để biết tên nhận dạng cho các sounddevice quản lý các soundcard trên máy mình, bạn có thể chạy trình Windows Media Player, chọn menu Tools.Option.Devices.Speakers, chọn button Properties rồi xem danh sách các sounddevice trong listbox "Audio device to use".
     
    Chuyên mục: Lập trình